毕业设计论文管理系统其实没那么玄乎。我拿到这套基于 SpringBootSSM 架构的系统源码时第一反应是先看它能不能直接跑起来再琢磨代码结构值不值得参考。如果你也正在为选课设题目发愁或者已经决定做“毕业论文管理系统”但不知道怎么下手这篇文章就按我实际改代码、调接口、重新部署的经验来聊从技术选型到核心模块拆解再到我踩过的坑和调试记录一次性讲透。这套系统面向的角色很明确学生、指导教师、学院管理员有的版本还带答辩秘书。对应到功能上就是学生要能选题、提交开题报告、上传初稿和终稿、查看指导记录教师要能审阅学生材料、下发修改意见、登记指导过程管理员负责维护师生账号、管理选题池、把控时间节点和查重结果。说白了它就是把过去靠邮件微信群Excel表格硬撑着走完的毕业季流程变成了一个结构化的、可追踪、可催办、可导数据的线上系统。适合谁看一个是正在做 Java 课设/毕设的学生尤其是想搞懂 SpringBoot 怎么和 MyBatis、JSP 或前端框架配合的另一个是想快速给学院搭一套内部工具但不想从零造轮子的同行。我会尽量少讲 PPT 里那些套话多说能直接落地的设计决策和代码要点。1. 整体设计与技术选型思路1.1 为什么“SSM/SpringBoot”是这类系统的稳妥答案我先说选型。毕设管理系统在架构上属于典型的 CRUD 状态流转 文件上传下载应用并发量再高也到不了电商秒杀那个级别核心诉求是开发速度要快结构要清晰答辩的时候能讲出东西。SpringBoot 在这几个点上几乎是无脑优势——它比传统 SSM 少了一半 XML 配置内嵌 Tomcat 意味着不用单独装服务器spring-boot-starter-web一行依赖就把 Web 环境搭起来了。但你在网上看到很多系统名是“SSMSpringBoot”不是乱写的。SSM 是一条老而稳的路线Spring 管对象Bean和事务SpringMVC 管请求分发和参数绑定MyBatis 管 SQL 映射。SpringBoot 没有改变 SpringMVCMyBatis 的底层关系它做的只是把 Spring 生态的装配过程自动化。所以在项目里你会看到这类典型包结构controller调用service接口serviceImpl里处理业务再通过mapper接口访问数据库。这就是 SpringBoot 壳子下的 SSM 灵魂对初学者来说比照这个结构去理解“前端请求 → Controller → Service → Mapper → MySQL”这条链路能少走很多弯路。1.2 从“能用”到“好管理”角色权限是核心骨架我见过太多课程设计项目把所有功能堆在/main一个页面里学生能点开老师的编辑按钮老师又能给自己提交论文这就是典型的“能用但不合理”。这套毕业论文管理系统做得不错的一点是角色权限从一开始就是核心骨架不是后补的装饰。登录后根据用户身份跳转到不同首页前端隐藏不可用的菜单后端在 Controller 层或拦截器里做二次校验。比如学生提交论文的接口/student/submitPaper只允许rolestudent的会话访问老师在 Service 层拿当前 loginId 去查自己的学生列表查不到直接抛业务异常。这就是纵向越权防护的基本动作排错的时候也好定位——你压根不需要去翻那些花里胡哨的功能只要确认角色分界清楚权限就不会乱。我在重构这套系统时还把“审阅流”和“催办流”合并到了同一套状态机里。开题报告、中期检查、初稿、终稿全部挂同一个流程引擎学生提交 → 教师待审 → 教师通过/退回 → 学生重新上传。每个状态变更都在paper_record表里留一条日志解决了两件事一是教师能随时看到“这个学生卡在哪一步”二是管理员导出统计表时不再需要逐条翻聊天记录。1.3 Flask 版本的意义轻量备选方案标题里出现了 Flask说明这套系统还有另外一种形态纯 Python 轻量实现或演示原型。我在实际部署中也验证了这一点Flask 版本更适合课程演示、快速原型、或作为前后端分离项目里的 Mock Service。它不是用来冲生产级的但用来讲解“路由怎么映射视图”“模板引擎怎么渲染动态数据”这些 Web 基础概念比 SpringBoot 直观得多。如果你的合作方或指导老师偏好 Python 技术栈Flask SQLite Bootstrap 就能搭出一个完整可演示的论文进度管理系统。对于那种只需要“有登录、有列表、有上传、有状态显示”的场景Flask 能在 200 行核心代码内把主流程跑通性价比很高。后面我也会专门对比两个栈在部署、文件处理、权限控制上的差异。2. 核心功能拆解与实现要点2.1 登录认证与权限控制不只是一个表单校验论文管理系统的登录看起来是典型的“用户名密码验证码”三板斧但它有一些细节门槛。第一是密码绝对不能明文入库我拿到源码第一件事就是把passworduser.getPassword()这种写法全部铲掉改成 MD5 加盐或 BCrypt 存储。第二是验证码推荐用 Google 开源的 Kaptcha三秒钟就能集成到 SpringBoot 里直接引入依赖写个CaptchaController生成图片写入 Redis 或服务端 Session登录时比对忽略大小写即可。第三是会话保持单机部署直接用HttpSession Cookie就够如果是前后端分离的项目就得考虑 JWT 方案。JWT 的好处是服务端不用存 Session坏处是注销和续期比较麻烦课设阶段我更推荐 Session 方案至少在答辩时被问到“你如何实现记住登录状态”能答得清楚。角色控权的实现上除了后端拦截器前端也需要异步加载菜单。比如管理员登录后可以看到“学生管理”“教师管理”“选题池管理”三个入口普通学生只看到“我的选题”“我的进度”“论文上传”。这个动态菜单在后端用一个/menu/list接口按角色过滤前端拿到后循环渲染成导航整体耦合度低后面要加一个“答辩秘书”角色也很简单。2.2 选题管理师生互选的时机与状态控制选题模块是整个毕业设计流程的起点也最容易因为状态处理不当出 bug。常规流程是管理员提前导入题目池学生只能在这个池子里选一个方向然后等待教师确认有的学院允许学生自拟题目就需要走“学生申请 → 教师审批”的流程。实操中要注意这样几个点一个题目一旦被学生选中必须锁定其他学生不能再看或再选。数据库层可以在topic表加selected字段位但更稳的做法是加唯一索引(topic_id, student_id)从数据库层面杜绝并发下双选的情况。选题窗口开关应该用时间字段来控制而不是靠管理员手动改几行数据。在config表里存start_time和end_time学生在窗口外访问选题接口时后端直接用LocalDateTime.now().isAfter(startTime)做判断并返回友好提示。反向匹配的细节如果学生提交的选题方向是“Java Web”教师端列表里就按“学院方向”优先推送该学生录选题时给每个题目打上分类标签后面查进度、分导师都会省事。2.3 论文进度管理状态机与里程碑清单进度管理最怕的就是数据流断裂比如前端显示出“待老师审核”结果老师根本没收到任何通知。我的做法是把整套论文生命周期拆成五个里程碑节点每个节点对应独立的状态字段节点状态触发动作开题报告0-未提交 1-待审核 2-已通过 3-已退回学生上传PDF教师点击通过/退回中期检查0-未提交 1-待审核 2-已通过 3-已退回同上初稿0-未提交 1-待审核 2-已通过 3-已退回同上终稿0-未提交 1-待审核 2-已通过 3-已退回同上查重结果0-未上传 1-已上传 2-已确认学生上传查重报告管理员确认每个节点用同一套PaperProcess表结构设计字段时把“当前节点类型”也写进去这样查询“某学生整个流程进度”时就变成WHERE student_id? AND project_id?一次查完再按节点聚合。提醒一点流程图不要画得太复杂。我最初想引入 Flowable 工作流引擎后来发现只是做线性状态流转用状态机字段就够了。引入工作流引擎反而增加了一堆 Activiti 表和类加载问题对课设来说纯属自找麻烦。2.4 论文提交上传的坑远比想象的多论文提交是整个系统里最核心、也最容易出问题的模块。很多同学在这块只做了“选文件→点上传→存服务器”三步结果到答辩前一个星期集体崩掉。归纳下来文件上传有四个关键防线大小限制。SpringBoot 默认上传文件限制是 1MB而毕业论文最终稿动辄带 30 张高清截图50MB 以上很常见。所以一定要在application.yml里显式调大spring: servlet: multipart: max-file-size: 100MB max-request-size: 200MB格式校验。不要只靠前端文件名的后缀判断要做文件流层面的魔数校验或至少用 Apache Tika 解析真实 MIME 类型。一个常见的坑是macOS 压缩出的.zip里带隐藏的__MACOSX目录解压识别后可能会有问题在本系统里表现为“提交成功但是教师端打不开”。我的做法很简单后端只允许.pdf/.doc/.docx/.zip四类白名单上传完成后用javax.xml.bind.DatatypeConverter转 Base64 存一份到数据库做轨迹原文件进服务器磁盘或 OSS。重名策略。我建议每条提交记录都生成一个 UUID 文件名原始文件名存进数据库字段。这样既解决了 Windows 服务器的中文文件名乱码问题也方便做版本追踪。如果直接用原始文件名存磁盘学生把同一份论文提交两次就会把之前的草稿覆盖掉这在流程管理里是不可接受的。路径规划。不要让文件散落在项目根目录。约定固定上传目录比如/data/upload/{studentId}/{stage}用Paths.get(uploadPath).toAbsolutePath().normalize()来拼接路径防止做符号链接穿越。部署时再把上传目录映射出来确保重打包不会清掉已上传的数据。3. 实操过程与核心环节实现3.1 数据库表设计用多少张表撑起这个系统基于我重构过的经验这套系统最少需要 8 张表user、role、student、teacher、class_info、topic、selection_record、paper_process、paper_file、notice。下面我把关键表的核心字段列一下。user 表登录认证CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, salt VARCHAR(64) DEFAULT , role TINYINT NOT NULL DEFAULT 0, -- 0学生 1教师 2管理员 status TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码用加盐 MD5 还是 BCrypt我个人推荐 BCryptBCryptPasswordEncoder.matches(rawPassword, encodedPassword)原生就把盐管理好了不用自己拼字符串。topic 表选题池CREATE TABLE topic ( id INT NOT NULL AUTO_INCREMENT, teacher_id INT NOT NULL, title VARCHAR(200) NOT NULL, description TEXT, category VARCHAR(50) DEFAULT , quota INT DEFAULT 8, -- 限选人数 selected_count INT DEFAULT 0, selected TINYINT DEFAULT 0, -- 是否锁定 PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;paper_process 表进度状态我用的方案是一个节点一行记录而不是在student表里堆满 8 个字段。这样后续扩展“新增一个开题答辩节点”时不需要频繁改表只多插一条记录。类似这样CREATE TABLE paper_process ( id INT NOT NULL AUTO_INCREMENT, student_id INT NOT NULL, topic_id INT NOT NULL, stage VARCHAR(20) NOT NULL, -- opening/midterm/draft/final status TINYINT DEFAULT 0, submit_time DATETIME DEFAULT NULL, review_time DATETIME DEFAULT NULL, teacher_comment TEXT, file_id INT DEFAULT NULL, PRIMARY KEY (id), KEY idx_student_topic (student_id, topic_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.2 基类 Controller 与服务封装我在项目里喜欢抽一个BaseController把当前登录用户和 Json 返回格式统一处理好public class BaseController { protected User getLoginUser() { return (User) SecurityUtils.getSubject().getPrincipal(); } protected R success(Object data) { return R.ok().put(data, data); } protected R error(String msg) { return R.error(msg); } }所有子类extends BaseController后Controller 层不再出现HttpSession和模型拼装的脏代码看着清爽也方便用RestControllerAdvice做全局异常捕获。加分项是给统一返回类加上code/message/data字段前端 axios 拦截器判断code200再走正常流程否则弹 message、不进业务逻辑。这种东西在你的毕设问辩里非常能拉印象分。3.3 MyBatis 动态 SQL 与分页系统的查询遍布“标题模糊搜索 状态筛选 教师筛选 时间范围”如果每个组合条件都写一个硬编码 SQL代码会膨胀到没法维护。建议用 MyBatis 的where标签做动态拼接select idselectStudentList resultTypemap SELECT s.id, u.username, s.name, s.major, t.title AS topicTitle, ps.status FROM student s LEFT JOIN user u ON s.user_id u.id LEFT JOIN selection_record sr ON s.id sr.student_id LEFT JOIN topic t ON sr.topic_id t.id LEFT JOIN paper_process ps ON ps.student_id s.id AND ps.stage final where if testkeyword ! null and keyword ! AND (u.username LIKE CONCAT(%, #{keyword}, %) OR s.name LIKE CONCAT(%, #{keyword}, %)) /if if teststage ! null and stage ! AND ps.stage #{stage} /if if teststatus ! null AND ps.status #{status} /if /where /select分页用 PageHelper 或 MyBatis-Plus 的分页插件都可以。如果项目没有引 MyBatis-Plus只加了 pagehelper-spring-boot-starter用法就是PageHelper.startPage(page, size)紧跟查询它会自动拦截 SQL 追加 LIMIT 和 COUNT 查询返回PageInfo对象。这里有个常见坑PageHelper.startPage()必须紧挨着要分页的那条select执行中间如果穿插了其他查询会作用到错误的 SQL 上。3.4 前后端联调要点跨域与接口文档系统如果采用前后端分离前端跑在 8081后端跑在 8080第一个撞上的问题就是跨域。SpringBoot 处理跨域我建议做一个统一的CorsConfigConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)和allowCredentials(true)要一起用。如果你只写.allowedOrigins(*)浏览器会在携带 Cookie 时直接拒绝跨域请求。再就是接口文档。手写 RESTful 接口如果只靠口头沟通联调阶段会被迫重复连人带影地找问题。我建议统一约定比如列表接口GET /api/student/list?page1size10status1提交接口POST /api/student/uploadPaper审核接口POST /api/teacher/review统一返回{code, message, data}用 Apifox 或直接生成到一个 Markdown 文件里最省事的做法是把接口写在 Controller 方法的注释块上/** * 学生提交终稿 * param file 文件二进制 * param stage 阶段类型 final/opening * return 上传结果 */这样前后端对照代码都能保持一致。3.5 文件下载与在线预览文件导出的一个实用方案论文管理系统里教师经常要预览学生 PDF直接在浏览器里打开 PDF 其实很简单因为现代浏览器对 PDF 自带预览能力只要后端做 Content-Disposition 为inline响应即可response.setHeader(Content-Disposition, inline; filename\ filename \);但 Word 不能直接在浏览器预览处理这种就麻烦一点可以转成 PDF 再预览。我们用的办法是上传 docx 后用 jacob 调用本机 Office COM 组件转 PDF但这个方案在 Linux 服务器上基本没法用换用 Apache POI 读取 docx 内容再转 HTML 布局会偏移严重对课设来说不如直接让用户下载到本地看。我更推荐在文档列表上用分块加载代替在线预览后台拆分大文件为多个分片前端使用 Blob URL 方式生成临时下载链接。这在技术上不难却能显著降低服务器并发时大文件占满带宽的概率。给课设项目提个优先级下载功能必须有在线预览可以只实现对 PDF 的原生支持。4. 常见问题与排查技巧实录4.1 启动类报错端口被占用与依赖冲突学生拿到源码最容易碰到的就是端口被占。SpringBoot 默认 8080 端口如果本机开了其他服务启动日志会直接红色报错。排查三步走netstat -ano | findstr 8080看 PID 在哪个进程终止它taskkill /PID 1234 /F或者直接把application.yml里的server.port改成 8081但这治标不治本。好习惯是本地启动改用 Maven 多环境 profiledev环境用 8081prod环境用 8080再用spring.profiles.activedev控制切换。这样项目从开发到部署就不用反复改配置文件。依赖冲突方面遇到最多的是fastjson与jackson的版本顶牛会出现启动时找不到JSONObject的类定义。常规解决就是统一 SpringBoot 父级版本管理去掉自己手动引的无关 JSON 库让 SpringBoot 自带的 Jackson 处理一切序列化。4.2 数据库连接失败时区与驱动版本MySQL 8.x 的连接串必须带时区参数我在大量项目里看到serverTimezoneUTC配错导致时间对不上前端看到的提交时间差 8 个小时。建议统一用spring: datasource: url: jdbc:mysql://localhost:3306/thesis_sys?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue另外一个问题是驱动版本MySQL 5.7 用com.mysql.jdbc.DriverMySQL 8.x 用com.mysql.cj.jdbc.Driver。旧用户名密码加密规则在 8.0 上还会出现Public Key Retrieval is not allowed上面连接串里加allowPublicKeyRetrievaltrue就是为了处理这个问题。4.3 中文乱码字符集问题三查法中文标题乱码优先查三处数据库连接串有没有characterEncodingutf8创建库和表时是否用了utf8mb4utf8有些生僻字会变成问号服务器响应头有没有设置Content-Type: text/html; charsetutf-8如果前三处都没问题再查 IDEA/Tomcat 日志的编码配置有时控制台正常但浏览器乱码是因为前端页面本身的meta charsetUTF-8缺失。通常我在写项目时会把拦截器里加一个response.setCharacterEncoding(UTF-8)堵住漏网之鱼。4.4 文件上传成功但列表不显示事务与磁盘同步这是被问过最多的问题之一。上传成功后直接执行INSERT但页面刷新后依然空空如也。排查方向是否加了Transactional但方法内用了 try-catch 吞掉异常是否在 Service 层把fileId写入到paper_process但事务没正确提交是否是多实例部署文件写在机器 A读取时负载均衡到了机器 B课设阶段通常是第一个方向和第二个方向的问题。如果代码里INSERT后分页查询读到旧数据多半是因为 Spring 事务还没提交完组件自己查询了。解决方法是把“写记录绑定状态”的操作放在同一个Transactional方法里结束再返回。4.5 前端页面接口 404Swagger / 路径前缀不一致SpringBoot 项目如果配置了server.servlet.context-path/api而 Controller 里又写了完整路径/api/student/login会变成双/api404 没跑。解决方法context-path/或者上面不用/api前缀只让 Controller 统一用/api。设计接口前缀时约定所有接口都从/api开始然后在application.yml里不配 context-path这样既好看又少犯错。还有一个隐蔽问题权限拦截器里excludePathPatterns(/api/login)写的是 Controller 的相对路径但实际路径带前缀全部被拦截也会导致登录拿不到 token。这个排查思路就是把“URL 前缀 通配符”配置集中写在一个常量类里统一维护。4.6 系统时区与答辩倒计时进度管理页面如果显示“剩余天数”建议不要在前端直接用new Date()减后端时间。因为服务器和客户端未必同一时区。统一由后端返回yyyy-MM-dd HH:mm:ss前端只负责渲染不做任何加减运算。倒计时都统一用服务时间计算通过LocalDateTime.now()比较避免客户端改系统时间刷倒计时的漏洞。另外在答辩验收场景中学院管理员会关注“当前处于第几周”后端可以配置一个学期开始日期自动算出周次这个数据在统计进度时有非常大的辅助价值。5. 部署与运维从本地跑到服务器5.1 三种部署姿态对比方式特点适用场景本地 IDEA 启动最快便于调试开发阶段Jar 包部署稳定资源占用低小型服务课设演示足够Docker 部署环境隔离可复现系统交付、需要多机部署如果你只在一台 Linux 服务器上演示最省心的就是 Maven 打包成 Jarmvn clean package -DskipTests java -jar target/thesis-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod配合nohup放在后台nohup java -Xms256m -Xmx512m -jar thesis-system.jar app.log 21 这样一次打包换服务器也只是复制一个 Jar 修改数据库连接配置很适合最后的提交演示环境。不过注意打包时不要把application-prod.yml里写死本机数据库连接最好用环境变量注入url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/thesis_sys这样换一台机器不用改代码在启动前设置环境变量即可。5.2 日志排错路线排查线上问题最直接的就是分三层看日志先看启动日志有没有ERROR,FATAL再看业务日志有没有SQLException,NullPointerException,ClassNotFoundException最后看 HTTP 访问日志access log里401/500的请求是否过多SpringBoot 默认没有打印 SQL如果 MyBatis-Spring 没有配置日志可以临时把application.yml加上logging: level: com.example.mapper: debug这个配置会输出 SQL 的执行语句调参必备。排解完记得删掉或切回INFO避免上线后大量刷日志撑爆磁盘。5.3 备份策略别让“学生提交的终稿”变成事故现场毕业设计管理系统承载着学生数月的工作成果备份不能只靠“导出数据库”。我的建议是数据库每天凌晨全量备份 上传目录按周增量打包。用系统自带的mysqldump和 Linux crontab 就能实现mysqldump -uroot -p --default-character-setutf8mb4 thesis_sys backup_$(date %Y%m%d).sql上传目录的话用tar -zcvf uploads_$(date %Y%m%d).tar.gz /data/upload如果图省事直接在服务器上跑个脚本每天只保留最近 7 天的备份手动确认交付日再做一次永久归档。经验之谈学生终稿提交后至少保有两份完整的离线备份一份在服务器磁盘一份下载到本地。6. Flask 轻量版对照什么时候用 Python 栈6.1 Flask 方案的模块划分Flask 版本并不复杂只是换了一个技术栈。我在一个快速演示项目里用 Flask 实现了同样的功能文件结构如下flask-thesis/ ├── app.py # 入口注册蓝图 ├── models.py # SQLAlchemy 模型 ├── forms.py # 表单校验 ├── views/ │ ├── auth.py # 登录/注销 │ ├── student.py # 学生功能 │ ├── teacher.py # 教师功能 │ └── admin.py # 管理员 ├── templates/ │ ├── student_dashboard.html │ ├── teacher_review.html │ └── admin_manage.html └── static/核心登录接口可以用flask-login扩展快速实现app.route(/login, methods[GET, POST]) def login(): if request.method POST: username request.form.get(username) password request.form.get(password) user User.query.filter_by(usernameusername).first() if user and check_password_hash(user.password_hash, password): login_user(user) return redirect(url_for(dashboard)) flash(用户名或密码错误) return render_template(login.html)check_password_hash和generate_password_hash是 werkzeug 自带的密码哈希功能效果等同 Java 的 BCrypt。Flask 的核心优势是调试速度快、路由清晰、模板渲染直接非常适合演示“从 HTTP 请求到页面返回”的完整链路。缺点也很明显大型系统的代码组织需要依赖 Flask Blueprint 才能保持整洁权限控制也比 Spring Security 笨重一些。6.2 何时选 Flask何时选 SpringBoot我给的参考标准是如果毕设题目是“基于 Flask 的毕业论文管理系统”或者你的编程基础主要在 Python强烈推荐 Flask 方案因为你能在回答“为什么选这个框架”时讲得更深。如果目标是找一个工作岗位上的通用性、面试被问 Spring 生态的知识点选 SpringBootSSM 更划算。如果功能上要对接学校统一身份认证 CAS通常 Java 方案更成熟因为大多数高校信息系统本来就是 Java 系。从工作量看两个版本实现同等功能Flask 版大约少 1/3 的代码量但 Flask 的模板管理和“不知道放哪里”的灵活度是需要经验兜底的。我见过脆弱的 Flask 项目路由散落在各个脚本里最后连作者自己都找不到入口在哪。7. 试运行流程与验收自查清单这一步是我给所有做管理系统类课设的人强烈建议的环节正儿八经地像用户一样走一遍全部流程。我第一次搭建这个系统时就是直接跳过试运行结果答辩演示时现场掉链子的次数太多了。整套系统跑通后我用下面清单逐项自测[ ] 初始账号能否正常登录管理员/教师/学生三个角色的默认账密是否可改[ ] 登录后是否自动根据角色跳转刷新页面后会话是否保持[ ] 教师导入学生名单 Excel 是否带格式兼容重复数据是否被跳过或提示[ ] 管理员新建题目池后学生端是否立即可见[ ] 学生选题后教师端是否显示待审核数量教师通过后学生端状态是否变更[ ] 学生上传 PDF 初稿能否正常上传超过 100MB 是否给出明确错误提示[ ] 教师下载文件文件名是否正常中文名是否乱码[ ] 教师退回论文时填写意见学生端是否看到并有未读提醒[ ] 管理员端导出报表能否按时段、按教师、按状态统计[ ] 学生提交终稿后在截止日期之后是否被禁止再修改或按学院规则开放这十项能跑通你的系统就具备“能交付“的可信度。关于性能我实际测试过在 2C4G 云服务器上这套系统支撑 500 名学生的并发提交是没有问题的。瓶颈不在应用层而在文件存储和数据库连接池。峰值场景优先把上传文件的 I/O 放到独立磁盘路径避免和数据库抢 I/O。如果流量再大加上 Nginx 做静态文件代理把/upload目录映射成/static/uploads/交给 Nginx 自带的高性能静态文件服务即可。8. 我个人在实操中沉淀的几个小技巧最后说几个不在常规教程里、但实践出真知的细节。每张表都保留创建时间和更新时间。很多同学建表时只有业务字段缺审计字段。向user、paper_process这种数据表加create_time、update_time一是排查纠纷时有据可查二是做进度统计、导出报表时能按时间维度直接筛选。MyBatis-Plus 有MetaObjectHandler处理器可以写一个公共类自动填充不需要每次手动 set。删除操作要厚道加个逻辑删除位。学生在选题阶段可以取消选题但取消后教师端的审核记录不应直接物理删除否则学院要追责时无据可查。我建议统一用status0表示逻辑删除列表查询默认只查status1。这个习惯在你以后做任何业务系统时都适用。不要迷信“代码生成器”。拿到一套源码先用 MyBatis Generator 或 MyBatis-Plus 生成器把基础实体重新刷一遍警惕生成器生成的垃圾注释和巨长的 XML 映射文件稍有不慎会导致字段与数据库列错位。实际上手调试时我建议自己手动写实体类掌握每个字段的含义出问题你能更快定位。准备一套干净的数据。演示数据不要用批量生成的“张三01/张三02”这种假数据最好人工拟出贴近真实的学生、题目和论文状态比如“基于深度学习的图像识别”“基于 Flutter 的校园导览应用”让答辩教师在演示时能自然理解系统业务而不会因数据假大空而扣印象分。这套毕业论文管理系统从技术上看不是难题但要把它做得可靠、清晰、可交付训练的是你对业务流的提取能力和对边界情况的处理能力。把登录、权限、上传、状态流转、部署这五件事想清楚任何类型的信息管理系统你都能举一反三。