Spring Boot学生宿舍管理系统开发实战指南
简介一套基于Spring Boot、MySQL和Bootstrap实现的学生宿舍管理系统源码面向Java毕业设计、课程设计与后台管理开发学习者。系统按角色分为管理员、宿管、学生三个界面管理员可维护学生、楼宇、宿舍、入住、宿管及管理员信息并支持数据导入导出表格宿管可管理学生、宿舍与入住信息学生端包含学生管理与宿舍管理模块。功能覆盖宿舍分配、信息维护与数据流转业务逻辑完整适合直接运行或二次开发。压缩包为rar格式共661个文件大小约72.37MB主要包含约400个JS文件、46个CSS文件、35个Java源文件及XML、SQL等配置脚本同时附带多个操作演示MP4与说明文档便于快速理解与部署。现已累计467人下载学习是一套实用的JavaWeb宿舍管理项目。包内还区分MySQL5与MySQL8版本工程统一了驱动与连接配置可减少环境兼容问题让使用者把精力集中在业务扩展上。1. 先别急着写代码这类系统到底解决谁的痛点如果你正在选题列表里看到“基于Java的学生宿舍管理系统”大概率面临两种处境要么是课程设计的指定题目要么是想用一个完整 Web 项目把 Java 后端技术串起来。这个题目的本质是一套典型的管理型 Web 应用核心业务只有三件事维护学生和宿舍的分配关系、记录报修和访客等日常动作、让管理员在网页上完成这些操作而不是继续依赖 Excel 和纸质台账。它不涉及高并发也不涉及复杂的算法和推荐策略真正的难点在“业务状态是否正确、是否可解释”。比如“一个床位不能被两个人同时入住”“退宿之后床位才能重新分配”这些业务规则落到代码和数据库约束里才是答辩时真正能讲清楚的东西。适合刚学完 Java Web 基础、想通过项目把 Spring Boot、MyBatis、MySQL 串起来的开发者也适合急需一个可演示系统的在校学生。这篇笔记沿着选型、建表、编码、避坑、进阶的顺序展开目标是让你从空项目一路走到能稳定演示的程度。2. 技术选型Java 宿舍系统用 Spring Boot 还是 Servlet 起步选型这件事我很少直接给出“最新最好”的答案。对这个题目来说合适的标准有三个你所在环境是否指定了必须手写 Servlet你还有多少时间可以用来调通环境以及答辩时你能不能把自己为什么这样选讲清楚。这三条往往比框架本身的新旧更影响最终成绩。2.1 三条常见开发路线该怎么选我整理过三条走的通的路线按技术栈和适用场景列在下面路线技术栈学习成本演示友好度适用场景纯 Servlet JSPServlet、JSP、JDBC、MySQL偏高请求处理和 SQL 拼接全靠手写样板代码多页面较朴素但能完整讲出请求-响应链路学校明确要求手写 Servlet 的课程设计Spring Boot 模板引擎Spring Boot 2.x、MyBatis、Thymeleaf、MySQL中等框架接管了依赖管理和内嵌服务器页面整齐单 jar 启动答辩演示流畅绝大多数课程设计和毕业设计前后端分离Spring Boot Vue MySQL REST需要额外掌握前端工程化和跨域处理界面现代但联调链路长一层基础扎实、想拿完整作品求职的进阶者绝大多数情况下我会推荐第二条路线Spring Boot 2.x Thymeleaf MyBatis MySQL。理由很实际Spring Boot 内嵌 Tomcat你不需要单独下载和配置一个 Tomcat也就少了一大类环境报错默认的依赖管理帮你锁住大部分 jar 版本新手不用在版本冲突里反复折腾面试和简历里这套组合的辨识度也更高。纯 Servlet 路线则保留在“学校指定”或“想真正看懂底层”这两个前提下不要为了炫技去选它时间成本容易失控。2.2 把版本匹配说清楚JDK、Spring Boot、MySQL 驱动很多照着老教程写代码的新手第一个翻车点不是业务代码而是依赖根本起不来。这里最容易踩的版本坑是 javax 和 jakarta 包名切换。Spring Boot 3.x 把大量接口从 javax.servlet 迁移到了 jakarta.servlet如果你继续用 JDK 8 老教程的 HttpServletRequest 写法编译会直接报类找不到。对这个管理系统来说我建议停留在 JDK 8 或 11搭配 Spring Boot 2.x这是被验证过最稳的组合网上能找到的参考资料也几乎都基于这组版本。一个最小可用的 pom.xml 核心片段如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.x/version relativePath/ /parent properties java.version8/java.version /properties dependencies !-- Web 依赖内含内嵌 Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Thymeleaf 模板引擎页面放在 resources/templates 下 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- MyBatis 与 Spring Boot 的集成包 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.x/version /dependency !-- MySQL 驱动较新版本后坐标是 mysql-connector-j -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok简化实体类 getter/setter -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里有几个参数要说清楚。java.version8决定了编译级别用了 JDK 11 的环境改成 11 即可但没有必要为这个系统上 JDK 17。Spring Boot 的父 POM 自带依赖管理所以 MySQL 驱动不用写版本号要注意的是驱动坐标新版本之后官方坐标从mysql-connector-java变成了mysql-connector-j继续用老坐标会导致依赖解析不到。MyBatis 的 starter 版本与 Spring Boot 版本需要兼容当前这个组合里用 2.3.x 是常见选择不建议选太新的版本去试错。2.3 数据源与连接池参数别用默认值硬跑Spring Boot 的自动配置会帮你创建数据源但默认参数往往不适合本地开发机。很多同学直接把教程里的 URL 抄过来结果报错后一头雾水。这里给一份适合这个项目规模的 application.yml 配置server: port: 8080 servlet: context-path: /dorm spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 你的密码 hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: trueserverTimezoneAsia/Shanghai是日期时间显示正常的第一个关键点否则查询出来可能比本地时间差八小时。useUnicodetruecharacterEncodingutf8保证中文写入不乱码。allowPublicKeyRetrievaltrue解决 MySQL 8 使用 caching_sha2_password 认证时偶尔出现的连接失败。Hikari 连接池不要贪大maximum-pool-size10对这种几千人的小系统已经绰绰有余调成 50 反而容易把本机 MySQL 的连接数打满。map-underscore-to-camel-case开启后数据库里的room_no会自动映射到 Java 的roomNo字段后面就不用写一堆 resultMap 了。提示context-path: /dorm会让所有接口路径多一个前缀访问地址是http://localhost:8080/dorm/login。这个前缀一旦定下来前端页面和拦截器放行路径都要保持一致。3. 数据库设计六张表拆出宿舍业务的完整骨架宿舍管理系统的表结构并不复杂但很多课程设计翻车在“把表拆得过碎”或者“把业务状态全堆在一张表里”。这一章按业务动作来拆表而不是按直觉来拆表。3.1 拆业务对象不是表越多越好是动作越多越拆先想清楚业务里有哪些对象和关系一个楼栋下有多个宿舍一个宿舍住多个学生这是典型的一对多关系学生可以发起报修来访人员需要登记管理员需要发布公告。把这些动作梳理出来就得到了管理员、楼栋、宿舍、学生、报修、访客六张核心表。一个判断标准是某个对象是否独立存在取决于它是否会被单独查询或单独流转状态。比如“床位”就不建议单独拆表。床位只是宿舍的一个容量属性大多数查询都是从“哪栋楼哪个房间”进入的单独拆一张床位表会让入住、调宿、退宿的逻辑复杂一倍收益却很小。真正需要单独建表的是报修单和访客记录这类有独立生命周期、需要状态更新的数据。核心表能少一张就少一张这是控制这个项目复杂度的第一步。3.2 建表 SQL学生、宿舍、报修、访客四类主表下面是这套系统最常用的建表语句MySQL 8 环境可直接执行-- 楼栋表 CREATE TABLE building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 楼栋名称如5号楼, floors INT NOT NULL DEFAULT 6 COMMENT 楼层数 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表 CREATE TABLE dormitory ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_no VARCHAR(20) NOT NULL COMMENT 房间号如501, capacity INT NOT NULL DEFAULT 4 COMMENT 标准入住人数, current_count INT NOT NULL DEFAULT 0 COMMENT 当前已入住人数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1可用 0停用, UNIQUE KEY uk_building_room (building_id, room_no), CONSTRAINT fk_dorm_building FOREIGN KEY (building_id) REFERENCES building(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表宿舍关系挂在学生侧 CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT NOT NULL DEFAULT 1 COMMENT 1男 2女, phone VARCHAR(20), dorm_id BIGINT, bed_no INT COMMENT 床位号取值 1~capacity, check_in_time DATETIME COMMENT 入住时间, UNIQUE KEY uk_stu_no (stu_no), UNIQUE KEY uk_dorm_bed (dorm_id, bed_no), CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 报修表 CREATE TABLE repair ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dorm_id BIGINT NOT NULL, stu_id BIGINT, content VARCHAR(255) NOT NULL COMMENT 报修内容, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待处理 1处理中 2已完成, create_time DATETIME NOT NULL, finish_time DATETIME, CONSTRAINT fk_repair_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 访客登记表 CREATE TABLE visit ( id BIGINT PRIMARY KEY AUTO_INCREMENT, stu_id BIGINT NOT NULL, visitor_name VARCHAR(50) NOT NULL, relation VARCHAR(20) COMMENT 与学生的关系, phone VARCHAR(20), in_time DATETIME NOT NULL, out_time DATETIME, CONSTRAINT fk_visit_stu FOREIGN KEY (stu_id) REFERENCES student(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时几个值得注意的设计决定。uk_dorm_bed (dorm_id, bed_no)的唯一索引是整个系统的最后一道防线它在数据库层面保证同一个宿舍的同一个床位号不可能出现两条记录后面并发入住的问题靠它兜底。current_count是冗余字段用来快速判断宿舍是否已满避免每次查询都去 count 学生表代价是入住和退宿时要同步维护它。外键在这个数据量下完全够用不建议为了“性能”取消外键课程设计里保留外键反而更容易讲清楚关系。3.3 数据量与索引这套系统其实要到这个量级才需要索引很多同学一上来就给每张表建五六个索引其实是过度设计。宿舍管理系统的数据量通常只有几千到几万学生表加上唯一索引就能覆盖绝大多数查询。索引优先服务高频查询场景按学号查学生、按楼栋和房间查宿舍、按状态查报修单。这两条辅助索引最常用CREATE INDEX idx_student_name ON student(name); CREATE INDEX idx_repair_status ON repair(status);idx_student_name服务管理员按姓名搜索学生的场景搜索量再大一点可以改成name和stu_no的联合前缀索引但现阶段单列索引足够。idx_repair_status虽然状态字段的选择性不高但报修列表页几乎总是按状态过滤这个索引能让慢查询从全表扫描降到索引扫描。注意不要给学生表加太多索引写多读少的小系统每个索引都会拖累插入和更新速度两个辅助索引已经是性价比最高的配置。4. 核心功能落地登录、入住登记与查询列表的完整链路这一章从空项目开始把三个核心链路写通工程骨架、登录拦截、入住登记。这些都是直接在代码里能看到效果的部分建议边看边跟着写。4.1 工程骨架从一个空的 Spring Boot 项目开始用 Spring Initializr 生成项目或者直接照上一章的 pom.xml 建一个 Maven 工程都可以。选好目录结构后面的代码才不会乱。我通常这样分层com.example.dorm ├── DormitoryApplication.java # 启动类 ├── controller/ # 页面跳转和接口入口 ├── service/ # 业务逻辑事务放在这一层 ├── mapper/ # MyBatis 的 Mapper 接口 ├── entity/ # 与数据库表对应的实体类 └── config/ # 拦截器、过滤器等配置启动类只需要很短一段代码SpringBootApplication MapperScan(com.example.dorm.mapper) public class DormitoryApplication { public static void main(String[] args) { SpringApplication.run(DormitoryApplication.class, args); } }MapperScan的作用是扫描指定包下的 Mapper 接口把它们注册成 Spring Bean。如果你不想用这个注解也可以在每个 Mapper 接口上单独加Mapper两种方式二选一不要同时用否则会出现重复注册的警告。启动类放在所有包的根目录是最稳妥的这样 Spring Boot 才能扫到 controller、service 这些子包里的组件否则控制台会报 404。4.2 登录鉴权用拦截器而非安全框架理由与代码这个项目没必要引入 Spring Security。Security 的过滤器链和默认登录页对这个规模的管理系统来说太重配置门槛甚至高于功能本身答辩时还容易被追问“你了解过滤器链的执行顺序吗”。用 Spring MVC 自带的 HandlerInterceptor 就能满足需求没有登录就拦截请求跳转到登录页。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求避免跨域场景下拿不到 session if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } Object admin request.getSession().getAttribute(admin); if (admin null) { // 未登录页面请求重定向到登录页接口请求直接返回 401 response.setStatus(401); response.setHeader(Location, /dorm/login); return false; } return true; } }拦截器里的核心逻辑是取 session 中的管理员信息。登录成功后执行session.setAttribute(admin, admin)后续每个请求进来都检查这个属性没有则说明未登录。这里的setHeader(Location, ...)只是给前端一个跳转提示实际页面跳转由前端拿到 401 状态后执行。接下来把这些配置注册到 Web 层Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /doLogin, /css/**, /js/**, /images/**, /error); } }addPathPatterns(/**)拦截所有路径excludePathPatterns放行登录页和静态资源。注意路径要带上前缀/dorm如果配置的 context-path 是/dorm那么前端页面里的 CSS 引用也要写成link href/dorm/css/style.css。漏掉静态资源放行会出现一种典型症状页面能打开但完全没有样式打开控制台全是 404。4.3 入住登记的核心 Service事务与床位校验入住是这套系统业务逻辑最集中的地方也是答辩时最容易被深挖的模块。设计上要做到学生必须是已导入但未入住的状态宿舍必须存在且未满床位号必须在容量范围内最后还要防住并发导致的重复分配。核心代码Service public class StudentServiceImpl implements StudentService { Resource private StudentMapper studentMapper; Resource private DormitoryMapper dormitoryMapper; Transactional(rollbackFor Exception.class) public void checkIn(String stuNo, String name, Long dormId, Integer bedNo) { // 1. 学生状态校验 Student stu studentMapper.findByStuNo(stuNo); if (stu null) { throw new BizException(该学号不存在请先导入学生信息); } if (stu.getDormId() ! null) { throw new BizException(该学生已入住不能重复入住); } // 2. 宿舍与床位合法性校验 Dormitory dorm dormitoryMapper.selectById(dormId); if (dorm null || dorm.getStatus() ! 1) { throw new BizException(宿舍不存在或已停用); } if (bedNo 0 || bedNo dorm.getCapacity()) { throw new BizException(床位号超出范围1~ dorm.getCapacity()); } if (dorm.getCurrentCount() dorm.getCapacity()) { throw new BizException(宿舍已满请先退宿或调宿); } // 3. 床位占用兜底校验 long used studentMapper.countByDormAndBed(dormId, bedNo); if (used 0) { throw new BizException(该床位已有人入住); } // 4. 分配床位并更新宿舍已住人数 studentMapper.updateDorm(stu.getId(), dormId, bedNo, LocalDateTime.now()); dormitoryMapper.increaseCurrentCount(dormId); } }Transactional(rollbackFor Exception.class)保证第 4 步的两个写操作要么都成功要么都回滚不会出现“学生分配了床位但宿舍人数没更新”的中间状态。业务校验放到事务里而不是事务外是为了让校验逻辑读到的事务内数据保持一致。真正的高并发场景下第 3 步的“先查再插”依然存在竞态窗口所以这条校验只能称之为“业务层的友好提示”数据库里的uk_dorm_bed唯一索引才是最后的兜底。想让系统更健壮可以在第 3 步之后捕获DuplicateKeyException并转成“床位刚被分配请刷新重试”的提示。4.4 查询与分页MyBatis 动态 SQL 的实战写法列表页是这个系统用得最多的页面模糊搜索加条件过滤是刚需。MyBatis 的 XML 文件里写动态 SQL 是标准做法select idselectStudentPage resultTypecom.example.dorm.entity.Student SELECT s.*, d.building_id, b.name AS building_name FROM student s LEFT JOIN dormitory d ON s.dorm_id d.id LEFT JOIN building b ON d.building_id b.id where if teststuNo ! null and stuNo ! AND s.stu_no LIKE CONCAT(%, #{stuNo}, %) /if if testbuildingId ! null AND d.building_id #{buildingId} /if /where ORDER BY s.id DESC /selectwhere标签会自动去掉第一个条件前面的AND避免写WHERE 11这种老式拼接。条件里用#{stuNo}是预编译参数能防止 SQL 注入LIKE 模糊查询要用CONCAT(%, #{stuNo}, %)而不是${stuNo}后者会把用户输入直接拼进 SQL。多表关联查询时resultType只能映射实体类已有的字段building_name这类额外字段如果要显示要么在实体类里补一个字段要么改用resultMap明确列映射。分页我用 PageHelper用法很固定PageHelper.startPage(pageNum, pageSize); ListStudent list studentMapper.selectStudentPage(query); PageInfoStudent page new PageInfo(list);startPage必须在紧随其后的查询语句之前调用它对下一条 SQL 生效中间不能插入其他查询。PageHelper 会自动改写原 SQL 生成 count 语句所以日志里看到两条 SQL 是正常的不要当成异常。前端 Thymeleaf 模板里直接取page.getList()渲染表格用page.getTotal()显示总条数即可。5. 避坑这类系统最常见的五个翻车点这五条坑是我看到同行和同学在这个项目上反复踩过的每一条都按“现象 → 原因 → 解决”的顺序写照着排查比重新翻教程快得多。5.1 日期时间显示成时间戳或英文格式现象页面列表里入住时间显示成一串数字比如1733123456789或者显示成Tue Nov 05 20:00:00 CST 2024这种英文格式。原因实体类用了java.util.DateJSON 序列化时默认转成毫秒值数据库的 DATETIME 本身不带时区JDBC 驱动和 JVM 时区不一致时又会出现八小时偏移。解决实体类字段用LocalDateTime并在字段上加格式化注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private LocalDateTime checkInTime;同时确保 JDBC URL 里带serverTimezoneAsia/Shanghai。三处统一后前端拿到的就是2024-11-05 20:00:00这种正常格式不会再出现时区玄学。5.2 同一床位被重复分配现象两个浏览器同时提交入住接口都返回成功或者列表里显示宿舍已满但某个床位还是能查出来空着。原因典型的是“先查后写”竞态。第 4.3 节的业务校验是在内存里判断床位空闲两个并发请求同时通过校验然后都执行插入于是同一床位出现两条数据。解决三层防线缺一不可。业务校验负责给普通用户友好提示current_count用UPDATE ... SET current_count current_count 1自增而不是先查后写最后是数据库的uk_dorm_bed唯一索引插入时由 MySQL 直接拒绝第二条记录。Service 捕获到这个唯一键冲突后转成可读提示属于体验优化但索引本身才是关键。5.3 查询结果全是 null现象数据库表里有数据页面列表却显示空白姓名控制台也没有任何 SQL 报错。原因MyBatis 默认不开启驼峰映射数据库的room_no字段无法映射到 Java 的roomNo属性字段值就静默丢失了。解决在 application.yml 里开启全局映射mybatis: configuration: map-underscore-to-camel-case: true改完配置记得重启应用。如果你的查询里有自定义别名或者多表联查的冗余字段还是要写resultMap或者给别名加上 AS比如SELECT b.name AS buildingName否则驼峰映射对带前缀的字段名也无能为力。5.4 Excel 导入名单时中文乱码现象Excel 里名字正常导入系统后变成“銆愭煖”这类乱码。原因全链路编码不一致。最常见的是导入工具按 UTF-8 读取随后代码里又按默认字符集解码了一次读 CSV 时没有显式指定字符集则会跟随操作系统默认编码。解决全链路统一 UTF-8。在 Spring Boot 里注册一个编码过滤器这是最省事的方式Bean public FilterRegistrationBeanCharacterEncodingFilter encodingFilter() { FilterRegistrationBeanCharacterEncodingFilter bean new FilterRegistrationBean(); bean.setFilter(new CharacterEncodingFilter(UTF-8, true, true)); bean.addUrlPatterns(/*); return bean; }读 CSV 文件时显式指定编码Files.newBufferedReader(path, StandardCharsets.UTF_8)不要在new InputStreamReader里省略 charset。页面表单提交和数据库连接的characterEncodingutf8配置也要同步检查四端统一才能根除乱码。5.5 打包部署后 404 或数据库连不上现象本地运行一切正常mvn package后用java -jar启动打开页面却是 404或者启动日志报Communications link failure。原因404 通常是 context-path 与前端请求路径不一致或者静态资源大小写拼错数据库连不上则多半是 URL 里的localhost指向了服务器本机而 MySQL 实际不在那里或者时区参数没带上。解决部署时先用命令行参数临时覆盖配置不用重新打 jarjava -jar target/dormitory-system-1.0.0.jar \ --spring.datasource.urljdbc:mysql://你的服务器IP:3306/dorm_db?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue \ --spring.datasource.password你的密码命令行参数的优先级高于 application.yml适合部署现场应急调整。确认数据库连上后看启动日志里有没有打印Tomcat started on port 8080有这句话再排查页面路径。如果服务器有防火墙记得检查对应端口是否放行这一步经常被误判成代码问题。6. 进阶技巧用 AOP 给宿舍系统加一层操作日志当系统已经能跑通所有功能想在答辩里多一个可讲的亮点操作日志是性价比最高的增强。它的业务价值很直观谁在什么时间给哪个学生分配了宿舍、处理了哪条报修全部记录下来这是普通 CRUD 系统最容易缺失的一环。实现上不用改任何业务代码借助 Spring AOP 就能完成。先定义一个自定义注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OpLog { String value() default ; }然后在需要记录的方法上加OpLog(入住登记)这样的注解。接着写一个切面在方法执行后读取操作人和耗时信息Aspect Component public class OpLogAspect { Around(annotation(opLog)) public Object record(ProceedingJoinPoint pjp, OpLog opLog) throws Throwable { long start System.currentTimeMillis(); Object result pjp.proceed(); ServletRequestAttributes attrs (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs ! null) { HttpServletRequest request attrs.getRequest(); String operator (String) request.getSession().getAttribute(admin); // 这里可以写入 op_log 表替换掉示例的 System.out System.out.printf(op%s, operator%s, cost%dms%n, opLog.value(), operator, System.currentTimeMillis() - start); } return result; } }切入点的写法annotation(opLog)表示只拦截标了OpLog的方法不影响其他接口。pjp.proceed()是原方法的执行放在方法之后取值是为了拿到最终结果。切面生效后每调用一次入住接口控制台就会打印一条操作记录换成数据库写入就是一张完整的管理审计表。验证切面是否生效有个简单办法写一个循环调用脚本连续请求几次入住接口看日志表是否新增记录。我现在的习惯是任何管理型系统的第一张表先建操作日志再写业务代码。后补切面很容易漏掉某些入口导致日志和实际管理动作对不上那样反而比没有日志更被动。希望这个思路能让你的宿舍管理系统在“能跑”之外多一个可讲、可验、可扩展的加分点。希望帮到你。本文还有配套的精品资源点击获取