Spring Boot毕业设计实战:从零搭建英语学习平台,搞定JWT鉴权与文件上传
2026/9/7 19:19:10 网站建设 项目流程

毕业设计选题目录里翻到“springboot英语学习平台”的时候,我第一反应是这题我会——无非是用户注册登录、单词列表、题组练习那套CRUD。真正动手之后才发现,要把它做成一个答辩时能拿得出手、演示时不露怯的完整项目,坑比想象中多得多。这篇我把整个从零搭建的过程拆开揉碎,从技术选型到表结构设计,从JWT鉴权到文件上传的异常处理,再到答辩前必须准备好的演示数据,一条条梳理清楚。不管你是打算直接参考这个题目,还是想换个方向但需要一套Spring Boot项目的完整落地思路,这篇都能帮你少走不少弯路。

1. 毕业设计从需求梳理到技术选型,为什么是Spring Boot

1.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或特殊字符导致写入报错。
  • 鉴权方式:JWT(JSON Web Token)。毕业设计不需要自己实现完整的OAuth2流程,JWT配合Spring Security或一个简单的拦截器就能解决问题。如果Spring Security配置起来觉得复杂,用拦截器+JWT工具类也是可以接受的方案。
  • 前端:考虑到不少同学是自己一个人完成前后端,直接用Vue 3 + Element Plus做管理端,用户端可以用Vue加简单样式实现,不必在UI上消耗太多精力。

这套选型不是最前沿的,但胜在资料多、坑浅、组合成熟。毕业设计的核心目标是在有限时间内,把系统完整地跑起来,逻辑清晰、功能闭环,技术选型的新旧倒在其次。

1.3 数据库设计,几张核心表之间的关联关系

表结构设计是后续所有开发的基础,这里给出我当时设计的核心表清单,可以直接照抄或者在此基础上调整:

  • t_user:用户表,字段包括id、username、password(BCrypt加密存储)、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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</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轻量得多,对毕设来说完全够用。

实现链路是这样的:

  1. 用户提交用户名和密码。
  2. 后端校验通过后,用JWT工具类生成token,同时在Redis(可选)里记录token与用户ID的映射,设置过期时间。
  3. 前端把token存到localStorage或者pinia,在axios请求拦截器里自动加上Authorization: Bearer token。
  4. 后端写一个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,逐片上传;每片上传时,后端把临时数据写入磁盘上的临时目录,用fileMd5+chunkIndex做文件名;全部片传完后,前端发起合并请求,后端按顺序合并所有分片,同时生成最终文件的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的区别。前者在允许携带凭证(allowCredentials=true)时支持通配符,后者在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 List<WordBook> 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.location=file:./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: 修复分片上传合并时文件名乱码”),会觉得你是一个有良好工程习惯的人。

对于时间线,我给一个参考节奏:第一周做需求拆分和数据库设计,第二到第三周完成后端所有核心接口,第四周做前端页面并完成联调,第五周造好演示数据、准备答辩材料。如果你们学校要求论文,那每天抽两小时写论文,和项目同步推进,不要等代码写完了再动手,时间会很赶。

如果你在实现过程中被某个问题卡住了,有一个小技巧:把完整报错信息、以及你自己对原因的判断一起发到技术社区,往往很快就能得到高质量回复。自己把问题描述清楚的这个过程,其实也能帮你理清思路。希望这篇内容能让你少踩几个雷,顺利把系统做出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询