1. 项目概述与核心价值
中小企业设备管理系统是制造业、服务业等领域的基础信息化工具,它解决了传统纸质台账或Excel管理带来的数据孤岛、流转低效、维保滞后等痛点。我们团队基于SpringBoot+Vue的全栈技术栈,开发了一套支持设备全生命周期管理的轻量级系统。
这个系统的核心价值在于:
- 实现设备档案电子化(基础信息、技术参数、供应商资料)
- 流程化设备运维(巡检计划、报修工单、保养记录)
- 动态监控设备状态(使用率、故障率、折旧分析)
- 移动端协同能力(微信小程序扫码快速报修)
提示:系统设计时特别考虑了中小企业的IT能力,安装部署仅需JDK+MySQL环境,无需专业运维人员介入。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计解析
2.1 后端技术选型
SpringBoot 2.7.x 作为基础框架,主要基于以下考量:
- 内嵌Tomcat简化部署(对比传统SSM需外置容器)
- 自动配置减少XML配置(starter机制整合MyBatis/Druid等)
- Actuator提供运维端点(/health、/metrics等)
数据库选用MySQL 8.0:
- 事务隔离级别设置为READ-COMMITTED
- 使用utf8mb4字符集支持emoji等特殊符号
- 配置连接池参数(初始连接数=5,最大连接数=50)
java复制// 典型MyBatis Mapper接口示例
@Mapper
public interface DeviceMapper {
@Select("SELECT * FROM device WHERE status = #{status}")
List<Device> findByStatus(@Param("status") int status);
@Update("UPDATE device SET last_maintain = NOW() WHERE id = #{id}")
int updateMaintainTime(Long id);
}
2.2 前端技术方案
Vue 3 + Element Plus 构建管理后台:
- 采用Composition API写法提升代码组织性
- 使用Vue Router实现动态路由(根据权限过滤)
- Pinia替代Vuex进行状态管理
javascript复制// 设备状态卡片组件示例
<template>
<el-card>
<div class="status-badge" :class="`status-${device.status}`"></div>
<h3>{{ device.name }}</h3>
<el-tag :type="statusMap[device.status].type">
{{ statusMap[device.status].text }}
</el-tag>
</el-card>
</template>
3. 核心功能实现细节
3.1 设备唯一标识体系
采用三段式编码规则:
code复制DEP[部门代码2位]-[设备类型3位]-[序列号4位]
示例:DEP01-CNC-0001
通过Redis原子计数器保证序列号唯一性:
java复制public String generateDeviceCode(String dept, String type) {
String key = "device:counter:" + dept + ":" + type;
Long seq = redisTemplate.opsForValue().increment(key);
return String.format("DEP%02d-%3s-%04d", dept, type, seq);
}
3.2 工单状态机设计
定义工单的6种状态和转换规则:
| 当前状态 | 允许操作 | 目标状态 |
|---|---|---|
| 待受理 | 分配 | 处理中 |
| 处理中 | 完成 | 已完成 |
| 处理中 | 转单 | 已转单 |
| 处理中 | 挂起 | 已挂起 |
java复制// 状态转换校验逻辑
public boolean canTransfer(WorkOrder order, String targetStatus) {
Map<String, List<String>> rules = Map.of(
"PENDING", List.of("PROCESSING"),
"PROCESSING", List.of("COMPLETED", "TRANSFERRED", "SUSPENDED")
);
return rules.getOrDefault(order.getStatus(), List.of())
.contains(targetStatus);
}
4. 典型问题解决方案
4.1 大文件上传优化
设备图片上传采用分片上传方案:
- 前端通过File.slice()切割文件(每片2MB)
- 后端合并片时使用文件锁防止并发冲突
- 增加MD5校验确保文件完整性
java复制// 分片合并逻辑
public void mergeChunks(String fileKey, int totalChunks) throws IOException {
File destFile = new File(uploadDir, fileKey);
try (FileChannel destChannel = new FileOutputStream(destFile, true).getChannel()) {
for (int i = 0; i < totalChunks; i++) {
File chunk = new File(chunkDir, fileKey + ".part" + i);
try (FileChannel srcChannel = new FileInputStream(chunk).getChannel()) {
destChannel.transferFrom(srcChannel, destChannel.size(), srcChannel.size());
}
chunk.delete();
}
}
}
4.2 定时任务补偿机制
设备定期保养任务采用Quartz实现,并增加补偿策略:
- 首次失败后延迟5分钟重试
- 三次失败后标记为异常工单
- 通过邮件通知管理员
properties复制# Quartz配置示例
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
5. 部署与运维实践
5.1 生产环境部署要点
推荐使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
5.2 性能调优经验
通过JMeter压测发现的优化点:
- MySQL连接池wait_timeout调整为300秒
- 添加设备列表查询的复合索引:
sql复制ALTER TABLE device ADD INDEX idx_dept_status (department_id, status); - 启用SpringBoot的Gzip压缩:
properties复制server.compression.enabled=true server.compression.mime-types=application/json
6. 源码结构导读
项目采用标准Maven多模块设计:
code复制device-mgmt
├── device-admin -- Vue前端工程
├── device-core -- 领域模型
├── device-dao -- MyBatis映射
├── device-service -- 业务逻辑
└── device-web -- REST接口
关键配置示例:
xml复制<!-- MyBatis分页插件配置 -->
<bean id="pageInterceptor" class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=mysql
reasonable=true
</value>
</property>
</bean>
在开发过程中我们发现,设备状态变更的并发控制需要特别注意。最初采用乐观锁方案,但在高并发场景下出现大量重试,后改为Redis分布式锁实现:
java复制public boolean updateDeviceStatus(Long id, int oldStatus, int newStatus) {
String lockKey = "device:lock:" + id;
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 执行状态更新逻辑
return deviceDao.updateStatus(id, oldStatus, newStatus) > 0;
}
return false;
} finally {
redisTemplate.delete(lockKey);
}
}
