Spring Boot毕业设计实战:从零搭建英语学习平台,搞定JWT鉴权与文件上传

📅 发布时间:2026/9/7 19:19:11
Spring Boot毕业设计实战:从零搭建英语学习平台,搞定JWT鉴权与文件上传
毕业设计选题目录里翻到“springboot英语学习平台”的时候我第一反应是这题我会——无非是用户注册登录、单词列表、题组练习那套CRUD。真正动手之后才发现要把它做成一个答辩时能拿得出手、演示时不露怯的完整项目坑比想象中多得多。这篇我把整个从零搭建的过程拆开揉碎从技术选型到表结构设计从JWT鉴权到文件上传的异常处理再到答辩前必须准备好的演示数据一条条梳理清楚。不管你是打算直接参考这个题目还是想换个方向但需要一套Spring Boot项目的完整落地思路这篇都能帮你少走不少弯路。1. 毕业设计从需求梳理到技术选型为什么是Spring Boot1.1 英语学习平台到底要做什么先把功能边界划清楚接手这个题目第一件事不是写代码而是把“英语学习平台”这六个字翻译成可落地的功能清单。很多同学在这里容易犯一个毛病——想做的功能太多知识点、背单词、听力、口语、阅读、写作全塞进去结果数据库表设计了三四十张代码写了一堆最后答辩时自己都讲不清楚模块之间的关系。我当时划定边界的原则是“三个核心场景”单词学习、课程观看、在线测评。围绕这三个场景再补上用户管理和数据统计两个支撑模块系统就完整了。具体功能拆解如下用户端注册、登录、个人信息维护、学习记录查看。单词模块按词书分组的单词列表、背诵进度记录、生词本管理。课程模块课程列表、课程详情、课程内容以图文和视频形式呈现。测评模块选择题测试、自动评分、错题记录。管理端用户管理、词书/单词管理、课程管理、测评题目管理。这个粒度对毕设来说比较合适。每个模块工作量均衡前端页面有展示性后端逻辑有复杂度特别是测评自动评分和学习进度记录而且无论论文里写需要分析还是答辩时演示操作流程都能快速找到抓手。1.2 技术选型背后的逻辑不选最新选最稳Spring Boot版本选择上我建议直接使用2.7.x系列而不是当下已经出到的3.x。原因很简单3.x基于Jakarta EE很多老教程和现成代码片段对不上网上搜到的大部分解决方案都是针对2.x的遇到问题排查起来要费不少劲。配套的技术栈我用了以下组合持久层框架MyBatis-Plus而不是纯MyBatis。毕设场景下基础的增删改查、分页查询、条件构造器能省下大量样板代码而且它对“表不存在时自动建表”有比较好的支持方案。数据库MySQL 5.7或8.0都行推荐8.0字符集直接utf8mb4省得后面因为emoji或特殊字符导致写入报错。鉴权方式JWTJSON Web Token。毕业设计不需要自己实现完整的OAuth2流程JWT配合Spring Security或一个简单的拦截器就能解决问题。如果Spring Security配置起来觉得复杂用拦截器JWT工具类也是可以接受的方案。前端考虑到不少同学是自己一个人完成前后端直接用Vue 3 Element Plus做管理端用户端可以用Vue加简单样式实现不必在UI上消耗太多精力。这套选型不是最前沿的但胜在资料多、坑浅、组合成熟。毕业设计的核心目标是在有限时间内把系统完整地跑起来逻辑清晰、功能闭环技术选型的新旧倒在其次。1.3 数据库设计几张核心表之间的关联关系表结构设计是后续所有开发的基础这里给出我当时设计的核心表清单可以直接照抄或者在此基础上调整t_user用户表字段包括id、username、passwordBCrypt加密存储、nickname、avatar、role区分管理员和普通用户、create_time。t_word_book词书表主要是词书名和简介。t_word单词表字段包括id、word、phonetic、translation、example_sentence、word_book_id。t_word_progress学习进度表记录某个用户在某本词书中背到哪个单词以及掌握程度未学、学习中、已掌握。t_course课程表标题、封面图、简介、视频地址或者图文内容的富文本字段。t_exam_paper试卷表。t_exam_question题目表题干、选项JSON格式、正确答案、所属试卷ID。t_exam_record测评记录表用户ID、试卷ID、得分、用时。t_exam_answer_detail答题明细表记录每道题用户选的答案方便后面做错题分析。表与表之间的关联逻辑是用户与单词通过进度表多对多用户与试卷通过测评记录关联试卷与题目一对多。核心思路就是“记录行为”而不是“只存结果”比如测评表不仅要存得分还要存答题明细这样论文里统计分析功能才有数据来源。2. 项目初始化与核心配置版本控制、自动建表与yml密文2.1 Maven依赖的坑特别容易忽略的log4j与mysql驱动的版本冲突新建Spring Boot项目这一步IDE选Spring Initializr或者直接去start.spring.io生成zip包都行。需要注意的是一些初始依赖的版本对应关系这里列一下我最终使用的核心pom.xml依赖版本实测稳定parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies一个常见的坑是MyBatis-Plus版本与Spring Boot 2.7的兼容性。3.5.x版本没有问题但如果你用了3.4.x部分自动填充功能会失效。另一个坑是mysql-connector-java的artifactId在8.0版本后改成了com.mysql用旧坐标在maven中央仓库虽然能拉到包但会有冗余提示多注意一下即可。2.2 yml配置里的学问MyBatis-Plus表不存在自动建表怎么配yml配置这部分基础的数据源配置大家都会写就不展开。我重点说一下两个有信息量的点。第一个是MyBatis-Plus的逻辑删除和字段自动填充配置。表里添加deleted字段逻辑删除和create_time/update_time自动填充然后在实体类上用注解标明这样删除操作会变成update能保留数据便于论文写数据分析。mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl第二个是“表不存在自动建表”的需求。热搜词里提到过“springboot mybatis 当表不存在自动建表”。如果你想让项目在任意一台新电脑上跑起来不用手动导SQL可以用类初始化建表的方式。我实现了一个简易方案启动时扫描指定包下的建表SQL脚本如果发现数据库里缺表就自动执行对应SQL。Component public class TableAutoCreator implements ApplicationRunner { Resource private DataSource dataSource; Override public void run(ApplicationArguments args) { // 这里resouces/sql目录下放置init-tables.sql // 每次启动时读取SQL中的建表语句用information_schema判断表是否存在 // 不存在则执行该建表语句 } }思路并不复杂用JDBC连接数据库查询information_schema.tables获取当前库已有表名集合然后逐条执行初始化SQL通过判断返回结果来决定是否需要执行。这个方法不用引入flyway这类重框架适合毕设场景答辩时还能展开说一句“系统支持自动初始化数据表便于部署迁移”算是加分项。2.3 配置信息加密数据库密码不要明文写在yml里毕设项目虽然不会遭遇真实的生产安全攻击但作为专业人士配置文件中数据库密码明文暴露始终是个减分项。热搜词里也有“springboot yml密文”这个词条顺带说下。最简单的处理方式是利用jasypt-spring-boot-starter做配置加密。用法是引入依赖后在yml里把敏感值替换成ENC(密文)启动时通过环境变量传入解密密钥。jasypt: encryptor: password: ${JASYPT_KEY}生成密文可以用下面这个命令String encryptedPassword new BasicTextEncryptor().encrypt(你的数据库密码, 你的密钥);把生成的密文填入yml再把密钥通过IDE的运行配置或命令行参数传入。这样做的好处是即使项目源码被传到公共仓库别人也拿不到真正的数据库密码。3. 核心功能模块怎么落地从JWT鉴权到学习进度与自动评分3.1 登录注册与JWT鉴权Spring Security与拦截器怎么选登录注册几乎是所有毕设系统绕不开的模块。我用JWT 自定义拦截器来实现比引入全套Spring Security轻量得多对毕设来说完全够用。实现链路是这样的用户提交用户名和密码。后端校验通过后用JWT工具类生成token同时在Redis可选里记录token与用户ID的映射设置过期时间。前端把token存到localStorage或者pinia在axios请求拦截器里自动加上Authorization: Bearer token。后端写一个OncePerRequestFilter或HandlerInterceptor拦截除登录、注册、获取验证码之外的请求解析token确认合法后传递用户信息到上下文。JWT的三个组成部分——Header、Payload、Signature——原理不复杂但要提醒一点不要在Payload里放敏感信息Base64编码不等于加密任何拿到token的人都可以解码看到Payload内容。我一般只存userId和role用户其他信息需要时再查库。核心工具类里最重要的几个方法是generateToken、parseToken、isTokenExpired代码结构如下public class JwtUtil { private static final String SECRET_KEY 你的密钥字符串建议长度至少32位; private static final long EXPIRE_TIME 7 * 24 * 60 * 60 * 1000L; public static String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); } }需要说明的是线上项目JWT的密钥应该用配置中心管理、定期轮换但毕设场景下写死在常量类里也可以答辩时被问到就说“生产环境会结合配置中心和密钥管理服务实现动态密钥”能自圆其说。3.2 单词学习模块的设计进度记录用状态机思维单词学习模块最核心的是“用户背到哪了”以及“掌握得怎么样”。我用一个t_word_progress表来记录用户在每个单词上的状态状态值有0未学、1学习中、2已掌握。获取学习进度时的SQL逻辑可以按照词书分组统计状态为0、1、2的单词数量然后返回给前端展示进度条。整个模块的接口设计如下GET /api/word-book/list获取词书列表。GET /api/word-book/{id}/words获取词书下所有单词可带分页。POST /api/progress/update更新某个单词的学习状态。GET /api/progress/summary获取当前用户在所有词书中的学习概览。有一个容易被忽略的细节是同一个用户在不同时间打开同一本词书进度应该按他最后学习的位置恢复。所以t_word_progress表里除了用户ID和单词ID还应该有一个更新时间字段用来排序拿到最新的学习位置。3.3 课程管理中的视频播放与文件上传大文件如何不卡死内存课程模块里视频或图文内容的展示是刚需。对于图片前端传base64在后端转存或者直接上传MultipartFile都行。真正麻烦的是大文件上传。热搜词里有一条“springboot 如何上传下载大文件”这确实是毕设项目里的隐形难点。如果你把视频文件直接通过MultipartFile接收并转存到本地磁盘默认的Spring MVC配置下单个文件超过1MB就可能报错需要调整max-file-size和max-request-size配置。spring: servlet: multipart: max-file-size: 200MB max-request-size: 200MB这只是第一步。更深层的问题是直接把整个文件加载到内存会撑爆JVM堆内存。我采用的方案是分片上传秒传校验。分片上传的思路是前端用JavaScript把大文件切成每片5MB逐片上传每片上传时后端把临时数据写入磁盘上的临时目录用fileMd5chunkIndex做文件名全部片传完后前端发起合并请求后端按顺序合并所有分片同时生成最终文件的MD5。秒传校验的逻辑是上传前先发一个预请求带着待上传文件的MD5后端查磁盘上是否已存在相同MD5的文件如果存在直接返回一个已存在标记前端跳过实际上传过程。这个模块做好之后不仅视频上传流畅了答辩演示时也特别有看点——因为这是真正贴近生产实践的技术点面试官或答辩老师问到“文件上传如果遇到大文件怎么处理”你能答得很透彻。3.4 测评模块的自动评分题目选项如何弹性存储测评模块我设计为管理端录入试卷和题目用户端在线答题交卷后系统自动评分保存测评记录和答题明细。题目选项的存储是个容易踩坑的点。如果你为每道题单独建一张选项表表数量很多且关联关系复杂。我采用的做法是选项用JSON数组存到t_exam_question表的options字段里正确答案用answer字段单独保存。public class ExamQuestion { private Long id; private Long paperId; private String questionType; // single, multiple, judge private String content; private String options; // JSON数组 [A.xxx,B.xxx,C.xxx,D.xxx] private String answer; // A 或 A,B 或 正确 private Integer score; }自动评分逻辑按照题型分流单选题答案等于answer即得分多选题答案与answer完全一致才得分不完全相同给部分分也可以看你自己定义判断题字符串匹配。核心代码写起来不复杂但要注意多选题答案比较时顺序问题——建议统一将答案和用户所选都排序后再比较。答题明细表的设计决定了后面能否做错题分析。每道题用户的答案、正确性都记录起来统计错题率最高的知识点就很简单了。4. 前后端联调与接口设计从跨域到Redis缓存再到异步推送4.1 前后端分离项目的跨域处理三种方案怎么选前后端分离就意味着必然要处理跨域。常见方案有三种后端加CrossOrigin、写全局CORS配置类、通过网关代理。我推荐使用全局CORS配置类而不是在每个Controller上加CrossOrigin注解原因很简单注解只能解决当前Controller的跨域如果新增Controller忘了加联调时又要排查半天全局配置类一次性解决所有接口。Configuration 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和allowedOrigins的区别。前者在允许携带凭证allowCredentialstrue时支持通配符后者在Java 8以后不允许通配符与allowCredentials同时使用。如果你用Spring Boot 2.4以上的版本这里很容易踩坑。还有一点由于前端通过axios发送请求会先发一个OPTIONS预检请求后端需要正确响应这个预检请求否则实际接口调用会报“CORS error”但浏览器开发者工具里看不到具体原因。上面的配置里专门加上了OPTIONS方法就是这个原因。4.2 Redis缓存热点数据词书列表和课程列表弹射起飞英语学习平台的词书列表、课程列表这类热点数据每次请求都查一次MySQL虽然也能跑但压力大且响应慢。引入Redis做缓存代码改动不大效果却立竿见影。我使用Spring Cache注解的方式快速接入缓存Service public class WordBookService { Cacheable(cacheNames wordBook:list, key all, unless #result null) public ListWordBook listAllWordBooks() { return wordBookMapper.selectList(null); } }启动类加上EnableCaching注解Redis连接信息配好即可。这里的核心要点是缓存失效策略。单词和课程内容更新后缓存里的旧数据需要及时清除。可以用CacheEvict注解在新增、修改、删除操作时主动清缓存保证数据一致性。CacheEvict(cacheNames wordBook:list, key all) public void saveWordBook(WordBook wordBook) { wordBookMapper.insert(wordBook); }关于Redis的安装Windows用户直接用官方的Redis-x64版本或者用Docker跑一个redis:7容器都行。注意Spring Boot 2.7默认使用Lettuce作为Redis客户端如果你在代码里配置了自定义的RedisTemplate序列化方式序列化器选Jackson即可不要用默认的JdkSerializationRedisSerializer因为缓存数据如果带类型信息JSON的可读性和跨语言兼容性会差很多。4.3 异步任务与消息通知用Spring的事件机制而不是硬编码英语学习平台里有一个场景用户完成一次测评后系统需要同时更新学习统计数据、写入测评记录、可能还要发一封邮件通知用户。如果所有逻辑都串行写在同一个方法里接口响应会慢而且任何一个环节报错都可能导致整体失败。更优雅的做法是利用Spring的事件发布与监听机制或者用Async异步执行。我在项目中采用了Spring自带的事件发布机制// 发布测评完成事件 applicationEventPublisher.publishEvent(new ExamCompletedEvent(this, examRecord)); // 监听并异步处理 EventListener Async public void onExamCompleted(ExamCompletedEvent event) { // 更新统计数据 // 发送邮件通知 }事件机制的好处是解耦——测评主流程只关心“记录是否保存成功”其他后续动作由监听器独立处理。哪怕邮件发送失败也不会影响用户交卷的主流程。邮件发送模块用spring-boot-starter-mail接入QQ邮箱SMTP或者163邮箱SMTP即可。注意QQ邮箱的授权码需要在邮箱设置里开启SMTP服务后生成不是登录密码。配置如下spring: mail: host: smtp.qq.com port: 465 username: your邮箱qq.com password: 你的授权码 properties: mail.smtp.ssl.enable: true线上邮件发送一般用消息队列削峰填谷但毕设场景下Spring事件Async已经完全够用而且代码简洁、面试时也能讲清楚异步解耦的思想。5. 线上部署与答辩准备从jar包打包到演示Demo造数据5.1 项目打包部署jar包方式与window环境的注意事项毕设答辩前项目最好能在一台干净的电脑上从零跑起来。打包方式建议直接利用Maven的package命令生成可执行jar包然后在服务器或本地Windows环境用java -jar运行。需要注意的是打包前要把yml配置里可能有本机路径的部分改掉比如文件上传的存储路径我统一改成相对路径配合外置配置文件或启动参数动态设置java -jar english-learning-platform.jar --spring.config.locationfile:./config/application.yml这样部署时只需要把jar包和一个config目录拷贝过去修改配置文件里的数据库地址和Redis地址就能换一台电脑启动。5.2 演示数据准备让你的系统看起来像个真正有人用过的产品这是毕设答辩最容易被忽略但实际最能加分的环节。很多同学系统上线后里面数据空荡荡演示时点开词书列表空空如也老师一看印象分就下来了。我建议提前准备一批高质量的演示数据词书至少准备2本每本书至少50个单词例句也填上转换数据时可以用网站抓取或者自己写个Python脚本生成。课程模块准备5-6个有封面的课程内容可以是排版好的图文说明视频的话网上找可商用的素材链接避免版权问题。测评模块准备2套试卷每套10-15道题涵盖单选、多选、判断三类题型。生成演示数据后再用一个普通用户的账号去真实地做一遍学习操作留下学习记录。这样演示的时候一登录进去首页的学习概览、进度统计都有数据效果完全不同。5.3 答辩时被频繁问到的问题提前准备好回答口径根据我自己的答辩经验老师大概率会从以下几个角度提问提前准备回答思路一是“系统安全性怎么保证”。回答要点是密码BCrypt加密存储、JWT无状态鉴权、配置信息加密、参数校验。能把这几点说全基本安全这块就过关了。二是“数据库为什么这样设计”。准备几个具体例子比如为什么要有单独的答题明细表为什么单词进度表不直接挂到单词表上。回答时强调“为后续数据分析和错题统计做了冗余设计”这个理由在毕设里非常站得住脚。三是“你这个项目有什么亮点”。这是拉开差距的时刻把分片上传、Redis缓存、事件异步解耦、自动建表这类实践抛出去哪怕代码量不大但能讲清楚设计思路老师就会觉得你不是在单纯“抄代码”。6. 项目做完之后这些事仍然值得做项目基本跑通之后不要急着提交。我自己在收尾阶段花了两天做代码审查和重构回头看去掉了很多不必要的重复代码尤其是一些Controller层和Service层之间的对象转换。额外做一件事把项目推到Git仓库并保持提交记录整洁。答辩老师如果翻了你的代码仓库看到清晰的commit记录比如“feat: 完成单词进度接口”、“fix: 修复分片上传合并时文件名乱码”会觉得你是一个有良好工程习惯的人。对于时间线我给一个参考节奏第一周做需求拆分和数据库设计第二到第三周完成后端所有核心接口第四周做前端页面并完成联调第五周造好演示数据、准备答辩材料。如果你们学校要求论文那每天抽两小时写论文和项目同步推进不要等代码写完了再动手时间会很赶。如果你在实现过程中被某个问题卡住了有一个小技巧把完整报错信息、以及你自己对原因的判断一起发到技术社区往往很快就能得到高质量回复。自己把问题描述清楚的这个过程其实也能帮你理清思路。希望这篇内容能让你少踩几个雷顺利把系统做出来。