拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SSM警务信息管理系统实战:从表结构设计到借车审批全解析

简介一套基于SSM框架的警务信息管理系统毕业设计源码包面向计算机相关专业本科生的毕设选题与课程设计也可作为SSM整合开发的实训参考。系统完整覆盖管理员管理、领导管理、人力资源管理、行政人员管理、警员管理、警车信息管理和借车记录管理七大业务模块能够帮助学习者理解权限分配、数据库建模和前后端联调等核心环节。压缩包共四百五十四个文件大小十四点五一MB包含一百一十个Java后端源码文件、五十五个Vue前端页面组件、一百六十二个SVG图标资源以及SQL初始化脚本、XML配置文件、部署说明Word文档、一键启动和构建脚本等项目结构清晰便于直接导入开发环境进行二次修改。资源另附系统表结构说明文档与部署操作指南配合安装、运行、打包脚本可在较短时间内完成环境搭建、建库建表和服务启动。目前已有五十一人学习适合正在准备毕业设计需要参考完整源码、业务模块划分或快速跑通项目的同学使用也可作为二次开发的基础版本。1. 从毕业设计到可运行的警务信息管理系统先看清这套SSM代码的边界这套基于 SSM 的警务信息管理系统说直白点就是一个“含权限管理的 CRUD 大礼包”。管理员管理、领导管理、人力资源管理、行政人员管理、警员管理、警车信息管理、借车记录管理七个模块把一个典型内部业务系统的主干撑得清清楚楚。和网上那些只有登录和增删查改的毕设不一样它的借车记录带着审批状态警车状态和借车流程联动而这正是很多学生卡住的地方。拿到手先看“1-警务信息管理系统表结构.docx”再依次执行 1-install.bat、3-build.bat、2-run.bat不要急着改代码。适合三类人做毕业设计需要一套 SSMJavaMySQL 完整体系的想研究 Vue 打包产物如何集成到 SSM 里的以及刚入门想摸清 SSM 请求生命周期的人。2. 表结构是这套系统的根警员、警车、借车记录怎么关联2.1 从表结构文档看实体关系与角色设计“1-警务信息管理系统表结构.docx”打开后你会看到典型的毕业设计风格表集合。核心不是“警务”两个字而是用户、角色、部门、警员档案、警车档案、借车记录这几张表。管理员、领导、人力资源、行政人员和警员这五类角色在答辩时经常被问“是不是建了五张用户表”。常见做法是用一张sys_user表加role_id和dept_id来区分而不是做五份重复的用户表。下面是我从表结构文档里提取出来的核心关系表。表名一句话说明关键字段sys_user登录账号和基础身份id, username, password, real_name, role_id, dept_idsys_role角色定义id, role_namedept部门/大队id, dept_namepolice_staff警员档案扩展id, user_id, badge_no, position, hire_datepolice_car警车档案id, car_no, model, status, insurance_dateborrow_record借车记录id, user_id, car_id, begin_time, end_time, purpose, status, approver_id注意sys_user里保存的是账号police_staff里保存的是更完整的警员档案两者通过user_id逻辑关联。人事管理页面展示的列表通常是一个JOIN查询sys_userLEFT JOINpolice_staffLEFT JOINdept而不是在每张表里重复存部门名称。如果你拿到手的表结构文档里没有打印出关联关系自己画 ER 图时也按这个方向画。2.2 借车记录为什么需要独立表和审批状态很多入门项目会把“借车”做成在警车表上加一个“当前借用人”字段这是典型的错误。原因是借车记录是过程数据警车状态是状态数据。只要还车借用历史就被覆盖了领导看不到上周谁用了车、用途是什么、有没有按时归还。借车记录单独设计本质上是一个轻量的流程记录。borrow_record至少要能回答四个问题谁借的、借的哪辆车、借多久、批没批。所以这里会看到user_id、car_id、begin_time、end_time、purpose、status、approver_id这些字段缺一不可。2.3 核心表和字段的建表SQL如果你手上的表结构文档没有附带可直接执行的 SQL可以按下述结构在 MySQL 5.7 里建表。这里只保留最核心的三张表警员档案和部门可以作为扩展字段加到sys_user上。DROP TABLE IF EXISTS borrow_record; DROP TABLE IF EXISTS police_car; DROP TABLE IF EXISTS sys_user; CREATE TABLE sys_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 建议存MD5或BCrypt密文, real_name VARCHAR(30) NOT NULL, role_id INT NOT NULL COMMENT 1管理员 2领导 3人事 4行政 5警员, dept_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE police_car ( id INT AUTO_INCREMENT PRIMARY KEY, car_no VARCHAR(20) NOT NULL UNIQUE, model VARCHAR(50), status TINYINT NOT NULL DEFAULT 0 COMMENT 0可用 1已借出 2维修 3报废, insurance_date DATE, remark VARCHAR(255) ); CREATE TABLE borrow_record ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, car_id INT NOT NULL, begin_time DATETIME NOT NULL, end_time DATETIME NOT NULL, purpose VARCHAR(200), status TINYINT DEFAULT 0 COMMENT 0待审批 1已批准 2已拒绝 3已归还, approver_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );逻辑说明DROP TABLE的书写顺序是先子表后父表否则外键关联会导致删不掉。这里没有建外键是为了避免导入脚本时因为表顺序或数据约束失败这在毕业设计场景下完全够用。status字段用TINYINT而不是字符串因为 Java 后端通过Integer判断状态更直接也更省空间。2.3.1 状态字段的取值约定借用记录的status和警车表的status语义不同建议在代码里定义常量类而不是整个项目里魔法数满天飞。位置0123police_car.status可用已借出维修中报废borrow_record.status待审批已批准已拒绝已归还注意两个状态机之间的联动逻辑申请提交时borrow_record.status0警车仍然是可用状态领导批准后borrow_record.status1此时才把police_car.status改成已借出还车时再恢复为可用。如果一提交就改警车状态会导致假审批占着车后面的人无法申请。2.4 审批链路在表结构上的具体体现审批是领导模块的核心动作。在表结构上需要关注borrow_record.approver_id和sys_user.role_id的配合。普通警员发起的申请approver_id是 null领导通过审批接口处理后approver_id被更新为当前领导的用户 id。这是审计最基本的字段必须保留。如果表里没有approver_id只有status那就只能看到“批没批”看不到“谁批的”审计链路直接断掉。这里给出的表结构比很多原始毕设多了一个approver_id你拿到项目后可以对照确认。3. 三个批处理脚本install/build/run到底帮你做了什么3.1 三个脚本的分工拿到代码后最先看到的就是 1-install.bat、2-run.bat、3-build.bat。这三个脚本的组合代表了这一类 SSM 项目的交付方式目的就是让你不用去记 Maven 命令和 Tomcat 路径。脚本职责失败时看什么1-install.bat建库、导入表结构MySQL 服务有没有启动密码对不对3-build.bat构建后端 war 包或前端 distMaven/npm 环境变量2-run.bat启动 Tomcat 并打开页面8080 端口是否被占用3.2 install.bat数据库初始化与编码问题1-install.bat 在所有脚本里的作用就是初始化数据库。Windows 环境下常见写法是调用 MySQL 自带的命令行工具执行建表 SQL如下所示。echo off set MYSQL_BINC:\Program Files\MySQL\MySQL Server 5.7\bin\mysql.exe %MYSQL_BIN% -uroot -p123456 -e create database if not exists police_db default charset utf8mb4; %MYSQL_BIN% -uroot -p123456 --default-character-setutf8mb4 police_db 1-警务信息管理系统表结构.sql echo 数据库初始化完成 pause逻辑说明-uroot和-p123456分别是用户名和密码注意-p后面不要有空格否则 MySQL 会以为密码是空格。--default-character-setutf8mb4是为了防止 SQL 文件里的中文注释在导入时变成乱码。是 Windows CMD 里的重定向符号把 SQL 文件内容交给 mysql 客户端逐行执行。注意如果导入后表名或注释出现问号优先检查 MySQL 服务端默认字符集而不是项目代码。3.3 build.bat前端构建还是后端打包从文件列表里的app.722bf5e4.css、chunk-vendors.8ee77068.css、style.css能看出前端是 Vue 项目构建后的产物文件名带哈希是 webpack 的指纹策略。所以这里的 build.bat 有两种可能的角色如果项目里同时保留有前端源码目录那 build 脚本会先执行npm install npm run build再把dist/目录里的文件和index.html拷贝到后端src/main/webapp/static/如果只有后端源码和打包好的静态资源那 build.bat 就是单纯的 Maven 打包。echo off cd /d %~dp0 call mvn clean package -DskipTests -Pprod copy target\police-admin.war C:\tomcat\webapps\police.war echo build finished pause逻辑说明cd /d %~dp0是个好习惯它让脚本无论在哪个目录被调用都能回到自己所在目录避免后面相对路径找不到pom.xml。-DskipTests跳过测试-Pprod激活生产环境 profile这个 profile 在pom.xml里通常配置打包时包含静态资源和数据库连接参数。copy目标目录是 Tomcat 的webappswar 包会被自动解压部署。如果你发现项目里还有src/main/frontend这样的目录那 build.bat 里大概率还有一段类似cd frontend npm run build xcopy /E dist ..\src\main\webapp\static\这时不要单独跑 Maven 打包否则前端页面会是旧的。3.4 run.bat启动 Tomcat 与端口调整2-run.bat 通常负责启动 Tomcat 并打开浏览器。一个比较可靠的模板是这样的echo off set JAVA_HOMED:\Java\jdk1.8.0_202 set CATALINA_HOMED:\tomcat-9.0.65 set CATALINA_OPTS-Xms256m -Xmx512m call %CATALINA_HOME%\bin\startup.bat timeout /t 8 /nobreak start http://localhost:8080/police/逻辑说明JAVA_HOME必须指向 JDK 的根目录而不是bin。SSM 项目大多跑在 JDK 8 上如果你装的是 JDK 11 以上Tomcat 8.5 和 9 可以运行但部分老项目会因为 JSP 编译问题报错。CATALINA_OPTS控制 JVM 内存系统资源有限就按 256m/512m 设置。timeout是为了给 Tomcat 一点启动时间否则浏览器打开时服务还没起来。如果出现端口冲突不要改代码去 Tomcat 的conf/server.xml找到Connector port8080 ... /改成 8081同时把 run.bat 里的访问地址改掉。3.5 静态资源映射为什么 app.xxx.css 会 404SpringMVC 默认会拦截/下的请求所以打包好的app.722bf5e4.css、style.css如果放在webapp/static下必须显式配置放行否则浏览器请求静态资源会直接打进 DispatcherServlet找不到映射后返回 404。常见的配置是在 SpringMVC 的 XML 或配置类里加入下面这段。mvc:resources location/static/ mapping/static/**/ mvc:default-servlet-handler/逻辑说明location是物理路径mapping是浏览器访问时的 URL 前缀。比如webapp/static/app.722bf5e4.css浏览器访问/contextPath/static/app.722bf5e4.css。如果你的index.html里资源引用路径没有带static前缀就要检查打包时的publicPath常见做法是把 publicPath 设为相对路径./这样解压 war 后访问路径不会因为 contextPath 变化而失效。4. 在SSM里实现借车审批与人事档案Mapper、Service、Controller三层实战4.1 SSM三层分工和Spring Boot的差异在 java 面试里经常被问到“SSM 的请求流程是怎样的”很多人只会背 DispatcherServlet、HandlerMapping 这一串名词但放到具体项目里就找不到 Controller 在哪。这套警务系统的代码很典型Controller只写参数接收和页面跳转Service写事务和业务判断Mapper只负责 SQL。Spring Boot 流行之后大家更喜欢用RestController和 MyBatis-Plus但 SSM 的边界感更强每一层该干什么一眼就能看出来。层次对应代码职责ControllerBorrowController.java接收 HTTP 参数封装返回结果ServiceBorrowRecordServiceImpl.java写事务、审批状态机MapperBorrowRecordMapper.xmlSQL 查询和更新4.2 借车申请的Controller层先看借车申请入口这里用 Controller 加ResponseBody直接返回 JSON页面是 Vue 通过 axios 来请求的。Controller RequestMapping(/car/borrow) public class BorrowController { Autowired private BorrowRecordService borrowRecordService; ResponseBody PostMapping(/apply) public Result apply(RequestBody BorrowRecordDTO dto, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { return Result.error(未登录); } BorrowRecord record new BorrowRecord(); BeanUtils.copyProperties(dto, record); record.setUserId(loginUser.getId()); record.setStatus(0); boolean ok borrowRecordService.applyBorrow(record); return ok ? Result.success(申请已提交) : Result.error(申请失败); } }逻辑说明RequestBody BorrowRecordDTO表示前端提交的是 JSON 字符串Spring 会用 Jackson 反序列化到 DTO。DTO 和实体类分开写避免前端传一个status就把审批状态改了。BeanUtils.copyProperties是 Spring 包里的属性拷贝工具字段名一致才能拷贝成功。HttpSession里放loginUser是 SSM 时代最常见的登录态方案登录拦截器在进入 Controller 前检查 session。4.3 Service层的事务与警车状态联动审批逻辑不能只改借车记录警车状态也要跟着变。这一步要放在同一个事务里否则会出现“借车记录已批准但警车还是可用”的脏数据。Service public class BorrowRecordServiceImpl implements BorrowRecordService { Autowired private BorrowRecordMapper borrowRecordMapper; Autowired private PoliceCarMapper policeCarMapper; Transactional(rollbackFor Exception.class) Override public boolean approve(int recordId, int approverId, int status) { BorrowRecord record borrowRecordMapper.selectById(recordId); if (record null) { throw new RuntimeException(记录不存在); } record.setStatus(status); record.setApproverId(approverId); borrowRecordMapper.updateById(record); PoliceCar car new PoliceCar(); car.setId(record.getCarId()); if (status 1) { car.setStatus(1); policeCarMapper.updateById(car); } else if (status 3) { car.setStatus(0); policeCarMapper.updateById(car); } return true; } }逻辑说明Transactional(rollbackFor Exception.class)写在实现类上让 Spring 对这个类产生代理如果selectById查不到记录直接抛 RuntimeException整个事务回滚不会出现只改了部分表的情况。approve方法被领导模块调用普通警员能调用到吗这取决于拦截器后面会讲到。4.3.1 为什么状态变更要放在同一个事务里这里有个常见的坑如果先更新borrow_record.status1再更新police_car.status1第二个 SQL 抛异常事务不开启的情况下借车记录已经改了警车还停在可用状态。表面上没有报错但领导再审批另一辆车时同一辆警车还能被借出。加上Transactional后第二个 SQL 失败第一个更新也会回滚。4.4 Mapper XML里的多表查询与条件拼接列表页需要显示借车人的真实姓名、部门、车牌号和当前状态这些字段分散在sys_user、dept、police_car里不能只查一张表。下面这段 XML 是典型的 MyBatis 多表关联查询。select idselectRecordWithUsers resultTypemap SELECT br.id, br.begin_time, br.end_time, br.purpose, br.status, su.real_name AS borrowUserName, pc.car_no, d.dept_name FROM borrow_record br LEFT JOIN sys_user su ON br.user_id su.id LEFT JOIN police_car pc ON br.car_id pc.id LEFT JOIN sys_user au ON br.approver_id au.id LEFT JOIN dept d ON su.dept_id d.id where if testcarNo ! null and carNo ! AND pc.car_no LIKE CONCAT(%, #{carNo}, %) /if if teststatus ! null AND br.status #{status} /if /where ORDER BY br.create_time DESC LIMIT #{offset}, #{pageSize} /select逻辑说明LEFT JOIN sys_user au是自关联用来拿审批人的姓名查询列表时可以直接给approverName对应的字段但这里 resultType 是 map所以也能从 map 里取。where标签会自动去掉第一个多余的ANDif保证没有传条件时不拼接那段 SQL。使用LIKE CONCAT(%, #{carNo}, %)而不是直接写%${carNo}%后者有 SQL 注入风险面试提一句会加分。分页参数offset和pageSize如果使用 PageHelper就要去掉手写的LIMIT。4.5 前端VueBootstrap怎么调用接口页面是 Bootstrap 样式但脚本部分用了 Vue 的轻量写法。列表页的请求逻辑类似下面这样。axios.post(/car/borrow/apply, { carId: this.selectedCarId, beginTime: this.beginTime, endTime: this.endTime, purpose: this.purpose }).then(res { if (res.data.code 200) { this.loadList(); } else { alert(res.data.msg); } });逻辑说明axios 的 post 方法默认会带上Content-Type: application/json所以后端用RequestBody能对上。this是 Vue 实例的 methods 上下文在setTimeout或 Promise 回调里使用箭头函数避免this丢失。loadList在成功后重新拉取列表比手动把新增的数据 push 到数组里更可靠至少排序和分页是对的。5. 部署排错和二次开发几个让答辩更顺的技巧5.1 用 fetch 快速验证后端是否启动不要每次都先点页面。项目启动后打开浏览器开发者工具 Console直接请求接口fetch(/car/borrow/list?page1limit10) .then(r r.json()) .then(d console.log(d));能拿到 JSON 说明后端、数据库、静态资源都没有阻断请求404 说明 contextPath 或拦截器路径写错500 说明 SQL 或空指针问题这时去看 Tomcat 日志里最近一段异常栈。5.2 初始化数据和默认账号表结构文档里大概没有初始数据需要手动插入。系统管理员密码按 MD5(123456) 生成插入INSERT INTO sys_user (username, password, real_name, role_id, dept_id) VALUES (admin, MD5(123456), 系统管理员, 1, 1); INSERT INTO police_car (car_no, model, status, insurance_date) VALUES (CONCAT(警, CAST(RAND()*1000 AS UNSIGNED), -PD), 大众帕萨特, 0, 2026-01-01);逻辑说明role_id1对应管理员dept_id1对应示例部门。如果管理员登录后看不到菜单先查sys_role和菜单表有没有关联数据再查登录接口是否查出了用户角色。注意如果登录时密码校验不过先确认后端用的加密算法是 MD5、SHA256 还是 BCrypt再决定是否继续用MD5()函数生成初始数据。5.3 不重新构建也能改页面样式前端的chunk-vendors.8ee77068.css是打包文件改它没有可维护性。最直接的办法是在 Tomcat 的webapps/police/static下新建一个custom.css然后在index.html的/head前手动加一行link relstylesheet href./static/custom.css。这样能避开每次前端打包、复制资源的流程演示现场调样式也更快。注意浏览器缓存改完后按CtrlF5硬刷新。5.4 三个安全细节提前写进答辩说明第一个密码不能明文存在数据库里第二个/car/borrow/approve这类接口必须做角色拦截普通警员调接口越权审批是最容易暴露的问题第三个MyBatis 里避免${}拼接用户输入。这三个点分别对应数据安全、权限控制和 SQL 注入每一项都能在答辩时展开讲两分钟比反复强调功能列表更有说服力。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门