news 2026/9/14 4:50:53

基于Java毕业选题系统:Spring Boot+Vue前后端分离与并发控制实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Java毕业选题系统:Spring Boot+Vue前后端分离与并发控制实战

简介:基于Java/JSP的毕业选题系统完整源码,面向Java Web学习者、毕业设计或课程设计学生,覆盖管理员、老师、学生三类角色的核心流程:管理员可维护系主任信息与系统运行,老师录入题目并审核学生选题,学生在线完成选题操作,体系完整且贴近真实教务场景。资源共547个文件,以JSP动态页面、CSS样式、JS交互脚本为主,并包含MySQL数据库文件(MDF/LDF)、多篇CAJ/DOCX论文资料及环境说明文档,整体压缩包仅8.65MB,导入Eclipse/IDEA配合Tomcat即可搭建运行,目录结构也较为清晰。已有52人学习下载。包内提供完整前后端源码、数据库脚本、论文及说明文档,既适合理解高校选题管理系统的模块设计与Web开发细节,也便于二次开发与论文撰写参考,是一份紧凑实用的Java Web项目样例。

1. 毕业选题系统源码包里的前后端到底在解决什么

每年三四月份,高校教务办公室就会收到大量纸质选题表,学生抢题靠手速,老师统计靠复制粘贴。“基于java的毕业选题系统”这类课设和外包项目因此长盛不衰。你拿到的源码包里通常是一个完整前后端项目、MySQL初始化脚本、说明文档和一篇LW论文,它本身是一条可复现的Spring Boot + Vue + MySQL工程主线。下面不打算逐行解读某个压缩包里的代码,而是从一线工程师的视角,把毕业选题系统拆成选型、数据模型、后端接口、前端联调和MySQL调优五个环节,讲清楚每一步为什么这么做、参数怎么设、并发抢题时看哪里。适合正在做毕业设计的学生、需要给高校搭建选题系统的开发者,以及想快速掌握前后端分离实战的Java程序员。

2. 基于 Java 的毕业选题系统:Spring Boot + Vue 选型与 MySQL 表设计

毕业选题系统不是一个复杂的业务,但涉及三种角色、一个强并发场景(抢题)和一个明确的状态流(待审核、已通过、被驳回),所以技术选型要围绕“快速交付、容易演示、抗并发”来定。这一章先讲清楚为什么主流源码包都选 Spring Boot + Vue,再落到可以直接导入的 MySQL 表设计。

2.1 前后端分离和单体 JSP,为什么选前者

先说原理。JSP 单体时代,Java 代码和 HTML 混在一起,后端改个字段必须把整个 war 包重新部署;而毕业选题系统要同时给学生端、教师端和管理员端提供页面,每个端的交互密度差异很大。采用前后端分离后,后端只用 Spring Boot 提供 REST 接口,前端用 Vue 构建单页应用,部署时前端静态文件扔 Nginx,后端独立跑在 Tomcat 或内嵌容器里。团队可以并行开发,学生也更容易在答辩时演示:点一下按钮,axios 请求打到后端,MySQL 返回结果,整个过程链路清晰。

这种结构对“源代码包”的整理也很友好,通常一个目录放 backend,一个目录放 frontend,数据库脚本单独放在 doc 或 sql 文件夹。如果你下载的压缩包是 JSP 老架构,建议迁移到前后端分离,再开始改需求;否则后续加接口、换皮肤的成本很高。需要注意,前后端分离不等于无状态,跨域、Token 刷新这些问题必须在一开始约定好,这也是“前后端分离项目实战”里最常见的坑。另一点是快速开发平台的选择,很多毕业设计会直接用若依框架生成前后端代码,但自己从 Spring Boot 搭一次过滤器和事务,才能理解抢题这种并发场景到底卡在哪里。

2.2 核心实体:学生、教师、选题与选题记录

业务上至少需要四个实体:用户(包含学生和教师)、选题、选题记录,以及一个用于审计的操作日志。学生和教师都放在 sys_user 表中,用 role 字段区分;选题表保存题目标题、描述、所属教师和最大人数;选题记录保存学生、题目、审定状态和版本号,版本号用于并发控制。下面的 MySQL DDL 是常用做法,很多 mysql 安装配置教程也会建议这样初始化库表,导入后先别急着改业务,看清楚唯一键和索引再动手。

CREATE DATABASE IF NOT EXISTS graduation_topic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE graduation_topic; CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL COMMENT '用户名/学号', password VARCHAR(100) NOT NULL COMMENT 'BCrypt 哈希值', role TINYINT NOT NULL COMMENT '1-学生 2-教师 0-管理员', department VARCHAR(100) DEFAULT NULL COMMENT '院系', real_name VARCHAR(50) NOT NULL COMMENT '姓名', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '0未删除 1已删除', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username), KEY idx_dept (department) ) ENGINE=InnoDB COMMENT='用户表'; CREATE TABLE topic ( id BIGINT PRIMARY KEY AUTO_INCREMENT, teacher_id BIGINT NOT NULL COMMENT '发布选题的教师ID', title VARCHAR(200) NOT NULL, description TEXT, max_students INT NOT NULL DEFAULT 1 COMMENT '最多可选人数', selected_count INT NOT NULL DEFAULT 0 COMMENT '当前已选人数', status TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0停用', deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_teacher (teacher_id) ) ENGINE=InnoDB COMMENT='选题表'; CREATE TABLE selection_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, topic_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0待审核 1通过 2驳回', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_student_topic (student_id, topic_id), KEY idx_topic_status (topic_id, status) ) ENGINE=InnoDB COMMENT='选题记录表';

这段 SQL 有四个重点:第一,sys_user 表唯一键放在 username 上,学号和工号统一走这个字段,避免重复账号;第二,topic 表用 selected_count 缓存当前人数,查询列表时不需要 count(*),但必须配合事务更新防止超卖;第三,selection_record 的唯一键 (student_id, topic_id) 保证一个学生对一个题目只能有一条申请;第四,version 字段留给乐观锁,SELECT 时取出,UPDATE 时带上,如果更新影响行数为 0,说明别人已经改过这条记录。索引上,idx_topic_status 正好能覆盖“按题目查状态”的后台审核列表,避免文件排序。

2.3 字段设计中的 3 个易错点:角色、状态和逻辑删除

这块设计不好,后面写接口会到处打补丁。第一,角色不要用字符串 'student' 存,字符串占用空间大且容易写错,用 TINYINT 存数字,接口层做枚举映射。第二,状态字段预留扩展,选题记录的状态常用“待审核、通过、驳回”,但你加上“撤回”后修改枚举即可;第三,所有业务表都加 deleted 逻辑删除,因为高校系统有审计要求,物理删除会被学生投诉“成绩没了”。下面的表格是推荐字段组合。

字段名类型默认值作用
roleTINYINT10-管理员 1-学生 2-教师
statusTINYINT00待审核 1通过 2驳回
deletedTINYINT00正常 1删除
versionINT0乐观锁判断
create_timeDATETIMECURRENT_TIMESTAMP记录创建时间

MySQL 创建索引时,不要看到 WHERE 就加索引,优先在唯一约束和外键字段上加。上面 selection_record 已经加了唯一键,隐含了索引;如果再单独加一个 student_id 的单列索引,会重复占用空间。这里真正需要补的是 topic 表的 selected_count 更新条件,例如在事务里先执行UPDATE topic SET selected_count = selected_count + 1 WHERE id = ? AND selected_count < max_students,影响行数为 1 才允许插入记录。这条语句也是后面所有并发控制的核心。

3. 用 Spring Boot 与 JWT 实现毕业选题后端接口与并发控制

后端是整个系统的中枢。这里我以 Spring Boot + MyBatis-Plus + Spring Security 为例,这套组合在“springboot vue前后端分离”项目里几乎成了默认配置。MyBatis-Plus 提供内置 CRUD,但复杂的更新必须自己写 SQL,尤其是乐观锁更新。这一章从登录鉴权讲到接口设计,再看如何用一条 UPDATE 挡住多人抢同一个题。

3.1 登录鉴权:基于 RBAC 的 JWT 令牌流程

原理是:用户提交用户名密码,后端调用 BCryptPasswordEncoder.matches 校验,通过后生成一个包含 userId、role 的 JWT。后续每次请求前端都会在 Authorization 头带上这个令牌,后端通过过滤器解析并放入 SecurityContext。这样接口里直接使用@PreAuthorize("hasRole('TEACHER')")就能限制角色。

常见的代码组织是:一个 SecurityConfig 注册过滤器链,一个 JwtAuthenticationFilter 继承 OncePerRequestFilter,一个 UserDetailsServiceImpl 查询 sys_user 表。JWT 的有效期参数建议设 2 小时,刷新令牌另设 7 天;不要把过期时间设成 24 小时,学生改完页面挂一晚上很容易出安全漏洞。下面摘一段 JWT 工具的生成逻辑。

public String generateToken(Long userId, Integer role) { long now = System.currentTimeMillis(); long expire = now + jwtProperties.getExpireMinutes() * 60 * 1000L; return Jwts.builder() .claim("uid", userId) // 放入 uid,供后续过滤器解析 .claim("role", role) // 放入角色,控制接口权限 .setIssuedAt(new Date(now)) .setExpiration(new Date(expire)) .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret()) .compact(); }

这段代码的关键参数是 claim 里的 uid 和 role,它们在后续 filter 里被取出并构造成 UsernamePasswordAuthenticationToken。HS256 是对称加密,密钥必须放到 application.yml 里用环境变量注入,不能硬编码在 java 文件中。expireMinutes 是从 properties 读取的整数值,调整它可以直接控制会话时长,例如写 120 表示 2 小时。

3.2 选题与审核的接口设计:路由、入参和返回体

学生端接口至少包括:分页查询可选题目、提交选题、查看我已选列表、取消待审核选题;教师端有发布题目、修改题目、审核学生选题;管理员端有用户管理和数据导出。接口风格统一为/api/v1/...,前端通过 axios 访问。下面是一个精简的接口表,具体路径可以在源码包的 controller 层找到。

方法路径角色说明
GET/api/v1/topic/page学生、教师分页查询选题,带 keyword、status 参数
POST/api/v1/selection学生提交选题申请
PUT/api/v1/selection/{id}/audit教师审核通过或驳回
GET/api/v1/selection/my学生当前学生已选列表
GET/api/v1/export管理员导出选题名单 Excel

Controller 层要做的是参数校验和响应包装。Spring Boot 可以统一返回 Result<T>,code 为 0 表示成功,非 0 给前端弹出错误提示。不要返回裸对象,否则前端拿到 null 字段很难排查。分页参数 pageNum 和 pageSize 要设置上限,一般 pageSize 最大 100,防止拖垮 MySQL。

@PostMapping("/selection") public Result<Long> submit(@RequestBody SelectionSubmitDTO dto) { // 校验学生身份,取 JWT 中的 uid Long studentId = SecurityUtils.getUserId(); // 乐观锁更新选题人数,返回 boolean 表示是否抢到 boolean ok = selectionService.submitSelection(studentId, dto.getTopicId()); return ok ? Result.success(0L) : Result.error(400, "人数已满或重复提交"); }

这段逻辑把校验和状态变更都收在 service 内部,Controller 不写业务。submitSelection 里会执行两步:先查 topic 当前 selected_count 是否小于 max_students,再 UPDATE topic SET selected_count = selected_count + 1 ...,最后 INSERT selection_record。如果 UPDATE 影响行数为 0,直接回滚,抛出异常让事务生效。

3.3 防止两个学生选同一个题目:事务和锁的顺序

后端最怕的坑是两个请求同时通过第一步校验,然后都去更新 selected_count,数据库行锁会阻塞第二个,但如果没有条件校验,第二个仍可能成功。所以 update 必须绑定原有条件。下面这段是核心 SQL。

-- 只有当前人数小于上限才更新,返回影响行数 1 表示抢到名额 UPDATE topic SET selected_count = selected_count + 1, update_time = NOW() WHERE id = #{topicId} AND status = 1 AND deleted = 0 AND selected_count < max_students

MyBatis 里执行后,如果返回 1,说明当前请求抢到了名额;返回 0,说明题目已停用或人数已满。同时 selection_record 的唯一索引 uk_student_topic 会兜底重复申请,即使两个请求并发插入,也只有一个能成功。这里要注意事务隔离级别,使用 MySQL 默认的 REPEATABLE_READ 即可,不需要提高到 SERIALIZABLE,否则高并发下性能下降明显。另一个建议是给 selection_record 的 status 字段一个索引,方便审核列表分页。

提示:不要用 synchronized 做本地锁,毕业选题系统如果部署多节点,本地锁在另一台服务器上完全无效。用数据库条件更新才是跨实例安全的方式。

4. 用 Vue3 + axios 实现毕业选题系统的登录与选题页面

前端我用 Vue3 + Vite + Element Plus,这是当前毕业设计和外包项目里最常见的组合。前端不再关心业务规则,只负责渲染后端字段,真正难的反而是路由守卫、请求拦截和防止重复点击。这一章不贴完整页面代码,只把三个最容易被扣分的点讲透。

4.1 vite 初始化与路由守卫:未登录跳转登录页

先用npm create vite@latest graduation-frontend -- --template vue初始化项目,再安装npm install vue-router@4 element-plus axios。路由表用 createWebHistory 模式,但部署到子路径时要设置 base,否则刷新后会 404。下面是一个基础的路由守卫片段。

const router = createRouter({ history: createWebHistory('/admin/'), routes }) // 每次跳转前检查 token,没有 token 就回登录页 router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } })

这里的 createWebHistory 传入了/admin/作为 base,部署到 Nginx 时 location/admin/内静态文件能得到正确路径。beforeEach 只做了 token 存在性检查,没有校验过期;更好的做法是把过期校验放到 axios 响应拦截器里处理 401。登录成功后的跳转路径取自 query.redirect,这样用户被踢回登录页后还能回到原页面。

4.2 封装 axios:统一携带 JWT 并处理 401

前后端分离项目里,axios 不能直接用,必须统一配置 baseURL、超时时间、请求拦截器和响应拦截器。请求拦截器从 localStorage 读取 token,响应拦截器专捕 HTTP 401。如果后端返回 401,就清 token 并跳回登录页。注意,token 用 localStorage 存是为了演示方便,生产环境更推荐 httpOnly cookie,但那样还要处理 CSRF,取舍要提前想清楚。

const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE || '/api/v1', timeout: 10000 }) // 请求拦截器:自动加 Bearer Token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:捕获 401 并跳登录页 request.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push({ path: '/login' }) } return Promise.reject(error) } )

这里 baseURL 通过环境变量 VITE_API_BASE 传入,例如开发环境指向http://localhost:8080/api/v1,生产环境由 Nginx 把/api/v1反向代理到后端,避免跨域。timeout 设 10000 毫秒,是给慢 SQL 留余量;如果你的毕业选题系统查询特别重,可以调到 15000,但别再高,否则用户会以为页面卡死。

4.3 选题页面:倒计时、禁用按钮与状态刷新

学生选题页最常见的交互是:列表左侧显示题目,右侧显示剩余名额,点击“选题”按钮后按钮立刻变为 loading,防止用户连点。下面是一个精简的 Vue 组件片段。

<script setup> import { reactive, onMounted } from 'vue' import { ElMessage } from 'element-plus' const state = reactive({ list: [], submittingId: null }) const submitSelection = async (topic) => { if (state.submittingId != null) return state.submittingId = topic.id try { const res = await request.post('/selection', { topicId: topic.id }) ElMessage.success(res.message || '选题成功') await loadList() } catch (e) { ElMessage.error(e.response?.data?.message || '选题失败') } finally { state.submittingId = null } } </script>

这里的 submittingId 是全局锁定标记,只要有一个请求在途,其他按钮全部 return。加载列表后,用 selected_count 和 max_students 比较,相等则禁用按钮并显示“已满”。Element Plus 的 el-table 自带 loading attribute,可以绑在 state.loading 上,不必再写额外遮罩。后端返回的 Result 结构要固定,前端直接使用 res.code 判断成功,而不是依赖 HTTP 状态码。

5. 毕业选题系统上线前:MySQL 索引与并发模拟验证

这一章是验收阶段最实用的部分。代码写完了,先别急着打包交报告,用下面三组操作验证并发正确性,再导一份截图放 LW 里会更有说服力。

5.1 给 selection_record 加唯一索引和普通索引

最核心的兜底是学生和题目之间不能重复申请,所以必须先加唯一索引。然后用普通索引服务“查一个学生的所有申请”和“查一个题目的所有申请”。

ALTER TABLE selection_record ADD UNIQUE KEY uk_student_topic (student_id, topic_id), ADD KEY idx_student (student_id), ADD KEY idx_topic (topic_id);

注意唯一索引和普通索引不要重复建,student_id 已经在联合索引的最左前缀里,单独建 idx_student 只会在“只看学生维度”时命中,如果联合索引已经经常使用,其实可以省掉。这个要结合你实际的慢查询来定。

5.2 模拟 50 个学生同时抢题

验证逻辑靠人肉点击不可靠。可以用 bash 的 seq + xargs 发送并发请求,观察返回码。

seq 1 50 | xargs -P 20 -I {} curl -s -X POST http://localhost:8080/api/v1/selection \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"topicId":1}' | grep -o '"code":[0-9]*' | sort | uniq -c

这里的 -P 20 控制并发数是 20,每次请求使用同一个 token 或不同的 token 才有意义;如果所有请求用同一个学生 token,后端应该拦截重复记录。观察统计里 code=400 的数量是否等于超出名额的数量,并发下不能出现 200 的条数大于 max_students 的情况。

5.3 用慢查询日志定位索引是否生效

如果抢题高峰期接口变慢,打开 MySQL 慢查询日志,看有没有全表扫描。临时开启后执行 EXPLAIN,检查 type 列是 ref 还是 ALL。

SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.5; EXPLAIN SELECT * FROM selection_record WHERE student_id = 1 ORDER BY create_time DESC;

如果 possible_keys 里出现 uk_student_topic 或 idx_student,并且 key 字段不是 NULL,说明索引已经命中。如果看到 filesort,说明排序字段没有索引,可以增加组合索引 (student_id, create_time)。这些验证步骤做完,再回头调整 topic 表的 selected_count 更新语句,毕业选题系统的并发防护就完整了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 4:50:31

Python信用卡客户高风险识别:从特征工程到评分卡的完整建模实践

简介&#xff1a;这是一份基于Python实现的信用卡客户高风险识别毕业设计项目&#xff0c;面向计算机相关专业在校生、课程设计及实训场景&#xff0c;解决从数据探索、清洗到聚类建模的完整流程。围绕历史信用风险、经济风险与收入风险三个维度&#xff0c;通过K-Means算法构建…

作者头像 李华
网站建设 2026/9/14 4:48:44

Claude 跑 MCP 增强 Selenium:模型 Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 4:48:22

FOC电流采样硬核指南:单/双/三电阻选型与ADC时序避坑

1. 这不是理论推导&#xff0c;是炸过三次MOS管后写下的电流采样实操手册FOC电流采样这件事&#xff0c;说白了就是让控制器“看清”电机绕组里真实流过的电流。但现实很骨感&#xff1a;你按教科书接好单电阻、设好ADC触发点、跑通SVPWM&#xff0c;一上电——“砰”一声&…

作者头像 李华
网站建设 2026/9/14 4:47:48

背包客主题静态网站实战:HTML+CSS+JS与Nginx部署全记录

做了个背包客主题的静态网站&#xff0c;项目名叫“千年之恋”&#xff0c;听着挺文艺&#xff0c;其实就是一个纯粹的HTMLCSSJavaScript前端练手作品。我做这个项目的出发点很简单&#xff1a;想用网页把一个古镇的传说和一路上的风景、住宿、车票、手绘地图这些旅行碎片串起来…

作者头像 李华
网站建设 2026/9/14 4:46:09

MiGPT:3步把小爱音箱接入ChatGPT,改造成你的专属AI语音助手

MiGPT&#xff1a;3步把小爱音箱接入ChatGPT&#xff0c;改造成你的专属AI语音助手 【免费下载链接】mi-gpt &#x1f3e0; 将小爱音箱接入 ChatGPT 和豆包&#xff0c;改造成你的专属语音助手。 项目地址: https://gitcode.com/GitHub_Trending/mi/mi-gpt 深夜你问&quo…

作者头像 李华