刚帮一个学弟审完他的毕设开题,题目就是“基于SSM框架的电子产品质量监督系统”。说实话,第一次看到这个题目时我第一反应是“又一个CRUD管理系统”,但仔细捋了一遍业务流程后我发现这个题目被大多数人低估了。电子产品质量监督并不是简单的商品信息增删改查,它背后是一条完整的管理链路:产品备案、标准库维护、抽检计划制定、检测任务派发、检测结果录入与判定、不合格产品处置、公告公示、投诉反馈闭环。任何一个环节没想清楚,系统做出来都只是“看起来像那么回事”,一追问业务逻辑就站不住脚。
这篇博文我打算完整拆一遍这个毕设题目的做法,从需求分析、数据库设计、框架选型逻辑、核心流程代码实现到答辩前的坑位排查,把我在类似项目上的经验全部写出来。无论你是自己要做这个题目,还是在给学弟学妹指导选题,这篇文章都能让你少走很多弯路。
1. 选题价值分析:质量监督系统到底在监督什么
很多同学看到“电子产品质量监督系统”这个题目,第一反应是做一个“商品管理+评价展示”的网站,这种理解其实只触及了皮毛。如果把质量监督理解为“给产品打分、展示合格不合格”,那系统就是一层皮,做完除了练手没有任何竞争力。真正要把这个毕设做出彩,必须先搞清楚质量监督的业务本质是什么。
电子产品质量监督的核心是“过程留痕”和“结论可追溯”。一件电子产品从企业申报、标准核验、抽样送检、实验室检测到结果公示,中间每一步都要有记录、有状态、有责任人。监督管理部门需要的不是一个简单的产品数据库,而是一套能还原“某批次产品为什么被判不合格”的完整证据链。
从角色上划分,这类系统通常涉及四类用户:企业用户(申报产品、提交质检申请、查看结果)、检测机构人员(接收任务、录入检测数据、出具报告)、监督管理员(制定抽检计划、审核结果、发布公告、处理投诉)、社会公众(查看公告、提交投诉建议)。权限设计必须围绕这四类角色展开,不同角色看到的数据范围完全不同,这就天然引入了RBAC权限模型的设计需求。
流程上,一个典型的电子产品质量监督业务周期可以拆解为以下环节:产品备案 → 制定监督抽查计划 → 确定抽检批次与样品 → 派发检测任务 → 录入检测原始数据 → 自动/人工判定 → 出具质量监督报告 → 公示合格与不合格名单 → 不合格产品处置与复查 → 投诉与反馈闭环。
理解这十条链路后你会发现,这个系统的真正难点不在“增删改查”,而在两个地方:第一是状态流转,一个抽检批次从“待派发”到“检测中”再到“已判定”“已公示”,每一步的合法状态迁移必须严格控制;第二是数据关联,产品、批次、任务、报告、公示、投诉这些实体之间不是孤立的表,而是通过业务事件串联起来的网状数据模型。
所以我的建议是:拿到这个题目先别急着建工程写代码,花两天时间把上述业务流程图画清楚,把角色和状态的迁移路径捋明白。这一步的价值比后面任何一个功能模块都大——它能直接决定你的数据库表和Service层接口怎么设计。
2. SSM框架选型逻辑:为什么不是Spring Boot,以及怎么配置才能少踩坑
2.1 毕设用SSM的真实理由
现在很多新项目已经转向Spring Boot+MyBatis-Plus的组合,那为什么这个毕设还要用SSM(Spring + Spring MVC + MyBatis)?这里面的取舍需要理解透彻,答辩时老师几乎必问。
SSM之所以在毕业设计里长盛不衰,有三个原因:一是教学体系中Spring、Spring MVC、MyBatis三门课程通常单独开课,SSM是“课本知识在项目里怎么组合”的最直接样本,用Spring Boot反而把很多配置封装掉了,老师想问底层的时候反而不好展示;二是SSM的配置全部显式可见,框架原理讲起来有抓手,比如Spring IoC容器怎么装配Bean、Spring MVC的前端控制器DispatcherServlet怎么拦截请求、MyBatis的SqlSessionFactory怎么读取Mapper,这些在SSM里都是能明明白白说清楚的;三是(这条很现实)大部分毕设参考代码和论文资料都是SSM版本,遇到问题容易找到参照。
但SSM的问题也很明显:依赖jar包版本容易冲突、XML配置繁琐、没有Spring Boot的自动装配和健康检查,环境搭建本身就够新手喝一壶。所以如果你不是特别想展示框架底层原理,也可以考虑Spring Boot重写,但既然题目写了SSM,我的建议是顺着题目来,把配置做扎实,这本身就是毕设工作量的一部分。
2.2 一份能跑起来的SSM核心配置参考
SSM整合的经典配置分为四层:web.xml配置Spring MVC入口与Spring容器监听器、spring-mvc.xml配置注解驱动与视图解析器、spring-mybatis.xml配置数据源与SqlSessionFactory、mybatis-config.xml配置全局参数。下面给出一套我实际用过的精简配置,版本按主流兼容的组合来选,避免陷入jar地狱。
web.xml中需要同时声明ContextLoaderListener(加载Spring根容器)和DispatcherServlet(加载Spring MVC子容器),子容器会继承父容器的Bean,这就意味着Service和Mapper放在根容器扫描,Controller放在子容器扫描,两套扫描路径不能重叠,否则Controller会被实例化两次或出现Bean冲突,这是SSM整合最常见的翻车点。
<!-- web.xml 核心片段 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>这里有个细节新手极易踩雷:DispatcherServlet映射的url-pattern如果用“/”,会覆盖Tomcat默认的静态资源处理器,导致css、js、jpg全部404。解决办法是在spring-mvc.xml中配置<mvc:default-servlet-handler/>,放行静态资源请求。
数据源和MyBatis整合这层,我优先推荐用Druid连接池,不仅因为性能稳定,更重要的是Druid自带监控页面StatViewServlet,调试时能直接查看SQL执行情况,排查慢查询和连接泄漏都非常方便。配置时注意:driverClassName、url、username、password四项必须与你本机的MySQL版本对应,时区参数serverTimezone要显式声明,否则连接MySQL 8.x会直接报错。
<!-- spring-mybatis.xml 核心配置 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/quality_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.quality.dao"/> </bean>2.3 Maven依赖版本的选择策略
SSM的依赖版本不能乱配,不同版本组合直接决定项目能不能启动。我个人实测过比较稳定的组合是:Spring 5.1.x + MyBatis 3.5.x + mybatis-spring 2.0.x + Druid 1.1.x + Java 8。这个组合的兼容性已经过大量项目验证,像Spring 6.0+或Java 17这种较新环境很多老教程样例跑不通,毕设图稳定就不必追求最新。
3. 数据库设计与权限模型落地:先想清楚这六个核心表
数据库设计是答辩时老师重点考察的环节。一张一张表讲太多,我直接抽取这个题目最核心的六张业务表拆解其设计思路,把这些表建明白,整个系统的数据骨架就立住了。
第一张是产品备案表,字段包括:产品编号、产品名称、品牌、型号规格、生产企业ID、执行标准编号、备案状态(草稿/已提交/已通过/已驳回)、备案日期。这张表是整个监督体系的入口,所有后续流程都围绕“产品”这个核心实体展开。要注意的是产品编号必须设计成业务编号而非自增主键,格式建议为“CP+年月日+序号”,比如CP20240601001,这样在公示、报告、投诉中引用时一眼可读。
第二张是抽样批次表。一次抽检不是针对单个产品,而是针对某个厂家、某个生产批次的一批样品,所以需要batch表记录:批次编号、关联产品ID、批次数量、抽样基数、抽样日期、抽样地点、抽样人员、状态字段。批次的引入把“产品档案”和“检测事件”解耦,一个产品可以经历多次抽检,每次抽检都能独立追溯,这是整个系统可追溯性的关键设计。
第三张是检测任务表。一次抽检可能涉及多项检测项目(如电气安全、辐射骚扰、低温试验等),检测任务表就是批次与检测项目之间的桥接表:任务编号、批次ID、检测机构ID、指定检测人员ID、任务状态(待接单/检测中/已完成/已退回)、计划完成日期、实际完成日期。
第四张是检测结果表。存储具体检测项的原始数据,包括:检测项名称、标准限值、实测值、单项结论(合格/不合格/不适用)、检测方法、检测设备编号、检测日期。这张表的粒度要到“每个批次下的每个检测项”,后续自动判定和生成报告都从这里取数据。
第五张是质量报告表。一份报告对应一个批次,报告编号、批次ID、综合结论(合格/不合格)、判定依据、签发人、签发日期、报告附件路径。报告的生成可以由系统汇总检测结果表自动起草,再由负责人人工复核签发,这个“自动起草+人工签发”的流程写进论文里是一个亮点。
第六张是公告公示表。公示编号、标题、内容、公示类型(合格/不合格/风险警示)、关联批次ID、公示日期、公示状态(待发布/已发布/已撤回)。对外的信息发布必须与内部业务数据关联,避免管理员手工复制黏贴产生数据不一致。
除了这六张业务表,还需要字典表、系统用户表、角色表、用户角色关联表、操作日志表。其中字典表用来维护检测项类型、产品品类、企业类型等可扩展枚举,设计成一个code和value的映射表,code存程序里,value存业务含义,这样前端下拉列表永远从字典表读取,不用改代码就能调整选项。
权限模型我建议用“用户-角色-权限”的RBAC实现,而不是简单地在用户表里加一个role字段。因为“检测人员”和“管理员”虽然是不同角色,但都可能有“查看报告列表”的权限,用多对多关系表才能灵活复用权限,也为以后加角色留空间。Spring MVC的拦截器按权限码(如quality:report:view、quality:batch:assign)拦请求,比按角色名判断更严谨。
4. 核心业务流程实现:状态机、事务控制与代码链路
4.1 任务状态流转的设计
质量监督系统的灵魂在状态流转。一个检测任务从创建到归档,状态必须单向或按规则迁移,不能随便乱跳。我推荐在Service层用一组常量定义状态,并在每次状态变更时做合法性校验,不要依赖前端传什么就改什么。下面用检测任务的状态变化举个例子。
public class TaskStatus { public static final int CREATED = 0; // 待派发 public static final int ASSIGNED = 1; // 已派发(待接单) public static final int TESTING = 2; // 检测中 public static final int COMPLETED = 3; // 已完成(待复核) public static final int APPROVED = 4; // 已审核通过 public static final int REJECTED = 5; // 已退回 }状态迁移的核心原则是:每个动作只允许固定的起点状态到固定的终点状态。例如“派发任务”只允许从CREATED到ASSIGNED,“确认接单”只允许从ASSIGNED到TESTING,“提交检测数据”只允许从TESTING到COMPLETED。控制在Service层实现,Controller层只负责参数接收和结果返回,这个分层习惯从毕设就开始养成,对你后面进公司写代码有直接帮助。
我自己的经验是:状态合法性校验必须写在开启事务的方法内部,不是校验完再开事务,而是校验和数据库更新必须在同一个事务里。否则校验通过后、事务提交前,另一个请求已经把状态改了,就会出现并发状态错乱。当然如果你是单机部署、作弊式的低并发场景,这个问题不明显,但这是体现“工程素养”的一个点,答辩时主动讲出来,面试官或者指导老师会对你刮目相看。
4.2 任务派发与检测结果提交的Service实现示例
为了让读者能直接抄作业,我写一段任务派发的核心Service代码,注释直接标注设计意图。
@Service public class TaskServiceImpl implements TaskService { @Autowired private TaskDao taskDao; @Autowired private TaskLogDao taskLogDao; @Override @Transactional(rollbackFor = Exception.class) public boolean assignTask(Long taskId, Long assigneeId, Date deadline) { Task task = taskDao.selectById(taskId); if (task == null) { throw new BizException("任务不存在"); } // 核心校验:只有待派发状态的任务才能被派发 if (task.getStatus() != TaskStatus.CREATED) { throw new BizException("当前状态不允许派发任务"); } task.setStatus(TaskStatus.ASSIGNED); task.setAssigneeId(assigneeId); task.setDeadline(deadline); task.setUpdateTime(new Date()); int rows = taskDao.updateById(task); if (rows != 1) { throw new BizException("派发任务失败"); } // 写一条操作日志,保证全流程留痕 TaskLog log = new TaskLog(); log.setTaskId(taskId); log.setOperatorId(SecurityUtil.getCurrentUserId()); log.setAction("ASSIGN"); log.setRemark("任务派发给检测人员:" + assigneeId); taskLogDao.insert(log); return true; } }注意上面代码中@Transactional(rollbackFor = Exception.class)的写法:默认情况下Spring事务只回滚RuntimeException和Error,普通的Exception是不会触发回滚的,所以必须显式指定rollbackFor。这个细节在很多毕设代码和网上的半吊子教程里都没写全,但它是事务控制正确的关键,也是容易被答辩老师追问的点。
检测结果的提交稍微复杂一些,因为一个批次下的多个检测项可能要分多次录入。我建议的做法是:前端一次提交该批次下所有检测项的结果,Service层用一个循环遍历插入,如果任何一条插入失败则整体回滚,然后重新计算出该批次的合格判定。
@Override @Transactional(rollbackFor = Exception.class) public void submitTestResults(ResultSubmitDTO dto) { // 1. 先校验任务状态 Task task = taskDao.selectById(dto.getTaskId()); if (task.getStatus() != TaskStatus.TESTING) { throw new BizException("任务不在检测中状态,无法提交结果"); } // 2. 循环插入各项检测结果 boolean allQualified = true; for (TestResultItem item : dto.getItems()) { item.setTaskId(dto.getTaskId()); item.setCreateTime(new Date()); int rows = resultDao.insert(item); if (rows != 1) { throw new BizException("结果数据保存失败"); } if (item.getConclusion() != ResultConclusion.QUALIFIED) { allQualified = false; } } // 3. 自动生成初步判定 String autoJudge = allQualified ? "合格" : "不合格"; taskDao.updateJudge(dto.getTaskId(), autoJudge); // 4. 状态推进到已完成,等待复核 taskDao.updateStatus(dto.getTaskId(), TaskStatus.TESTING, TaskStatus.COMPLETED); }4.3 MyBatis动态SQL与多条件查询的实践
质量监督系统后台必然需要多条件组合查询,比如按产品名称、企业名称、状态、日期范围筛选任务列表。MyBatis的<where>加<if>组合是最常规的写法,但有两点要注意。
第一,多条件查询的SQL必须写<where>标签而不是直接写WHERE 1=1加<if>。虽然1=1能跑,但“永真条件”这种写法在代码评审里是会被批评的,而且存在SQL注入拼接的坏习惯暗示(虽然MyBatis的#{}有预编译,但风格不好)。<where>标签会在第一个条件成立时自动去掉多余的AND前缀。
<select id="selectTaskPage" resultType="com.quality.entity.Task"> SELECT * FROM t_quality_task <where> <if test="productName != null and productName != ''"> AND product_name LIKE CONCAT('%', #{productName}, '%') </if> <if test="status != null"> AND status = #{status} </if> <if test="startDate != null"> AND create_time >= #{startDate} </if> <if test="endDate != null"> AND create_time <= #{endDate} </if> </where> ORDER BY create_time DESC </select>第二,日期区间的比较要用>=和<=这种XML转义写法,直接写>=是会标签解析报错的。SQL注入本身在MyBatis里基本不用担心,#{}参数占位会走PreparedStatement,但如果您用了${}做动态排序字段拼接,那就得注意白名单校验,这个我在后面排坑部分细说。
5. 数据看板与报告生成:让系统看起来“不止是毕设”的三个加分模块
5.1 质量合格率统计看板
纯表单式管理系统的观感就是一堆列表页面,答辩演示时没什么冲击力。加一个统计看板能让整个系统的业务价值直观起来。我推荐用ECharts做三个核心图表:近12个月的产品合格率趋势折线图、按产品类别的合格率柱状图、不合格原因分布饼图。数据接口在后台写三个统计SQL,用Map返回前端,前端直接用Ajax发一个GET请求拿到数据灌进图表。
趋势图的SQL思路是先按月份分组,统计每个月已判定任务的合格数与总任务数,再用子查询算出比率。这里要特别提示的是:很多新手会把统计语句写得死板,比如直接group by month(create_time),但你选了跨年数据就会把不同年份的同月份合并,所以必须加year(create_time)一起分组。
5.2 Excel批量导入导出
毕设系统如果只有手动录入,数据准备阶段就够你受的。强烈建议给“产品备案”和“检测结果录入”两个模块增加Excel导入能力,给“报告列表”和“公示列表”增加导出能力。后端用Apache POI,一行数据对应一个JavaBean字段,用反射或逐行set都可以,代码量不大,但带来的操作体验提升非常明显,而且“批量导入导出”是答辩评分里常见的实用功能加分项。
导入时记得做模板校验和错误行提示:比如导入产品备案表时,某一行的产品名称重复或标准编号不存在,要能告诉用户“第6行数据错误:执行标准编号不存在”,而不是整批失败回滚让用户自己一行行找。这个“给用户找错”的细节,很多商业系统都没做好,你做了就很显眼。
5.3 邮件与站内信通知
任务派发后,检测人员需要知道有新任务;报告签发后,企业用户需要知道结果已出。系统内做站内信(消息表+已读未读状态)成本很低,却能让流程“活”起来。更进一步,如果你愿意多花一天时间接入JavaMail,在关键节点用Spring的事件机制异步发送邮件通知,这个“异步解耦”的设计思路在论文里也值得一段阐述,答辩时讲“派发任务后通过消息队列异步通知相关人员”比讲“查询列表SQL怎么写”高一个档次。
6. 答辩前必须排掉的五类坑,附排查链路
6.1 静态资源被拦截导致页面样式全丢
现象:页面打开只有纯HTML文字,CSS和JS全部404。排查思路:先按F12看Network标签,确认请求URL和状态码;再看DispatcherServlet的url-pattern是否为“/”;最后检查spring-mvc.xml里有没有配置<mvc:default-servlet-handler/>和<mvc:resources>映射。绝大多数情况是第2第3步没做。还有一种隐蔽情况是项目部署名带了版本号,比如context-path配成了/quality,但页面里引用资源用了绝对路径/cs/js/app.js,这种要把资源路径改成${pageContext.request.contextPath}开头。
6.2 MyBatis驼峰映射失效导致Bean属性为null
现象:数据库字段是create_time,Java属性是createTime,查询出来该字段一直为null,其他字段正常。原因:MyBatis默认并没有开启驼峰命名自动映射。解决方式是在mybatis-config.xml中配置:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>配置完还需要注意:如果某张表的列名和实体属性名对不上(比如有前缀t_),这个常规映射就覆盖不了,必须写resultMap显式映射。排查时先确认配置是否生效(SQL日志输出中是否有该setting),再确认列名是否真的只是下划线差异,有时候字段少一个前缀就会导致匹配失败。
6.3 事务不生效:悄悄回滚还是根本没开事务
现象:Service方法明明加了@Transactional,第一条插入成功、第二条抛异常后第一条居然没有回滚。排查链路非常典型,我来梳理一下。首先确认Spring的xml里有没有配<tx:annotation-driven transaction-manager="transactionManager"/>,没配则注解完全无效。其次确认事务管理器DataSourceTransactionManager的dataSource和SqlSessionFactory的dataSource是不是同一个对象,如果配了两个不同的DataSource实例(哪怕连接的是同一个数据库),事务边界就无法覆盖到MyBatis的操作。第三,确认@Transactional是否加在了public方法上,加到private方法上事项不会生效的。最后检查方法是不是在同一个类的内部被this调用,比如A方法调用同类B方法,B上有事务注解,调用时B不会走Spring代理对象,事务也会失效。这个“同类内部方法调用导致事务失效”的坑,是面试高频题,毕设里同样适用。
还有一个实际问题:后端抛了异常,但前端页面没提示,看起来像“操作成功但其实没执行任何修改”。这通常是被全局异常处理器吞掉了,或者Controller里catch Exception后没有重新抛出。建议写一个统一的@ControllerAdvice异常处理类,对BizException返回友好信息,对未知异常返回日志标记和通用错误提示,这样开发期排错和生产期体验都兼顾了。
6.4 使用${}拼接参数导致的SQL注入与排序失效
MyBatis的${}会被当成字符串直接拼接到SQL中。常见的使用场景是动态排序列名ORDER BY ${sortField} ${sortOrder}。这里必须做白名单校验,不然前端传个sortField=1;DROP TABLE x虽然未必能执行多条,但至少是严重的安全隐患。我的写法是预置一个Map映射限制合法字段:
private static final Map<String, String> SORT_FIELD_MAP = new HashMap<>(); static { SORT_FIELD_MAP.put("createTime", "create_time"); SORT_FIELD_MAP.put("status", "status"); SORT_FIELD_MAP.put("productName", "product_name"); } // 使用前先判断传入值是否在白名单里 String column = SORT_FIELD_MAP.getOrDefault(sortField, "create_time");排序方向字段直接限定为asc、desc两种取值,否则用默认desc。这条看似小细节,写进论文的“系统安全设计”章节是加分项。
6.5 分页查询的总记录数错误与页码错乱
如果手写LIMIT分页,要单独用一条COUNT语句统计总条数;如果使用PageHelper插件,要注意版本与MyBatis版本的兼容性。PageHelper 5.x在MyBatis 3.5下表现稳定,但使用上有一个大坑:PageHelper.startPage()必须紧跟在要分页的查询语句之前,中间不能有别的SQL操作,否则分页参数会被下一次不期望的查询消费掉,导致统计条数和数据列表对不上。我推荐的做法是把分页参数单独封装成PageRequest对象,Service里先执行业务查询或校验,最后一步再调startPage和查询方法,中间不要插入任何Dao操作。
7. 防爬虫与系统安全:Controller层的实用防护
既然系统对外开放了公告公示和企业查询功能,就必须考虑被爬虫抓数据的可能。我的方案是在Controller层加一个简单的防爬拦截器:同一IP在单位时间内的请求次数超过阈值就拒绝服务并记录日志。实现不复杂,用本地缓存(比如ConcurrentHashMap)维护IP计数,配合定时清理窗口即可。
另外一个更实用的小技巧是:公示列表和公告详情的接口返回数据里对联系电话、联系人姓名做脱敏处理,手机号中间四位用*号替换。这个“数据脱敏”在真实业务里是数据安全合规要求,在毕设答辩里讲出来也很加分。企业用户查询自己的数据时展示完整信息,公众查询展示脱敏信息,这是很清晰的权限数据范围控制逻辑,恰好呼应了第一章节分析的“不同角色看到的数据范围完全不同”。
安全这块还有一个容易被忽略的点:MySQL连接参数里要加useSSL=false,避免每次连接都做SSL握手影响性能;数据库密码不要硬编码在jdbc.properties里明文保存,虽然毕设不会有人审计你的配置库,但养成从环境变量读取配置的习惯没有坏处。
8. 部署演示与论文撰写的六个实操建议
最后聊点落地层面的东西。系统写完后部署演示这一关,我见过太多人在答辩现场因为环境问题翻车,以下是几条亲身踩过的教训和最终沉淀下来的操作建议。
第一,本机演示前一定要在命令行手动启动MySQL服务并确认端口监听正常。很常见的情况是:代码没问题、配置没问题,但MySQL服务没启动,或者端口被占用,页面一打开数据库连接失败直接白屏。答辩前自己走一遍从清理项目到启动的完整流程,不要一直开着IDE所以没暴露问题。
第二,建议准备一份干净的初始数据脚本。系统里需要有企业用户、检测机构用户、管理员账号,至少十个产品备案记录,三到五个不同状态的任务批次,其中要有一个已完成全部流程并发布了合格公示的完整数据案例。演示时直接拿这套数据走完整个流程,效果远比现场从零录入强得多。
第三,Tomcat的端口设置要固定成习惯。很多同学的电脑上8080端口已经被其他进程占用,建议改用9090或8081,并在演示前测试访问路径。不要用IDE内置浏览器打开页面,答辩时万一弹不出来就尴尬了,用系统默认浏览器提前访问好地址。
第四,论文的技术路线图不要画成网上一抓一大把的架构图模板。你完全可以按照这个系统的真实分层结构,画一张包含“表现层(JSP/HTML+Ajax)+控制层(Spring MVC)+业务层(Spring声明式事务)+数据层(MyBatis)”的层次图,每层标注你实际用到的具体技术点,比如拦截器、@ControllerAdvice、PageHelper、Druid连接池。这张图才是你系统真正长成的样子,答辩时指着图讲比空口背概念有力得多。
第五,论文里务必有一节专门写“系统测试”,不要只写“本系统经过测试运行稳定”这种空话。至少给出功能测试用例表:测试模块、测试步骤、预期结果、实际结果、是否通过,挑10到15个关键用例(用户登录、权限拦截、任务派发、结果录入、报告生成、数据导出)就够了。有余力的同学再加一段简单的性能测试:用JMeter模拟50个并发用户循环访问公告列表接口,记录响应时间和错误率,写进论文里,这就是“系统性能满足日常业务需求”的量化论据,比任何自夸都有说服力。
第六,不要太执着于把每个功能都做全做满。如果时间紧张,宁可把“检测任务流转”“报告生成与公示”这两条主链路打磨得完整顺畅,也不要平均用力做出五个半成品模块。答辩时老师最看重的永远是“一条核心业务流程从头到尾完整走通”,且每一步都有数据和日志佐证,而不是功能菜单数量。
我在实际做过几个类似的质量管理系统之后,最大的体会是:这类系统的价值感不在于界面多花哨,而在于“业务闭环的完整性”和数据之间严丝合缝的关联。当你把一个抽检批次从派发到检测再到公示的全过程完整跑通,并且每一步都有状态记录、操作日志和权限控制,这个系统就已经具备了进入真实业务场景的雏形,自然也就配得上一个亮眼的毕设成绩。