
简介面向Java相关专业毕业设计场景这份基于SSM框架的警务信息管理系统可用于毕设选题、课程实训或项目二次开发参考。系统覆盖管理员、领导、人力资源、行政人员、警员、警车信息及借车记录七大管理模块贴合警务单位日常管理流程前端采用Vue构建界面后端以Java实现业务逻辑配套MySQL数据库。资源包共454个文件约14.51MB以Java源码、Vue前端组件、SQL建表脚本、XML配置及部署脚本为主另含SVG图标、样式表与说明文档。压缩包内附install、run、build等一键部署脚本以及《部署说明》《表结构设计》文档便于快速搭建运行环境、理解数据表关系完整掌握从下载到运行的各环节目录结构清晰。目前已有52人学习浏览适合正在筹备毕业设计或需要参考SSM整合方案、警务信息化业务建模的读者。1. 从值班室的Excel台账说起SSM警务信息管理系统到底解决什么问题如果你去基层派出所值班室转一圈大概率还能看到两三台电脑轮流抢着录入警情、案件信息的场景。谁接的警、录入到哪一步、案件现在流转到哪个民警手里、超时没处理全靠一本Excel台账加微信群吼。这不是技术落后是信息管理没跟上。基于SSM的警务信息管理系统本质就是把这类散落在Excel和口头交接里的数据收进一个规范化、可追踪的Web系统里SSM负责把请求、业务逻辑、数据库操作一条线串起来让你能在浏览器里完成用户管理、警情登记、案件审批、值班安排和统计查询。这套东西适合两类人动手一类是需要完成毕业设计的Java方向学生想找一个技术栈正统、业务不复杂但五脏俱全的练手项目另一类是确实想把业务流程整理成系统的开发者想看看SSM这套经典Java组合在管理类系统里是怎么落地的。接下来我按自己做这类系统的习惯从选型、建表、写代码到部署排查一条线讲清楚。2. 选型定模块为什么是SSM以及警情与案件模块怎么拆在动代码之前先明确技术栈选择。SSM是Spring、SpringMVC、MyBatis三个框架的组合Spring管对象生命周期和事务SpringMVC管HTTP请求的路由分发MyBatis把Java方法和SQL语句映射起来。这套组合在2015年到2020年之间的Java Web项目里几乎就是标准答案放到毕业设计语境下它有三个不可替代的优势第一分层清楚Controller、Service、Mapper三层结构让答辩时的逻辑讲解特别顺畅第二MyBatis让你能精确控制SQL写复杂查询时不至于被ORM框架的规则绑住手脚第三Spring的IoC和AOP是Java面试必问的高频知识点答辩时从项目里顺手引出来就是加分项。2.1 技术栈选型与版本搭配不要为省事去下载最新的版本很多新手直接用最新版Spring和JDK结果跑了几天都在调兼容性。常见做法是选择经过大量项目验证的稳定组合JDK 1.8、Tomcat 8.5、MySQL 5.7、Maven 3.6。Spring用4.x版本MyBatis用3.4.xMyBatis-Spring用1.3.x。为什么不追新因为SSM时代的资料、博客、毕设参考代码几乎都是在这套版本下跑的遇到问题搜到的解决方案能直接抄。JDK 1.8和Spring 4.x搭配连javax.servlet的包名都不用纠结。组件推荐版本说明JDK1.8长期维护兼容性最好环境变量配置教程最多Tomcat8.5支持Servlet 3.1对应SpringMVC的注解配置MySQL5.7InnoDB默认引擎事务支持稳定Maven3.6.x依赖管理构建war包Spring4.3.xIoC、AOP、事务管理MyBatis3.4.x动态SQL能力强这个版本组合在Maven中央仓库里都能找到不需要修改镜像源。构建工具用Maven而不是Gradle因为绝大多数学校和部署环境默认支持Maven项目结构导出war包后丢进Tomcat的webapps目录就能运行省去Gradle在Tomcat里额外配置的麻烦。2.2 功能模块与数据库表设计一张权限表管住三类角色警务信息管理系统不是一个模糊概念的堆砌它在毕设题目里的标准解读是把派出所日常业务信息管理起来。按最常见的需求拆分核心模块至少包括用户管理、警情管理、案件管理、值班安排、操作日志。角色一般分三类管理员、民警、领导。管理员管用户和权限民警录警情和处理案件领导做查看和审批。我在建表阶段强烈建议把警情和案件拆成两张表而不是混在一张表里带个type字段。警情是事件发生的原始记录案件是警情经过初查后符合立案条件的正式记录。两者的生命周期完全不同警情可能处理完就结案归档案件则要经历立案、侦查、破案、移送起诉多个阶段。混在一起会导致后续的状态字段逻辑混乱。拆开之后警情表和案件表用case_no字段做关联案件表里记录from_alert_no指向源警情编号。建表是这套系统里最值得花时间的部分。我列出基础的五张核心表结构字段根据你的业务可以增减但类型和命名建议保持一致风格。-- 用户表 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role VARCHAR(20) DEFAULT police, dept VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 警情表 CREATE TABLE alert_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, alert_no VARCHAR(20) NOT NULL COMMENT 警情编号, title VARCHAR(100), content TEXT, address VARCHAR(200), reporter VARCHAR(50), report_time DATETIME, status TINYINT DEFAULT 0 COMMENT 0待处理 1处理中 2已办结, assign_user_id BIGINT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 案件表 CREATE TABLE case_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, case_no VARCHAR(20) NOT NULL UNIQUE, from_alert_no VARCHAR(20), case_name VARCHAR(100), case_type VARCHAR(50), status TINYINT DEFAULT 0 COMMENT 0初查 1已立案 2侦办中 3已破案 4移送起诉, handle_user_id BIGINT, approve_status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );这三张表的生产语句里有几个细节要说明。第一user表和企鹅的命名我全部用下划线小写风格避免Linux服务器上表名大小写敏感带来的坑。第二status字段用TINYINT而不是VARCHAR因为状态值在代码里用常量或枚举比较比字符串更不容易写错。比如我见过有人把状态值写成处理中数据库里录入成处理中 带了个空格等查询时怎么都匹配不上。第三关联字段assign_user_id、handle_user_id不建外键约束只用普通索引。外键在并发写入时会让锁范围扩大而且毕设项目的数据量用不到数据库层的完整性约束应用层代码控制逻辑数据库只存数据。2.3 分层的工程结构让答辩官一眼看到你的架构意识工程目录结构在IDEA里按Maven标准布局创建包名写清楚层级不要把所有类扔进一个包里。我习惯的划分是com.police.system ├── controller # SpringMVC控制层 ├── service # 业务逻辑层接口 │ └── impl # 接口实现 ├── mapper # MyBatis数据访问接口 ├── entity # 数据库实体类POJO ├── interceptor # 登录拦截器 ├── config # 配置类或XML配置 └── common # 通用工具、常量、响应封装Controller只接收参数并调用Service不在Controller里写业务判断Service层不出现HttpServletRequest这样的Servlet对象保持业务逻辑与Web容器解耦Mapper接口方法名和XML里的statement id一一对应命名规则统一用insertXxx、selectXxxByCondition。这样分层在做敏捷答辩时可以很自然地说清楚请求从Controller进来经Service处理事务由Mapper操作数据库逻辑链条完整。实际代码量控制在三千行以内就能完成核心功能这个量级对毕设来说不多不少太少了显得单薄太多了后面改需求会改到怀疑人生。3. 用SSM实现用户登录与权限控制从建表到拦截器的一串代码用户登录和权限控制是这套系统里唯一贯穿所有模块的公共逻辑也是答辩时最容易被追问的部分。很多人在这个环节翻车就是因为在Controller里写了一大堆if判断当前用户是否有权限代码又臭又长还没法复用。正确做法是用SpringMVC的拦截器统一处理权限校验业务代码里完全不用关心用户是否已登录。3.1 登录接口与密码加密密码不要明文存数据库先写登录的Service实现。密码存储强烈建议不要用MD5直接加密直接用加盐的方式。所谓加盐就是在用户密码后面拼一段随机的字符串再计算哈希这样即便两个用户设置了相同的密码存库的结果也不一样防止彩虹表撞库。Spring自带的DigestUtils里封装了MD5方法但加盐逻辑需要自己拼。我这里用更简单的处理MD5加密密码加上用户名拼接后再次加密作为简单盐值。Service public class UserServiceImpl implements UserService { Resource private UserMapper userMapper; Override public User login(String username, String password) { // 数据库里存的是用户名密码拼接后做MD5的结果 String encodedPwd DigestUtils.md5DigestAsHex( (username password).getBytes(StandardCharsets.UTF_8)); return userMapper.selectByUsernameAndPwd(username, encodedPwd); } }这段代码逻辑很简单关键在于MD5计算的对象是用户名和密码的组合。两次不同用户的密码哪怕相同因为盐值用了用户名所以算出来的哈希值也不同。但要注意一个隐藏细节StandardCharsets.UTF_8必须显式指定字符集不要用默认编码否则在Windows上开发正常部署到Linux服务器上中文密码会匹配不上这是经典的中文乱码坑。控制器接收登录参数成功后把用户信息放进Session然后跳转主页Controller public class LoginController { Resource private UserService userService; RequestMapping(value /login, method RequestMethod.POST) public String doLogin(String username, String password, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(loginUser, user); return redirect:/index; } return redirect:/login?errortrue; } }用户对象直接存入Session即可。不要放一个空的userId再每次查库这会多一次无谓的数据库访问。页面跳转用redirect而不是forward防止用户刷新页面时表单重复提交。登录接口的信息承载用参数接收而不是RequestBody的JSON对象是为了降低前端的对接成本直接在表单里写username和password两个name属性就能提交。3.2 登录拦截器与角色权限控制面向切面而不是if-else拦截器是这套系统的权限核心。扩展SpringMVC的HandlerInterceptorAdapter在preHandle方法里判断Session中是否存在用户。如果没有就重定向到登录页有就放行。对于管理员才允许访问的路径比如用户管理还需要额外判断角色字段。public class LoginInterceptor extends HandlerInterceptorAdapter { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } // 管理员接口校验 String uri request.getRequestURI(); if (uri.contains(/user/) !admin.equals(loginUser.getRole())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权限访问); return false; } return true; } }这段拦截器把权限判断收敛到了一个地方。用户管理相关的路径以/user/开头只有角色为admin的用户能通过。普通民警访问管理接口时会收到403响应。不要在Controller里再用if判断角色那会让权限逻辑散落各处后面想调整权限规则时改到吐血。拦截器注册需要在SpringMVC配置文件中完成。如果项目用注解配置类就实现WebMvcConfigurer接口重写addInterceptors方法Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /logout, /css/**, /js/**, /images/**); } }这里最关键的参数是excludePathPatterns。你系统的静态资源路径和登录接口必须排除否则刷新页面时CSS和JS全被拦截页面会变成没有样式的纯HTML而且登录接口被拦截后用户连登录都做不到。SSM的常用注解在这一步体现得比较集中Configuration、Controller、Service、Resource、RequestMapping每个注解各司其职没有多余的装饰。这是新手最容易忽略的地方也是查错时第一眼看的地方。3.3 权限模型的边界角色不等于权力这套用拦截器做权限控制的方案只适用于角色数量少且权限边界清晰的系统。警务管理系统里三类角色够用但如果你后面加了协警或文职角色且他们能访问的菜单组合各不相同角色字段的方式就不够用了。那时候需要引入RBAC模型加一张权限表和一张角色权限关联表通过查表判断是否有权限。但那种复杂度对毕设来说偏重了。骨架期先用角色字段撑起来答辩时如果老师问权限如何扩展你就回答引入RBAC表结构把拦截器从查角色改成查权限码即可不需要真的把扩展代码写进去。4. 走通案件登记与分页查询MyBatis动态SQL与状态流转的落地写法案件管理模块是整个系统的业务中枢它最考验代码设计能力的地方不是增删改查本身而是两个点一个多条件组合查询的性能与写法另一个是案件状态在各环节的流转控制。前者用MyBatis动态SQL解决后者用状态字段加Service层校验解决。4.1 案件登记接口事务、参数绑定与返回结果封装案件登记由民警操作录入案件名称、类型、来源警情编号等信息。它涉及一次插入案件表、一次更新警情表的状态所以在Service方法上加Transactional确保两步操作要么都成功要么都回滚。Transactional(rollbackFor Exception.class) Override public boolean registerCase(CaseInfo caseInfo) { // 生成案件编号日期 4位随机数 String caseNo AJ new SimpleDateFormat(yyyyMMdd).format(new Date()) String.format(%04d, (int)(Math.random() * 10000)); caseInfo.setCaseNo(caseNo); caseInfo.setStatus(0); // 初查 int insertResult caseMapper.insertCase(caseInfo); // 同步更新警情表状态为已转案件 AlertInfo alertInfo new AlertInfo(); alertInfo.setAlertNo(caseInfo.getFromAlertNo()); alertInfo.setStatus(2); // 已办结 int updateResult alertMapper.updateStatusByAlertNo(alertInfo); return insertResult 0 updateResult 0; }Transactional注解默认只回滚RuntimeException受检异常不会触发回滚。加rollbackFor Exception.class是明确告诉Spring所有异常都回滚防止异常被捕获后数据处于一半写入的尴尬状态。Controller接收页面表单提交的数据SpringMVC会把表单字段自动绑定到CaseInfo对象中同名属性上。这一步的坑在于前端表单里input的name必须和CaseInfo的属性一一对应比如caseName对应input namecaseName否则绑定一个空对象进去数据库里全是NULL。登记成功后跳转到列表页。RequestMapping(/case/register) public String register(CaseInfo caseInfo, Model model) { boolean success caseService.registerCase(caseInfo); if (success) { return redirect:/case/list; } model.addAttribute(errorMsg, 案件登记失败请检查警情编号); return case/register; }操作成功后用重定向避免表单重复提交失败时返回表单页并携带错误信息。这里的错误信息字段名和页面里${errorMsg}对应模板引擎用JSP的情况下Model中的属性会直接被EL表达式取到。如果这里不写Model model参数页面就取不到errorMsg用户看到的只是死板的重定向回表单页不知道失败原因。4.2 多条件分页查询MyBatis动态SQL的三种场景与PageHelper的整合列表页几乎必然带查询条件按案件编号、案件类型、状态、登记时间范围。如果每个条件单独写一个Mapper方法组合起来就是地狱。MyBatis的动态SQL在这里发挥最大优势一个方法覆盖所有组合场景。select idselectCaseListByCondition resultTypecom.police.system.entity.CaseInfo SELECT * FROM case_info where if testcaseNo ! null and caseNo ! AND case_no LIKE CONCAT(%, #{caseNo}, %) /if if testcaseType ! null and caseType ! AND case_type #{caseType} /if if teststatus ! null AND status #{status} /if if testbeginTime ! null AND create_time gt; #{beginTime} /if if testendTime ! null AND create_time lt; #{endTime} /if /where ORDER BY create_time DESC /selectwhere标签会自动处理掉第一个AND前缀这是MyBatis的一个细节忘了用它SQL拼接时很容易出现WHERE AND case_type...的语法错误。if标签判断空字符串和null都必须写否则前端传一个空字符串过来MyBatis会把它当成有值拼进SQL导致查不出数据。分页用PageHelper拦截器。它底层是通过MyBatis拦截器在SQL执行前自动拼接LIMIT子句无需手动写分页SQL。在MyBatis配置中注册插件然后在Service调用前静态设置页码和每页条数。Override public PageInfoCaseInfo getCasePage(int pageNum, int pageSize, CaseInfo condition) { PageHelper.startPage(pageNum, pageSize); ListCaseInfo caseList caseMapper.selectCaseListByCondition(condition); return new PageInfo(caseList); }PageHelper.startPage后紧跟着的第一条查询会被拦截处理。这里有个严重警告startPage和Mapper调用之间绝对不能有其他数据库操作比如插入日志否则分页会应用错SQL上。我见过一个同学在startPage后先执行了一次字典表查询结果给字典查询也加了LIMIT页面数据直接显示不全。PageInfo对象里自带total、pageNum、pageSize、pages等分页属性在JSP页面用${pageInfo.total}就能显示总条数不需要再额外查COUNT。4.3 状态流转的赋值逻辑只靠前端按钮控制就是灾难案件状态从初查到移送起诉共五个阶段前端列表页每个操作按钮控制状态值。如果只靠前端传status值来更新用户直接改请求参数就能把案件从初查改成移送起诉绕过了中间环节这是典型的越权更新。Service层必须校验状态转移的合法性。public static final MapInteger, ListInteger ALLOWED_TRANSITIONS new HashMap(); static { ALLOWED_TRANSITIONS.put(0, Arrays.asList(1)); // 初查 - 立案 ALLOWED_TRANSITIONS.put(1, Arrays.asList(2)); // 立案 - 侦办 ALLOWED_TRANSITIONS.put(2, Arrays.asList(3)); // 侦办 - 破案 ALLOWED_TRANSITIONS.put(3, Arrays.asList(4)); // 破案 - 移送起诉 } public boolean updateCaseStatus(Long caseId, Integer oldStatus, Integer newStatus) { ListInteger allowedNext ALLOWED_TRANSITIONS.get(oldStatus); if (allowedNext null || !allowedNext.contains(newStatus)) { throw new BusinessException(非法的案件状态流转); } return caseMapper.updateStatus(caseId, oldStatus, newStatus); }状态流转时Mapper的更新SQL必须带上oldStatus作为条件防止并发情况下两个人同时操作同一案件导致状态覆盖。update idupdateStatus UPDATE case_info SET status #{newStatus} WHERE id #{caseId} AND status #{oldStatus} /updateUPDATE语句影响行数为0时说明数据已经被别人改掉了此时抛出并发冲突异常让用户重试。这个方案是用数据库的行锁机制做乐观锁不需要额外加version字段是最轻量的并发控制做法。5. 部署避坑war包与MySQL的五个高频故障排查毕设项目做到能跑只是第一步真正折磨人的是部署环节。开发环境一切正常到了别人的机器上就是跑不起来这类问题我统称为玄学问题。实际上没有玄学所有怪异的报错都有明确的触发原因只是你没往那个方向想。这一章把警务信息管理系统部署中最高频的五个故障逐个拆开每条都按现象到原因到解决的排查方式写这些情况我基本每次部署都会遇到一两个。5.1 用Maven构建war包别跳过测试构建命令很简单但参数有讲究mvn clean package -DskipTests -Pprodclean清掉target目录下的旧产物package执行完整构建流程生成war包-DskipTests跳过单元测试这个项目未必写了测试但保留参数能防止代码里无效的测试类阻塞构建-Pprod激活prod环境的Maven profile。Profile功能用于区分开发环境和生产环境的配置数据库地址、账号密码、日志级别通常在pom.xml里配置成多个profile激活prod时使用生产配置。如果你只有一个环境这个参数可以不写。构建成功后在target目录下找到police-system.war把这个文件复制到Tomcat的webapps目录下启动Tomcat即可自动解压部署。不要手动解压war包Tomcat会自动处理手动解压反而可能因为目录结构不对而报错。5.2 数据库配置的三种写法properties、JNDI与Druid连接池SSM项目的数据库配置一般写在jdbc.properties里被Spring的配置文件加载。我们的版本组合里用Druid连接池做数据库连接管理。jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/police_system?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordrootURL里的四个参数必须齐全。useUnicodetruecharacterEncodingutf-8保证中文不乱码useSSLfalse跳过SSL握手从而避免MySQL 5.7里因为证书问题报错serverTimezoneAsia/Shanghai必须显式指定时区否则MySQL驱动会报连接超时或CannotGetJdbcConnection异常。Druid连接池在Spring的XML配置里如下注册bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialSize value5/ property nameminIdle value5/ property namemaxActive value20/ /beaninitialSize是连接池启动时的初始连接数minIdle是最小空闲数maxActive是最大活跃数。毕设场景数据量不大这三个参数保持5、5、20即可。如果你在服务器上发现连接数一直涨且不回落把minIdle调高一些这是Druid的常见行为。5.3 中文乱码Linux上最常见的部署杀手现象Windows上开发运行全部正常部署到Linux服务器后页面显示正常但插入数据库的中文全部变成问号。原因分三处Linux系统默认字符集不是UTF-8、Tomcat请求响应字符集设置缺失、数据库表字符集不对。系统字符集只要LANG环境变量是UTF-8就行这通常不是主因。Tomcat方面确保server.xml里的Connector加了URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8/这里最容易忽略的是Spring的CharacterEncodingFilter是否在web.xml里配置。它负责把请求体的字符集设置为UTF-8如果不配置POST表单提交的中文在进入Controller前就已经乱码。filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingforceEncoding设为true表示请求和响应都强制UTF-8编码。只设encoding为UTF-8时如果客户端没有显式带Content-Type请求体可能还是按平台默认编码解析所以forceEncoding必须写。数据库方面建库语句要显式指定utf8mb4字符集utf8mb4比utf8能多存一些特殊字符是MySQL推荐的标准写法。5.4 Invalid bound statement、NoClassDefFoundError等启动期崩溃现象Tomcat部署后启动几分钟后访问页面直接报500错误查看catalina.out日志发现Invalid bound statement (not found): com.police.system.mapper.CaseMapper.selectCaseListByCondition。原因Mapper接口找到了但对应的XML文件没有被MyBatis加载。Spring配置MyBatis的SqlSessionFactoryBean时mapperLocations属性没有指向resources下的mapper目录。bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean如果用了classpath:mapper/*.xml确认src/main/resources/mapper/下面确实存在XML文件且文件名和Mapper接口的完全限定名对应。还有一个隐蔽坑MyBatis的XML文件放在java目录下而不是resources目录下时Maven默认不会把XML打包进war包。在pom.xml的build节点里补充资源插件配置把xml文件也纳入打包范围。NoClassDefFoundError通常意味着jar包版本冲突或缺失比如spring-web和spring-webmvc版本不一致。检查pom.xml里Spring各依赖版本号是否统一这个错误本质是类加载器找不到对应的类大多数情况是版本漂移导致的。5.5 数据库密码特殊字符导致的连接失败现象本地MySQL密码是root正常。部署到服务器密码设置成Pssw0rd!修改jdbc.properties后启动报错Access denied for user。原因和!在properties文件里有特殊含义。在Spring的占位符${}里可能被误解析!在某些环境中会被shell转义密码里的特殊字符破坏了配置加载。解决把密码里的特殊字符去掉或者用Properties文件的Unicode转义写法再或者密码值用单引号包裹。实际上最省事的方式是设置一个纯字母数字的数据库密码数据库服务器的安全策略允许的情况下这是毕设场景下最不做不必要的麻烦的选择。5.6 Druid监控页面的生产关闭Druid自带一个监控页面可以查看SQL执行情况开发调试很有用。但部署到公网服务器时务必关闭否则任何人都能通过访问/druid/index.html查看你的数据库连接信息和SQL语句这是数据泄露级别的风险。在web.xml里设置Druid的allow和deny参数servlet servlet-nameDruidStatView/servlet-name servlet-classcom.alibaba.druid.support.http.StatViewServlet/servlet-class init-param param-nameallow/param-name param-value127.0.0.1/param-value /init-param init-param param-namedeny/param-name param-valueall/param-value /init-param /servlet允许访问的IP只留本机。但这只能挡住HTTP层如果服务器本身配置了公网端口转发还是要注意防火墙规则。对毕设而言如果你是部署在学校机房内网问题不大如果放在有公网IP的云服务器上这步必须做。6. 上线前的验证与改进给自己一张不翻车的验收清单系统开发完成后不要急着写答辩PPT先用一张验收清单把所有功能过一遍。这个习惯我从第一次做毕设项目养成到后来做企业项目也一直沿用。清单覆盖功能流、边界条件、数据一致性三个层面测试时按清单逐条确认发现问题就记录在案。检查项测试方法预期结果登录成功输入正确账号密码跳转主页Session生成登录失败输入错误密码回到登录页并提示错误未登录访问页面直接访问/index重定向到/login非管理员访问用户管理用民警账号访问/user/list返回403页面警情转案件录入合法警情编号案件生成警情状态变已办结案件非法状态流转初查案件直接调移送起诉抛出异常提示非法流转分页参数异常页码传0或负数PageHelper自动修正为第一页多条件查询案件名称模糊类型精确返回交集结果中文数据插入中文姓名和地址页面和数据库均无乱码并发状态更新两个窗口同时更新同一案件只有一个成功这里提一个所有SSM项目都通用的坑Controller直接返回对象时默认使用Jackson的序列化配置日期字段会变成时间戳或格式错乱的字符串。如果你有Ajax接口返回JSON需要在实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解再在Spring配置里配置Jackson的ObjectMapper统一日期格式。这个细节不处理前端拿到的日期就是2024-05-01T10:30:00.00000:00这种难看的字符串。事务配置验证还要看一个常见坑Service实现类里内部方法调用比如register方法里调用了同一个类的updateAlertStatus方法updateAlertStatus方法上的Transactional不会生效。因为Spring的AOP代理默认基于接口代理内部调用绕过了代理对象。解决方式要么把两个方法拆到不同的Service类要么在启动类上使用EnableTransactionManagement后把内部方法改成在另一个注入的Bean里调用。这个细节可能是答辩老师最喜欢问的深入题答上来就是加分项。后续改进方向上如果时间充裕优先做两件事一是操作日志切面用Spring AOP记录每个用户的关键操作这个需求在答辩时容易出彩二是接入Redis缓存热点查询把案件列表的查询结果缓存起来这个点能体现你对性能的思考。两件事都涉及新增技术栈但代码改动量都不大一两天能写完。做不完也没关系在论文里把思路写清楚答辩时说明白扩展方向即可不用追求代码全部落地。这套SSM系统我自己前前后后带人做过好几遍从建表到部署踩过的坑基本都记在这里。最深刻的教训是依赖版本能用稳定组合就不要尝鲜权限校验必须在服务端做而不能依赖前端控制部署环境的变化优先查字符集和配置而不是怀疑代码逻辑。每一步照着这个流程走这门毕业设计不仅能安稳落地还会在答辩时比较从容。希望帮到你。本文还有配套的精品资源点击获取