资讯动态

SpringBoot在线刷题系统实战:从表设计到并发判分的完整指南

发布时间:2026/10/9 3:07:40 来源:尧图企业网站定制
简介基于SpringBoot的在线刷题系统毕业设计文档定位为计算机相关专业学生及SpringBoot/Vue技术学习者的完整参考范本聚焦线上教育场景针对传统线下刷题中错题整理困难、教师统计不便、纸张浪费与试卷不易保存等问题给出了系统性解决方案。资源包共1个文件类型为docx文档大小约5.79MB内容涵盖中英文摘要、关键词、目录、绪论及系统设计等论文标准模块结构完整且层次清晰方便按章节查阅。已有72人学习。文档详细阐述了以JavaSpringBoot为后端、Vue为前端、MySQL为数据库的在线刷题系统方案具体介绍了管理员编辑题目、发布试题以及学生刷题、查看错题本等核心功能并深入讨论了课题背景、国内外研究现状、研究目的与意义等绪论内容。借助该文档读者可快速理解在线教育类项目的整体设计思路掌握前后端分离架构下的功能模块划分、数据库交互及核心业务流程同时还能看到SpringBoot轻量自动化与Vue组件化开发的实际运用特别适合毕业设计开题、论文撰写、项目复盘或全栈实战参考。1. SpringBoot在线刷题系统从一个课程设计到能扛住千人在线的题库引擎一份名叫“基于SpringBoot的在线刷题系统”的项目文档常被人当成学生作业来看待但只要真做过这类系统的人都知道刷题系统的难点从来不在增删改查而在“判分正确性”和“做题体验”这两件小事上。它能解决的核心问题是让题库从 Excel 表变成一套可随机抽题、即时判分、自动错题收集的线上服务公务员备考、驾校理论、企业内部资格认证考试都能直接复用这套骨架。适合三类人阅读正在做课程设计的在校学生、要给公司搭培训考核平台的后端开发、以及想把手动组卷流程线上化的业务负责人。这篇文章我会按自己落地同类系统的顺序从表结构讲到并发判分把值得抄的代码和值得避的坑一起交给你。2. 选型与数据模型刷题系统的地基先立住2.1 技术栈为什么这么选做在线刷题系统SpringBoot 是当前最稳妥的选择这一点没什么争议。它把配置、安全、数据访问、缓存都整合进了一套自动配置体系里哪怕团队里有人只会写 SQL也能在半天内把项目跑起来。我一般会在此基础上加三个组件MyBatis-Plus 负责数据层简化、Redis 负责任做题热度缓存、JWT 做无状态登录。选 MyBatis-Plus 是因为它自带逻辑删除、分页插件和代码生成器比原生 MyBatis 少写约 30% 的样板代码选 Redis 不是为了炫技而是刷题系统里“今日刷题数”“热门题目”这类读多写少的统计放数据库里会让主库白白扛压力JWT 则是为了让移动端和网页端共用同一套鉴权不用维护 Session 集群。数据库选 MySQL 就够用表数量一般在十张左右单表数据量在上万道题时 MySQL 依然轻松。如果你预判题库会到百万级建议从第一天就把题目表按题库分类 ID 做水平分表而不是后期靠缓存硬扛。ORM 层不要开驼峰映射之外的黑魔法刷题系统的查询逻辑要可读性强排查一个“为什么这道题没进错题本”的问题时清晰的 SQL 能省你两小时。2.2 核心表设计题库、题目、刷题记录我的物理模型里有一张题目表t_question一张选项表t_question_option一张刷题会话表t_practice_session一张答题记录表t_answer_record一张错题表t_wrong_question。之所以不把选项塞进题目表是因为多选题、判断题、排序题的结构各不相同选项独立成表后未来加“图文解析”“视频解析”字段时不用动主表结构。判分依据不存选项原文而是存选项编码如 A/B/C/D这样题目内容修改时历史答题记录仍然可以精确回放。刷题会话表是整个系统的灵魂它记录的是“某用户某一次进入刷题模式后的整体状态”包括题目集合快照、当前做到第几题、正确率、开始时间。注意这里有个关键设计题目集合要做成快照而不是实时从题库表查询。否则管理员中途改了某道题正在做题的用户会遇到“提交的答案和当时看到的选项对不上”的诡异问题。2.3 建表 SQL 与几个字段的取舍下面是这套模型精简后的核心建表语句实际落地时你可以在此基础上追加create_time、update_time和逻辑删除字段CREATE TABLE t_question ( id bigint NOT NULL AUTO_INCREMENT, category_id bigint NOT NULL COMMENT 题库分类ID, type tinyint NOT NULL COMMENT 题型:1单选 2多选 3判断, difficulty tinyint NOT NULL DEFAULT 2 COMMENT 难度:1易 2中 3难, content text NOT NULL COMMENT 题干, analysis text COMMENT 答案解析, status tinyint NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_question_option ( id bigint NOT NULL AUTO_INCREMENT, question_id bigint NOT NULL, option_code varchar(8) NOT NULL COMMENT A/B/C/D, option_text varchar(500) NOT NULL, is_correct tinyint NOT NULL DEFAULT 0 COMMENT 1正确 0错误, PRIMARY KEY (id), KEY idx_question (question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_practice_session ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, category_id bigint NOT NULL, question_ids text NOT NULL COMMENT 题目ID快照,逗号分隔, current_index int NOT NULL DEFAULT 0, total_count int NOT NULL, correct_count int NOT NULL DEFAULT 0, status tinyint NOT NULL DEFAULT 0 COMMENT 0进行中 1已完成, PRIMARY KEY (id), KEY idx_user_status (user_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这道表结构里比较容易被忽视的是t_practice_session.question_ids它把一次刷题涉及的所有题目 ID 用逗号拼接后存入文本字段。有人会觉得这违反了第一范式但在刷题场景下这是刻意为之用户在刷题途中题目表里的题被管理员误删或修改时会话里的快照依然能完整展示题目内容提交判分时再逐题回表校验即可。代价是统计“某道题被多少人做过”时需要拆字符串或用冗余表这个统计频次不高放进定时任务里做就行。选项表里把is_correct直接放在这张表上可能有人会担心“普通用户在查询题目时把正确答案也带出来了”。这个问题我会在第 5 章展开这里先说结论查询接口永远不要直接返回这张表给前端要单独做一个 VO 把正确标记剥掉这种问题一旦上线被用户抓包抓到口碑直接翻车。3. 刷题主流程抽题、提交、判分的完整链路3.1 抽题策略随机、按分类、按错题加权刷题系统的第一个用户体验分水岭在抽题。如果每次打开都是按 ID 顺序出题用户很快会背下答案顺序刷题就失去了意义。常见的做法是提供三种抽题模式随机抽取、按分类专项练、错题优先。随机抽取直接用数据库ORDER BY RAND()在题目量大时性能很差我的做法是先用SELECT id FROM t_question WHERE category_id ? AND status 1查出该分类下全部可用 ID然后用 Java 的Collections.shuffle()洗牌后截取需要的数量——题库量在一万道以内时这个方案比 SQL 随机排序快一个量级而且方便生成可回溯的快照。具体实现如下注意这段代码写在 service 层事务传播级别要设置为REQUIREDpublic ListLong pickQuestionIds(Long categoryId, Integer count, String mode) { // 1. 查出该分类下所有启用的题目ID LambdaQueryWrapperQuestion wrapper new LambdaQueryWrapper(); wrapper.eq(Question::getCategoryId, categoryId) .eq(Question::getStatus, 1); ListLong allIds questionMapper.selectList(wrapper).stream() .map(Question::getId).collect(Collectors.toList()); // 2. 洗牌 Collections.shuffle(allIds); // 3. 错题优先模式: 额外查出该用户的错题ID, 挪到序列前部 if (wrong.equals(mode)) { ListLong wrongIds wrongQuestionMapper.findWrongIdsByUser(userId, categoryId); allIds.removeAll(wrongIds); wrongIds.addAll(allIds); allIds wrongIds; } // 4. 截断 int limit Math.min(count, allIds.size()); return allIds.subList(0, limit); }pickQuestionIds是核心入口参数mode控制出题策略。这里有一个我踩过的性能坑不要用selectList查出题目全量字段t_question里存着大段解析文本一次性查 500 道题会占用大量内存且拖慢网络传输。正确做法是只查出id列或者用selectObjs直接拿主键列表ListObject ids questionMapper.selectObjs(new QueryWrapperQuestion() .select(id) .eq(category_id, categoryId) .eq(status, 1));错题优先模式里allIds.removeAll(wrongIds)的去掉的是所有错题 ID再重新放到最前面这是一个目的性很强的小设计——错题本里的题如果还按随机位置出现用户可能刷完整套都遇不上几道复习效率很低。实际业务里这里的removeAll耗时也不可忽视如果 later 发现这里成了瓶颈把两个 List 都转成 HashSet 再操作即可。3.2 提交判分在事务里算清对错并更新会话判分是刷题系统最不能出错的地方。我的设计是每次用户提交一道题的答案就调用一次submitAnswer方法该方法在单次事务里做四件事——写答题记录、判分、更新会话统计、按需写入错题本。四件事必须在一个事务里完成否则会出现答案记录有了但错题本没写用户复查时发现记录的答案是错的这种数据不一致。判分的核心代码在下面前端通过SubmitAnswerDTO传题号和用户勾选的选项编码列表后端按题型分发到不同的判分策略Transactional(rollbackFor Exception.class) public SubmitResult submitAnswer(SubmitAnswerDTO dto) { // 1. 查题目 Question question questionMapper.selectById(dto.getQuestionId()); if (question null) { throw new BizException(题目不存在或已删除); } // 2. 查标准答案 ListQuestionOption options optionMapper.selectList( new LambdaQueryWrapperQuestionOption() .eq(QuestionOption::getQuestionId, dto.getQuestionId())); boolean isCorrect judge(question.getType(), options, dto.getUserAnswers()); // 3. 插入答题记录 AnswerRecord record new AnswerRecord(); record.setUserId(dto.getUserId()); record.setSessionId(dto.getSessionId()); record.setQuestionId(question.getId()); record.setUserAnswers(String.join(,, dto.getUserAnswers())); record.setIsCorrect(isCorrect ? 1 : 0); answerRecordMapper.insert(record); // 4. 更新会话 PracticeSession session sessionMapper.selectById(dto.getSessionId()); if (session.getCurrentIndex() session.getTotalCount()) { session.setCurrentIndex(session.getCurrentIndex() 1); } if (isCorrect) { session.setCorrectCount(session.getCorrectCount() 1); } int updated sessionMapper.updateById(session); if (updated 0) { throw new BizException(会话状态更新失败请重试); } // 5. 写错题本 if (!isCorrect) { saveWrongQuestion(dto.getUserId(), question.getId()); } return new SubmitResult(isCorrect, question.getAnalysis()); } private boolean judge(Integer type, ListQuestionOption options, ListString userAnswers) { SetString correctCodes options.stream() .filter(o - o.getIsCorrect() 1) .map(QuestionOption::getOptionCode) .collect(Collectors.toSet()); SetString userSet new HashSet(userAnswers); return correctCodes.equals(userSet); }判分逻辑在judge方法中使用Set.equals这是一个非常关键的选择。单选和多选都可以用它统一处理单选时正确选项是单元素集合多选时元素全部匹配才相等。判断题如果把“对”编码成 A、“错”编码成 B同样走这套逻辑。这里我不想用list.contains逐个比较因为多选题乱序选择时用户可能选了 B、C标准答案是 C、B列表比较会误判而 Set 天然无视顺序。事务里sessionMapper.updateById(session)返回 0 是并发下最容易发生的情况——两个线程同时读到同一个 currentIndex各自加一后写回其中一个写回会被行锁阻塞但版本号机制会让它失败。我在会话表里加了乐观锁版本字段versionMyBatis-Plus 的Version注解会拦截并让后写者更新失败。上面代码里通过检查updated 0来感知这种情况然后抛出异常让事务回滚。这里抛出异常比静默成功更安全用户会看到“提交失败请重试”但至少数据没错乱。你要是觉得体验不够好可以在失败时改走一个“重读会话再提交”分支。3.3 错题回收同一道题只进一次错题本错题本能重复收集同一道题是所有刷题系统里反馈最多的问题。用户刷十次同一道错题错题本里出现十条记录复习时根本不知道该以哪条为准。我的做法是错题表对(user_id, question_id)建唯一索引写入时用INSERT IGNORE或先查后插。为了防止并发环境下“先查后插”出现竞态直接走数据库的唯一索引更靠谱public void saveWrongQuestion(Long userId, Long questionId) { WrongQuestion wrong new WrongQuestion(); wrong.setUserId(userId); wrong.setQuestionId(questionId); // 重复错题不重复插入, 只更新时间戳, 便于后续按时间排序复习 wrongQuestionMapper.insertOrUpdate(wrong); }wrongQuestionMapper.insertOrUpdate这张表上定义了唯一索引uk_user_question底层 SQL 是INSERT ... ON DUPLICATE KEY UPDATE last_wrong_time NOW()。前者会保证记录唯一性后者会把错误时间刷新这样在“错题复习”模式下可以按最近错误时间倒序排列把那些总是记不住的题顶到最前面。这比每条错误都留一条记录、靠查询时DISTINCT去重的方案省心得多。针对在 session 中已经提交过的错题如果还想保留历史错误次数做统计我额外加了一张t_wrong_count表记录每题累计错几次。这样切分出两个职责t_wrong_question管当前错题集t_wrong_count管统计。不然单表既要查唯一记录又要累加错误次数查询和写入都会变重。4. 后台管理与进度统计管理员和用户各取所需4.1 题库的批量导入Excel 解析与事务回滚刷题系统真正上线后管理员最头疼的是维护题库。一道题一道题手工录入不现实批量导入 Excel 是刚需。导入功能我要在代码里演示两个关键点解析引擎如何校验格式、如何保证一批数据要么全进要么全不进。我用 EasyExcel 做解析因为它比 POI 原生 Api 省掉很多样板代码。写一个监听器在invoke方法里逐行转成实体并校验出现格式问题时抛出运行时异常触发整体回滚public class QuestionExcelListener implements AnalysisEventListenerQuestionExcelRow { private final QuestionService questionService; private final ListQuestionExcelRow cache new ArrayList(); private static final int BATCH_SIZE 200; public QuestionExcelListener(QuestionService questionService) { this.questionService questionService; } Override public void invoke(QuestionExcelRow row, AnalysisContext context) { // 基础校验: 题干和选项不能为空, 选项编码必须在A-Z范围内 if (StringUtils.isBlank(row.getContent())) { throw new BizException(第 context.getCurrentRowIndex() 行题干为空); } if (row.getCorrectAnswer() null || row.getCorrectAnswer().trim().isEmpty()) { throw new BizException(第 context.getCurrentRowIndex() 行正确答案未填); } cache.add(row); if (cache.size() BATCH_SIZE) { saveBatch(cache); cache.clear(); } } Override public void doAfterAllAnalysed(AnalysisContext context) { if (!cache.isEmpty()) { saveBatch(cache); cache.clear(); } } Transactional(rollbackFor Exception.class) public void saveBatch(ListQuestionExcelRow rows) { for (QuestionExcelRow row : rows) { Question q new Question(); q.setCategoryId(row.getCategoryId()); q.setType(row.getType()); q.setDifficulty(row.getDifficulty()); q.setContent(row.getContent()); q.setAnalysis(row.getAnalysis()); questionService.save(q); // 选项行写入 option 表 saveOptions(q.getId(), row.getOptions(), row.getCorrectAnswer()); } } }代码中BATCH_SIZE设置为 200背后的考虑是每 200 行提交一次事务防止一万道题的 Excel 只开一个事务导致 undo log 膨胀而Transactional加在saveBatch上配合外层 catch 里抛出任意运行时异常就能做到“本批次内已插入的题目全部回滚”。这里需要特别提醒的是 EasyExcel 的监听器invoke里抛出的异常只有外层调用read()时捕获运行时异常并继续上抛事务才会整体回滚不要自己在监听器里 try-catch 吞掉异常。批量导入后通常还要做一步“题目预览确认”。我的做法是导入结果落一张临时表页面展示前 20 条样例数据管理员确认无误后点击“正式发布”才把status置为 1。这样可以防止格式不统一导致用户端出现题干里带换行符、选项文本里有 HTML 标签这类问题。4.2 统计接口个人刷题报告怎么算刷题系统的留存靠即时反馈。用户刷完一套题后最想知道三件事正确率、排名、和上一次比有没有进步。这部分涉及三个统计查询我会把它们放在一个StatisticsService里避免散落在各 Controller 里造成逻辑不一致。首先是个人统计接口。一个 SQL 汇总了总做题数、正确数、连续刷题天数public UserStatsDTO getUserStats(Long userId) { // 查用户最近30天刷题记录 ListAnswerRecord records answerRecordMapper.selectList( new LambdaQueryWrapperAnswerRecord() .eq(AnswerRecord::getUserId, userId) .ge(AnswerRecord::getCreateTime, DateUtil.offsetDay(new Date(), -30))); UserStatsDTO stats new UserStatsDTO(); stats.setTotalCount(records.size()); stats.setCorrectCount(records.stream().filter(r - r.getIsCorrect() 1).count()); stats.setWrongCount(records.size() - stats.getCorrectCount()); // 正确率按题型拆开 MapInteger, Long countByType records.stream() .collect(Collectors.groupingBy(AnswerRecord::getType, Collectors.counting())); // 连续刷题天数 stats.setStreakDays(calcStreakDays(userId)); return stats; }这个接口的数据口径是“最近 30 天”对应页面上的“本月学习报告”。calcStreakDays是一个独立方法——它查询用户最近每天的做题数从今天往前数遇到做题数为 0 的那天就停。这个“连续刷题天数”是刷题应用里最常见的激励指标实现很简单但很容易写错边界如果查询结果没有今天的记录连续天数直接返回 0而不是昨天还刷了就返回 1。特别注意时区问题create_time存的是 UTC 的话统计“今天”时要先转成东八区日期。排名统计更简单刷题排行榜核心是“有效刷题数”即当日提交且有正确结果的记录数。为了防止有人开着脚本刷量我做了个防作弊过滤单日做题数大于 500 条的记录直接从榜单剔除这是一个业务规则而非 SQL 条件写死在统计入口处。排行榜缓存到 Redis 里键名为rank:practice:{yyyyMMdd}失效时间为当天 24 点前。5. 在线刷题系统的高频踩坑与排查清单5.1 并发提交答案时成绩错乱现象用户连续快速点击提交按钮后台会出现同一道题被记录两次或者本次会话的 currentIndex 跳了两格但正确率只按一次算。原因前端没做按钮防抖后端接口也没有幂等控制同一个提交请求被发了两遍。第二遍请求进来时事务重新启动读取到的是第一次提交后的会话数据又做了一次插入记录和累加操作。这是典型的“缺少请求幂等”问题不是偶发问题在高频点击下必然出现。解决前端在submitAnswer接口的 axios 封装里加一个“ loading 期间禁用提交按钮”的锁这是最直观的防护。后端我在 Redis 里给每次提交生成一个唯一请求号requestId用户在进入下一题时拿到这个 ID提交时随身带上。后端先SETNX这个 requestId如果已存在就拒绝本次提交Boolean first redisTemplate.opsForValue() .setIfAbsent(submit: dto.getRequestId(), 1, 10, TimeUnit.SECONDS); if (Boolean.FALSE.equals(first)) { throw new BizException(请不要重复提交); }5.2 题目答案解析泄露给普通用户现象用户抓包发现查询题目详情的接口返回了is_correct字段或者未登录状态下也能访问刷题记录接口看到标准答案。原因查询详情时直接把t_question_option表的数据通过 MyBatis-Plus 的selectById返回到前端没做字段脱敏。更隐蔽的问题是在“错题本详情”里为了给用户展示正确答案后端返回了answerRecord.userAnswers如果这个字段直接把用户提交的错误答案和标准答案放在同一张表前端稍加调试就能把标准答案勾出来。解决设计两个不同的 VO。一个是UserQuestionVO只包含题干、选项文本不包含is_correct另一个是AnswerReviewVO在错题回顾场景才返回正确答案和解析。Controller 层严禁直接返回 Entity从根源上避免这个问题。另外在t_question表再加一个is_show_analysis字段在“练习模式”下默认不展示解析只有用户提交后才能看到。5.3 Redis 缓存与数据库的数据不一致现象用户刷题后正确率已经更新但排行榜数据到第二天才刷新或者题目修改后用户看到的还是旧题目内容。原因统计结果和排行榜写在 Redis 里但更新时只写了数据库忘了删缓存或更新缓存。这类问题在分布式场景下几乎必然出现越早设计好缓存更新策略越省事。解决我的做法是“先更新数据库再删除缓存”。比如修改题目内容时先执行update t_question set content...成功后删除 Redis 中的题目缓存键。下次查询时发现缓存不存在拉取数据库重新写入。这套 Cache-Aside 模式足够支撑刷题系统的访问量不要轻易上更复杂的延迟双删大多数团队完全不需要那个复杂度。排行榜则采用“每 5 分钟从数据库重算一次”的定时任务而不是实时增量更新逻辑简单维护成本低。5.4 事务回滚失效导致脏数据现象批量导入 Excel 时前 100 行成功导入第 101 行格式错误调用方以为全部回滚了但数据库中已经存在前 100 条数据。原因事务在监听的invoke方法里抛异常后回调函数捕获了异常但没有抛出到read()调用方异常被吞事务感知不到错误就没有执行回滚。这是 EasyExcel 结合事务使用时最经典的翻车点。解决在监听器的doAfterAllAnalysed和invoke里捕获异常后先存到throwableHolder解析完成后统一检查是否有异常有则直接抛出并带出错误行号。Override public void invoke(QuestionExcelRow row, AnalysisContext context) { try { validate(row); } catch (Exception e) { throwableHolder.set(new BizException(第 context.getCurrentRowIndex() 行数据异常: e.getMessage())); throwableHolder.get().printStackTrace(); } // 数据照常缓存, 但最终的saveBatch入口检查throwableHolder }这个坑很多项目上线半年后才会被触发一次碰上一次就能让管理员对你失去信任。提前在导入前把“异常后全回滚”的预演做一遍比事后打补丁省心得多。5.5 刷新页面丢进度现象用户在刷题切到第 30 题时误点浏览器刷新再次进入刷题页要从第 1 题重来。原因刷题会话的当前进度保存在前端内存变量中没有在每次提交答案后同步到后端t_practice_session.current_index刷新后后端没有恢复进度的凭据。解决前端在每次提交答案后调用一个轻量心跳接口syncSessionProgress把当前题号同步到后端。后端在getSessionDetail接口返回时把current_index带到前端前端初始化时自动滚动到该题。另外一个更稳的做法是后端在提交答案接口内部已经做了字段更新前端只需要在刷新时调用一次“恢复会话”接口读取进度即可这样即使前端代码出 Bug 丢掉了本地状态后端数据依然是最终准绳。如果前端用了 Vue 或 React 的全局状态管理也可以把放会话信息的 store 做持久化但后端兜底依然是必须要有的。6. 进阶把刷题系统做得更耐用的几个小技巧做到这一步基础版刷题系统已经能跑通了。接下来几个技巧能明显提升“产品感”同时不需要引入太重的架构组件。第一个建议是给刷题加“间隔重复”策略。不要只按错误次数排序错题而是参考记忆曲线的简化做法一道题第一次做错后隔 1 天再次推送如果再错隔 2 天再错隔 4 天形成一个递增复习间隔。实现上只需要在t_wrong_question表增加两个字段review_interval_days和next_review_date。每次用户做错时更新这个间隔。这个功能不复杂但对用户粘性的提升非常明显因为用户会在“刚好快忘掉”的时间点再次看到该题。第二个建议是保证“提交答案”接口的响应速度。刷题场景对延迟非常敏感用户点完提交后超过一秒没有响应就会开始怀疑答案是不是错了。所以在判分链路里尽量避免在事务里做远程调用或复杂计算。我见过有人在判分逻辑里接了一个文本相似度算法结果接口平均耗时飙到三秒那套系统最后被用户弃用了。判分接口只做以下几件事数据库读一次题目与答案、业务判断一次、写入记录。如果未来要查重或语义分析请移到异步消息队列中不要阻塞主链路。第三个建议是给刷题记录做数据归档。刷题系统跑一年后t_answer_record表里可能有百万级数据查询报告会明显地变慢。我的做法是按年分表比如t_answer_record_2025查询时根据时间范围路由到对应表。老数据超过两年后可以转存归档库用户端保留“近一年”明细即可。这套方案用在刷题系统上性价比很高不需要引入大数据组件。最后说一个我从某导师那里学到的习惯——每次发布新功能前自己手动模拟一遍“从注册到错题复习”的完整刷题流程中间故意打错几道题确认错题本记录了错误答案、首页统计数字和报告一致、退出登录再登录后进度还在。这套冒烟测试流程我坚持了三年靠它拦截了至少五次只改一行代码就可能导致判分异常的低级改动。做在线刷题系统的意义就在于好用、准确这两个字上先把核心链路打磨利落再去加花哨的互动功能这能让你的系统比大多数同类品走得更远。希望这篇笔记提供的设计思路和排障经验能帮到你少走我当年走过的弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑