一、基础防守:脱口而出的技术细节

1. 技术栈(标准回答模板)

"我的毕设是一个基于微信小程序的在线刷题系统,采用前后端分离架构。前端使用微信小程序原生开发,自定义了TabBar组件;后端基于 Spring Boot 3.1.5 + Java 18,持久层采用 JPA 和 MyBatis 双 ORM 混合使用——JPA 处理常规 CRUD,MyBatis 处理复杂的统计查询。数据库用的 MySQL 8.0,同时引入了 Redis 做验证码缓存和 Token 管理。安全方面使用 Spring Security + JWT 做无状态认证,密码用 BCrypt 加密存储。API 文档用 Swagger/OpenAPI 自动生成。"

2. 数据库表设计(核心,必须能画出来讲清楚)

你的项目有 16 张表,面试时重点讲这几张核心表和它们的关系:

用户表 users

- 字段:id、openid(微信唯一标识)、username、password(BCrypt加密)、nickname、avatar_url、phone、role(USER/VIP)、status(ACTIVE/INACTIVE)、total_check_in_days

- 话术:"用户表支持两种登录方式——账号密码登录和微信授权登录。微信登录通过 openid 关联用户,密码使用 BCrypt 加密存储,强度因子设为10。"

题目表 questions + 选项表 question_options

- questions:id、content(TEXT)、type(SINGLE/MULTIPLE/JUDGE/FILL 四种题型)、answer、analysis(解析)、bank_id、chapter_id、knowledge_point_id、difficulty_level(EASY/MEDIUM/HARD)、correct_rate、status

- question_options:id、question_id、option_key(A/B/C/D)、content、is_correct

- 话术:"题目表和选项表是一对多关系,我把选项独立成表而不是用 JSON 存储,这样方便做选项级别的查询和统计。题目通过 bank_id、chapter_id、knowledge_point_id 三个外键形成 题库 → 章节 → 知识点 → 题目 的四级层次结构。"

答题记录表 user_answers

- 字段:id、user_id、question_id、bank_id、answer(用户选的答案)、is_correct、mode(sequence/random/chapter/mock 等练习模式)、practice_time(做题用时)、exam_id(模拟考试关联)、is_marked(考试标记)

- 话术:"这张表记录了用户每一次答题的详细信息——谁、在什么时间、做了哪道题、选了什么答案、对还是错、用了多长时间。通过 mode 字段区分不同的练习模式,exam_id 关联模拟考试场景。"

错题表 wrong_questions

- 字段:user_id、question_id、bank_id、wrong_count(错误次数)、review_count(复习次数)、mastered(是否已掌握)、last_wrong_time、last_review_time、status(pending/reviewing/completed)

- 话术:"错题本不是简单地记录错了哪道题,而是追踪了错误次数、复习次数、掌握状态。用户答对后自动标记为已掌握,再次答错会重置为未掌握,形成一个闭环的学习反馈机制。"

其他重要表(简要提及):

- question_bankschaptersknowledge_points:三级知识体系

- mock_papers + mock_paper_questions:模拟试卷系统

- study_plans:学习计划(支持状态机:NOT_STARTED → IN_PROGRESS → COMPLETED/OVERDUE)

- daily_check_in:每日打卡

- user_ranks:排行榜(定时任务每10分钟更新)

- practice_records:练习记录汇总

数据库触发器(加分项):

"我在数据库层面设计了触发器来维护数据一致性——比如 questions 表的 INSERT/DELETE 触发器会自动更新 question_banks 表的 question_count 字段;mock_paper_questions 的增删改触发器会自动重新计算试卷总分和及格分数(60%);user_answers 的 INSERT 触发器会判断是否是用户第一次做某个题库的题,自动更新做题人数。这样避免了在应用层做这些统计,保证了数据的最终一致性。"

3. 前后端交互方式

"前后端通过 RESTful API 交互,数据格式为 JSON。小程序端封装了统一的 request 工具类,所有请求自动携带 JWT Token 进行身份认证。当 Token 过期(收到 401 响应)时,会自动触发 Token 刷新机制——通过微信的 wx.login 重新获取 code,调用后端登录接口换取新 Token,同时将刷新期间的其他请求放入队列,等 Token 刷新完成后自动重发,避免用户感知到登录状态丢失。"

二、项目亮点包装(你的项目已经有这些,直接讲)

亮点一:知识点标签体系 + 错题本智能管理(你已经实现了!)

话术:"我设计了一个四级知识体系——题库 → 章节 → 知识点 → 题目。每道题都关联了具体的知识点标签。当用户做错题时,系统自动将其加入错题本,并记录错误次数和复习次数。错题本支持'掌握状态追踪'——用户复习时答对会标记为已掌握,但如果再次答错会重置为未掌握状态,形成一个持续的学习反馈闭环。同时系统会统计每个知识点的正确率,帮助用户识别薄弱环节。"

"在错题统计方面,我用原生 SQL 做聚合查询而不是在应用层遍历,避免了 N+1 查询问题,计算掌握率、复习率等指标。"

亮点二:Redis 缓存应用(你已经集成了!)

话术:"项目中引入了 Redis 主要用于两个场景:一是验证码的存储和过期管理——手机验证码存入 Redis 并设置 5 分钟 TTL,过期自动失效,比存数据库更高效;二是为后续的热点题目缓存做了基础架构准备。Redis 使用 StringRedisSerializer 做序列化,避免了默认 JDK 序列化的可读性和兼容性问题。"

如果导师追问"题目缓存具体怎么做",你可以说:

"题目数据变动频率低,适合做缓存。我的设计思路是:将热门题库的题目列表缓存到 Redis,设置合理的 TTL(比如 30 分钟),当管理员在后台修改题目时通过 Cache Evict 策略主动失效。这样在高并发场景下(比如考前突击),大部分请求直接命中 Redis 缓存,不需要查询 MySQL,可以显著降低数据库压力。"

亮点三:JWT 无状态认证 + Token 自动刷新(你已经实现了!)

话术:"安全方面,我采用 JWT 做无状态认证。用户登录后,后端用 HMAC-SHA 算法签发 Token,Token 中包含用户 ID 和过期时间。前端每次请求在 Header 中携带 Bearer Token。关键的设计是 Token 自动刷新机制——当前端检测到 401 响应时,会自动用微信 code 重新登录获取新 Token,并且用请求队列缓存刷新期间的其他请求,刷新完成后批量重发,整个过程对用户无感知。密码使用 BCrypt 加密,强度因子为 10。"

亮点四:排行榜系统 + 定时任务(你已经实现了!)

话术:"排行榜采用定时任务机制,每 10 分钟通过 Spring 的 @Scheduled 注解触发一次排名计算。排名算法是我自己设计的加权评分模型——基础分(满分60分)= 答题数 × 正确率,加分项包括学习时长(最多15分,每小时0.5分)和连续打卡天数(最多25分,每天1分),总分上限100分。这个算法既鼓励答题质量,也鼓励学习的持续性。"

亮点五:模拟考试系统 + 随机组卷(你已经实现了!)

话术:"模拟考试支持固定试卷和随机组卷两种模式。随机组卷时,管理员可以配置组卷规则——包括题型分布(单选/多选/判断/填空各多少题)、难度分布(简单/中等/困难的比例)、知识点覆盖要求等。系统根据这些规则从题库中智能抽题生成试卷。考试过程中支持题目标记功能,交卷后自动批改并生成成绩报告。"

三、模拟面试真题演练

Q1:"你的刷题小程序后端是怎么和微信前端进行数据交互的?"

"通过 RESTful API 进行交互,数据格式为 JSON。后端用 Spring Boot 提供 HTTP 接口,Controller 层分为 admin 和 wx 两套——admin 服务管理后台,wx 服务小程序端。前端封装了统一的 request 工具类,支持 GET/POST/PUT/DELETE 四种方法,所有请求自动在 Header 中携带 JWT Token。后端通过 Spring Security 配置了 CORS 跨域策略,允许小程序域名的请求。当 Token 过期时,前端有自动刷新机制,通过请求队列保证并发请求不会丢失。"

Q2:"如果题库数据量从一千条增加到一百万条,你的系统该如何优化?"

"我会从几个层面优化:

1. 数据库层面:我的表已经建了索引(bank_id、chapter_id、type 等字段都有索引),百万级数据下索引效果会很明显。同时引入分页查询,前端用无限滚动加载,避免一次性加载全部数据。

2. 缓存层面:热门题库的题目列表缓存到 Redis,设置合理的 TTL,减少数据库查询压力。排行榜数据已经是定时任务预计算的,不会因为数据量增大而变慢。

3. 查询优化:对于随机抽题,百万级数据下 ORDER BY RAND() 性能很差,可以改用'先查 ID 范围再随机取'的方式。对于全文搜索场景,可以引入 Elasticsearch。

4. 架构层面:如果继续增长,可以考虑按题库 ID 做分库分表,读写分离等。"

Q3:"在开发过程中,你遇到过最大的技术难点是什么?"

推荐故事一:Token 过期导致的并发请求问题

"开发过程中遇到的最大难点是 Token 过期时的并发请求处理。最初的实现是 Token 过期后直接跳转登录页,但用户体验很差——用户正在做题,突然被踢到登录页,做题进度全丢了。

后来我发现问题更复杂:首页 onShow 时会并行发起 4 个请求(用户统计、今日计划、排行榜、推荐题库),如果 Token 恰好在这时过期,4 个请求都会收到 401,会触发 4 次 Token 刷新,导致竞态条件。

我的解决方案是设计了一个请求队列机制:用一个 isRefreshing 标志位控制,第一个收到 401 的请求触发 Token 刷新,后续的 401 请求不再重复刷新,而是把自己的重试函数放入队列。等 Token 刷新成功后,批量执行队列中的所有请求。这样既避免了重复刷新,又保证了所有请求都能正确重发。这个方案参考了 Axios 拦截器的设计思路,我在微信小程序的 wx.request 基础上做了适配。"

推荐故事二:JPA N+1 查询导致的性能问题

"另一个难点是错题统计的性能问题。最初我用 JPA 的 findAll 查出所有错题记录,然后在 Java 代码里遍历计算统计数据。数据量小的时候没问题,但当用户错题达到几百条时,因为 Question 实体关联了 QuestionBank、Chapter、KnowledgePoint、Options 等多个实体,JPA 的懒加载会触发大量的 N+1 查询——查一条错题记录就要额外查 4-5 张关联表,几百条错题就是上千次 SQL 查询。

我的解决方案是:对于统计类查询,绕过 JPA 直接用原生 SQL 做聚合计算,一条 SQL 就能算出总数、已复习数、已掌握数。对于列表查询,使用 @JsonIgnoreProperties 控制序列化深度,避免不必要的关联加载。这个优化让错题统计接口的响应时间从 2-3 秒降到了 100 毫秒以内。"

Q4:"为什么选择 JPA 和 MyBatis 混合使用?"

"这是我在开发过程中逐步演进的决策。项目初期用 JPA 做 CRUD 非常方便,Repository 接口定义方法名就能自动生成 SQL。但后来做排行榜统计、错题分析这类复杂查询时,JPA 的 JPQL 写起来很别扭,而且生成的 SQL 不够优化。所以我引入了 MyBatis 来处理这类复杂的统计查询和多表关联查询,用原生 SQL 可以精确控制查询性能。两者各取所长——JPA 负责简单的增删改查和实体映射,MyBatis 负责复杂的报表统计查询。"

Q5:"你的系统如何防止作弊或恶意请求?"

"目前实现了几个层面的防护:

1. 身份认证:所有接口都需要携带 JWT Token,Token 中包含用户 ID 和过期时间,使用 HMAC-SHA 签名防篡改。

2. 密码安全:用户密码使用 BCrypt 加密存储,强度因子为 10,即使数据库泄露也无法还原明文密码。

3. 验证码防刷:手机验证码存在 Redis 中,设置 5 分钟 TTL 自动过期,防止验证码被重复使用。

4. 数据校验:提交答案时会校验题目是否存在、用户是否有权限等。

如果要进一步加强,可以加入接口限流(Rate Limiting)——比如用 Redis 的滑动窗口算法限制单个用户每分钟的请求次数,以及答题时间戳校验防止脚本刷题。"

四、额外加分话术

关于项目架构的思考:

"后端 Controller 层我做了 admin 和 wx 的分包设计,这样不同端的接口职责清晰,后续如果要加权限控制也很方便——admin 接口要求管理员角色,wx 接口只需要普通用户 Token。"

关于代码质量:

"我使用了 Lombok 减少样板代码,用 DTO 模式做数据传输避免实体直接暴露,全局异常处理器统一处理 BusinessException,Swagger 自动生成 API 文档方便前后端联调。"

关于学习计划的状态机设计:

"学习计划有四种状态:NOT_STARTED → IN_PROGRESS → COMPLETED/OVERDUE,状态转换由后端控制,前端只能触发'开始'和'更新进度'操作,保证了状态流转的合法性。"

---

把这些内容熟练掌握,面试时根据导师的提问灵活组合回答即可。核心原则:每个回答都要体现"我为什么这样设计"的思考过程,而不只是"我用了什么技术"。