简介:一款面向Java后端初学者及毕业设计/课程设计人员的英语单词学习管理系统源码包。项目以Java为核心搭建后端服务,覆盖用户注册登录、单词库管理、随机测试/填空练习等多种学习模式、基于艾宾浩斯遗忘曲线的复习提醒,以及学习进度统计等典型功能,适合用来理解MVC分层设计、MySQL数据库交互和前后端协作方式。压缩包共791个文件,约12.64MB,主要包含127个java后端源码、51个vue页面组件、156个js逻辑文件、46个css样式、42个html页面,并附有sql数据库脚本、yml/properties配置文件和安装/启动批处理脚本,整体前后端结构清晰,便于导入开发工具直接运行调试。目前已有100人学习下载,可作为课程设计或毕业设计的完整参考。除核心代码外,资料还包含项目备份、辅助配置与说明文档,有助于梳理从数据库建表、后端接口实现到前端页面联调的完整流程,对提升Java Web项目开发与排错能力很有帮助。
1. 英语单词学习管理系统,放在毕业设计里到底要做什么
“英语单词学习管理系统”这个题目,几乎每年都会出现在计算机毕业设计选题清单里。软件工程和计算机科学与技术专业的人选它,主要图的是需求清晰、技术栈常规、答辩容易讲透。它本质上不是要做一个多酷的背单词 App,而是把“背单词”这个场景拆成用户管理、词库维护、学习记录和统计报表四个模块,打通一整套 Web 业务链路。所以只要按 Web 项目规范完整做完,拿到一个不错的毕设成绩并不难。适合用 Java 技术栈(Spring Boot + MySQL + 前端模板或 Vue)实现,三到四个月的业余时间完全够,新手也能一步步跟上。
2. 先把系统拆明白:功能边界、表结构与测试数据
在动手写第一行代码之前,我一般会先花两天时间把系统的功能边界和数据模型定下来。这个阶段省下来的事,后面都会变成血泪教训——最典型的例子是:表结构一边写代码一边改,Controller 里塞满了各种临时逻辑,最后答辩演示时连数据都理不顺。毕业设计不是企业级项目,评分的重点恰好是“完整、规范、能讲明白”,所以先拆功能、再建表、最后造数据,这个顺序别省。
2.1 功能边界先定下来:撑起一场答辩的模块清单
一个合格的英语单词学习管理系统,至少要覆盖下面几块:
- 用户模块:注册、登录、退出、修改密码。不要觉得这些都是杂活,答辩时老师最可能追问的就是密码加密和登录态怎么保持。
- 单词库模块:按考试词库(CET-4、CET-6、考研)分组展示单词,支持分页、搜索、按难度筛选。
- 学习模块:用户选择词库开始学习,点击“认识/不认识”标记单词,生成学习记录。
- 统计模块:按日期展示学习数量、复习次数、掌握单词总数的趋势曲线,最好再来一个签到日历。
- 管理员模块:可以向词库批量导入单词,管理用户状态。
单看列表会觉得每个模块都是增删改查,但组合起来之后,一个人从注册到每天背单词、到复习打卡、到看数据周报的主线就成立了。答辩时讲这条主线,比零散地讲“我做了十个 CRUD 接口”要有说服力得多。在设计时我还会把权限分成 user 和 admin 两种角色,在 user 表里用一个 role 字段就够了,不要为此引入复杂的权限框架。
2.2 建表是第一步:从用户表到学习记录表的设计细节
功能边界定了,表结构就顺理成章。这里我给出一个最常用的 4 表模型:user(用户)、word_group(词库分组)、word(单词)、study_record(学习记录)。学习计划不单独建表,而是通过查询当天学习记录聚合出来,省一张表,逻辑也更直接。
CREATE DATABASE word_study DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名,唯一', `password` varchar(100) NOT NULL COMMENT 'BCrypt 加密后的密码,不是明文', `email` varchar(100) DEFAULT NULL COMMENT '邮箱,可选', `role` tinyint(4) DEFAULT 0 COMMENT '0-普通用户,1-管理员', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `word_group` ( `id` int(11) NOT NULL AUTO_INCREMENT, `group_name` varchar(50) NOT NULL COMMENT '如 CET-4、CET-6、考研词汇', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='词库分组表'; CREATE TABLE `word` ( `id` int(11) NOT NULL AUTO_INCREMENT, `word` varchar(100) NOT NULL, `phonetic` varchar(255) DEFAULT NULL COMMENT '音标,如 /əbˈsɜːd/', `meaning` varchar(500) NOT NULL COMMENT '中文释义,可放入多个词性释义', `example` varchar(500) DEFAULT NULL COMMENT '典型例句', `group_id` int(11) DEFAULT NULL COMMENT '单词归属的词库', `difficulty` tinyint(4) DEFAULT 1 COMMENT '难度 1-5', PRIMARY KEY (`id`), KEY `idx_group` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单词表'; CREATE TABLE `study_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `word_id` int(11) NOT NULL, `status` tinyint(4) DEFAULT 0 COMMENT '0-未学习,1-学习中,2-已掌握', `review_count` int(11) DEFAULT 0 COMMENT '复习次数', `next_review_time` date DEFAULT NULL COMMENT '计划下次复习日期,按遗忘曲线计算', `last_review_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_word` (`user_id`, `word_id`), KEY `idx_next_review` (`next_review_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户学习记录表';四张表里的关键设计,我单独说几点。第一,user.password 字段长度给了 100,而不是常见的 32,因为 BCrypt 加密结果固定是 60 位字符串,要留够余量。第二,study_record 用(user_id, word_id)做联合唯一键,保证同一个用户对同一个单词只有一条累计记录,复习次数靠 update 递增,不会出现一张表里两条重复记录。第三,我提前预留了next_review_time字段,答辩时加“智能复习提醒”功能就不需要改表结构,后面我会专门讲这个点。
2.3 造一批能看的测试数据:单词库批量导入的两种方式
表建好之后,最麻烦的是单词数据从哪来。网上能搜到很多四六级词汇表,拿到手通常是 Excel 或 CSV。常见做法是写一个批量导入接口,让管理员上传 CSV;另一个更省事的方式是直接把数据整理成 insert 语句放进 init.sql,答辩前执行一遍就完事。
# 用 Python 把 Excel 词汇表转成 UTF-8 的 CSV,方便后端导入 import pandas as pd df = pd.read_excel("CET4.xlsx") df = df[["word", "phonetic", "meaning", "example"]] df.to_csv("cet4_words.csv", index=False, encoding="utf-8-sig")转换成 CSV 时有个细节要注意:编码一定要用utf-8-sig,这样 Excel 打开不乱码,Java 读入时按 UTF-8 解析也稳妥。后端导入接口解析 CSV 时逐行插入,每 500 条批量提交一次,SQL 别写成单条 insert 循环 3000 次,容易出现连接超时。测试数据的量我一般控制在 300 个单词左右——太少演示没内容,太多翻页变慢,答辩反而减分。
3. Spring Boot 后端落地:认证、单词查询与学习记录核心接口
后端框架我首推 Spring Boot 2.7.x 配 MyBatis 2.3.x,这套组合在毕业设计里非常常见,网上资料多,遇到问题也好查。下面按项目初始化、用户认证、单词和学习接口三个部分来拆,每一步我都会给出代码和背后的参数取舍。
3.1 项目初始化与目录分层:Maven 依赖和包结构一次配好
用 IDEA 的 Spring Initializr 创建项目时,依赖勾选 Spring Web、MyBatis Framework、MySQL Driver、Lombok 就够了,额外再加一个 jjwt 做认证、一个 PageHelper 做分页。这些依赖加完之后,pom.xml 大致长这样:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</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>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>目录分层的约定其实是这个部分最该抄作业的点。我一般按 controller、service、mapper、entity 四层分,包名统一为com.xxxx.wordstudy。controller 只负责接收参数和返回统一数据结构;service 放业务逻辑;mapper 是 MyBatis 的接口层,XML 放在src/main/resources/mapper目录下。很多人喜欢把 SQL 用注解直接写在接口上,我不建议——毕设涉及的表虽然不多,但查询条件一变,注解里的 SQL 就变得极难维护。
3.2 注册登录与 JWT 认证:用户体系的轻量实现
密码存储一定要用 BCrypt,不能用明文或者简单的 MD5。Spring Security 我不引入,太重,只引一个spring-security-crypto的 BCryptPasswordEncoder 即可。
// 注册接口:接收用户名和密码,密码 BCrypt 加密后入库 @PostMapping("/api/auth/register") public Result register(@RequestBody RegisterRequest req) { if (userService.findByUsername(req.getUsername()) != null) { return Result.error(400, "用户名已存在"); } User user = new User(); user.setUsername(req.getUsername()); user.setPassword(new BCryptPasswordEncoder().encode(req.getPassword())); user.setRole(0); userService.create(user); return Result.success(null); }注册逻辑不难,但有两个点要说明。一是即使前端做了表单校验,后端还是要判空和查重,这决定接口能不能直接被人拿脚本刷。二是BCryptPasswordEncoder.encode()每次生成的结果都不一样,这是有意为之——BCrypt 内部会随机加盐,所以不要拿明文直接比对两次加密结果,要用matches(raw, encoded)去验证。登录时签发 JWT,我把生成的 token 直接放在响应体里,前端存到 localStorage,后续请求通过Authorization: Bearer <token>带上。
@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; // 签名密钥,至少 32 字节 @Value("${jwt.expire}") private Long expire; // 过期时间,单位毫秒,默认 24h public String generateToken(Integer userId, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .compact(); } }注意 jjwt 0.11.x 要求签名密钥至少 256 位,如果你的 secret 配成了“abc123”这种短字符串,运行时会直接抛WeakKeyException,这是一个非常容易踩的坑。配套的拦截器实现HandlerInterceptor,在 preHandle 里解析 token、把 userId 放进 request attribute,后端业务接口直接拿request.getAttribute("userId")就能知道当前用户是谁。放行白名单只有注册、登录两个接口,其余/api/**一律拦截。
3.3 单词查询与学习记录:统一返回格式的两个核心接口
先把所有接口的返回格式统一成Result类,结构是{code, message, data}。这样前端只处理一种响应结构,不需要为每个接口写单独的数据解析。
@GetMapping("/api/words") public Result getWords(@RequestParam(required = false) Integer groupId, @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "20") Integer pageSize) { PageHelper.startPage(page, pageSize); List<Word> words = wordMapper.selectByCondition(groupId, keyword); PageInfo<Word> pageInfo = new PageInfo<>(words); return Result.success(pageInfo); }这一段里有两个容易忽略的细节:第一,PageHelper.startPage只对紧随其后的第一条查询生效,所以调用它之后要立刻执行 mapper 方法,中间不要插入其他数据库操作;第二,keyword是模糊搜索参数,Mapper XML 里用<if test="keyword != null">AND word LIKE CONCAT('%', #{keyword}, '%')</if>动态拼接。分页返回给前端的结构里包含了 total、pageNum、pageSize 这些字段,前端翻页组件直接就能用。
再看学习记录的上报接口,这也是业务主线的核心:
@PostMapping("/api/study/record") public Result submit(@RequestBody StudyRecordRequest req, HttpServletRequest http) { Integer userId = (Integer) http.getAttribute("userId"); StudyRecord record = studyRecordMapper.findByUserIdAndWordId(userId, req.getWordId()); if (record == null) { // 第一次学这个词:插入状态为“学习中”,复习次数 1 record = new StudyRecord(); record.setUserId(userId); record.setWordId(req.getWordId()); record.setStatus(1); record.setReviewCount(1); record.setNextReviewTime(LocalDate.now().plusDays(1)); studyRecordMapper.insert(record); } else { // 再次学习:复习次数 +1,如果前端标记“已掌握”则同步状态 record.setReviewCount(record.getReviewCount() + 1); if (req.getStatus() == 2) { record.setStatus(2); } record.setNextReviewTime(calcNextReviewTime(record.getReviewCount())); studyRecordMapper.update(record); } return Result.success(null); }这里的取舍是:学习记录不搞每次点击都插一条明细表,而是只维护一条累计记录,复习次数递增,时间字段更新。好处是统计查询非常快,坏处是你没法还原“用户某天具体点了哪些词”的过程——但毕设不需要这种审计粒度,把一条记录维护好就行。calcNextReviewTime我先按 reviewCount 用固定档位(1 天、2 天、4 天、7 天、15 天)返回一个 LocalDate,这就是最简单的记忆曲线调度,后面第 6 章我会展开讲怎么让它变成一个加分项。
4. 前端页面与学习流程:从登录到统计报表的完整交互
后端接口写完,整个系统的骨架就有了,剩下的是把页面和交互做出来。前端方案先定下来,这会直接影响你后面几周的开发节奏。
4.1 前端方案怎么选:JSP 服务端渲染还是 Vue 前后端分离
这俩方案都能完成,区别在开发的粒度和答辩的呈现:
| 方案 | 优点 | 缺点 | 适合人群 |
|---|---|---|---|
| JSP + Thymeleaf | 项目结构简单,不用起两个服务,部署就是一个 jar | 页面交互弱,异步刷新要手写 jQuery | 后端主修,想少碰前端 |
| Vue + Element UI | 页面体验好,组件现成,统计图表接入方便 | 要同时掌控前后端两个工程,部署时要合并构建产物 | 前后端都想展示,愿意多花时间 |
Vue 在这种带学习流程、数据仪表盘的系统里体验明显好一档,所以我一般情况下推荐 Vue。实际开发也不用自己从零搭脚手架,直接npm create vue@latest初始化一个项目,用 vue-router 管理页面路由,用 axios 封装请求。接口地址做一道代理:开发环境下把/api代理到localhost:8080,这样前端不会遇到跨域问题。唯一要提醒的是,答辩时老师可能问“为什么选择这个框架”,你要能说出 Vue 组件化复用和虚拟 DOM 两层理由,而不是只会说“大家都用这个”。
4.2 学习页面交互:背单词的状态流转与接口对接
学习页是整个系统的体验核心。我的实现思路是:进入页面先加载今日待学单词列表,通常按next_review_time <= 今天这个条件从后端拉 10 到 20 个单词,然后一张卡片一张卡片地过。前端部分关键代码如下:
// 学习页核心逻辑:加载单词,标记认识/不认识 export default { data() { return { words: [], // 今日单词列表 currentIndex: 0, // 当前卡片下标 showMeaning: false // 是否显示释义 }; }, computed: { currentWord() { return this.words[this.currentIndex] || {}; } }, methods: { async loadTodayWords() { const res = await axios.get('/api/study/today', { headers: { Authorization: 'Bearer ' + localStorage.getItem('token') } }); this.words = res.data.data; }, markWord(known) { // known=true 表示认识,提交 status=2;false 则 status=1 axios.post('/api/study/record', { wordId: this.currentWord.id, status: known ? 2 : 1 }); this.currentIndex++; this.showMeaning = false; } }, mounted() { this.loadTodayWords(); } };这里有个交互细节容易被忽视:展示卡片时先只显示单词和音标,用户点击“显示释义”后再出现中文含义,然后再让他选认识还是不认识——如果一上来就把释义全铺开,用户可以直接照着念“认识”,这个学习流程就失真了。数据流上,loadTodayWords只获取next_review_time <= 当前日期的单词,每提交一次就重新调整下次复习时间,这样就形成了一个“每天进来有词可学,学完标记完就清空”的闭环。
4.3 统计数据可视化:用 ECharts 把学习进度画出来
统计页是答辩时视觉冲击力最强的一页,也是我建议你提前花心思的地方。统计模块我一般配置两个图表:最近 30 天学习趋势折线图和一个 90 天签到日历热力图。ECharts 是经过验证的方案,接入成本很低:
import * as echarts from 'echarts'; // 学习趋势折线图:x 轴为日期,y 轴为当日学习单词数 async function loadTrend() { const res = await axios.get('/api/study/statistics', { params: { days: 30 } }); const dates = res.data.data.map(item => item.study_date); const counts = res.data.data.map(item => item.total_count); const chart = echarts.init(document.getElementById('trendChart')); chart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: dates }, yAxis: { type: 'value', minInterval: 1 }, series: [{ type: 'line', smooth: true, areaStyle: {}, data: counts }] }); }用 ECharts 要留意一个坑:图表容器 div 在组件刚挂载时宽度可能还是 0,导致图表渲染不完整。常见做法是把初始化放到nextTick里,或者给容器写死宽度和高度。统计接口的 SQL 并不复杂,用GROUP BY study_date对学习记录按天聚合就行。答辩时可以先让图表用本地缓存数据展示完整效果,再切到真实接口刷新一次,两相对比很直观。
5. 避坑与排查:环境、打包到演示的常见问题
做这个毕设的过程中,我见到最多的大概率不是业务逻辑难写,而是环境问题和打包问题。下面这五条是我自己踩过、也看身边同学反复踩的坑,按出现频率排了个序。
5.1 MySQL 驱动与时区导致项目启动失败
现象:Spring Boot 启动完毕后,第一次访问任何数据库接口,立刻抛出java.sql.SQLException: The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zone。第一次看到这串乱码似的报错,很容易误以为是代码问题,其实整个后端还没跑到业务逻辑。
原因:MySQL 8 的 JDBC 驱动必须显式指定时区,而 JDBC URL 里没带serverTimezone。驱动默认去读 JVM 的时区,一旦 JVM 时区不是标准 ID,就会报这种编码错乱的长串错误。
解决:把 application.yml 里的连接地址写成:
spring: datasource: url: jdbc:mysql://localhost:3306/word_study?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true username: root password: your_password随手把allowPublicKeyRetrieval=true也带上,避免 MySQL 8 默认 caching_sha2_password 插件在非 SSL 连接下额外带来的认证问题。配置完重启一次,这个坑基本不会再出现。
5.2 MyBatis Mapper XML 绑定失败
现象:项目能正常启动,但一调用某个 mapper 接口就报Invalid bound statement (not found): com.example.wordstudy.mapper.WordMapper.selectByCondition。很多人第一反应是接口写错,实际上接口和 XML 各写各的,往往找不到对应关系。
原因:出现这个报错的绝大部分原因是 mapper XML 文件没有被 Maven 编译到 classpath;其次才是 namespace 写错,或者 Mapper 接口和 XML 文件名不一致。Spring Boot 项目里如果 XML 放在src/main/java下面,构建时会被当成普通源码过滤掉。
解决:首先确认 XML 放在src/main/resources/mapper下,而不是src/main/java下;其次在 pom.xml 中显式声明 resources 包含 XML:
<resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources>我的习惯是同时配上mybatis.mapper-locations=classpath:mapper/*.xml,多一点保障。另外打开 XML 看一眼 namespace,必须与接口全限定名逐字一致,包括包路径的大小写。
提示:如果以上都没问题还是报错,先执行
mvn clean再重新启动。旧编译产物里残留的损坏 XML 偶尔也会造成这种玄学问题。
5.3 打包部署后静态资源和路由 404
现象:本地 IDE 里页面和接口都正常,mvn clean package生成 jar 后丢到服务器上启动,浏览器访问直接白屏或 404。你在本地跑的是开发模式,前端由 Node 服务托管;打包后没人再管前端路由了,这一课只有在部署时才会补上。
原因:这是一个很典型的“本地没毛病,一打包就翻车”的问题。如果你用的是 Vue 前后端分离,npm run build出来的静态文件需要放进 Spring Boot 的static/目录,或者在启动类里重写资源映射;否则 jar 里根本没有 index.html,访问根路径自然 404。
解决:我一般会把 Vue 打包后的 dist 内容复制到src/main/resources/static/,并且前端路由用 hash 模式而不是 history 模式。hash 模式下 URL 带#/,服务器完全不需要路由 fallback,天然规避刷新 404。如果你非要 history 模式,就得注册一个ErrorPageRegistrar把所有非接口路径转发到 index.html,这个工作对毕设来说不划算。
5.4 中文乱码:数据库字符集与 HTTP 编码
现象:单词库的中文释义通过接口返回后,要么显示???,要么在数据库里就已经是乱码。查单词接口时,日志里能看到正确数据,响应给前端就乱掉,前后端互相甩锅的场景很常见。
原因:三处某处不一致:库表字符集不是 utf8mb4,JDBC URL 没有 characterEncoding 参数,或后端响应头没有指定 UTF-8。任一处不对,中文就可能在流转过程中变形。
解决:建库时就执行:
CREATE DATABASE word_study DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;JDBC URL 加characterEncoding=utf8,然后 Spring Boot 中把编码强制打开:
server: servlet: encoding: charset: UTF-8 force: true建表语句里也统一写CHARSET=utf8mb4,这三处建议建库第一天就对齐,而不是等数据脏了再清理。
5.5 答辩演示现场翻车:数据丢失和环境状态
现象:答辩现场打开系统,单词列表空、图表无数据,或者连接数据库直接失败。前一天在开发机上明明一切正常,结果演示机一开,啥都变了。
原因:演示机不是开发机。要么 MySQL 服务没启动,要么库里的数据还留在本地演示机上,要么数据库账号没有远程访问权限。这类问题的核心不是技术难度,而是“环境启动的确定性”没解决。
解决:至少做到三步。准备一个init.sql,里面包含建库、建表、插入管理员账号和 300 个单词的种子数据,演示前一键执行;把 MySQL 注册成系统服务,或者用 docker-compose 来启动:
# docker-compose.yml:演示环境一键起 MySQL services: mysql: image: mysql:8.0 container_name: wordstudy-mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: word_study volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql最后把application.yml中的数据库地址、账号密码外置成独立配置文件,换机器时只改一处。答辩前按这个清单走一遍,基本不会翻车。
6. 答辩前的加分动作:把记忆曲线和演示流程做扎实
最后一个部分是给有余力的同学准备的。系统能跑、页面能看,只是达到及格线,真正让答辩老师觉得“这个学生理解了系统”的动作,是把记忆曲线这个点做透。
我在表设计里特意预留了next_review_time字段,现在把它用起来。所谓记忆曲线的核心逻辑是,每个单词学完之后,在 1、2、4、7、15 天各安排一次复习,每次复习完成后自动计算下一次的日期。实现上只需要在上面的calcNextReviewTime方法里按复习次数查一张档位表,再更新记录即可。首页的“今日待复习”列表就是查next_review_time <= CURDATE() AND status = 1的数据,接口加上这个条件,功能就完整了。这项工作量不大,但答辩时可以明确说出“基于遗忘曲线规律设计复习策略”,比泛泛的增删改查有说服力多了。
演示流程我的建议是:先花两分钟讲数据库表结构和关系,再用管理员账号导入一个新词库、展示批量导入的接口效果,然后切到普通用户,完整走一遍“今日学习 → 标记认识 → 查看统计图表”的闭环,最后点开记忆曲线相关的待复习列表收尾。这个顺序背后有逻辑:先让老师看到你的设计能力,再让他们看到实现能力,最后用数据展示收束。
最后说一句个人的血泪经验:答辩前一周,把你所有需要演示的数据放进初始化脚本随代码一起走,不要只存在于本地的 MySQL 里——当年我就是因为临时换了一台演示机,数据库数据没带过去,差点在现场变成无数据可讲。这一课让我后来做什么项目都会把种子数据当成代码的一部分管理。希望这条经验能帮到你,把毕业设计这条路走得再稳一点。
本文还有配套的精品资源,点击获取