Spring Boot重构装修公司管理平台:从架构设计到上线实践
2026/9/16 14:18:09 网站建设 项目流程

简介:面向高校毕业设计及课程作业场景,这份基于Spring Boot与Vue的装修公司管理平台源码,覆盖管理员/员工注册登录与角色权限控制、项目阶段跟踪与任务分配、客户信息及沟通记录、预算编制与报价单生成、材料库存预警与采购订单管理等核心业务,适合JavaWeb学习者深入参考前后端分离架构与RBAC权限设计。资源共431个文件,以Java后端、Vue前端组件、JavaScript逻辑、SVG图标资源为主,并包含SQL初始化脚本、Maven配置及项目文档,压缩包大小10.23MB,目录结构清晰,便于导入IDE直接运行调试与二次开发。已有47人学习下载,可据此快速搭建装修公司管理原型,理解权限拦截、CRUD模块组织及供应链数据流转等关键实现,对完成毕业设计或课程报告有直接帮助。

1. 装修公司管理平台,为什么值得用Spring Boot重构一遍

装修公司的管理和其他行业差别很大:设计师签合同在前,工长施工在后,材料员下单穿插其中,财务还要按节点付款。很多中小装修公司至今靠Excel加工资表协同,材料超支、工期延误都发生在Excel和微信聊天记录里,数据完全断开。用Spring Boot搭建装修公司管理平台,本质不是做一个增删改查后台,而是把合同、工地、材料、验收串联成一条可追踪的状态链。Spring Boot适合这种业务的原因是启动快、自动配置成熟、一个小团队两周就能跑通最小闭环。但它真正考验人的不是CRUD,而是工程目录规范、持久层选型、文件上传和线上可观测性。下面按实际落地顺序展开,给的是能直接复制改造的代码和参数,适合正在自己搭后台的Java工程师参考。

2. Spring Boot四层架构映射装修业务,先把目录规范立住

2.1 装修业务怎么拆进四层架构

Spring Boot项目常用的四层架构是Controller、Service、Repository、Entity。装修公司管理平台也沿用这套分层,但业务上有自己的侧重。Controller层负责接收HTTP请求、做基础参数校验和返回统一结果;Service层放业务规则,比如报价单审核、材料付款状态流转、工地竣工校验;Repository层负责数据库访问;Entity层把数据库表映射成Java对象,状态字段用枚举而非字符串。

容易踩的坑是把查询逻辑全部堆在Controller里。比如“某工长名下所有工地的材料超支汇总”这种报表,正确做法是在Service里组装查询参数,再调用Repository的聚合查询。如果Controller直接操作Repository,后续要加数据权限、操作审计时,就得翻遍所有接口。目录结构建议至少包含controller、service、repository、entity、dto、config、common这几个包,dto用于前后端参数传递,common放统一返回对象和异常处理。

2.2 按Spring Boot目录规范搭工程骨架:最小依赖

常见做法是用Spring Initializr生成工程,或者手动创建Maven项目。核心依赖只需要三个starter,下面是pom.xml的关键片段。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

spring-boot-starter-web提供内嵌Tomcat和Spring MVC,负责处理装修平台的HTTP请求;starter-data-jpa提供Repository、实体管理和事务支持;mysql-connector-j是MySQL驱动。这个组合不用手动指定版本号,只要parent里声明了spring-boot-starter-parent。如果项目需要权限控制,再加spring-boot-starter-security;需要参数校验,加spring-boot-starter-validation,但不要一上来把Redis、MQ全加进去,依赖越多启动越慢,维护成本也越高。

2.3 持久层选型:MyBatis-Plus还是Spring Data JPA

装修公司管理平台的持久层,团队习惯写SQL的选MyBatis-Plus,习惯领域模型的选Spring Data JPA。下面是两种方式在查询工地列表时的对比。

对比项MyBatis-PlusSpring Data JPA
SQL控制力强,XML或注解SQL一般,复杂查询要Specification
多表关联自由join@ManyToOne或Specification
实体生成反向生成表结构先建实体再同步表
适合场景报表多、SQL优化频繁状态流转多、领域模型清晰

我在装修业务里一般这样选:合同、工地、材料下单这类强状态流转的模块走JPA,因为枚举状态和@Version乐观锁处理起来很顺手;财务报表、材料汇总这类表连接复杂的场景走MyBatis-Plus。需要说明的是,不建议一个平台同时上两套持久层框架,除非模块边界非常清楚,否则事务管理和缓存一致性会变得很痛苦。

如果用JPA,Repository层只需要写接口:

public interface ProjectRepository extends JpaRepository<Project, Long> { List<Project> findByStatus(ProjectStatus status); }

这个接口由Spring Data在启动时自动生成实现,方法名里的findByStatus会解析成where status = ?。复杂查询可以加@Query注解写JPQL,或者使用Specification做动态条件。参数名必须与实体属性名一致,否则启动报错。

3. 装修公司核心表设计:合同、工地、材料与状态机

3.1 从Excel搬到MySQL:合同和工地拆成两张表

装修业务中,一份装修合同对应一套房产,但会拆成多个施工阶段和多次材料批次。如果直接把Excel宽表搬进来,后续验收、付款、材料核销都会变成噩梦。常见做法是拆成contract、project、milestone、material_order四张核心表。

表名关键字段说明
contractid, customer_name, project_address, amount, sign_time合同主体
projectid, contract_id, leader, manager, status, start_time, end_time工地实体
milestoneid, project_id, title, planned_date, actual_date, state施工节点
material_orderid, project_id, material_name, quantity, price, verify_status材料下单

核心关联是project.contract_id指向contract.id。合同和工地分开的原因在于:合同内容相对固定,而工地状态频繁变化,拆开可以避免每次改状态都更新合同行,减少数据库锁冲突。milestone表里每个项目会插入水电、泥瓦、木工、油漆等固定节点,actual_date为空表示还没完成,这是后续自动判断工期的数据基础。

3.2 施工节点状态机用枚举约束,不用字符串

工地状态、节点状态、材料核验状态都应该用Java枚举,不要直接用字符串散落在代码里。枚举能校验合法值,还能把状态描述集中管理。以下是一段工地状态的枚举定义:

public enum ProjectStatus { PENDING("待开工"), CONSTRUCTING("施工中"), MATERIAL_VERIFIED("材料已核验"), FINISHED("已竣工"); private final String desc; ProjectStatus(String desc) { this.desc = desc; } }

实体字段上使用@Enumerated(EnumType.STRING),数据库存的是字符串,可读性好,以后调整枚举名也不影响历史数据。状态转移规则放在Service层统一校验,比如只有CONSTRUCTING状态才允许登记节点验收,FINISHED状态不允许再上传材料单。不要在每个Controller里散落if判断,否则状态流会越改越乱。

3.3 用Spring Data JPA Specification做工地动态筛选

管理端经常需要按地区、状态、项目经理筛选工地,条件组合不固定,写多条SQL很冗余。用JPA Specification可以动态拼接查询条件。以下是一个按状态和项目经理查询的示例:

public List<Project> searchProject(ProjectQuery query) { Specification<Project> spec = (root, cb, cq) -> { List<Predicate> predicates = new ArrayList<>(); if (query.getStatus() != null) { predicates.add(cb.equal(root.get("status"), query.getStatus())); } if (StringUtils.hasText(query.getLeader())) { predicates.add(cb.like(root.get("leader"), "%" + query.getLeader() + "%")); } return cb.and(predicates.toArray(new Predicate[0])); }; return projectRepository.findAll(spec); }

Specification里root.get("status")使用的是实体属性名,不是数据库列名。返回值可以叠加Pageable实现分页,例如projectRepository.findAll(spec, PageRequest.of(0, 10))。这里有个常见误区:like查询里模糊匹配值要自己拼接%号,不要写在数据库字段上,否则索引失效。

4. Spring Boot装修现场图片上传:文件与业务参数怎么一起收

4.1 本地磁盘还是对象存储

装修现场图片、材料验收单、报价单扫描件,单张大小从几百KB到几MB。小型装修管理平台通常私有化部署,数据量不会短期爆炸,此时直接存本地磁盘最省事;如果部署在多台服务器,或者需要CDN加速访问,再考虑接入对象存储。本地和对象存储的切换点,取决于文件访问带宽和备份策略,而不是盲目跟风。

存储方式优点缺点
本地磁盘部署简单、无额外费用扩容困难、多机共享麻烦
对象存储扩容简单、自带冗余需要SDK和网络配置、按量计费

初期阶段,把文件存本地磁盘,数据库里只保存相对路径,是最容易维护的方案。

4.2 MultipartFile接口同时接收文件和业务字段

装修平台回传现场照片时,前端需要同时提交工地ID和照片类型,文件和其他参数要放在同一个请求里。Spring Boot使用MultipartFile接收文件,其他字段用@RequestParam接收。以下是一个上传接口的实现:

@PostMapping("/upload") public Result<String> upload(@RequestParam Long projectId, @RequestParam String type, @RequestParam MultipartFile file) throws IOException { String originalName = file.getOriginalFilename(); String ext = originalName.substring(originalName.lastIndexOf('.')); String saveName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + ext; Path dir = Paths.get(uploadDir, projectId.toString(), type); Files.createDirectories(dir); file.transferTo(dir.resolve(saveName)); return Result.ok("/uploads/" + projectId + "/" + type + "/" + saveName); }

参数说明:projectId用来定位所属工地,type区分验收照片还是材料单,file就是上传的文件对象。文件名使用时间戳加UUID前缀,避免中文名乱码和重名覆盖。Files.createDirectories(dir)会递归创建目录,transferTo要求父目录已存在,否则抛IOException。这里的uploadDir通过配置类注入,通常指向项目运行目录下的./upload

4.3 配置文件上传大小与静态资源映射

Spring Boot默认上传限制是1MB,装修现场照片很容易超限。需要在application.yml中显式修改:

spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB web: resources: static-locations: file:${upload.dir:/tmp/}

max-file-size限制单个文件大小,max-request-size限制一次请求总大小。这里把上传目录设置为20MB,足够放一张高清验收图。static-locations指定本地磁盘目录对外暴露为静态资源路径,配置后浏览器可以直接访问/uploads/projectId/type/fileName。需要注意,配置这个属性后默认的classpath:/static会被覆盖,如果你想同时保留前端打包的静态资源,需要把classpath路径也加进去,用逗号分隔。

5. Actuator未授权访问与Filebeat日志采集,上线前要处理的三个点

5.1 只暴露health端点并限制访问

Spring Boot Actuator提供了大量内部信息,很多平台为了调试把所有端点打开,结果导致env、configprops里的数据库密码裸奔。安全做法是只暴露health和info,其余全部关闭。

management: endpoints: web: exposure: include: health,info
端点是否暴露说明
health探活和基本健康状态
info版本和描述信息
env含环境变量与配置值
configprops含数据源等内部配置
metrics视情况需要监控时可单独开启

如果一定要暴露metrics,建议加上spring-boot-starter-security,并对/actuator路径做认证拦截。后续接入Prometheus时也需要metrics端点,但绝不能裸奔在公网。

5.2 用Filebeat把装修平台的日志送进ELK

日志只存在服务器上,排查问题就需要登录机器,效率很低。常见做法是Filebeat作为日志采集器,读取application.log后传给Logstash或直接送Elasticsearch。下面是filebeat.yml的核心配置:

filebeat.inputs: - type: filestream paths: - /var/log/decoration-platform/*.log output.elasticsearch: hosts: ["es-node:9200"]

filestream类型会记录读取位置,日志文件切割后不会重复发送。paths路径要对应Spring Boot配置的logging.file.name目录。Docker部署时,Filebeat容器和Spring Boot容器挂载同一个日志卷,Filebeat能直接读取宿主机日志文件。这个组合能快速定位线上问题,比如用户报“上传照片后页面转圈”,搜索日志里的异常堆栈比逐个服务器翻tail要快得多。

5.3 Spring Boot 3.x到4.x迁移:DataSourceAutoConfiguration位置变化

Spring Boot 4.x里自动配置类按模块重组后,网上最常见的问题是DataSourceAutoConfiguration找不到。很多自定义starter或老项目会显式写@Import(DataSourceAutoConfiguration.class),升级后包路径发生变化,编译时直接报ClassNotFoundException。这里给一个排查思路:先看启动类里是否有Import自动配置类的代码,再让IDE搜索org.springframework.boot.autoconfigure.jdbc包下的DataSource类。大多数正常项目不会直接引它,报这个错的多半是自定义starter写死了旧包名。升级前对比官方迁移指南里的package变化列表,逐项替换即可。

6. 用Spring事件机制把装修进度通知写进Service层

6.1 事件发布解耦状态流转与通知逻辑

工地节点完成后,需要通知业主验收、提醒材料员下单。如果把短信、消息通知代码直接塞进Service,Service会越来越臃肿。用Spring事件机制,可以让状态流转只发事件,通知逻辑放进Listener。先定义事件:

public class ProjectStatusChangedEvent extends ApplicationEvent { private final Project project; public ProjectStatusChangedEvent(Project project) { super(project); this.project = project; } }

Service层在状态变更后调用applicationEventPublisher.publishEvent(new ProjectStatusChangedEvent(project))。事件发布后,Spring容器同步调用所有匹配的Listener。这样做的好处是Service不需要依赖短信SDK,后续加微信通知只需要增加新Listener。

6.2 异步监听与失败补偿

Listener上加@Async后,通知发送不阻塞验收主流程。前提是启动类上要有@EnableAsync

@Async @EventListener public void handleProjectStatusChanged(ProjectStatusChangedEvent event) { sendSms(event.getProject()); }

短信发送失败时,不要直接在Listener里吞掉,要写入一张notification_record表记录失败原因和重试次数,由定时任务每分钟补偿一次。重试时注意按项目ID聚合,避免同一工地状态抖动导致重复通知。这个事件机制比在Service里硬编码通知逻辑清晰得多,也是Spring Boot项目里管理状态联动最自然的做法。

本文还有配套的精品资源,点击获取

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

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

立即咨询