1. 星云ERP系统概述与开发背景
星云ERP(Enterprise Resource Planning)是一套面向中小型企业的综合管理系统,它整合了财务、供应链、生产、人力资源等核心业务流程。我在2022年初开始接触这套系统时,发现市场上同类产品要么功能冗余要么价格高昂,而星云ERP恰好填补了轻量级解决方案的空白。
这套系统采用微服务架构设计,前端基于Vue.js+ElementUI,后端使用Spring Boot+MyBatis技术栈,数据库支持MySQL和PostgreSQL双引擎。特别值得一提的是其模块化设计——企业可以像搭积木一样按需启用采购管理、库存预警、生产排程等23个功能模块。我在实际部署中发现,这种设计使系统在4核8G的服务器上就能流畅运行200人规模的并发操作。
2. 开发环境搭建实战记录
2.1 基础环境配置
开发环境的准备往往是最容易踩坑的环节。经过多次实践,我总结出最稳定的环境组合:
- JDK 11(特别注意不要用JDK 17,会遇到Hibernate兼容性问题)
- Node.js 14.x(16.x版本会导致webpack编译异常)
- Maven 3.6.3(新版在依赖解析时会有奇怪报错)
bash复制# 数据库初始化脚本示例(MySQL)
CREATE DATABASE nebula_erp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON nebula_erp.* TO 'erpadmin'@'%' IDENTIFIED BY 'ComplexPwd123!';
重要提示:一定要配置MySQL的innodb_buffer_pool_size为物理内存的70%,这是影响ERP系统性能的关键参数。我在测试环境曾因默认配置导致查询响应超过5秒。
2.2 前后端分离架构配置
前端项目需要特别注意跨域问题。在vue.config.js中要配置代理:
javascript复制devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
后端则需要配置CORS(虽然Nginx层也能做,但开发阶段建议代码层处理):
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600);
}
}
3. 核心模块开发要点
3.1 采购订单状态机设计
采购流程的状态转换是业务核心,我采用状态模式实现:
java复制public interface PurchaseOrderState {
void confirm(PurchaseOrder order);
void cancel(PurchaseOrder order);
void complete(PurchaseOrder order);
}
// 具体状态实现示例
public class DraftState implements PurchaseOrderState {
@Override
public void confirm(PurchaseOrder order) {
order.setState(new ConfirmedState());
auditService.log(order.getId(), "订单确认");
}
// 其他方法实现...
}
状态转换规则需要特别注意:
- 草稿→已确认:需校验供应商资质
- 已确认→已完成:需关联入库单
- 任何状态→已取消:需记录取消原因
3.2 库存实时预警实现
采用Redis+定时任务的混合方案:
java复制@Scheduled(cron = "0 0/30 * * * ?")
public void checkInventory() {
List<Warehouse> warehouses = warehouseMapper.selectAll();
warehouses.forEach(wh -> {
Long lowStockCount = inventoryMapper.countLowStock(wh.getId());
if (lowStockCount > 0) {
String key = "alert:warehouse:" + wh.getId();
redisTemplate.opsForValue().set(key, lowStockCount, 2, TimeUnit.HOURS);
alertService.sendLowStockAlert(wh.getManagerId(), lowStockCount);
}
});
}
4. 生产环境部署方案
4.1 服务器选型建议
根据实际负载测试结果:
- 50用户以下:2核4G云服务器+SSD磁盘
- 50-200用户:4核8G+200GB SSD
- 200用户以上:建议K8s集群部署
4.2 Docker Compose部署示例
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: SafeRootPwd@456
MYSQL_DATABASE: nebula_erp
volumes:
- ./mysql-data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6
command: redis-server --requirepass YourRedisPass123
ports:
- "6379:6379"
backend:
build: ./server
ports:
- "8080:8080"
depends_on:
- mysql
- redis
部署经验:一定要配置MySQL的slow_query_log,我们曾通过这个日志发现了一个全表扫描的SQL,优化后使报表生成速度从47秒降到1.3秒。
5. 系统调优实战记录
5.1 JVM参数优化
经过JMeter压测后确定的参数:
bash复制JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m -XX:+UseG1GC
-XX:MaxGCPauseMillis=200"
关键调整:
- G1垃圾回收器替代默认的ParallelGC
- 设置最大GC停顿时间200ms
- 禁用显式GC调用(-XX:+DisableExplicitGC)
5.2 SQL优化案例
优化前的慢查询:
sql复制SELECT * FROM order_detail
WHERE order_id IN (SELECT id FROM orders WHERE create_time > '2023-01-01')
优化方案:
sql复制SELECT d.* FROM order_detail d
JOIN orders o ON d.order_id = o.id
WHERE o.create_time > '2023-01-01'
配合添加的复合索引:
sql复制CREATE INDEX idx_orders_createtime ON orders(create_time);
CREATE INDEX idx_detail_orderid ON order_detail(order_id);
6. 二次开发扩展实践
6.1 自定义报表开发
利用系统提供的API扩展生产报表:
java复制@RestController
@RequestMapping("/api/custom-report")
public class ProductionReportController {
@GetMapping("/efficiency")
public ResponseEntity<ReportResult> getEfficiency(
@RequestParam String workshop,
@DateTimeFormat(pattern="yyyy-MM-dd") Date startDate) {
// 业务逻辑实现
}
}
前端集成要点:
javascript复制// 在main.js中注册全局组件
import CustomReport from '@/components/report/CustomReport.vue';
Vue.component('custom-report', CustomReport);
6.2 第三方系统对接
与微信小程序对接的签名验证示例:
java复制public boolean verifyWechatSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { WECHAT_TOKEN, timestamp, nonce };
Arrays.sort(arr);
String tempStr = StringUtils.join(arr);
String actualSignature = DigestUtils.sha1Hex(tempStr);
return actualSignature.equals(signature);
}
7. 运维监控体系建设
7.1 Prometheus监控配置
application.yml中暴露指标端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: nebula-erp
对应的Prometheus job配置:
yaml复制scrape_configs:
- job_name: 'nebula-erp'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['erp-server:8080']
7.2 日志收集方案
采用ELK栈处理日志时,建议的Logstash过滤器:
ruby复制filter {
grok {
match => { "message" => "\[%{TIMESTAMP_ISO8601:timestamp}\] %{LOGLEVEL:level} %{DATA:thread} - %{DATA:class} - %{GREEDYDATA:msg}" }
}
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
}
8. 典型问题排查手册
8.1 数据库连接池耗尽
症状:出现"Timeout waiting for connection from pool"错误
排查步骤:
- 检查连接池配置(建议HikariCP配置):
yaml复制spring: datasource: hikari: maximum-pool-size: 20 leak-detection-threshold: 5000 - 使用以下SQL查找未关闭的连接:
sql复制SHOW PROCESSLIST; - 添加连接归还监控:
java复制@Aspect @Component public class DataSourceMonitor { @AfterReturning("execution(* javax.sql.DataSource.getConnection(..))") public void afterGetConnection(JoinPoint jp) { log.debug("Acquired connection from pool"); } }
8.2 前端内存泄漏
Chrome开发者工具的排查方法:
- 打开Performance面板录制操作过程
- 使用Memory面板拍摄堆快照
- 比较快照查找未被释放的组件
- 常见问题:未解绑的全局事件监听器
解决方案示例:
javascript复制// 错误写法
mounted() {
window.addEventListener('resize', this.handleResize);
}
// 正确写法
mounted() {
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
}
