
简介基于Java/JSP实现的毕业选题系统源码包面向高校毕业设计、课程设计及Java Web学习者。系统覆盖管理员、教师、学生三类角色管理员能维护系主任信息并负责系统日常运行教师可录入毕业设计题目、审核学生选题学生则通过网页完成在线选题业务链路完整可直接参考其模块划分与数据库设计。资源共547个文件以353个gif操作演示和61个jsp页面文件为主辅以css样式、js交互脚本、jar依赖包以及docx/caj论文与说明文档另含MySQL数据库文件压缩包整体仅8.65MB轻量便于部署。已有53人学习浏览适合需要快速落地毕设功能或理解选题流程的开发者。包内附带前后端完整代码、数据库脚本和文档可在Eclipse/IDEA配合Tomcat环境下导入运行便于二次开发与论文撰写。1. 毕业选题系统用 Java 做为什么毕业设计都爱选这个题每年三、四月份高校教务系统的选题入口一开几千名学生同时刷新页面教师端还要手动维护几十条题目很多人最后把 Excel 用成了核心业务库。基于 java 的毕业选题系统目标就是把“教师申报题目—学生志愿选题—管理员审核分配—结果导出”这条链路做成一个完整的前后端应用后端用 Java 写接口前端做操作页面数据沉到 MySQL 里附带的说明文档再把部署步骤和模块设计讲明白。这个标题适合两类人一类是正在找课程设计或毕业设计题目的学生另一类是刚学完 Spring Boot想拿一个“有真实业务流转、有角色权限、有完整前后端”的案例练手的新手开发者。它不炫技但能一次覆盖建库、联调、部署、答辩全流程。2. 选题系统拆开看技术栈选型和三个端口的模块边界2.1 SSM、Spring Boot 还是前后端分离三个技术路线的取舍清单标题里写了 java、完整前后端、mysql但“java”这三个字能落地的方式差别很大。我见过不少毕设源码包同一个选题在不同年份迭代出了三代技术路线工作量完全是两个量级。技术路线适合人群前后端耦合度答辩讲解难度面试认可度SSM JSPJava Web 刚入门高JSP 里混 Java 和 HTML低代码都在 controller 里一般Spring Boot Thymeleaf想快速交付课设中后端渲染页面中讲清楚 ModelAndView 即可中Spring Boot Vue 前后端分离求职方向或想练接口设计低前端调接口高要讲跨域、鉴权、联调更高这个系统的业务不算复杂很多人的第一反应是“没必要做前后端分离”。但标题里明确带了“完整前后端”这个词而且你现在去看招聘市场前后端分离项目实战经验几乎成了 Java 工程师的基础门槛所以我的建议是只要时间够六周以上就直接走 Spring Boot Vue 的路线。JSP 这条路最容易被低价源码包采用改造空间小答辩时老师让你当场改个页面样式你要翻半天 JSP 标签库体验很糟糕。选型上还有一个反直觉的坑别因为自己 Vue 不熟就退回 Thymeleaf。Thymeleaf 确实不用配跨域、不用管 npm但学期末的调试成本并不低于前端工程——后端模板渲染一旦报错你要同时懂 HTTP 状态码、模板语法和 Java 对象模型排错链路比“前端看 Network、后端看日志”要长得多。2.2 学生端、教师端、管理员端模块边界先厘清再写代码拿到这个源码包你面对的不是十几个不明所以的文件夹而是一套按角色划分的用例集合。毕业选题系统的核心角色只有三类看起来简单但大部分翻车项目都死在角色职责混在一起。角色核心用例涉及数据表关键页面学生浏览题目、提交志愿1-3个优先级、查看审核结果user、topic、select_record选题大厅、我的志愿教师申报题目、审核学生志愿、确认录取名单user、topic、select_record题目管理、志愿审核管理员账号管理、题目审批、专业维护、统计导出user、major、topic、select_record控制台、报表导出三类角色的用例有重叠也有边界。教师的“申报题目”和管理员的“审批题目”是两个动作不是同一个学生的“提交志愿”和教师的“确认学生”也是两个动作。我一般会先画一张角色-功能矩阵再按矩阵去拆 controller、service、mapper 三层而不是把所有方法堆在一个TopicController里。拿到源码后我建议按这个顺序读代码先看controller目录里有哪些路由再对应用例矩阵找“申报、审核、选题、导出”四个核心方法。搜索时优先找listPage、submit、audit、export这四个方法名它们基本串起了整个系统的主链路。前端工程则从router/index.js开始看路由权限能判断页面是动态路由还是静态路由——很多源码包在这块写得很随意接口鉴权形同虚设。3. MySQL 数据库设计五张核心表与一条状态机的落地3.1 user 表与角色设计为什么角色权限做在单表里更快数据库设计是这套系统里最能看出“有没有真跑过”的部分。很多毕设源码包会表结构一大片但实际业务只用了其中两张表。我比较推荐的最小核心表是五张user用户表、major专业表、topic题目表、select_record选题记录表再加一张可选的notice公告表。先看用户表和专业表CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT BCrypt 加密后的密码, role_type tinyint NOT NULL DEFAULT 2 COMMENT 0管理员 1教师 2学生, real_name varchar(50) NOT NULL COMMENT 姓名, user_no varchar(30) DEFAULT NULL COMMENT 工号或学号, major_id int DEFAULT NULL COMMENT 专业ID教师可不填, phone varchar(20) DEFAULT NULL COMMENT 联系方式, status tinyint NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE major ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, major_name varchar(50) NOT NULL COMMENT 专业名称, dept_name varchar(50) DEFAULT NULL COMMENT 所属学院, PRIMARY KEY (id), UNIQUE KEY uk_major_name (major_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT专业表;用户表这里有个设计取舍为什么不把admin、teacher、student拆成三张角色表因为这个体量下用 RBAC 三张表属于过度设计角色字段加一个tinyint就够。登录接口拿到用户名后先查出role_type再决定放行到哪个前端路由这个逻辑简单直接。如果你的需求真的要做精细化权限点再把它升级成rolepermission两张表也不晚。这段设计里还藏着一个 MyBatis 相关的细节很多源码包用 MyBatis-Plus 根据实体类反向生成建表语句user_no会被自动映射成userNo之类的驼峰字段。如果你自己建库建议显式设置map-underscore-to-camel-case: true否则查询结果会全空这是一个非常典型的坑。3.2 topic 表与选题记录表用 status 字段串起审核状态机题目表和选题记录表是整个系统的业务核心也是最容易设计错的地方。CREATE TABLE topic ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, teacher_id int NOT NULL COMMENT 教师 user.id, title varchar(100) NOT NULL COMMENT 题目名称, description text COMMENT 题目内容与要求, major_id int DEFAULT NULL COMMENT 限选专业, max_students int NOT NULL DEFAULT 3 COMMENT 可选人数上限, selected_count int NOT NULL DEFAULT 0 COMMENT 已确认人数冗余字段, status tinyint NOT NULL DEFAULT 0 COMMENT 0待审核 1通过 2退回, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_teacher (teacher_id), KEY idx_major (major_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT题目表; CREATE TABLE select_record ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, student_id int NOT NULL COMMENT 学生 user.id, topic_id int NOT NULL COMMENT 选题 ID, priority tinyint NOT NULL DEFAULT 1 COMMENT 志愿优先级 1/2/3, status tinyint NOT NULL DEFAULT 0 COMMENT 0待审核 1通过 2退回, create_time datetime DEFAULT CURRENT_TIMESTAMP, audit_time datetime DEFAULT NULL COMMENT 审核时间, audit_comment varchar(255) DEFAULT NULL COMMENT 退回原因, PRIMARY KEY (id), KEY idx_student (student_id), KEY idx_topic (topic_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选题记录表;两张表各有一个status字段含义不同。topic.status管的是“这道题目本身能不能被学生看到”select_record.status管的是“这个学生的这条志愿是否被教师确认”。我见过很多新手把这两个状态混到一起导致教师通过了题目、但学生看不到或者学生选了题、教师审核的却是题目状态。状态值用tinyint而不是把“待审核”“已通过”直接写进表里是为了统计时不用到处写case when。查询“某位教师名下的通过题目”时一条 SQL 就能把排序和过滤做干净SELECT id, title, selected_count, max_students, create_time FROM topic WHERE teacher_id 1 AND status 1 ORDER BY create_time DESC, status ASC;这里的ORDER BY create_time DESC, status ASC其实是 mysql 排序里很常用的写法先按创建时间倒序把最新题目排前面再用status保证同批数据里已通过的状态优先展示。selected_count是一个典型的冗余字段它可以用子查询算出来但选题页高频展示已选人数实时聚合会导致每次查询都扫select_record冗余字段在这个场景下是合理的。更新的时机放在“教师确认通过志愿”后不要放在学生提交志愿时。3.3 说明文档LW里最该画的三张图ER 图、流程图与架构图标题里的“说明文档LW”是答辩时老师最先翻的东西。很多学生花两周写完代码最后两天赶文档画图全靠复制粘贴结果 ER 图的表名字段对不上源码。我的经验是文档里最值得认真画的图只有三张。第一张是 ER 图只要画清user、major、topic、select_record四张表以及表之间的关联关系。重点标出“一个学生可以提交多条选题记录但同一题只能有一条有效记录”。第二张是业务流程图从“教师申报题目”开始到“管理员审批通过”再到“学生提交志愿、教师确认、管理员发布结果”用箭头把状态流转画出来这张图直接对应第 3.2 节的状态机。第三张是系统架构图按 Vue 前端、Spring Boot 接口层、Service 业务层、Mapper 数据层、MySQL 数据库分四层画标注每个请求的调用链路。这三张图不需要多精美但必须和代码一致。文档里出现的所有接口路径、状态枚举值、表名字段名都应该能对着源码找到这是答辩时最容易加分也最容易翻车的地方。4. 本地部署跑通完整前后端从 JDK 检查到 Vue 联调的最小操作4.1 环境检查JDK、Maven、Node、MySQL 的四条命令部署这套系统前先把环境捋一遍。很多源码包报错八成不是代码问题而是环境版本对不上这一类问题我最常看到的是“玄学启动失败”其实一点都不玄先跑四条命令java -version mvn -v node -v npm -v mysql --version这四条命令分别检查 JDK、Maven、Node、MySQL。重点看前两个输出。如果java -version显示的是 1.8而项目里的pom.xml用的是 Spring Boot 3.x那启动会直接报错因为 Spring Boot 3.x 强制要求 JDK 17。反过来如果项目是 Spring Boot 2.x用 JDK 17 虽然能起但会碰到反射相关的警告不如老老实实装 JDK 8 或 11。判断标准很简单打开pom.xml看spring-boot-starter-parent的版本号3.x 配 JDK 172.x 配 JDK 8/11。mvn -v找不到命令是环境变量没配好不是没装好。Windows 下需要把 Maven 的bin目录手动加到系统变量Path里。MySQL 也是同样道理你在 Windows 上刚装完 MySQL 5.7 或 8.0cmd 里敲不出mysql命令不用重装去环境变量里加一行 MySQL 的bin目录路径就行。4.2 建库导数据一条 mysql 命令把 SQL 导进本地实例源码包里通常会带一个db_init.sql或init.sql里面是建库、建表、导入初始账号的完整脚本。拿到手后先在本地建一个空库再把数据导进去。mysql -u root -p -e CREATE DATABASE IF NOT EXISTS graduation_topic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -u root -p graduation_topic db_init.sql第一条命令用mysql -e执行建库语句IF NOT EXISTS防止重复建库报错CHARACTER SET utf8mb4直接指定字符集这是后面避免中文乱码的关键。第二条命令把db_init.sql导入graduation_topic库注意库名要写在重定向之前。如果你更习惯进入 MySQL 控制台操作等价的写法是mysql -u root -p mysql source D:/code/graduation_topic/db_init.sql;Windows 下用source命令时路径里的反斜杠容易出问题建议全部改成正斜杠比如D:/code/...。导入完成后可以用SHOW TABLES;确认有没有生成user、topic、select_record这几张表。4.3 后端改造与启动application.yml 里必须改的三个地方后端工程里需要改的配置基本都集中在application.yml或application.properties。先找到这个文件改三个地方数据库连接地址、用户名、密码。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/graduation_topic?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 mybatis-plus: mapper-locations: classpath*:mapper/*.xml type-aliases-package: com.example.graduation.entity configuration: map-underscore-to-camel-case: trueurl里的graduation_topic要和 4.2 里建的库名一致username和password改成你本地 MySQL 的登录账号。password一定要加引号如果密码里有#、、:这些特殊字符YAML 会把它们当成注释或分隔符导致连接串被截断报出莫名其妙的Access denied。driver-class-name这里写的是com.mysql.cj.jdbc.Driver这是 MySQL 8.x 驱动的写法。老项目里常见的com.mysql.jdbc.Driver是 5.x 时代的驱动路径如果你本地装的是 MySQL 8.0建议统一用前者。Spring Boot 2.7 以上版本可以自动识别驱动不写这行也能跑但显式写出来启动日志里排查数据库问题时更直观。后端启动命令很简单在工程根目录执行mvn spring-boot:run如果你想打包成 jar 再启动用mvn clean package -DskipTests java -jar target/graduation-0.0.1-SNAPSHOT.jar-DskipTests是跳过单元测试。我一般会带上它因为很多源码包里的测试类没有配置测试环境数据库直接打包会被测试挂掉。看到Started Application in xx seconds就说明后端起来了你可以先访问http://localhost:8080看看有没有接口返回。4.4 前端跑起来与联调npm 依赖和 proxy 转发的常见报错前端工程和后端是两个独立进程联调的核心是解决跨域。主流做法是在前端的vue.config.js里配代理而不是在后端写 CORS 过滤器因为代理更接近生产环境里 Nginx 的做法。const { defineConfig } require(vue/cli-service) module.exports defineConfig({ devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这段配置把前端 8081 端口收到所有以/api开头的请求转发到后端 8080。两个点容易踩坑一是前端 axios 请求的 baseURL 必须带上/api前缀否则请求不会命中代理规则二是后端的 controller 路由里不一定有/api这个前缀如果代理把/api原样转发给后端后端会报 404。常见做法是后端在application.yml里加一个server.servlet.context-path: /api让所有接口统一挂到/api下这样前后端路径就对齐了。前端启动命令npm install npm run devnpm install卡住或者超时是这类源码包最常见的翻车点。可以先执行npm cache verify清一下缓存再看是不是网络源太慢换成国内镜像源后重新安装会快很多。启动后浏览器访问http://localhost:8081能出现登录页、并且点击登录能跳转就说明前后端联调通了。5. 避坑记录从端口冲突、白屏到中文乱码的 5 次现场排错5.1 前端白屏后端日志一切正常CORS 跨域拦截了你的登录请求现象后端启动没有报错前端npm run dev也正常但浏览器打开登录页后点击登录一直转圈Network 面板里请求标红或超时。原因前端页面跑在 8081接口跑在 8080跨域请求被浏览器拦截。如果前端没有走 proxy后端也没有配置 CORS 过滤器浏览器会先发一个OPTIONS预检请求后端没处理直接返回了错误。解决优先按 4.4 的方式配置前端 proxy这是开发环境里最干净的路子。如果确实要在后端放行可以加一个全局 CORS 配置类允许 8081 来源访问。但注意生产环境不要把allowedOrigins写成*否则带登录凭证的请求会被浏览器拒收。5.2 MySQL 密码带特殊字符yml 默默把 # 后面的内容当成了注释现象后端启动报Access denied for user rootlocalhost但你在命令行里用同样的密码能正常连上 MySQL。原因application.yml里password: abc#123YAML 解析器把#后面的123当成了注释实际生效的密码是abc自然连不上。解决密码用引号包起来password: abc#123。另外 MySQL 8.0 默认加密方式是caching_sha2_password如果你的项目用的还是 mysql-connector-java 5.x 老驱动即使密码对了也会报认证失败。两个办法选一个把驱动版本升级到 8.x或者在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 新密码;。我建议直接升级驱动不要改数据库加密方式因为改回去会让数据库安全性降级。5.3 中文乱码连接串里缺了 characterEncoding 与 useUnicode现象数据库里中文显示正常但前端页面拿到的是???或者乱成一串不可读字符。原因三层都可能出问题。第一层是数据库实例或表本身不是 utf8mb4第二层是 JDBC 连接串没带编码参数第三层是db_init.sql文件本身的编码是 ANSI导入时中文已经变成乱码。解决数据库建表时指定DEFAULT CHARSETutf8mb4JDBC URL 里加上useUnicodetruecharacterEncodingutf8导入 SQL 前先用编辑器把文件另存为 UTF-8 编码注意不要带 BOM否则第一条建表语句会报语法错误。这三件事都做完乱码基本能根治。5.4 端口被占用netstat 找到进程或者换一个 server.port现象后端启动到一半报Web server failed to start. Port 8080 was already in use.前端也偶尔会遇到 8081 被占。原因你机器上已经有另一个 Java 进程或开发服务器占用了 8080Spring Boot 默认端口抢不到。解决Windows 下先执行下面的命令定位占用进程netstat -ano | findstr 8080netstat -ano会把端口和进程 PID 列出来findstr 8080过滤出目标端口。看最后一列的 PID然后执行taskkill /PID 那个PID号 /F强杀进程。如果那个进程不能杀就在application.yml里把server.port改成 8082同时把前端 proxy 的target改成 8082。端口冲突本身不是大事但很多人只改后端端口、不改前端代理导致联调时又白屏一次。5.5 说明文档和源码对不上LW 落后于代码部署别照着文档硬抄现象说明文档里写“系统基于 Spring Boot 2.3 Vue 2”但pom.xml里是 Spring Boot 3.2前端用的是 Vue 3 Element Plus。按文档里的部署步骤走第一步就卡住。原因这是毕业题源码包特别常见的情况——文档是早期模板留下的代码在后期升级过但文档没跟着改。说白了就是文档和源码是两拨人写的东西。解决所有版本号以pom.xml的spring-boot-starter-parent和package.json里的dependencies为准。部署时完全不要看文档里的环境要求直接看我第 4 章的顺序操作。等到你要交 LW 时再把文档里的技术栈描述、接口列表、界面截图逐一更新成当前版本。这一步逃不掉我吃过亏答辩老师对着文档查源码发现类名都对不上场面很尴尬。6. 二开加分项给系统加一个“选题结果导出”报表功能最后聊一个进阶技巧这也是让这套系统从“能跑”变成“能交差”的关键一步把最终选题结果导出成 Excel。这个功能不在核心业务链路上但它天然需要你把状态机、关联查询、前端下载交互串起来答辩时讲出来很加分。实现不复杂常见做法是用 EasyExcel 替代 Apache POI避免写太多样板代码。先给后端加一个导出接口GetMapping(/export) public void export(RequestParam(required false) Integer majorId, HttpServletResponse response) throws IOException { ListExportVO list selectRecordService.listPassedRecords(majorId); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenametopics.xlsx); EasyExcel.write(response.getOutputStream(), ExportVO.class) .sheet(已选题学生) .doWrite(list); }接口里只做两件事查数据和写文件。listPassedRecords(majorId)在 Service 层做关联查询SQL 的过滤条件是select_record.status 1也就是只导出教师已经确认通过的记录majorId参数是可选的管理员可以按专业维度筛选。ExportVO是一个普通 DTO字段上加上 EasyExcel 的ExcelProperty(学号)注解列名就自动生成了。写这个接口时有一个很容易踩的坑直接导出所有select_record而不过滤status 1。这样会把待审核和退回的记录也导出去管理员拿到的报表和实际发布结果对不上。另一个坑是学生可能同时有多条志愿记录其中一条通过、另外两条退回导出时必须在 SQL 里做去重只保留确认通过的那条。验证方法很简单先手动让一个学生选题跑到教师端审核通过再调一次导出接口打开 Excel 确认只有一条记录。如果导出的记录数和学生实际被录取数对不上八成是状态过滤条件写漏了。我每次部署完这类系统都会把导出功能作为最后的验收项。因为导出本身就是一次全链路测试前端要能正确发起下载请求、后端要能跨域返回文件流、数据库状态要准确、关联查询要没写错——它把整条链路都串起来了。文档写完再对照源码走一遍这三个状态比反复读代码检查有效得多。希望帮到你。本文还有配套的精品资源点击获取