1. 项目概述
天盛装潢公司管理系统是一套面向中小型装饰企业的全流程业务管理解决方案。这个基于SpringBoot的后台系统我从零开始参与了架构设计,前后迭代了三个大版本,目前已经稳定运行在30多家装潢公司的日常业务中。
装潢行业有个特点:每个项目周期长、参与方多、材料种类繁杂。传统Excel+纸质单据的管理方式经常出现工期延误、材料对不上账、客户投诉无记录等问题。这套系统正是为了解决这些痛点而生,核心功能覆盖了客户管理、项目进度跟踪、材料采购、财务对账等关键业务场景。
2. 技术架构解析
2.1 为什么选择SpringBoot
在技术选型阶段我们对比了多个方案,最终选择SpringBoot主要基于几个实际考量:
- 快速迭代需求:装潢行业的业务流程变化频繁,SpringBoot的约定大于配置特性让我们能快速响应需求变更
- 中小企业的服务器环境:客户服务器配置普遍不高(2核4G是常态),SpringBoot的内嵌Tomcat和轻量级特性完美适配
- 团队技术栈:我们团队有丰富的Spring生态开发经验,使用SpringBoot可以复用现有技术资产
2.2 核心模块设计
系统采用经典的三层架构,但针对装潢业务做了特殊优化:
code复制com.tiansheng
├── config # 定制化配置
├── controller # 业务入口
│ ├── client # 客户管理
│ ├── project # 项目管理
│ ├── material # 材料管理
│ └── finance # 财务管理
├── service # 业务逻辑层
├── dao # 数据访问层
└── model # 领域对象
特别说明材料管理模块的设计:
java复制@Entity
public class Material {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(nullable=false)
private String name; // 材料名称
@Column(nullable=false)
private String spec; // 规格参数
@Column(nullable=false)
private String unit; // 计量单位
@Column(precision=10, scale=2)
private BigDecimal price; // 最新采购价
// 关联项目用料记录
@OneToMany(mappedBy="material")
private Set<ProjectMaterial> usages;
}
3. 特色功能实现
3.1 项目进度可视化
装潢项目平均周期3-6个月,我们开发了里程碑看板功能:
javascript复制// 前端使用Gantt图展示
gantt.init({
tasks: [
{id: 1, text: "设计方案确认", start_date: "2023-06-01", duration: 7},
{id: 2, text: "水电改造", start_date: "2023-06-08", duration: 14},
// ...其他工序
],
links: [
{source: 1, target: 2, type: "0"} // FS依赖关系
]
});
实际使用中发现两个关键点:
- 必须支持拖拽调整:现场施工经常遇到突发情况需要调整工期
- 自动预警功能:当关键路径延误超过3天时触发短信通知
3.2 材料库存预警
材料管理中最容易出问题的环节:
- 我们实现了动态安全库存算法:
java复制public class MaterialService {
// 根据历史用量计算安全库存
public int calculateSafetyStock(Long materialId) {
List<UsageRecord> records = dao.findLatestUsage(materialId, 6);
if(records.size() < 3) return 0;
double avg = records.stream().mapToInt(UsageRecord::getAmount).average().orElse(0);
return (int) Math.ceil(avg * 1.2); // 上浮20%
}
}
- 采购建议功能:
- 自动关联在途订单
- 考虑项目预留量
- 支持一键生成采购单
4. 部署实践指南
4.1 生产环境配置
经过多次优化后的推荐配置:
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/decoration?useSSL=false&serverTimezone=Asia/Shanghai
username: prod_user
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20 # 根据MySQL配置调整
connection-timeout: 30000
server:
tomcat:
max-threads: 200 # 装潢行业早晚高峰明显
min-spare-threads: 20
重要提示:一定要关闭JPA的ddl-auto,生产环境必须使用Flyway管理数据库变更
4.2 性能调优经验
在客户现场遇到的典型问题及解决方案:
- 材料列表加载慢
- 问题:当材料种类超过5000条时,页面响应超过3秒
- 解决方案:
java复制配合前端增加分页(每页50条)@EntityGraph(attributePaths = {"category"}) // 解决N+1查询 @Query("select m from Material m where m.status = 1") Page<Material> findAllActive(Pageable pageable);
- 报表生成OOM
- 原因:一次性导出全年数据导致内存溢出
- 改进:改用Apache POI的SXSSFWorkbook实现流式导出
5. 典型业务场景示例
5.1 客户签约流程
标准业务流程实现:
java复制@Transactional
public Contract signContract(Long clientId, ContractDTO dto) {
// 1. 验证客户资质
Client client = clientRepository.findById(clientId)
.orElseThrow(() -> new BusinessException("客户不存在"));
if(client.getCreditLevel() < 3) {
throw new BusinessException("客户信用等级不足");
}
// 2. 创建项目
Project project = new Project();
project.setClient(client);
project.setStartDate(dto.getStartDate());
// ...其他字段设置
// 3. 生成合同
Contract contract = new Contract();
contract.setProject(project);
contract.setSignDate(LocalDate.now());
// ...合同明细处理
// 4. 持久化
projectRepository.save(project);
return contractRepository.save(contract);
}
5.2 材料领用流程
装潢现场最频繁的操作:
- 工长提交领用申请
- 仓库管理员审核
- 扫码出库
- 自动扣减库存
关键实现点:
java复制@Transactional
public MaterialOut applyMaterial(Long projectId, MaterialApplyDTO dto) {
// 库存检查
Material material = materialRepository.findById(dto.getMaterialId())
.orElseThrow(() -> new BusinessException("材料不存在"));
if(material.getStock() < dto.getAmount()) {
throw new BusinessException("库存不足");
}
// 记录出库
MaterialOut out = new MaterialOut();
out.setProject(projectRepository.getById(projectId));
out.setMaterial(material);
out.setAmount(dto.getAmount());
out.setOperator(SecurityUtils.getCurrentUser());
// 扣减库存
material.setStock(material.getStock() - dto.getAmount());
materialRepository.save(material);
return materialOutRepository.save(out);
}
6. 踩坑记录与优化建议
6.1 事务处理陷阱
在材料管理模块我们曾遇到一个隐蔽bug:
- 现象:偶尔出现库存扣减但出库记录丢失
- 原因:在Controller层开启了事务,但异常被自定义异常处理器吞掉
- 解决方案:
java复制// 错误示例
@RestController
@RequestMapping("/material")
public class MaterialController {
@Transactional // 不要在Controller层加事务
public Result apply(@RequestBody ApplyDTO dto) {
// ...
}
}
// 正确做法
@Service
public class MaterialService {
@Transactional // 事务注解放在Service层
public void apply(ApplyDTO dto) {
// ...
}
}
6.2 缓存一致性问题
项目进度看板曾出现数据显示不及时:
- 原因:多终端修改后未清除缓存
- 优化方案:
java复制@CacheEvict(value = "projectProgress", key = "#projectId")
public void updateMilestone(Long projectId, MilestoneDTO dto) {
// 更新逻辑
}
6.3 安全加固要点
在客户现场部署时发现的安全隐患:
-
默认的SpringBoot Admin端点未做保护
- 修复:增加基础认证
java复制@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/actuator/**").authenticated() .and().httpBasic(); } } -
材料价格接口未做权限控制
- 修复:增加@PreAuthorize注解
java复制@PreAuthorize("hasRole('PURCHASE')") @PostMapping("/price/update") public Result updatePrice(@RequestBody PriceDTO dto) { // ... }
这套系统在实际运行中最大的收获是:装潢行业的数字化不是简单的流程电子化,而是要深入理解每个业务环节的痛点。比如我们最初设计的材料出库流程很规范,但实际使用时工长们反映太繁琐,后来增加了"紧急领用"模式(事后补审批),才真正被接受使用。