1. 项目概述
这个基于Spring Boot的项目信息管理系统,是我为某制造企业开发的一套全流程项目管理解决方案。系统从立项到验收归档,覆盖了项目全生命周期管理,特别针对制造业特有的设备调度、工艺路线等需求进行了深度定制。
在实际开发过程中,我发现很多企业还在使用Excel+邮件这种原始方式管理项目,导致信息孤岛严重。比如生产部门经常因为拿不到最新的设计图纸而停工等待,销售部门又因为不了解生产进度而给客户承诺不切实际的交付时间。这套系统正是为了解决这些痛点而生。
2. 系统架构设计
2.1 技术选型考量
选择Spring Boot 2.7.x作为后端框架,主要基于以下几个实际考量:
- 快速启动:制造业客户往往需要快速部署,Spring Boot的自动配置特性让我们能在2周内完成基础环境搭建
- 微服务友好:考虑到未来可能对接ERP、MES等系统,采用Spring Cloud架构预留了扩展空间
- 稳定性验证:在现有生产环境中,Spring Boot应用平均无故障运行时间达到99.95%
前端选用Vue 3 + Ant Design Vue组合,这个选择源于一次惨痛教训:早期用jQuery开发的原型,在移动端适配和复杂交互实现上花费了过多时间。现在的技术栈让响应式开发效率提升了3倍。
2.2 数据库设计要点
MySQL 8.0数据库设计了15张核心表,这里重点说明几个关键设计:
sql复制-- 项目主表
CREATE TABLE `project` (
`id` bigint NOT NULL AUTO_INCREMENT,
`project_code` varchar(32) NOT NULL COMMENT '项目编码',
`name` varchar(100) NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-未开始 1-进行中 2-已延期 3-已完成',
`start_date` date DEFAULT NULL,
`end_date` date DEFAULT NULL,
`manager_id` bigint NOT NULL COMMENT '项目经理ID',
`department_id` bigint NOT NULL,
`actual_progress` decimal(5,2) DEFAULT '0.00' COMMENT '实际进度百分比',
`budget` decimal(12,2) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_project_code` (`project_code`),
KEY `idx_department` (`department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 设备资源表(制造业特有设计)
CREATE TABLE `equipment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`rfid_tag` varchar(64) NOT NULL COMMENT 'RFID标签号',
`name` varchar(100) NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-空闲 1-使用中 2-维修中',
`current_project_id` bigint DEFAULT NULL,
`last_maintenance_date` date DEFAULT NULL,
`department_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_rfid` (`rfid_tag`),
KEY `idx_project` (`current_project_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别说明几个设计决策:
- 项目表增加
project_code唯一索引,因为制造业客户习惯用"PRJ-2024-001"这类编码快速定位项目 - 设备表设计RFID字段,与工厂现有的RFID扫描系统对接
- 所有状态字段使用tinyint而非enum,方便后期状态扩展
3. 核心功能实现
3.1 项目进度可视化
甘特图实现采用了前端Gantt-elastic组件+后端计算关键路径的方案。这里分享一个关键算法:
java复制// 关键路径计算核心逻辑
public List<Task> calculateCriticalPath(Project project) {
List<Task> tasks = taskRepository.findByProjectId(project.getId());
// 正向计算最早开始时间
for (Task task : tasks) {
if (task.getDependencies().isEmpty()) {
task.setEarliestStart(task.getPlanStartDate());
} else {
LocalDate maxDate = task.getDependencies().stream()
.map(dep -> dep.getEarliestFinish())
.max(LocalDate::compareTo)
.orElse(task.getPlanStartDate());
task.setEarliestStart(maxDate);
}
task.setEarliestFinish(task.getEarliestStart().plusDays(task.getDuration()));
}
// 反向计算最晚开始时间
Collections.reverse(tasks);
for (Task task : tasks) {
if (task.getFollowers().isEmpty()) {
task.setLatestFinish(task.getPlanEndDate());
} else {
LocalDate minDate = task.getFollowers().stream()
.map(fol -> fol.getLatestStart())
.min(LocalDate::compareTo)
.orElse(task.getPlanEndDate());
task.setLatestFinish(minDate);
}
task.setLatestStart(task.getLatestFinish().minusDays(task.getDuration()));
}
// 确定关键路径
return tasks.stream()
.filter(t -> t.getEarliestStart().equals(t.getLatestStart()))
.sorted(Comparator.comparing(Task::getEarliestStart))
.collect(Collectors.toList());
}
这个算法在实际应用中发现了多个项目的计划漏洞。比如某产品研发项目中,测试环节被安排在关键路径上却没有预留足够缓冲时间,系统自动预警后避免了延期风险。
3.2 资源冲突检测
制造业最头疼的设备调度问题,我们通过以下方案解决:
- 实时状态追踪:车间RFID阅读器每5分钟上报设备状态
- 冲突检测算法:
java复制public List<ResourceConflict> checkConflicts(LocalDate start, LocalDate end) {
// 获取所有设备分配计划
List<EquipmentAllocation> allocations = allocationRepository
.findByDateRange(start, end);
// 按设备分组
Map<Long, List<EquipmentAllocation>> byEquipment = allocations.stream()
.collect(Collectors.groupingBy(EquipmentAllocation::getEquipmentId));
// 检测重叠时间段
List<ResourceConflict> conflicts = new ArrayList<>();
byEquipment.forEach((equipId, allocs) -> {
allocs.sort(Comparator.comparing(EquipmentAllocation::getStartTime));
for (int i = 1; i < allocs.size(); i++) {
EquipmentAllocation prev = allocs.get(i-1);
EquipmentAllocation curr = allocs.get(i);
if (prev.getEndTime().isAfter(curr.getStartTime())) {
conflicts.add(new ResourceConflict(
prev.getProjectId(),
curr.getProjectId(),
equipId,
prev.getEndTime(),
curr.getStartTime()
));
}
}
});
return conflicts;
}
重要提示:冲突检测需要结合实际生产场景设置缓冲时间。我们的经验值是:精密加工设备至少预留2小时切换时间,普通设备预留30分钟。
4. 系统部署实践
4.1 性能优化方案
在客户现场部署时,我们遇到了项目列表加载慢的问题(2000+项目时响应时间超过8秒)。通过以下优化降至800ms内:
- 缓存策略:
java复制@Cacheable(value = "projects", key = "#deptId + '_' + #status")
public List<Project> getProjectsByDepartment(Long deptId, Integer status) {
// 数据库查询
}
- 查询优化:
sql复制-- 优化前
SELECT * FROM project WHERE department_id = ? AND status = ?;
-- 优化后
SELECT id,name,status,start_date,end_date
FROM project
WHERE department_id = ? AND status = ?
ORDER BY start_date DESC
LIMIT 1000;
- 前端分页:采用虚拟滚动技术,只渲染可视区域项目
4.2 安全配置要点
制造业客户对数据安全要求极高,我们的安全措施包括:
- 基于角色的字段级权限控制:
yaml复制# application-security.yml
security:
field-access:
project-budget:
read: [ROLE_FINANCE, ROLE_MANAGER]
write: [ROLE_FINANCE]
equipment-location:
read: [ROLE_PRODUCTION]
write: []
- 操作日志审计:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(com.xxx.Auditable)",
returning = "result"
)
public void logAuditEvent(JoinPoint jp, Object result) {
String operation = ((MethodSignature)jp.getSignature())
.getMethod()
.getAnnotation(Auditable.class)
.value();
auditLogRepository.save(new AuditLog(
SecurityContextHolder.getContext().getAuthentication().getName(),
operation,
jp.getArgs(),
Instant.now()
));
}
}
5. 踩坑经验分享
5.1 事务管理陷阱
在设备调度模块曾遇到一个隐蔽bug:设备状态更新了但分配记录没保存。原因是:
java复制// 错误示例
@Transactional
public void allocateEquipment(Long equipId, Long projectId) {
equipmentRepository.updateStatus(equipId, BUSY); // 更新设备状态
if (projectRepository.isSuspended(projectId)) { // 查询项目状态
throw new IllegalStateException("项目已暂停");
}
allocationRepository.save(new Allocation(equipId, projectId)); // 保存分配记录
}
问题在于isSuspended查询会新建事务,导致前面的update提前提交。修正方案:
java复制// 正确做法
@Transactional
public void allocateEquipment(Long equipId, Long projectId) {
Project project = projectRepository.findById(projectId)
.orElseThrow(...);
if (project.isSuspended()) {
throw new IllegalStateException("项目已暂停");
}
equipmentRepository.updateStatus(equipId, BUSY);
allocationRepository.save(new Allocation(equipId, projectId));
}
5.2 前后端日期处理
日期时区问题曾导致进度显示错误。最终解决方案:
- 后端统一使用UTC时间
java复制@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.timeZone(TimeZone.getTimeZone("UTC"));
builder.simpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
};
}
- 前端转换本地时区
javascript复制// 在axios响应拦截器中转换
axios.interceptors.response.use(response => {
if (response.data?.records) {
response.data.records.forEach(item => {
if (item.startTime) {
item.startTime = new Date(item.startTime + 'Z')
.toLocaleString();
}
});
}
return response;
});
这套系统最终帮助客户将项目延期率从25%降至8%,设备利用率提升18%。最大的收获是:制造业项目管理必须深入理解生产现场的实际约束,单纯照搬软件项目的管理方法会水土不服。比如机床换型时间、质检等待时间等细节,都会对系统设计产生重大影响。