1. 项目定位与功能模块梳理
1.1 这个系统到底解决什么问题
接触过建筑行业管理流程的朋友应该都有感触,大部分中小型施工单位的工程管理还停留在Excel加纸质单据的阶段。工程立项信息散落在不同人的电脑里,进度情况靠微信群里口头汇报,材料领用记在仓库本子上,月底对账的时候翻半天才能凑齐数据。这种模式下最直接的后果是:领导想了解项目整体进度,得挨个打电话;材料积压或短缺没人提前预警;几个项目并行时人员调配全凭经验。
做基于Java的建筑工程信息管理系统,核心目标就是把工程从立项、施工、材料领用、进度跟踪到完工归档的全生命周期数据统一收口到一个平台里。系统需要让三类人受益:管理层能实时看到每个工程的进度比例和资金占用情况;项目经理能把任务拆解到具体责任人,并跟踪每个节点是否延期;一线员工可以快速上报进度、申请材料,不用再拿着单据跑签字流程。
从毕设角度来说,这个选题覆盖了信息管理的完整链路,有CRUD、有流程状态流转、有权限控制、有数据统计,难度适中。相比纯电商或纯内容管理系统,它的业务逻辑更贴近真实场景,答辩时能讲的东西也多——这直接影响评分。
1.2 用户角色与核心功能清单
在实际动手前,先把角色和功能边界画清楚,后面做数据库设计和编码才能少走弯路。我建议按三类角色设计权限:
系统管理员:负责员工账号开通与禁用、系统基础参数配置、工程数据全局查看与维护。管理员不参与具体施工业务,但拥有最高数据权限。
项目经理:可以在自己负责的工程下创建子任务、分配施工人员、录入材料计划、填报工程进度、发起合同收款登记。项目经理看到的数据范围限定在自己名下的工程。
普通施工人员/材料员:施工人员只能查看自己被分配的任务,上报当日或本周完成进度;材料员负责材料入库、出库登记,查看库存预警信息。
功能模块划分直接决定后期开发的粒度。完整清单建议如下:
| 模块 | 核心功能 | 对应角色 |
|---|---|---|
| 工程信息管理 | 工程立项、工程列表、详情查看、修改、归档 | 管理员、项目经理 |
| 进度管理 | 里程碑节点设置、进度填报、进度明细、延期预警 | 项目经理、施工人员 |
| 材料管理 | 材料字典维护、入库登记、出库登记、库存查询、低库存预警 | 材料员 |
| 人员管理 | 员工档案、账号绑定、项目分配 | 管理员 |
| 合同管理 | 合同登记、收付款记录、应收应付提醒 | 项目经理、管理员 |
| 统计报表 | 工程状态分布、月度进度趋势、材料出入库汇总 | 管理员、项目经理 |
这套功能不算堆砌,每个模块都有明确的使用场景。比如材料管理里如果只有入库没有出库,那库存台账就永远不准,报表模块也会失去依托。所以我在做设计时坚持一个原则:宁可模块少一点,也要把每个模块的数据链路走通。
2. 技术选型的取舍逻辑
2.1 为什么是Java + SSM而不是其他组合
这个话题在毕设选型时几乎每次都会被问到。我的观点很直接:毕设项目的首要目标不是追新技术,而是在有限时间内做出功能完整、能跑通、答辩能讲清楚的作品。Java配合SSM(Spring + SpringMVC + MyBatis)这套组合在这个定位下几乎是最优解。
Spring负责对象管理和事务控制,SpringMVC负责请求路由和参数绑定,MyBatis负责SQL与Java对象的映射。三个框架各司其职,分层清晰,本身就是为"教科书式的MVC项目"设计的。相比之下,Spring Boot + MyBatis Plus虽然配置更少,但自动化程度太高,很多底层机制被隐藏,答辩时翻车概率反而更高——老师问"Spring自动配置的原理是什么",答不上来就尴尬了。当然,如果学校明确要求Spring Boot,那也可以把Spring Boot理解为"SSM的增强版",核心分层思想不变。
另一个备选是Spring Boot + JPA,但JPA在复杂多表查询和报表SQL上灵活性不如MyBatis。工程管理系统里有一堆按条件动态拼接SQL的查询场景,比如"按工程名称模糊查询+按状态筛选+按时间范围过滤",MyBatis的动态SQL写起来直观可控,这一点在开发效率上优势很明显。
2.2 前端方案选型:JSP + Layui还是Vue前后端分离
前端部分我推荐JSP + Layui的组合,理由很简单:这套方案不用单独启前后端两个服务,打成一个War包扔进Tomcat就能跑,部署演示都方便。Layui的表格、表单、弹窗组件覆盖后台管理系统的绝大多数场景,自带样式,不用自己调CSS。
有人可能会提"现在都流行前后端分离,用Vue + ElementUI更高级"。确实高级,但要考虑一个现实问题:前后端分离意味着要处理跨域、Token鉴权、前端构建打包、部署时还得用Nginx做静态资源转发,这些对毕设来说都是额外的工作量和答辩风险点。如果是为了学技术,前后端分离可以做;如果是为了完成毕设,JSP + Layui把精力集中在后端业务逻辑上,产出比更高。
顺带说一句,如果老师要求不能使用JSP,那我的备选方案是Thymeleaf模板引擎。它同样能配合SpringMVC做服务端渲染,语法上比JSP舒服一些,但整体思路一致,替换成本很低。
2.3 三层架构与包结构设计
项目包结构对应三层架构,这一点在写代码前就要定好,后面所有代码都按这个骨架填充:
com.example.construction ├── controller // 控制器层,接收请求、调用服务、返回视图 ├── service // 业务逻辑层,处理核心业务规则 │ └── impl // Service接口实现 ├── mapper // MyBatis数据访问接口 ├── entity // 数据库表对应的实体类 ├── vo // 视图对象,用于封装页面展示所需的数据 ├── interceptor // 登录拦截器、权限校验 ├── util // 工具类,如日期处理、导出Excel └── config // 配置类,如MyBatis配置、SpringMVC配置三层结构的好处在于职责边界清晰:Controller里不写SQL,Service里不出现HttpServletRequest,Mapper只负责数据库交互。后期调试时出现异常,很快就能定位是参数问题、业务逻辑问题还是SQL问题。
3. 数据库表结构与核心设计思路
3.1 从业务出发的ER模型
数据库设计是整个系统的地基,表关系如果理不清,后面所有模块都会打架。我在设计时围绕"工程"这个核心实体向外扩展。一张工程表立案主键后,进度、材料、合同都通过外键关联到工程ID,员工则通过"项目分配表"和工程建立多对多关系。
核心表清单如下:
- t_user(用户表):登录账号、密码、关联员工ID、角色类型、状态。
- t_employee(员工表):姓名、手机号、岗位、所属部门。用户表和员工表分开,好处是以后做考勤、工资等扩展时不再依赖登录账号。
- t_project(工程表):工程编号、名称、地点、建设单位、开工日期、计划完工日期、实际完工日期、预算金额、工程状态。
- t_project_task(任务表):任务名称、所属工程、负责人、计划开始/结束时间、实际进度、优先级。
- t_progress_record(进度记录表):记录每次填报的进度内容、完成百分比、填报人、填报时间。
- t_material(材料表):材料名称、规格型号、单位、当前库存数量、预警阈值。
- t_material_record(材料出入库记录表):类型(入库/出库)、关联材料、数量、经办人、关联工程、记录时间。
- t_contract(合同表):合同名称、合同金额、合作单位、签订日期、已收款金额、应收剩余金额。
- t_project_employee(项目人员分配表):工程ID、员工ID、分配角色。
3.2 几张关键表的字段详解
工程表是整个系统的数据枢纽,字段设计直接影响业务表达的完整度。我这里给出一个参考DDL,实际使用时按需增减:
CREATE TABLE t_project ( id INT PRIMARY KEY AUTO_INCREMENT, project_no VARCHAR(32) NOT NULL UNIQUE COMMENT '工程编号', project_name VARCHAR(100) NOT NULL COMMENT '工程名称', location VARCHAR(200) COMMENT '工程地点', builder VARCHAR(100) COMMENT '建设单位', leader_id INT COMMENT '项目经理ID,关联t_employee', budget DECIMAL(14,2) DEFAULT 0 COMMENT '预算金额', start_date DATE COMMENT '开工日期', plan_end_date DATE COMMENT '计划完工日期', actual_end_date DATE COMMENT '实际完工日期', status TINYINT DEFAULT 0 COMMENT '0-立项 1-施工中 2-已完工 3-已归档', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记', KEY idx_leader (leader_id), KEY idx_status (status) ) COMMENT='工程主表';这里有两个容易被忽略的设计点。第一是deleted逻辑删除字段,业务系统做删除操作时尽量用UPDATE标记而不是物理DELETE,这样数据可追溯,误删还能恢复。第二是status字段的数值含义一定要在代码里定义成常量或枚举,不能散落在各处魔法数字。进度记录表单独建一张表的意义在于保留每次填报的历史轨迹,项目经理可以看趋势,答辩时也是一个亮点。
材料出入库记录表同样要精细设计。出库时必须带关联工程ID和用途说明,这样月底统计"哪个工程消耗了哪些材料"就有据可查,而不是只有一个总数。数量字段我会用DECIMAL(12,2)而不是INT,因为部分建材以外单位按千克、米计量,可能出现小数。
4. 关键功能模块的实现细节与代码逻辑
4.1 登录认证与基于拦截器的权限控制
用户登录这块常见的坑是只验证账号密码,然后把用户信息往Session一塞就完事。这样做的后果是任何登录用户都能直接访问管理员接口,只要把URL改一改。我建议在系统中加一个登录拦截器,再加一个简单的角色校验工具。
登录拦截器核心逻辑如下:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口和静态资源 String uri = request.getRequestURI(); if (uri.contains("/login") || uri.contains("/static/") || uri.contains(".css") || uri.contains(".js")) { return true; } HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { // 未登录跳转到登录页 response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }角色权限在Service层加一个前置判断。比如删除工程的操作,只有管理员角色才能执行。判断逻辑抽出公共方法,防止每个接口重复写:
public void checkAdmin(User user) throws RuntimeException { if (user == null || !"admin".equals(user.getRoleType())) { throw new RuntimeException("无权限执行该操作"); } }密码存储这块注意不能用明文。至少用MD5加盐,更高要求可以用BCrypt。MD5加盐的思路是:注册时取用户名和一个随机字符串拼接后再进行哈希,把盐值一起存入数据库,登录验证时取出盐值重新计算。虽然MD5本身的强度一般,但对毕设系统来说已经足够防御"脱库后直接撞库"的风险。
4.2 工程信息CRUD与分页条件查询
工程列表页是整个系统中访问量最大的页面,一般要支持工程名称模糊查询、状态筛选、日期范围筛选,同时分页显示。MyBatis配合PageHelper插件是标准解法。Controller层代码:
@GetMapping("/project/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String projectName, Integer status, String startDate, String endDate, Model model) { PageHelper.startPage(pageNum, pageSize); List<ProjectVO> list = projectService.queryProjectList(projectName, status, startDate, endDate); PageInfo<ProjectVO> pageInfo = new PageInfo<>(list); model.addAttribute("pageInfo", pageInfo); return "project/list"; }对应的Mapper XML里用动态SQL拼接条件:
<select id="queryProjectList" resultType="com.example.construction.vo.ProjectVO"> SELECT p.*, e.employee_name AS leaderName FROM t_project p LEFT JOIN t_employee e ON p.leader_id = e.id <where> <if test="projectName != null and projectName != ''"> AND p.project_name LIKE CONCAT('%', #{projectName}, '%') </if> <if test="status != null"> AND p.status = #{status} </if> <if test="startDate != null and startDate != ''"> AND p.start_date >= #{startDate} </if> <if test="endDate != null and endDate != ''"> AND p.plan_end_date <= #{endDate} </if> AND p.deleted = 0 </where> ORDER BY p.create_time DESC </select>几个细节要注意。日期比较用字符串的话,必须保证前端的日期格式和数据库格式一致,比如都是yyyy-MM-dd;LIKE查询用CONCAT拼接而不是直接在参数里拼%,目的是防止SQL注入。LEFT JOIN查询项目经理姓名时,如果员工表还没有对应记录,结果就是NULL,前端显示要做好空值处理。
新增和编辑工程可以用同一个表单页面,通过ID是否为空判断是新增还是修改。提交时后端做必填校验和唯一性校验,工程编号的唯一性在数据库层面已经加了UNIQUE约束,但在Service层提前查一次,能给出更友好的提示信息。
4.3 进度模块的状态机设计与上报流程
工程进度不是简单的一个百分比数字,它需要按任务拆解再汇总。我的设计是:项目经理创建任务时设定计划起止时间和里程碑节点,施工人员每天或每周上报进度,系统根据所有任务的平均完成度汇总到工程总进度。
任务状态用整型表示,状态流转要遵循固定规则:
- 0-待开始:创建后默认状态,只有项目经理可以修改任务信息。
- 1-进行中:第一次上报进度后自动变为进行中,施工人员可以继续上报。
- 2-已延期:超过计划完成日期但进度未到100%,系统自动标记。
- 3-已完成:进度达到100%时状态变为已完成。
状态流转判断放在Service层统一处理:
public void reportProgress(Integer taskId, int percent, String remark, Integer workerId) { ProjectTask task = taskMapper.selectById(taskId); if (task == null) { throw new RuntimeException("任务不存在"); } // 校验当前用户是否被分配到此任务 if (!workerService.isAssigned(taskId, workerId)) { throw new RuntimeException("您未被分配到此任务"); } // 已完成的任务不能再上报 if (task.getStatus() == 3) { throw new RuntimeException("任务已完工,无需重复上报"); } // 写入进度记录 ProgressRecord record = new ProgressRecord(); record.setTaskId(taskId); record.setPercent(percent); record.setRemark(remark); record.setReportBy(workerId); progressMapper.insert(record); // 更新任务当前进度和状态 task.setCurrentPercent(percent); if (percent >= 100) { task.setStatus(3); } else { task.setStatus(1); if (task.getPlanEndDate().before(new Date())) { task.setStatus(2); } } taskMapper.updateById(task); }延期判断我不想依赖定时任务去扫表,那样要引入Quartz或Spring的@Scheduled配置,增加复杂度。我的方案是在列表查询时实时计算:凡是状态不是已完成、且计划完成日期小于当前日期的任务,在一段SQL里直接打上延期标记,展示给用户。这样不落库,逻辑简单,效果一样。
4.4 材料库存管理与低库存预警
材料模块的难点在于入库和出库操作必须保证库存数据一致性。这里的关键是数据库事务:每次出库登记,插入一条material_record记录,同时更新material表的库存字段。两个操作要么都成功,要么都失败,否则对账必错。Spring的@Transactional注解可以保证这一点:
@Transactional(rollbackFor = Exception.class) public void outbound(MaterialRecord record) { // 1. 校验库存是否充足 Material material = materialMapper.selectById(record.getMaterialId()); if (material.getStock() - record.getQuantity() < 0) { throw new RuntimeException("库存不足"); } // 2. 插入出入库记录 materialRecordMapper.insert(record); // 3. 扣减库存 material.setStock(material.getStock() - record.getQuantity()); materialMapper.updateById(material); }低库存预警不依赖消息推送,在库存列表页查询时加一个判断条件即可,也可以用一张视图或子查询把"当前库存小于预警阈值"的材料单独列出。实操中我更倾向于在SQL里过滤一次,页面顶部显示预警条数:
SELECT COUNT(*) FROM t_material WHERE stock <= warn_threshold AND deleted = 0材料管理还有一个容易忽略的细节:同一材料不同批次价格不同,后续做成本核算会有争议。简单处理的话,在出库时用加权平均价计算成本,这个公式在报表模块有用。加权平均价 = 该材料所有入库记录金额总和 / 入库数量总和,实现不复杂,但能体现出系统设计上的周全。
4.5 统计报表的SQL聚合与图表展示
报表模块是展示系统价值的窗口,也是答辩时比较加分的部分。我实现了三个方向的统计:工程状态分布、近6个月进度填报趋势、材料出入库月度汇总。
工程状态分布很简单,按status字段做GROUP BY,然后传入前端用饼图展示。关键在统计数据的时间维度设计。近6个月进度趋势统计的是每月上报的记录条数和平均完成百分比,SQL大致这样:
SELECT DATE_FORMAT(report_time, '%Y-%m') AS month, COUNT(*) AS reportCount, ROUND(AVG(percent), 2) AS avgPercent FROM t_progress_record WHERE report_time >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(report_time, '%Y-%m') ORDER BY month图表展示这块,我推荐使用ECharts,在JSP页面引入它的JS文件,后端传JSON数据到前端,初始化图表。ECharts的饼图、柱状图、折线图文档齐全,照着样例改改数据就能用。
报表模块还要注意一个权限细节:普通施工人员不应该看到整个公司的汇总报表,所以查询时Service层要先判断角色,非管理员只能查看与自己相关的工程数据。这个逻辑通过"登录用户ID"作为查询参数一层层往下传,而不是直接查全表后在前端过滤。
5. 实操过程记录:从零到可演示的完整流程
5.1 开发环境与版本选择
先列一下我实操时的环境版本,这些版本组合验证过,兼容性没有问题:
- JDK 1.8:经典稳定,毕设项目完全够用,不需要追求JDK 17的新特性。
- Maven 3.6+:依赖管理和项目构建。
- MySQL 5.7:支持事务、视图、存储过程,性能足够。也可用8.0,注意驱动版本要对应。
- Tomcat 8.5或9.0:SSM项目打War包部署。
- IntelliJ IDEA:开发工具,装好Lombok插件可以少写很多getter/setter。
数据库连接池我用Druid,配置一个监控页面,能查看SQL执行情况,这在排查慢SQL时非常有用。
5.2 项目骨架生成与配置
创建Maven项目后,pom.xml需要引入的依赖大概有这些:spring-context、spring-webmvc、spring-jdbc、mybatis-spring、mybatis、mysql-connector-java、druid、jstl、lombok、jackson-databind、pagehelper。写pom.xml的时候注意版本冲突。我的经验是先确定一个spring.version版本,所有spring相关模块都用同一个版本号,避免出现NoSuchMethodError这类让人摸不着头脑的异常。
配置文件的组织建议:application.properties放数据库连接、日志级别等。spring-mvc.xml配置组件扫描、视图解析器、静态资源放行。spring-mybatis.xml配置数据源、SqlSessionFactory、Mapper扫描和事务管理器。
<!-- spring-mybatis.xml 关键配置 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/construction?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="你的密码"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean>编码问题建议在最开始就统一处理。数据库连接URL加characterEncoding=utf8、页面设置UTF-8、IDEA全局编码改成UTF-8、Tomcat的Connector加上URIEncoding="UTF-8"。这四层少了哪一层,都有可能出现中文乱码,而且这种问题很难排查,不如前置扫清。
5.3 推荐编码顺序:先基础后业务
我的实操顺序是:建数据库表 → 编写实体类 → 编写Mapper接口和XML → 编写Service接口与实现 → 编写Controller → 编写JSP页面。按这个顺序推进的原因是每一层都依赖下一层,如果先把所有实体类建好再去写其他层,思想负担最小。
优先实现的功能模块顺序是:登录模块 → 工程管理 → 进度管理 → 材料管理 → 合同管理 → 报表统计。登录模块是其他所有功能的前提;工程是数据核心,先把工程的增删改查做通,后面进度、合同都围绕工程ID来写,思路不混乱。
写JSP页面时,我建议先写一个公共的头部和侧边栏include页面,把所有菜单链接放进去。后面每加一个功能页面只需要引入这个公共部分,避免了每个页面重复复制菜单HTML的问题。这个做法节省的时间远比想象的多。
5.4 本地调试与部署演示要点
开发阶段调试用的方式是IDEA里配置Tomcat,直接运行项目。Debug模式用得很勤,主要是看Service层的数据流转,特别是查询条件拼接是否正确。
部署演示前要检查的事项我列一下,都是实际踩过的坑:
- 数据库初始化脚本要在演示环境重新执行一遍,确认所有表都建好,基础字典数据(如材料类别)有初始记录。
- 管理员账号手动插入一条,密码加密后写入。
- Tomcat的端口号避免和其他服务冲突,统一用8080即可。
- 演示用的测试数据要有一定数量,比如10多条工程、50多条进度记录、若干材料出入库记录,这样报表和列表页看起来才有说服力。空荡荡的页面对答辩效果影响很大。
- 导出功能如果需要,可以用POI封装一个简单Excel导出工具。材料出库明细导出、工程信息导出,这两个导出在答辩演示时比较实用。
6. 常见问题与排查技巧实录
6.1 Maven依赖冲突与下载缓慢
SSM项目几十个依赖,版本冲突几乎是必然遇到的。常见的报错有ClassNotFoundException和NoSuchMethodError,前者可能是jar没下载完整,后者一般是同个类存在多个版本导致加载到旧版。我的排查习惯是用IDEA的Maven面板看依赖树,或者执行mvn dependency:tree,直接找到重复依赖,然后用 标签排除不需要的版本。
Maven下载慢的问题,可以换国内镜像源。在settings.xml里配置阿里云镜像,下载速度提升非常明显,这一步在环境搭建时第一时间做好,后面能省大量等待时间。
6.2 页面跳转404与请求路径不对
SSM项目里404分两种情况:一是XML配置中视图解析器的前缀和后缀不对,导致Controller返回的"project/list"定位不到JSP文件;二是Controller的@RequestMapping路径和前端表单提交的action不一致。
最有效的排查方式是按顺序查:网络请求发了没有、请求URL是否命中了Controller、Controller return的视图名能否解析到文件。可以在Controller方法里加System.out.println打印日志确认第一层是否到达,前端F12看一眼网络请求的状态码,基本能定位。
6.3 日期格式化与前端显示差异
Java实体里用Date类型的字段,传给JSON序列化后默认是一长串时间戳或者带毫秒的ISO格式,前端显示为"2024-03-01T08:00:00.000+00:00",非常难看。解决办法是在字段上加@JsonFormat注解:
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date planEndDate;如果页面是JSP直接通过EL表达式显示日期,则在C标签格式化:<fmt:formatDate value="${project.planEndDate}" pattern="yyyy-MM-dd"/>。注意在JSP上不加timezone配置时,本地日期和数据库日期可能因为时区差异差8小时,这个坑我印象很深,统一数据库连接参数和JSON格式的时区后彻底解决。
6.4 事务失效的典型场景
@Transactional注解不起作用,是个经典面试题也是实操常踩的坑。最常见的场景是同一个类内部方法调用,比如ServiceImpl里的方法A调用了自身类的方法B,B上有@Transactional,但实际上事务不生效。因为事务是通过Spring的代理对象实现的,自调用走的是this.method()而不是代理对象。解决方式是注入自身代理或拆分成不同的Service类调用。
另一个场景是事务方法里try-catch吞掉了异常。如果方法内部把SQL异常抓了并返回null,事务管理器看不到异常,自然不回滚。事务要生效,异常必须抛出来,rollbackFor指定为Exception.class。
6.5 数据统计结果与预期不符
报表模块最常遇到的问题有两个:一个是统计口径错误,比如把"所有入库记录数"当成"材料入库总数量",一个是分组维度不对。我的排查方法是先把报表对应的SQL单独在Navicat里执行一遍,确认原始结果正确,再检查Controller传参是否正确、图表数据接收是否正确。
Material出入库记录如果外键关联到工程的ID,统计报表时要用INNER JOIN关联工程表才能过滤出有效数据。批量删除时外键关联导致删除失败的问题也很常见,解决方案是在删除前判断是否有子表记录引用,或者给外键字段加上ON DELETE CASCADE,但这只适合强耦合关系,谨慎使用。
7. 个人实操体会与这个项目的扩展方向
整个项目从表设计到完整跑通演示,我做下来大概花了两周半。前三天基本都在设计数据库和搭建环境,真正写业务代码反而是后面一周多的事情。经验感受最深的就一点:数据库设计得越细致,后端的编码就越顺畅。一开始偷懒把材料记录表和合同记录表字段设计得过于精简,结果做报表模块时发现缺少需要字段,又回头改表和实体类,改动成本比一开始就设计好高出一倍。
这个系统如果后续要做扩展,我有三个方向比较推荐。第一个是把工作流加进来,比如请假审批、材料采购审批,可以引入简单的状态机加待办表,而不需要上Activiti那么重的流程引擎。第二个是增加消息通知功能,比如任务分配后给相关人发送提醒,延期任务主动推送邮件,可以用Spring的事件机制实现。第三个是移动端适配,不做原生App,做一个H5版本让施工人员在手机浏览器上填报进度和材料申请,针对工地环境使用频率比较高的场景。
最后分享一个小技巧:工程管理系统的演示数据一定要贴近真实。我在做演示数据时参考了住宅小区、市政道路、厂房改造三种不同类型的工程,每种工程下的任务、材料记录都不一样。这样答辩演示时,老师问"系统能不能适应不同类型工程"之类的问题,你可以直接指着一套数据说明,比口头解释有力得多。这也是我做完整个项目后最满意的一部分。