新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于B/S架构的毕业设计选题系统:双选流程与高并发实现

发布时间:2026/9/26 7:10:06来源:尧图网络
基于B/S架构的毕业设计选题系统:双选流程与高并发实现
每年三四月份各大高校的教务通知群里就开始刷屏——“毕业设计选题系统即将开放请在规定时间内完成选题确认逾期不候”。后台的老师们这时候往往是最焦头烂额的手动Excel统计选题、学生反复来问名额还剩多少、老师们被几十封邮件和私信淹没。我当年给自己导师做的第一个Java Web项目就是解决这个问题的毕业设计选题系统。基于B/S架构用Java技术栈从零搭建把“教师出题—学生选题—双向确认—过程管理”整条链路搬上网页。这篇文章就基于这个项目把我做系统时的完整设计思路、数据库建模、核心代码实现和踩坑记录完整梳理一遍给正在做类似毕业设计或者真实交付项目的同学一个可直接复现的参考。1. 为什么是B/S架构从选题场景倒推技术选型1.1 高校选题场景的三大痛点在做技术选型之前我先把“用户在哪、在什么环境里用、有多少人用”这几个问题想清楚了。毕业设计选题这个场景有很明显的特殊性首先它的使用时间高度集中一般就是每年春季学期开学前后的一到两周全年级几百上千名学生和几十上百位老师会在同一时间段内涌入系统其次它的参与角色多不是单纯的学生选题那么简单还牵涉课题申报、院系审核、管理员发布、学生多轮选择、教师确认名额等多个环节第三个痛点是信息不透明选题名额剩多少、老师倾向于接收谁、中途有没有学生放弃这些信息在传统的Excel接力赛里根本无法实时同步。其实最开始我也纠结过要不要用传统的方式做一个Excel表格共享、再把最终结果发到群里。但这个方案稍微推演一下就会发现根本行不通几十个老师同时编辑一个表格版本冲突就已经够让人崩溃学生没法在实时名单上看到哪些题目有空缺老师也没办法只接收自己认可的学生。所以系统化、在线化的双向选择平台在这个场景里基本是刚需。一句话总结这个系统要解决的核心问题就是“让正确的课题找到合适的学生让合适的学生匹配到不超员的课题”。1.2 B/S架构为什么比C/S架构合适这里有个很常见的争论为什么不用C/S架构C/S架构比如局域网版的客户端系统在校园网环境里也能跑但有两个绕不开的问题。第一客户端分发成本很高几百个学生要逐台安装配置不同院系的电脑环境千奇百怪光解决环境问题就能耗掉一整周第二系统升级要重装客户端每次改一个需求就要全部人更新一遍运维成本完全不可控。B/S架构只需要部署一个Web服务器学生和老师打开浏览器输入网址就能用这是它最大的优势。我最终选的部署方案是校内一台服务器上跑一个Java的Web应用数据库用MySQL浏览器端不挑设备Windows、Mac、手机浏览器都能访问。实测下来300人左右的学院选题高峰期几百并发只要服务器配置不拉胯2核4G起步、8G更稳Spring Boot默认的Tomcat线程池完全扛得住。这也是B/S架构在这个场景里最有说服力的地方入口统一、升级简单、并发可扩展。如果你胆子大一点还可以把系统部署到云服务器学生在宿舍躺着用手机就能完成选题体验上会比机房排队好太多。1.3 角色权限体系三类用户如何互相制约这个系统在权限上有个很关键的设计原则不同角色的操作界面和功能必须严格隔离不能让学生打开管理员的菜单。我按实际业务划分了四个角色系统管理员、院系管理员、教师、学生。它们之间的制约关系是层层递进的具体可以看下面这个表角色核心职责关键操作边界约束系统管理员全局配置与数据维护账号管理、学年学期参数、开放关闭选题时间、数据统计不干预具体课题的双选内容院系管理员本院课题审核与调剂审核教师课题、处理调剂申请、查看本院数据只能操作自己院系的数据教师出题与选人申报课题、查看申请、确认或拒绝学生、过程评价只能管理自己名下的课题学生选题与提交材料浏览课题、申请课题、提交阶段文件同一学期只能有一条有效申请这套角色模型看起来不复杂但它决定了整体的功能菜单和页面权限。实现的时候我用Spring Security加自定义注解或者直接用一个拦截器做角色校验都是可以的。如果项目周期紧我建议先用拦截器加角色字段做精细鉴权把主要精力放在核心业务流程上权限安全后面再补也不迟。提醒一句做权限体系的时候不要只在前端隐藏菜单后端接口也要做角色校验。很多人图省事只在前端控制结果学生换个接口请求直接能看到老师的数据这在毕业设计答辩和真实上线中都是大忌。2. 双向选择流程如何从零落地状态机驱动的业务链路2.1 从课题申报到双选成功的完整时序这是整个系统最核心的地方。双向选择不是学生点一下“选中”就完事它是一个完整的状态链路。我把整个流程拆成六个环节第一步教师登录系统申报课题。课题至少要填这些字段课题名称、研究方向、课题简介、对学生的要求、可用名额、课题类型比如工程设计还是理论研究以及学年学期。第二步院系管理员审核课题审核通过后的课题进入“待发布”状态之后由管理员统一发布。审核这一环不能省否则每年都会出现题目重复、用词不当或者研究方向对不上的课题被直接放出来。第三步学生浏览已发布的课题并选择自己心仪的课题提交申请。注意这里我设计成“申请”而不是“直接占用”这是双向选择的关键区别。第四步教师查看申请列表逐个确认或拒绝。教师确认后课题的剩余名额减一学生和教师都收到最终确认通知。第五步如果名额满了课题状态自动变为“已满员”其他学生还能浏览但不能继续申请。第六步管理员可以发起“调剂”环节对未成功双选的学生开放调剂由管理员或院系管理员手动分配课题。2.2 双向选择的核心策略先到先得还是教师主导“双向选择”这四个字的关键在于它既要照顾学生的意愿也要尊重教师的意见。所以在系统设计上我把选择策略做成可配置的。策略A是先到先得学生提交申请后只要名额还有就直接占用一个名额。策略B是申请制学生提交申请后课题状态依然显示“可申请”但最终需要教师确认后学生才算正式被接收。这两种策略各有应用场景。比如基础课程设计、题目明确且不挑人的课题先到先得效率高而毕业设计这种带有研究性质的课题多数老师希望先看到学生的成绩和方向再进行确认更适合申请制。最终我的系统里是让教师在课题发布时自主选择策略。这个设计在答辩时是一个非常亮眼的点因为你能明确说出我的系统用状态机驱动了不同策略下的业务流转而不是一个写死的逻辑。从工程角度讲策略选择本质上是给课题表加一个字段然后在选题Service里根据这个字段走不同的分支代码量并不大但业务适应性会强很多。2.3 状态机设计让流程有据可循双选流程能顺利跑通本质上靠的是严格的状态控制。我定义了两套状态课题状态和选题记录状态。课题状态包括草稿、待审核、审核通过、已发布、已满员、已关闭选题记录状态包括已申请、教师已通过、教师已拒绝、已放弃、调剂中、双选完成。每套状态对应一组允许的操作。比如学生只能申请“已发布”且未满员的课题只有处于“已申请”状态的记录教师才能确认或拒绝一旦记录进入“双选完成”任何一方都不能随意修改只能由管理员介入处理。这个状态机看起来简单但实际开发里我建议把它集中在一个枚举类中管理而不是散落在Service的各个if/else里。当你后续要加“撤销申请”“名额释放”之类的功能时只需要在枚举里扩展一个状态再补上对应的流转方法改动非常小。这一点我在维护了一版之后体会特别深状态散着写的版本改一次崩一次集中管理之后整条链路清晰了太多。3. 数据库设计核心表结构与数据一致性把住最后防线3.1 核心表的拆分与字段设计数据表设计是这类管理系统的地基设计得好后面开发顺风顺水设计得不好后期就是不停地打补丁。我把核心表拆成了六张左右结构简洁但能覆盖完整业务链路表名关键字段作用说明sys_user用户ID、账号、密码、姓名、角色编码、所属院系、状态统一管理四类角色账号topic课题ID、教师ID、课题名称、研究方向、简介、名额、剩余名额、策略类型、状态承载教师申报的课题主数据select_record记录ID、课题ID、学生ID、教师ID、申请时间、状态、备注记录每次双选交互链路stage_file阶段ID、记录ID、阶段类型、文件路径、提交时间、评分状态记录开题、中期、结题阶段材料notice通知ID、标题、内容、发布时间管理员发布公告和通知sys_config参数名、参数值存储学年学期、开放时间等全局配置3.2 名额控制为什么不能只靠应用层这里要重点讲一个大坑课题名额的控制必须放在数据库层面做约束不能只靠Java代码里的判断。假设学生A和学生B同时并发提交申请如果两个请求都通过了“剩余名额大于0”的判断然后各自执行UPDATE语句把剩余名额减一就很容易出现名额被扣成负数的情况。这个问题的标准解法有两个。第一个是使用带条件的UPDATEUPDATE topic SET remaining remaining - 1 WHERE id ? AND remaining 0执行后判断影响行数是否大于0如果等于0就说明名额已经抢完直接提示学生课题已满。这种“乐观锁式的SQL条件更新”比先SELECT再UPDATE要安全得多。第二个是给select_record表加唯一约束比如UNIQUE(student_id, semester)保证同一个学生在同一学期只能有一条有效的选题记录防止学生重复下单。如果系统允许学生同时申请多个课题那就再加一个状态条件用部分唯一索引保证同一时间只能有一个待确认记录这个要看你的数据库版本支不支持。3.3 数据一致性与历史回溯做这类系统还有一个容易被忽略的点历史记录不能随意覆盖要有状态变更日志。比如教师先拒绝了学生A但后来学生A又申请了其他课题这个流程如果只靠主表字段覆盖后面溯源就很难。我的做法是加一张选题操作日志表记录谁在什么时间对哪条选题记录执行了什么操作、操作前状态、操作后状态。这张日志表平时看着没什么用但在真实运营中价值非常大。比如学生申诉“我明明申请了为什么老师没看到”一查日志就能定位是哪个环节出了问题。而且这在毕业设计答辩中也是一个很有说服力的亮点系统具备完整的可审计性。数据一致性还要注意一点凡是涉及“扣名额 插记录”这种组合操作必须放在同一个事务里否则就会出现记录插入成功但名额没减的脏数据。这个我用Transactional(rollbackFor Exception.class)处理后面在排查部分会再展开。4. 技术选型与核心代码实现把高并发名额扣减落到实处4.1 技术栈怎么选Spring Boot MyBatis-Plus MySQL这个项目我最终用的是Spring Boot 2.7 MyBatis-Plus MySQL 8 Thymeleaf开发起来比传统的SSM框架舒服得多。为什么不用SSH或者纯Servlet说句实话Spring Boot把配置的大量复杂度都吃掉了内置了Tomcat打一个jar包就能跑非常适合这种中小型Web项目。而MyBatis-Plus相比原生MyBatis的一个巨大优势就是省去大量的单表CRUD代码内置的分页插件、条件构造器能让你把精力放在业务逻辑而不是样板代码上。如果你的需求是前后端分离后端只写REST接口前端用Vue3加Element-Plus这样做出来的页面会更好看也能体现对现代前端框架的理解。但如果你是一个人做毕业设计时间比较紧用Thymeleaf做服务端渲染反而效率更高不用处理跨域、不用维护两套环境、部署也更简单。我在做这个项目时用的Thymeleaf加一点点原生JavaScript整个工程结构非常清爽。当然下面是个人偏好的组合大家可以根据自己的情况调整。4.2 核心模块实现学生提交选题与教师确认我拿“学生提交选题”这个接口来做示例把核心逻辑拆成Service层的一段关键代码。学生提交选题时Service要做三件事校验课题状态和名额、校验学生是否已有有效申请、插入选题记录并扣减名额。Transactional(rollbackFor Exception.class) public Result applyTopic(TopicApplyDTO dto) { Topic topic topicMapper.selectById(dto.getTopicId()); // 1. 校验课题状态和名额 if (!TopicStatus.PUBLISHED.equals(topic.getStatus()) || topic.getRemaining() 0) { return Result.error(课题不存在或已满员); } // 2. 校验学生是否已存在有效申请 Long exists selectRecordMapper.countValidApply(dto.getStudentId()); if (exists 0) { return Result.error(你已有待确认的选题申请); } // 3. 带条件更新名额防止并发超售 int updated topicMapper.decrementRemaining(dto.getTopicId()); if (updated 0) { return Result.error(手慢了课题名额已被抢完); } // 4. 插入申请记录 SelectRecord record new SelectRecord(); record.setTopicId(dto.getTopicId()); record.setStudentId(dto.getStudentId()); record.setStatus(SelectStatus.APPLIED); selectRecordMapper.insert(record); return Result.success(); }这里最关键的是第3步的带条件更新SQL它是整个双选系统在高并发下不会出乱子的核心保证。对应的Mapper方法大致是这样Update(UPDATE topic SET remaining remaining - 1 WHERE id #{topicId} AND remaining 0) int decrementRemaining(Long topicId);如果你只做“先查后改”并发一高就会超额这段代码在面试或者答辩的时候是可以直接拿来说的亮点代码。教师确认学生申请的接口类似先校验记录状态必须是“已申请”然后更新记录状态为“已通过”如果这个课题是申请制还要做一次名额校验。注意教师确认和名额扣减也要放在同一个事务里因为“未通过时名额不下调、通过时名额减一”这两个动作必须原子发生。4.3 过程管理模块从选题完成后到毕业答辩选题完成后系统并没有结束后续还有一个很重要的过程管理模块。我把这个过程拆成几个阶段开题报告提交、中期检查、论文终稿提交、答辩成绩记录。每一个阶段本质上都是一张“阶段记录 文件上传”的组合表。我的实现是一张阶段配置表定义了一个学年里有哪几个阶段节点比如开题截止日期、中期截止日期一张阶段材料表记录每个学生每个阶段的上传状态和文件路径。教师登录后在“过程管理”菜单看到自己名下学生逐个审定阶段材料是否通过。这里要注意一个点文件上传一定要做好类型和大小限制否则学生经常传个几十MB的录屏过来直接把服务器磁盘打满。文件上传我用的是本地磁盘存储方案生产环境更好的做法是用OSS或MinIO对象存储。毕业设计为了简单可以先把文件放在服务器指定目录下数据库里存相对路径。唯一要注意的是文件名不要直接存用户上传的原名因为中文名和特殊字符很容易导致路径问题我习惯用UUID重命名原文件名单独存一个字段展示的时候再拼回去。这样既能保证存储安全也不会丢失原始信息。4.4 权限控制落地与菜单动态渲染这一节说说权限控制怎么落地。如果不想引Spring Security那套重东西可以自己写一个HandlerInterceptor在请求进入Controller之前通过用户角色和请求路径做校验。我这里采用了基于角色的方法每个用户登录后把角色标识放进Session再自定义一个注解RequireRole(teacher)加在教师接口的方法上拦截器里读取注解并判断当前用户角色是否匹配。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String value(); }前端这边的菜单就简单了登录之后根据角色渲染不同的菜单列表菜单数据从数据库或后端接口读取这样不同角色看到的功能入口天然隔离。这个方案实现成本低角色只有四类完全够用如果是大型多租户系统再考虑引入Spring Security那套中小型项目完全不必上。权限这块要提前设计不要等到功能写完了再回头加否则每个接口都要改非常痛苦。5. 常见问题与排查技巧实录并发、事务与部署避坑5.1 并发选同一课题导致超额怎么办这是最容易翻车的一个问题。我的排查思路是三步走。第一步检查表结构有没有唯一约束给选题记录表加上(student_id, semester)的唯一索引很多问题可以从源头阻断。第二步检查名额扣减SQL有没有写成带条件的UPDATE如果写成了UPDATE topic SET remaining remaining - 1 WHERE id ?那并发一高必出负数。第三步给状态流转加一个乐观锁版本号避免两个请求同时把同一个状态覆盖掉。这三步做完超额问题基本就绝迹了。另外在实际压测的时候建议用JMeter或者Postman的Runner功能模拟两三个学生同时点申请专门验证临界情况。不要觉得麻烦这个验证过程五分钟搞定却能帮你发现潜在的并发隐患。我当时就是用一个脚本并发请求了20次果然复现了超额问题然后才改成条件更新的SQL。这种“现场发现问题、现场修复”的经历在答辩讲出来反而特别加分。5.2 提交后页面不刷新、状态还是旧的这个问题的根源多半在浏览器缓存或者前后端异步请求没有正确刷新局部视图。如果是服务端渲染提交成功之后一定记得用Redirect而不是Forward防止用户刷新页面时重复提交表单。如果是Ajax请求提交成功后重新调用查询接口刷新列表不要只改本地的临时数据。还有一个细节用了Transactional的方法要注意回滚条件设成rollbackFor Exception.class否则有些RuntimeException之外的异常不会触发回滚就会出现“记录插入了但名额没减”这种脏数据。这个问题非常隐蔽因为普通业务里很少抛非运行时异常但只要你的代码里自定义了一个Checked Exception并且Service方法标注了throws就极容易踩坑。我在学生选题的Service方法上把这些都处理好了后面代码稳定很多。5.3 中文乱码、文件上传失败等基础坑中文乱码这个问题十个人里九个遇到过。排查清单在这里数据库连接URL要加characterEncodingutf8JDBC驱动连接参数要配置useUnicodetruecharacterEncodingutf8项目统一使用UTF-8编码HTTP请求和响应的字符集也要设置。文件上传失败则优先确认spring.servlet.multipart.max-file-size和max-request-size有没有设置默认1MB很快就会超标其次确认上传目录的写权限很多服务器上/tmp目录是受限的。我把容易踩的几个基础坑整理成了一张速查表问题现象排查方向解决方案中文乱码数据库连接、页面编码、HTTP字符集统一UTF-8连接URL加characterEncoding文件上传超限Spring配置文件未设置大小调大max-file-size和max-request-size上传后文件打不开文件名被改、存储路径错误用UUID重命名原文件名独立存储刷新页面重复提交使用了Forward而非Redirect提交成功后Redirect重定向到查询页状态被并发覆盖缺少乐观锁或唯一约束加version版本号、加唯一索引5.4 部署与演示环节的实用建议最后给一个非常实在的建议使用Spring Boot打包成单个Jar包内置Tomcat配一个MySQL数据库部署真的就是一条命令的事。Jar包放到服务器上nohup java -jar xxx.jar 就能启动。如果服务器上装了运维面板或者Docker那更简单Docker一条docker run命令挂载好数据库和目录就能跑。部署前记得把数据库初始化脚本跑一遍并确认服务器防火墙放行了8080端口不然只能本机访问问题排查起来会绕弯。答辩演示前务必准备一份预置数据两个教师、十个课题、若干学生账号、几条已经走了部分流程的选题记录。如果你现场从零开始注册教师再录课题光填表单就能耗五分钟体验极其糟糕。预置好数据双击打开首页就是丰富的业务状态答辩老师印象会好很多。另一个实用技巧是准备好一个“高并发抢名额”的演示脚本现场模拟多个学生同时申请同一个课题展示名额不会超卖这比口头讲“我的系统有并发控制”有说服力得多。我自己在做这个项目时还有一个体会状态机的设计比UI界面重要得多把业务流转理清楚了后面所有功能都是水到渠成。顺序做错代码越写越乱。这个选题系统做完之后后续如果要扩展可以做消息通知、图表统计、批量导出Excel、延期管理等功能只要底层的表结构和状态流转留好了这些都是增量开发的事情。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

边缘计算任务调度器QPLMTS算法:多目标优化与工程落地指南 2026/9/26 7:47:45

边缘计算任务调度器QPLMTS算法:多目标优化与工程落地指南

简介:这份源码包面向计算机、通信等专业的高年级本科生与研究生,以及从事边缘计算方向研究的开发者,提供一套基于QPLMTS算法的边缘计算场景任务调度器完整实现,可用于课程设计、期末大作业或算法验证实验。压缩包共8个文件&#x…

阅读更多 →
haproxy八种负载均衡算法详解与业务选型避坑指南 2026/9/26 7:47:45

haproxy八种负载均衡算法详解与业务选型避坑指南

半夜两点被一条告警吵醒,这种事情,凡是搞linux服务器运维或者后端的人应该都不陌生。那次告警的内容很直白:haproxy后面两台linux服务器,一台CPU已经跑到90%,另外一台只有8%。我盯着监控曲线愣了几秒,又回头…

阅读更多 →
员工绩效数据集分析预测:从Excel到可解释的离职风险评分 2026/9/26 7:47:44

员工绩效数据集分析预测:从Excel到可解释的离职风险评分

简介:这份资源面向希望上手机器学习实战的初学者与数据分析从业者,围绕员工绩效与薪资数据集,提供从数据预处理、特征工程到建模预测的完整分析链路,帮助读者理解回归任务在真实业务场景中的落地方式。压缩包共23个文件&#xff0…

阅读更多 →
基于Jev与Vercel AI Gateway的简历智能匹配系统实践 2026/9/26 7:47:44

基于Jev与Vercel AI Gateway的简历智能匹配系统实践

上个月我接了个有点尴尬的内部需求:HR 那边要处理几百份简历,按岗位 JD 做一轮智能初筛,输出的不能只是“匹配/不匹配”,还得有可解释的评分、匹配点和风险点。我一开始想拿关键词匹配糊弄过去,结果一测就被打脸——简…

阅读更多 →
Multi-Agent上下文隔离:不可变快照与血缘追踪实战 2026/9/26 7:47:44

Multi-Agent上下文隔离:不可变快照与血缘追踪实战

1. 为什么“上下文组织”是Multi-Agent系统里最常被忽视的致命瓶颈 我第一次在客户现场看到一个五层嵌套的Agent工作流崩溃,不是因为模型调用失败,也不是因为工具链断裂,而是因为第三层subagent把第一层用户原始提问里的关键约束条件——“仅…

阅读更多 →
C语言停车场管理系统:栈与队列实现及课程设计避坑指南 2026/9/26 7:47:38

C语言停车场管理系统:栈与队列实现及课程设计避坑指南

简介:这份资源面向计算机相关专业学生与C语言初学者,提供一套完整的数据结构课程设计参考方案,解决停车场管理场景下的建模与编码实践问题。项目以链栈为核心数据结构,实现了车辆进出登记、增删查改、停留时长计算与费用结算等逻辑…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉