ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Java的教务系统开发指南:从权限模型到并发选课实战

基于Java的教务系统开发指南:从权限模型到并发选课实战 简介这是一个基于Java的教务查询系统练手项目使用SSMSpringSpringMVCMybatis整合开发并引入Shiro安全框架、C3P0数据源、log4j日志及Bootstrap前端框架适合初学Java后端、希望熟悉SSM整合流程的开发者参考。压缩包共271个文件大小35.32MB包含55个Java源码、76个class编译文件、42个XML配置、28个JSP页面、31个jar依赖库以及少量SQL脚本、样式表和文档目录结构清晰便于按代码、配置与页面分类查阅。项目实现了课程、教师、学生、选课等典型教务模块能够帮助读者理解SSM分层架构、权限控制与数据库交互的完整代码组织方式。目前已有411人学习浏览适合用来对照练习、巩固框架整合与教务场景开发基础。1. 基于Java开发的教务系统不只是增删改查权限模型和并发选课才是分水岭做过几个学期的课设辅导和项目重构之后我越来越确认一件事基于Java开发的教务系统是Java Web方向最值得新手完整走一遍的项目没有之一。它没有电商那种高并发秒杀场景也没有推荐系统那种算法复杂度但正好覆盖了Java后端日常工作的核心拼图——Spring Boot的工程结构、MyBatis的数据访问、MVC的分层设计、权限控制、事务管理以及最让人头疼的并发选课。很多初学者拿到这个题目第一反应是把学生表、课程表、成绩表建好然后写几个CRUD接口跑起来给老师演示一遍就交差。但实际的教务系统痛点恰恰不在这几张表上一门课被几百个学生同时抢选数据库怎么保证不超选成绩被老师重复提交怎么保证幂等一个学生能看哪些成绩、改哪些课权限边界画在哪里这些才是把课设水平拉开到工程水平的分水岭。这篇文章适合两类人一类是Java基础已经学完、正在找课程设计或毕业设计落地项目的学生另一类是想在面试前攒一个能讲清楚设计取舍的完整项目的初级开发。我会从技术选型讲到建表从权限模型讲到并发细节最后补充排错经验——全程不绕弯子直接照着做就行。2. 教务系统的技术选型单体应用加经典Java全家桶为什么默认不碰微服务2.1 先定边界一个教务系统本质上在管哪几件事开始写代码之前先搞清楚教务系统这个标题到底在指什么东西。真实环境里的教务系统非常庞杂排课、考务、教材、毕业审核都能塞进来但以Java课程设计和大多数中小学校的需求来看核心域就四件事基础信息管理学生、教师、班级、课程与开课计划、选课与成绩管理、用户认证与权限控制。把这四块做扎实系统就能真正投入使用反过来如果不定义清楚边界很容易在“实验报告管理”这类边缘功能上消耗大量时间。明确了边界技术选型就清晰了。这是一个典型的中后台管理系统并发量按学校规模算峰值也就是几百个学生同时选课。这个量级下单体应用就是最合理的选择Spring Boot加MyBatis加MySQL这套组合拳足够扛住而且生态成熟出了问题网上随便一搜就有答案。微服务、消息队列、分布式缓存这些东西在这个场景下不但没有收益反而会把自己的项目复杂度推高好几倍。有人会问那面试时聊微服务怎么办我的看法是在一个单体应用里把事务边界、接口设计、权限模型讲清楚比硬拆几个服务更有说服力。你完全可以在项目描述里写一句“按业务模块划分包结构未来可按需拆分”这比没有流量就上微服务的空谈务实得多。2.2 版本选型JDK到底用8还是17MyBatis还是MyBatis-PlusJava环境变量的配置是很多新手的第一道坎但只要你把JDK装好、JAVA_HOME配好、path里加上bin目录然后命令行执行java -version能通过这个前置就算完成了。真正需要纠结的是版本JDK 8还是17我调试过不少学生的项目翻车案例大多是这两种一是JDK 17编译却把Language level设成了8报“源发行版 17 需要目标发行版 17”的警告二是用JDK 8跑Spring Boot 3.x的项目直接启动失败。Spring Boot 3.x要求JDK 17起步而很多教材和网上的老教程还在用Spring Boot 2.x配JDK 8。我的实用建议是默认选JDK 8加Spring Boot 2.7.x这是目前兼容性最稳的组合所有第三方依赖都找得到对应版本遇到报错也好排查。如果你是做毕业设计想体现新意选JDK 17加Spring Boot 3.x也行但尽量用MyBatis-Plus 3.5.3以上的版本否则有兼容性问题。持久层框架我直接推荐MyBatis-Plus别犹豫——它在MyBatis基础上帮你把单表CRUD的样板代码全部省掉了你只需要写业务代码而那些多表关联查询用注解SQL或者XML都能解决。前端这块分两种情况如果这是课程设计、时间紧直接用JSP加Bootstrap或者Thymeleaf加Semantic UI所有页面渲染都在服务端完成部署时一个WAR包打进Tomcat就行如果是毕业设计或想积累求职作品集用Vue 3加Element Plus做前后端分离后端只出JSON接口。注意一点前后端分离的项目在答辩时要演示跨域配置和联调过程这本身就是加分项。2.3 项目骨架Maven多模块结构怎么搭才能让代码不烂在后面很多人拿到Spring Boot习惯把所有代码堆在一个工程里controller、service、mapper、entity全都按技术分层。小项目没问题但教务系统这种功能模块异常清晰的项目更好的做法是按业务模块分包而不是按技术角色分包。我自己的习惯是这样一个包结构edu-admin ├── pom.xml ├── edu-common # 通用模块统一返回结果、异常处理、工具类 ├── edu-system # 系统模块用户、角色、权限、登录 ├── edu-teaching # 教学模块课程、开课、选课、成绩 └── edu-framework # 框架配置Spring Security、MyBatis、Redis配置具体到每个模块内部再用controller/service/mapper/entity分层。这种“先业务后技术”的组织方式能让你在后期加功能的时候只动一个模块而不是在一堆包名相似的文件里来回跳转。Maven的parent聚合工程负责统一管理依赖版本子模块之间通过dependency互相引用。在写第一行业务代码之前我强烈建议你先把统一返回结果和统一异常处理建好。教务系统要跟前端或JSP页面打交道如果每个接口返回的数据格式都不一致联调时会让人崩溃。用一个ResultT类包住code、message和data再用RestControllerAdvice捕获全局异常把参数校验失败、业务异常、系统异常区分开处理这个地基打好了后面的开发速度会快很多。3. 数据库设计是教务系统的地基从E-R图到建表语句一次把表结构定到可上线3.1 核心表拆解学生、课程、选课、成绩之间的关系不能只靠外键数据库设计是整个教务系统里最不该省时间的一块因为业务表关系一旦定错后面返工的代价极大。教科书式做法是先画E-R图但实际项目的图往往比教科书里复杂得多——因为真实的约束都在细节里。教务系统的核心表大概有这几张学生表、教师表、课程表、开课表、选课表、成绩表、用户表、角色表。先说课程与开课的区别课程表存的是课程基本信息比如课程代码、课程名称、学分、学时而开课表记录的是“这个学期谁来上这门课”包含授课教师ID、上课时间、上课地点、选课容量、已选人数。为什么必须拆开因为同一门高等数学春季和秋季很可能是不同老师上时间地点也变了。很多新手只建一张课程表结果一到排课就傻眼了。再来看选课表和成绩表。选课表在关系上应该关联学生ID和开课ID而不是直接关联课程ID——这样解锁学期这个维度学生选的实际上是“这个学期的这门课”。成绩表则需要关联选课ID因为一次选课记录对应一条最终成绩分数录入、修改都在这张表上做。有些设计把成绩字段直接嵌到选课表里这样虽然少一张表但成绩的修改记录、补考记录就没地方记了所以我宁可多拆一张表出来。外键要不要建我的建议是开发阶段不建物理外键但在业务逻辑里确保引用完整性。理由有二一是物理外键在后期做数据迁移和数据清洗的时候是很大的负担二是学校的选课系统经常要做删除和恢复操作外键约束很容易让删除顺序变成噩梦。你可以通过索引和业务校验来维护一致性“逻辑外键”是很多生产系统的实际做法。3.2 建表语句实践字符集、引擎、索引该怎么定下面是学生表、课程表、开课表、选课表、成绩表的核心建表SQL。我用的是MySQL 8.0的语法字符集用utf8mb4而不是utf8——因为utf8在MySQL里存不了emoji和部分生僻字学生名字里有生僻字时你会感谢这个选择的。-- 学生表 CREATE TABLE student ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, student_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 1 COMMENT 性别:1-男 2-女, class_name VARCHAR(50) DEFAULT NULL COMMENT 班级, major VARCHAR(50) DEFAULT NULL COMMENT 专业, status TINYINT DEFAULT 1 COMMENT 状态:1-在读 0-离校, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表; -- 开课表 CREATE TABLE course_open ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, course_id BIGINT NOT NULL COMMENT 课程ID, teacher_id BIGINT NOT NULL COMMENT 教师ID, semester VARCHAR(20) NOT NULL COMMENT 学期,如2025-2026-1, capacity INT NOT NULL DEFAULT 60 COMMENT 选课容量, selected_count INT NOT NULL DEFAULT 0 COMMENT 已选人数, class_time VARCHAR(100) DEFAULT NULL COMMENT 上课时间,如周一3-4节, classroom VARCHAR(50) DEFAULT NULL COMMENT 上课地点, PRIMARY KEY (id), KEY idx_semester_course (semester, course_id), KEY idx_teacher_semester (teacher_id, semester) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT开课表;selected_count这个字段是不是冗余是的但它是刻意为之的冗余目的是让选课页展示剩余名额时不用每次都COUNT(*)去扫选课表。代价是每次选课和退课都要更新这个数字事务里要保证更新顺序一致否则会出现显示有坑位但实际选不进去的情况。索引方面联合索引(semester, course_id)是查询最高频的路径教师查询开课列表用(teacher_id, semester)这两组索引基本覆盖了99%的查询场景。3.3 选课与成绩表重复选课、重复录成绩怎么在数据库层防住接下来是选课表和成绩表这两张表的业务约束比前面几张都要特殊。-- 选课表 CREATE TABLE course_selection ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, student_id BIGINT NOT NULL COMMENT 学生ID, open_id BIGINT NOT NULL COMMENT 开课ID, select_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, status TINYINT DEFAULT 1 COMMENT 状态:1-已选 0-退课, PRIMARY KEY (id), UNIQUE KEY uk_student_open (student_id, open_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课表; -- 成绩表 CREATE TABLE course_score ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, selection_id BIGINT NOT NULL COMMENT 选课ID, regular_score DECIMAL(5,2) DEFAULT NULL COMMENT 平时成绩, exam_score DECIMAL(5,2) DEFAULT NULL COMMENT 考试成绩, total_score DECIMAL(5,2) DEFAULT NULL COMMENT 总评成绩, score_level VARCHAR(10) DEFAULT NULL COMMENT 等级:优秀/良好/中等/及格/不及格, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_selection (selection_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT成绩表;这里有两个容易忽视的设计决策。第一个是选课表的唯一键(student_id, open_id)这是防止重复选课的数据库兜底方案——哪怕你的接口被前端重复调用、或者消息重试数据库这层都能拦下来。第二个是成绩表用selection_id做唯一键意思是“一次选课只能有一条成绩记录”老师重复提交成绩时走的是更新而不是新增这保证了成绩的幂等性。有些设计会漏掉这两个唯一键结果选课表里出现同一个学生选了同一门课两条记录成绩表里出现同一学生同一门课多个分数——到那种时候修复数据的成本会让你怀疑人生。4. 核心功能实现登录认证、权限控制、选课并发逐个写成可运行的代码4.1 登录认证与权限模型基于角色的访问控制RBAC怎么落地教务系统至少有三类角色学生、教师、管理员。它们对同一份数据的操作权限完全不同——学生只能查自己的成绩、选自己的课教师能管自己课程的教学班、录成绩管理员拥有全部权限。这种场景标准的解法是RBAC基于角色的访问控制在用户和角色之间建立多对多关系角色再绑定权限。Spring Security是Java生态里做认证授权最成熟的框架但它也出了名地难学——尤其是它那一整套过滤器链新手经常被绕晕。我的建议是项目里用一个轻量级的实现核心原理是登录成功后签发一个Token后续请求通过Token识别身份通过角色判断是否有权限。先把这个机制跑通再去啃Spring Security的配置就轻松多了。下面是一个用拦截器做登录校验和角色鉴权的核心骨架Component public class AuthInterceptor implements HandlerInterceptor { Autowired private StringRedisTemplate redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 String uri request.getRequestURI(); if (uri.startsWith(/api/auth/)) { return true; } // 从Header获取用户身份标识 String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } // 从Redis校验Token并取出用户基本信息 String userInfo redisTemplate.opsForValue().get(login_token: token); if (userInfo null) { response.setStatus(401); return false; } // 解析出用户ID和角色ID存入request后续Controller直接用 request.setAttribute(currentUser, userInfo); // 管理端接口,校验管理员角色 if (uri.startsWith(/api/admin/) !userInfo.contains(\role\:\admin\)) { response.setStatus(403); return false; } return true; } }这段代码的逻辑很直接拦截器里先放行登录接口然后检查Token是否在Redis中存在最后对管理端接口做一次简易的角色校验。用Redis存Token的好处是当你把用户踢下线时只需要删掉Redis里的键不用等Token自然过期——这在学生毕业离校、教师离职的场景下太实用了。注意真实项目里我不会在拦截器里用contains去判断角色而是把用户信息解析成对象后按角色ID判断这里是为了让你看清楚主流程。配置上在WebMvcConfigurer里注册这个拦截器并指定拦截路径。用户表、角色表、用户角色关联表三张表加上加密存储的密码字段BCrypt加密绝对不要明文存密码这个权限模型的基础就完整了。4.2 选课接口的并发控制超选问题不是玄学是锁和事务的排列组合把选课次数最多的接口拿出来单独聊——学生选课。如果你直接写一个“先查已选人数若小于容量则插入选课记录、已选人数加一”的朴素实现那么只要两个学生同时点选课就可能出现超选——两人同时查到已选人数是59容量是60各自都认为可以选结果是61人。这种情况在课设演示时很难复现但在真实上线后的第一时间必然出现。解决这个问题最常用的方案是使用数据库的行锁也就是SELECT ... FOR UPDATE把“查询名额”和“更新名额”锁在同一事务里保证同一门课在同一时刻只有一个事务在修改已选人数。下面这个方法是经过验证可行的Transactional(rollbackFor Exception.class) public ResultVoid selectCourse(Long studentId, Long openId) { // 1. 锁住开课记录,防止并发超选 CourseOpen open courseOpenMapper.selectByOpenIdForUpdate(openId); if (open null) { return Result.error(开课记录不存在); } // 2. 检查名额 if (open.getSelectedCount() open.getCapacity()) { return Result.error(选课已满,无法选择); } // 3. 防止重复选课(数据库唯一键兜底) int exist courseSelectionMapper.checkExist(studentId, openId); if (exist 0) { return Result.error(请勿重复选课); } // 4. 插入选课记录 CourseSelection selection new CourseSelection(); selection.setStudentId(studentId); selection.setOpenId(openId); courseSelectionMapper.insert(selection); // 5. 已选人数1 courseOpenMapper.increaseSelectedCount(openId); return Result.success(选课成功); }对应的MyBatis-Plus查询语句是selectByOpenIdForUpdate在XML里写SELECT * FROM course_open WHERE id #{openId} FOR UPDATE。这个FOR UPDATE就是在告诉数据库把这行记录锁住其他事务想改必须先等我提交。注意锁要加在事务的最前面让锁持有的时间最短事务里尽量只做数据库操作不要在锁范围内调用外部HTTP接口更不要在锁范围内发短信、发邮件。如果你的选课场景再大一些可以用Redis的分布式锁但不要过度设计。对于单个MySQL实例的教务系统FOR UPDATE已经够了。这个方案的重心是先把正确性保证住再考虑性能优化。4.3 成绩录入事务边界和权限校验一个都不能少成绩录入是教师端的核心操作。这个功能看起来简单——一个老师对着一张名单填分数然后保存但有两个坑一是老师只能改自己授课班级的成绩二是成绩重复提交时要走更新而不是插入。权限校验应该放在Service层而不是等到Controller再去判断。因为Service层是业务逻辑的正确归属位置Controller只负责参数接收和结果返回。老师登录后我们根据Token里的教师ID去查开课表确认这门课确实是他的然后再执行后续的更新操作。成绩的计算逻辑比如平时成绩占40%、期末成绩占60%也要放在Service层这样无论从哪个入口提交成绩规则都一致。这里有一个值得注意的细节成绩表用selection_id做唯一键意味着同一名学生的一门课只有一条成绩记录。教师第一次录入时走insert再次提交时走update。判断是该insert还是该update最稳妥的方式是先按selection_id查一次存在就更新不存在就插入。还有一种做法是使用MySQL的INSERT ... ON DUPLICATE KEY UPDATE语法一条SQL搞定但可读性稍差我用的是前者——因为辅导的学生往往更容易理解和维护这种朴素的写法。5. 避坑指南教务系统开发中的六个高频翻车现场从环境到数据一个不落5.1 中文乱码Tomcat、JSP页面、MySQL三层都要统一字符集每一次我帮学生排查中文乱码最后的结论几乎都是同一个三层字符集没有统一。JSP页面显示的从上到下分别是请求编码、响应编码、数据库连接编码、数据库表字符集任何一层不对中文就会变成问号。最典型的场景是前端提交的中文数据存进数据库后变成“”或者页面显示乱码。解决方法是按下述清单检查JSP或HTML页面头部设置charsetUTF-8Spring Boot的application.yml里配置server.tomcat.uri-encodingUTF-8MySQL连接串中加上characterEncodingutf8数据库表和字段的字符集使用utf8mb4。这个方法的价值在于趁项目刚开始做就统一字符集配置能省掉后面大把排错时间。如果你用的是IDEA开发还有一个容易忽略的点IDEA的全局文件编码、项目文件编码要和Properties文件的编码都设为UTF-8。多数情况下Properties配置文件默认是ISO-8859-1你写中文注释进去基本必乱码。5.2 数据库连接失败Public Key Retrieval is not allowed和时区配置这个报错在大学机房和Windows本地开发时极常见MySQL 8.0连接时报Public Key Retrieval is not allowed。原因是MySQL 8默认使用caching_sha2_password认证插件如果连接串没有指定allowPublicKeyRetrievaltrue驱动在无法安全获取公钥时会直接拒绝连接。解决方法是修改JDBC连接串加两个参数jdbc:mysql://localhost:3306/edu_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrueserverTimezone必须显式设置这也是一个高频报错点——不设置的话会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。设置成Asia/Shanghai就解决了。另外MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver和旧版的com.mysql.jdbc.Driver不一样如果你的Spring Boot版本和驱动版本不匹配启动时还会在这上面卡一会儿。5.3 选课超卖MyBatis二级缓存和FOR UPDATE打架有一个坑非常隐蔽你在Mapper上开启了MyBatis的二级缓存同时又在选课查询上使用了FOR UPDATE。二级缓存会把查询结果缓存起来但只要第一次查询时开课人数是59缓存里就会一直记着59后面的请求走缓存不查库FOR UPDATE根本不会生效超选问题就“死灰复燃”了。解决办法很简单禁用Mapper上的二级缓存至少对选课这条链路不要使用。选课、库存这类写多读少的数据追求缓存反而是给自己找麻烦。MyBatis的一级缓存是会话级别的事务结束后自动失效问题不大二级缓存是跨会话的默认没开也不会自动开但在网上抄配置时容易不小心加进去。5.4 Spring Boot启动失败排查端口被占用和数据库连不上是最常见的两个原因启动报错很多人第一反应是代码写错了但我见过最多的情况其实是端口被占用。8080端口被别的进程占着Spring Boot会提示Port 8080 was already in use这时候在命令行执行netstat -ano | findstr 8080找到占用进程然后杀掉或者改项目端口就行。另一个容易忽视的启动问题数据库没启动就去跑Spring Boot项目。如果你用的是本地MySQL先确认MySQL服务是启动状态Windows下可以在服务管理器里查MySQL服务或者在命令行执行mysql -u root -p验证能否连上。数据库连不上时启动报错会持续很长一段时间看起来像项目卡死了实际上是一直在尝试重连。5.5 前端传来的参数校验别全在Controller里写Bean Validation更省事很多新手习惯在Controller里写一大堆if (xxx null) { return error; }一个方法里能堆十几行校验代码。教务系统里学生选课传的studentId、openId成绩录入传的成绩分数都需要校验如果全用if堆代码会越来越难维护。我一般用Validated加JSR-303注解来做参数校验在实体类的字段上加NotNull、Min、Max注解Controller方法参数上加Validated框架会自动完成校验不通过时抛异常由全局异常处理器转成统一返回结果。这样做的好处显而易见代码量少一半校验规则跟着字段走不会因为Controller里漏写校验导致脏数据进库。5.6 数据库密码直接写死在配置文件里最后这条也许不算“技术错误”但在求职简历上聊到项目时它是加分点。教务系统的application.yml里数据库账号密码、Redis密码直接明文写在配置里这在本地开发没问题但代码一旦推到GitHub上被公开整个数据库就裸奔了。解决方式有两种如果项目部署在Linux服务器用环境变量注入密码配置中心比如Nacos对这个量级的项目来说又太重了。环境变量方案最合适在application.yml里写成${DB_PASSWORD:默认值}既能本地跑通部署时又不会泄露真实密码。6. 把教务系统从课设推到可部署从打包到部署的四个实用细节课程设计答辩通过不等于项目结束把Java编写的教务系统真正部署到服务器上跑起来才算走完了一个完整闭环。这个阶段的技术含量不亚于写业务代码——很多同学代码在本地能跑部署到Linux服务器上问题一个接一个。我的习惯是用Maven打成可执行的JAR包部署而不是打WAR包放Tomcat。Spring Boot内置了Tomcatjava -jar一条命令就能启动省去了单独管理外部Tomcat的麻烦。打包命令是mvn clean package -DskipTests打包成功后在target目录下会生成一个edu-admin.jar。用scp传到服务器上然后执行nohup java -jar edu-admin.jar --spring.profiles.activeprod app.log 21 --spring.profiles.activeprod指定生产环境的配置nohup让程序在后台运行输出日志写到app.log。首次部署容易踩的坑是服务器MySQL的字符集没配置导致写入中文出现乱码还有防火墙没放开8080端口外部访问不到。服务器上执行mysql -u root -p进入MySQL后运行show variables like character%;确认character_set_server是utf8mb4不是的话修改my.cnf后重启服务。部署完之后验证环节很多人都做得不够。我的做法是至少在服务器上把这三条链路完整走一遍学生登录选课教师登录录入成绩管理员查看统计报表。特别要关注的是选课高峰期时的表现——找三五个同学同时点选同一门只剩一个名额的课看最终选课人数是否超过容量。别把这个验证当成走过场它直接检验第4.2节里FOR UPDATE是否真的生效了。还有两个细节值得做一是定期备份数据库对于教务系统来说数据就是命我的做法是写一个简单的定时脚本每天凌晨用mysqldump全量备份并用find -mtime 30自动删除30天前的备份文件二是给管理端的敏感操作加上日志记录谁在什么时间改了成绩、调整了选课容量都要能追溯这在课程答辩和真实使用中都是加分项。说实话我自己第一次部署教务系统时光是因为服务器时区没同步导致时间错乱就排查了一整晚。后来养成的习惯是新环境第一步先检查和配置字符集、时区、防火墙全部确认无误再部署业务代码。希望这些经验能让你少走几段弯路把部署这件事一次做顺利。本文还有配套的精品资源点击获取
返回列表