☰
企业EAM系统开发实战:Vue+Spring Boot全栈架构解析
2026/10/14 5:05:35 网站建设 项目流程

1. 项目背景与核心价值

企业资产管理(EAM)系统是现代企业数字化转型的基础设施之一。传统资产管理依赖人工台账和Excel表格,存在数据孤岛、流程割裂、追溯困难等痛点。我们团队最近为一家中型制造企业实施的这套系统,通过前后端分离架构实现了资产全生命周期管理,上线后使设备利用率提升27%,盘点效率提高近10倍。

这个系统的核心价值在于三个维度:一是通过标准化编码体系解决资产标识混乱问题;二是借助工作流引擎实现从采购到报废的闭环管理;三是利用数据分析看板辅助决策。特别在制造行业,设备停机成本每小时可能高达数万元,预防性维护功能直接关系到企业效益。

2. 技术架构设计解析

2.1 前端技术选型

采用Vue 3组合式API开发管理后台,主要基于以下考量:

  • 相比React更温和的学习曲线,适合企业IT团队后续维护
  • Element Plus组件库提供丰富的业务组件(如资产树形选择器)
  • ECharts对设备状态可视化支持更友好

关键配置示例:

// vite.config.js 生产环境优化 build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('element-plus') || id.includes('echarts')) { return 'vendor' } } } } }

2.2 后端架构设计

Spring Boot采用分层架构:

com.asset ├── config # 安全/持久化配置 ├── controller # RESTful API ├── service # 业务逻辑 │ ├── impl # 实现类 ├── repository # JPA接口 └── model # 实体类

特别设计了双数据源:

  • 主库:MySQL 8.0(InnoDB集群)
  • 从库:Redis缓存热点数据(如部门资产统计)

2.3 数据库关键设计

资产主表结构优化要点:

CREATE TABLE `asset` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `asset_code` VARCHAR(20) NOT NULL COMMENT '资产编码规则: DEPT+YYYYMM+5位序列', `name` VARCHAR(100) NOT NULL, `category_id` INT NOT NULL COMMENT '关联资产分类表', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1-在用 2-闲置 3-维修', `purchase_date` DATE NOT NULL, `net_value` DECIMAL(12,2) COMMENT '净值计算=原值-累计折旧', `location_id` INT COMMENT 'GIS坐标编码', `qr_code` VARCHAR(255) COMMENT '二维码存储路径', PRIMARY KEY (`id`), UNIQUE INDEX `udx_code` (`asset_code`), INDEX `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3. 核心功能实现细节

3.1 资产全生命周期工作流

采用Activiti引擎驱动状态变更:

graph TD A[采购申请] -->|审批| B[入库登记] B --> C[领用分配] C --> D[日常使用] D -->|故障| E[维修流程] D -->|闲置| F[调拨流程] D -->|报废| G[处置流程]

关键状态机实现:

@Transactional public void changeAssetStatus(Long assetId, AssetStatus newStatus) { Asset asset = repository.findById(assetId) .orElseThrow(() -> new BusinessException("资产不存在")); // 状态校验逻辑 if (asset.getStatus() == AssetStatus.SCRAPPED) { throw new BusinessException("已报废资产不可操作"); } // 记录变更日志 AssetLog log = new AssetLog(); log.setAssetId(assetId); log.setOldStatus(asset.getStatus()); log.setNewStatus(newStatus); logRepository.save(log); asset.setStatus(newStatus); repository.save(asset); }

3.2 移动端盘点方案

混合开发方案选型对比:

方案优点缺点适用场景
纯Web跨平台扫码性能差简单盘点
UniApp开发快功能受限原型验证
React Native性能好学习成本高大型项目
Flutter体验优生态较弱重UI需求

最终采用Cordova+WebView方案:

  1. 使用ZXing插件实现毫秒级扫码
  2. 本地SQLite存储离线盘点数据
  3. 通过WebSocket实现实时进度同步

3.3 折旧计算模块

财务部门特别要求的双余额递减法:

public BigDecimal calculateDepreciation(Asset asset) { BigDecimal cost = asset.getOriginalValue(); BigDecimal salvage = asset.getSalvageValue(); int years = asset.getUsefulLife(); // 双倍余额递减法公式 BigDecimal rate = BigDecimal.valueOf(2.0) .divide(BigDecimal.valueOf(years), 10, RoundingMode.HALF_UP); BigDecimal bookValue = cost.subtract(asset.getAccumulatedDepreciation()); BigDecimal depreciation = bookValue.multiply(rate); // 最后两年转为直线法 if (asset.getUsedYears() >= years - 2) { depreciation = bookValue.subtract(salvage) .divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP); } return depreciation.compareTo(BigDecimal.ZERO) > 0 ? depreciation : BigDecimal.ZERO; }

4. 性能优化实战记录

4.1 千万级数据查询优化

资产历史表分库方案:

  • 按年度水平分片(2023_asset_history)
  • 建立时间维度索引
  • 冷数据归档到ClickHouse

查询优化前后对比:

优化措施查询耗时(ms)内存占用(MB)
原始查询4200850
添加索引1200320
分页优化800150
结果缓存5080

4.2 高并发领用控制

分布式锁实现方案对比:

// Redisson分布式锁实现 public boolean acquireAsset(Long assetId, Long userId) { RLock lock = redissonClient.getLock("asset:" + assetId); try { if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { Asset asset = repository.findById(assetId); if (asset.getStatus() == AssetStatus.AVAILABLE) { asset.setUserId(userId); repository.save(asset); return true; } } } finally { lock.unlock(); } return false; }

4.3 文件导入性能提升

Excel导入优化策略:

  1. 使用EasyExcel替代POI(内存降低60%)
  2. 采用批量插入(batchSize=1000)
  3. 异步处理机制
@Async public void importAssets(MultipartFile file) { EasyExcel.read(file.getInputStream()) .head(AssetImportDTO.class) .registerReadListener(new AssetImportListener()) .sheet() .doRead(); } // 自定义监听器实现分批入库 class AssetImportListener implements ReadListener<AssetImportDTO> { private List<Asset> cache = new ArrayList<>(1000); @Override public void invoke(AssetImportDTO data, AnalysisContext context) { Asset asset = convertToEntity(data); cache.add(asset); if (cache.size() >= 1000) { batchInsert(); } } private void batchInsert() { repository.saveAll(cache); cache.clear(); } }

5. 部署与运维方案

5.1 容器化部署

Docker Compose编排示例:

version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d redis: image: redis:6 ports: - "6379:6379" backend: build: ./backend ports: - "8080:8080" depends_on: - mysql - redis frontend: build: ./frontend ports: - "80:80"

5.2 监控体系搭建

Prometheus监控指标示例:

  • 资产API成功率
  • 折旧计算耗时
  • 并发领用等待数
  • 盘点任务队列深度

Grafana看板配置关键点:

  1. 设置资产状态分布饼图
  2. 折旧计提进度时序图
  3. 接口响应时间热力图

6. 典型问题排查实录

6.1 扫码枪兼容性问题

现象:部分工业扫码枪无法触发Web页面事件 排查过程:

  1. 抓取键盘事件发现部分设备发送KeyCode异常
  2. 使用input事件替代keydown事件
  3. 添加300ms防抖处理

最终解决方案:

// 扫码枪专用输入组件 <template> <input v-model="scanValue" @input="handleScan" ref="scannerInput" class="scanner-input" /> </template> <script setup> const scanValue = ref('') let timer = null const handleScan = () => { clearTimeout(timer) timer = setTimeout(() => { if (scanValue.value.length >= 8) { submitScan(scanValue.value) scanValue.value = '' } }, 300) } </script>

6.2 折旧计算精度问题

故障现象:年末资产净值出现0.01元差异 根本原因:BigDecimal除法未统一精度 修复方案:

// 统一配置计算精度 @Bean public ArithmeticUtils arithmeticUtils() { return new ArithmeticUtils(6, RoundingMode.HALF_UP); } // 所有计算使用工具类 public BigDecimal calculate(Asset asset) { return arithmeticUtils.divide( asset.getOriginalValue(), BigDecimal.valueOf(asset.getUsefulLife()) ); }

6.3 并发领用超发问题

复现步骤:

  1. 用户A和B同时申请同一资产
  2. 系统检查时都显示可用
  3. 最终两人都获得使用权

解决方案对比:

方案实现复杂度性能影响可靠性
数据库乐观锁低小中等
SELECT FOR UPDATE中较大高
分布式锁高中等最高

最终采用组合方案:

  1. 前端限制重复提交
  2. 后端加Redis分布式锁
  3. 数据库最终校验

7. 扩展方向建议

  1. 物联网集成:通过MQTT协议接入设备实时数据
  2. 预测性维护:基于设备历史数据训练LSTM模型
  3. 数字孪生:Three.js实现资产3D可视化
  4. 区块链存证:关键操作上链存证

实际项目中我们接入了PLC设备数据采集,通过以下指标预测故障:

  • 电机振动频率
  • 轴承温度变化率
  • 电流波动系数
# 简化的预测模型示例 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense model = Sequential([ LSTM(64, input_shape=(30, 5)), # 30个时间步,5个特征 Dense(1, activation='sigmoid') ]) model.compile(loss='binary_crossentropy', optimizer='adam')

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询