资讯动态

基于ThinkPHP与Vue的考研学习系统设计与实现

发布时间:2026/10/18 14:22:51 来源:尧图企业网站定制
1. 从零到上线这套考研系统到底解决什么问题考研学习系统这个项目核心关键词就是“thinkphp vue”。后端用 ThinkPHP 做接口层和业务层前端用 Vue 搭单页应用前后端分离配合一套完善的权限体系和刷题流程做成一个完整的考研学习平台。很多同学每年考研都在信息收集上吃大亏资料散落在各个群里、学长学姐的网盘链接、公众号文章、B站视频东拼西凑复盘的时候又发现刷过的题没有记录、错题没有沉淀、学习进度没有数据反馈。这套系统想解决的就是这个问题把题库、学习资料、错题本、学习计划和个人进度管理整合到一个平台里让考生在一个入口完成从查资料到刷题再到复盘的全流程。技术栈选择上ThinkPHP 是国内非常成熟的 PHP 框架它对中小型系统的开发效率极高内置了数据库 ORM、验证器、中间件、路由分发等组件不需要自己从头造轮子。Vue 则负责前端交互层组件化开发让页面结构清爽配套 Vue Router 和 Axios 就能实现完整的前后端交互闭环。这套代码适合谁去学习和参考首先是准备做毕业设计或者课程设计的计算机专业学生这类完整的前后端分离系统是很好的项目素材其次是有一定 PHP 基础、想通过学习一个实际项目来理解前后端分离架构的开发者最后是那些想快速搭一个在线教育类产品原型的团队可以直接在这套系统上做改造。1.1 技术选型背后的取舍逻辑我在设计这套系统的时候技术选型没有追求“越新越好”而是遵循一个原则团队最熟悉、生态最完整、部署成本最低的方案就是好方案。ThinkPHP 6.x 版本选择是经过考量的。选它不是因为它在 PHP 世界里性能最强而是因为它的文档非常全遇到问题搜索历史解决方案的成本极低。对于考研学习系统这种典型的信息管理系统瓶颈从来不在 PHP 本身而在于业务逻辑是否清晰、数据库设计是否合理。ThinkPHP 内置的查询构造器配合链式操作写复杂多表联查很方便提供的迁移工具和填充器也能很好地配合数据库版本管理。前端选 Vue 而不是 React出发点很直接Vue 的中文社区资源丰富配套的 Element UI 组件库做后台管理界面是原生生态特别是表格、表单、弹窗、分页这些后管系统高频组件开箱即用。考研系统里有大量的列表页面和表单交互用 Element UI 可以把视觉和交互统一起来减少造的轮子。我还特意把前端工程化做成了 Vue CLI 模式。虽然 Vite 已经成为主流选择但 Vue CLI 的 Webpack 方案兼容性更好在很多学校服务器、老旧操作系统上构建都不会出现兼容性问题。毕业设计场景里导师很多时候只是在本地跑一下看看效果一个稳的构建工具比一个快的构建工具更友好。提示某些场景下“老”不等于“差”。项目目标是业务落地和技术学习选成熟方案可以省下大量排查环境问题的时间。1.2 系统功能模块全景拆解功能模块设计上我参考了市面上的主流在线学习平台比如刷题App和网课平台的结构结合考研学习这个场景做了裁剪。核心模块一共五块打通后形成完整业务闭环。第一块是用户模块。用户分为考生和管理员两个角色。考生可以注册、登录、修改资料、重置密码管理员负责审核用户、管理角色权限、查看系统数据。这里的重点是把权限做扎实避免考生误入管理端操作。我采用的是 RBAC 模型用户关联角色角色关联权限节点权限控制细化到按钮级别。第二块是学习资源模块。管理员可以发布学习资料包括图文、PDF附件和视频链接。资料按科目分类比如政治、英语、数学、专业课每一类下面还可以再设置二级标签比如英语可以分阅读、作文、真题。考生在前端可以浏览、搜索、收藏资料收藏后可在个人中心快速查找。第三块是题库模块。这是整个系统的核心模块管理员可以批量导入选择题、判断题等客观题。题库按科目和难度分类考生进入刷题模式后系统按科目筛选题目支持随机抽题和顺序做题两种模式作答后即时反馈对错并展示答案解析。第四个模块是错题本。考生做错的题目自动沉淀到个人错题本中每道错题记录被错次数和最近一次错误时间。考生可对错题进行“已掌握”标记错题就会从待复习队列中移除。这个模块虽然开发不复杂但对考生的复盘价值很大。第五个模块是学习计划。考生可以创建学习计划按天安排任务任务可以关联到具体的科目和资料。系统根据计划时间线生成进度百分比考生在首页可以看到当前学习进度和计划完成率。管理员则可以在后台看到全部用户的活跃度和核心功能使用率。每个模块都尽量做到数据独立、逻辑清晰模块之间通过用户 ID 和学习科目产生关联避免那种每个功能都互相依赖的高耦合设计。2. 数据库设计、表结构规划和数据关联数据库是整个系统运行的地基。如果表结构设计不合理后期写业务逻辑的时候就会被大量冗余字段和多表联查反复折磨。这套系统的库我命名为postgraduate_db一共设计了十余张核心表下面挑最重要的几个拆开说。2.1 用户表和权限表的设计细节用户表student_user是系统的核心表。字段包括id、username、password存的是密码哈希值不是明文、nickname、avatar、phone、email、status账号状态1正常0禁用、create_time和update_time。这里特别说明密码存储方式。创建用户的时候用 PHP 内置的password_hash()函数加密验证用password_verify()。这两个函数比 MD5 加盐的模式要省事得多也是官方推荐的安全做法。市面上找得到的很多教学项目还在用 MD5 加密密码这个习惯不太好一旦数据库泄露所有账号都裸奔。权限相关的表包括role角色表、access_node权限节点表和user_role用户角色关联表。角色表字段有role_name、role_desc、status。权限节点表记录的是模块的控制器和方法名比如admin/user/lists和admin/topic/save。管理员创建角色的时候勾选节点就能控制某个角色登录后台后能访问哪些功能。权限这块还有一个细节后台的每次请求都要做节点校验。我在 ThinkPHP 的路由中间件里写了一个checkPermission()方法请求进来后先判断登录状态再根据当前用户的角色获取对应的权限节点集合如果请求的地址不在集合里就返回 403。对于使用前后端分离的项目前端还需要做一层按钮级别的显隐控制后端做的是最终的鉴权兜底两层都不能省。2.2 题库表和学习记录表是怎么关联的题库相关的表有三张核心的是question表。字段包括id、subject_id科目ID、question_type题型1单选2判断、content题干、option_a到option_d四个选项、answer正确答案、analysis解析、difficulty难度1简单2中等3困难、status上架状态和create_time。科目表subject的字段很简单name、sort_order、status。题目通过subject_id关联科目这样在刷题的时候按科目筛选就很方便WHERE subject_id 1 AND status 1一条语句就搞定了。学习记录的表设计是整个系统设计里比较花心思的地方。我做的是快照机制考生每次作答一道题系统就向answer_record表中插入一条记录包含user_id、question_id、subject_id、is_correct1正确0错误、create_time和answer_content考生填写的答案。同时用一张user_progress表维护用户总体的学习进度字段有user_id、subject_id、total_answered累计做题数、total_correct累计正确数、total_wrong累计错误数。每次作答后先插明细再更新汇总。这样首页的统计面板可以直接读汇总表不用每次 COUNT 一遍所有的答题记录数据量大了之后这种做法的性能差异会非常明显。错题本是实时查询还是冗余存储这里是个设计决策点。我采用冗余存储的思路wrong_question表专门存错题记录每次答题错误时就插入一条答对时如果该题已经在错题本里就标记为“已掌握”。为什么不直接查answer_record来统计错题呢因为考生可能同一道错题答了三次其中两次是错的一次是对的如果实时查明细就要做去重和状态判断查询复杂度高。冗余一张错题表后业务代码只需要关注“当前这道题是否在错题本里”逻辑简单得多。提示很多刚做系统的人会觉得冗余表是浪费空间但在实际业务里空间换查询性能和时间成本是非常划算的决策。只要保证数据同步的逻辑写清楚冗余不会带来什么问题。3. 接口设计、前后端联调和核心功能实现一个系统能不能顺畅地跑起来前后端能不能高效协作接口设计占了大半因素。下面按接口分层、前端路由和后端控制器、几个核心功能的具体实现来拆解。3.1 RESTful 接口规范和统一返回体设计既然选择了前后端分离接口约定就是头等大事。我的做法是 RESTful 风格加统一返回结构。ThinkPHP 6 的路由定义中我用Route::get、Route::post、Route::put、Route::delete四个方法分别对应查询、新增、修改、删除操作。统一返回体的 JSON 格式是这样的{ code: 200, msg: 请求成功, data: { items: [], total: 23 } }code代表业务状态码200 是成功401 未登录403 无权限500 服务器异常400 参数错误。前端 Axios 的响应拦截器统一处理这个结构如果返回的 code 不是 200就弹出对应的提示信息。这样做避免了每个接口都写一遍错误处理逻辑。接口清单大致如下POST /api/login用户登录POST /api/register用户注册GET /api/user/info获取当前用户信息GET /api/subject/list获取学科列表GET /api/question/list分页获取题目POST /api/answer/submit提交答案GET /api/wrong/list获取错题本列表POST /api/wrong/master标记错题已掌握GET /api/plan/detail获取学习计划POST /api/plan/save保存学习计划GET /api/stats/dashboard获取首页统计数据每个接口的入参和出参都严格定义前端在开发的时候可以并行进行不会出现那种后端没写完就联调不了的黑洞场景。3.2 登录鉴权和 Token 机制的前后端处理登录这里我选择了 JWTJSON Web Token方案。用户登录成功后后端生成一个包含用户 ID 和过期时间的 Token 返回给前端前端把 Token 存储在本地存储中每次请求时在请求头里带上Authorization: Bearer token。ThinkPHP 侧实现 JWT 鉴权是在app/middleware.php文件里注册一个全局中间件。中间件里做的事情有三件检查请求头里是否存在 Token解析 Token 并校验签名和过期时间把解析出来的用户 ID 绑定到当前请求实例上。需要做公共接口和受保护接口的区分。登录、注册、获取学科列表这些接口不需要鉴权我定义了一个白名单数组在中间件里优先判断当前请求地址是否在白名单中。如果在就直接放行否则才走 Token 校验流程。前端 Vue 侧的 Axios 配置如下// request.js import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: /api, timeout: 15000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response { if (response.data.code 401) { localStorage.removeItem(token) router.push(/login) } return response.data }, error { Message.error(error.message || 请求失败) return Promise.reject(error) } ) export default service这里有个我实际踩过的坑有些同学会把 Token 存到 Cookie 里但这容易遭受 CSRF 攻击而且前后端分离模式下 Cookie 的作用域管理也比较麻烦。存 localStorage 配合请求头传递是当前比较通用的做法只要注意不要在系统里明文打印 Token 就好。3.3 刷题流程的完整代码实现刷题是考研系统的核心场景我把这个流程拆成几个环节获取题目、提交答案、写入答題记录、更新错题本、返回解析结果。后端提交答案的方法核心代码如下// app/api/controller/AnswerController.php public function submit() { $params $this-request-post(); $userId $this-request-userInfo[id]; $questionId $params[question_id] ?? 0; $answerContent $params[answer] ?? ; $subjectId $params[subject_id] ?? 0; if (!$questionId || $answerContent ) { return json([code 400, msg 参数不完整]); } // 获取题目正确答案 $question QuestionModel::find($questionId); if (!$question) { return json([code 400, msg 题目不存在]); } $isCorrect strtoupper(trim($answerContent)) strtoupper($question[answer]); // 写入答题明细 AnswerRecordModel::create([ user_id $userId, question_id $questionId, subject_id $subjectId, answer_content $answerContent, is_correct $isCorrect ? 1 : 0 ]); // 更新学习进度汇总 $progress UserProgressModel::where(user_id, $userId) -where(subject_id, $subjectId) -find(); if ($progress) { $progress-total_answered; if ($isCorrect) { $progress-total_correct; } else { $progress-total_wrong; } $progress-save(); } else { UserProgressModel::create([ user_id $userId, subject_id $subjectId, total_answered 1, total_correct $isCorrect ? 1 : 0, total_wrong $isCorrect ? 0 : 1 ]); } // 错题本逻辑 if ($isCorrect) { WrongQuestionModel::where(user_id, $userId) -where(question_id, $questionId) -update([is_mastered 1, mastered_time time()]); } else { $wrong WrongQuestionModel::where(user_id, $userId) -where(question_id, $questionId) -find(); if ($wrong) { $wrong-wrong_count; $wrong-last_wrong_time time(); $wrong-is_mastered 0; $wrong-save(); } else { WrongQuestionModel::create([ user_id $userId, question_id $questionId, subject_id $subjectId, wrong_count 1, last_wrong_time time(), is_mastered 0 ]); } } return json([ code 200, msg 提交成功, data [ is_correct $isCorrect, answer $question[answer], analysis $question[analysis] ] ]); }这段代码包含了本题的核心交互逻辑每条答题记录都留痕、进度表实时更新、错题本自动维护。前端的答题页面上用户在点击“下一题”之前就看到了对错和解析交互反馈即时体验比较流畅。前端刷题页面的 Vue 组件核心部分大致如下template div classquestion-panel h3{{ question.title }}/h3 div classoptions div v-for(option, index) in options :keyindex :class{ active: selected option.key, correct: showResult option.key question.answer, wrong: showResult selected option.key option.key ! question.answer } clickhandleSelect(option.key) {{ option.key }}. {{ option.content }} /div /div div v-ifshowResult classanalysis p正确答案{{ question.answer }}/p p{{ question.analysis }}/p /div div classbuttons el-button clickprevQuestion上一题/el-button el-button v-if!showResult typeprimary clicksubmitAnswer提交答案/el-button el-button v-ifshowResult typeprimary clicknextQuestion下一题/el-button /div /div /template前端保持一个当前题目索引的状态点击“下一题”就向后端拉取新的题目数据并替换当前题目。整个过程不会刷新页面刷题体验很像在线考试系统的真实操作感受。3.4 学习计划模块的进度计算逻辑学习计划模块是一个容易被低估的模块因为表面上它只是简单的增删改查但实际上进度计算的逻辑需要仔细考虑。我在study_plan表里存了计划的总任务数和已完成任务数。total_task是用户创建计划时填写的天数finished_task是用户每天完成任务后主动点击“打卡”时累加的数值。还有一个更细粒度的plan_task表记录计划下的每一天。字段有plan_id、plan_date计划日期、task_content任务内容比如“完成英语阅读2篇”、is_finished是否完成和finished_time。用户点“打卡”时前端会发送计划 ID 和日期后端根据计划 ID 找到当天对应的任务行将is_finished置为 1然后重新统计这个计划的总完成数更新到study_plan表。这种方式看起来多写了一张表但好处很明显用户可以回看之前每天的任务安排和完成情况形成坚持打卡的正向循环而不是只有一个冰冷的百分比。进度计算的核心代码// 打卡接口 public function checkIn() { $params $this-request-post(); $planId $params[plan_id] ?? 0; // 用当天日期匹配任务 $today date(Y-m-d); $task PlanTaskModel::where(plan_id, $planId) -where(plan_date, $today) -find(); if (!$task) { return json([code 400, msg 今天没有学习任务]); } $task-is_finished 1; $task-finished_time time(); $task-save(); // 重新统计完成数 $finishedCount PlanTaskModel::where(plan_id, $planId) -where(is_finished, 1) -count(); // 更新计划表进度 PlanModel::where(id, $planId)-update([finished_task $finishedCount]); return json([code 200, msg 打卡成功]); }有个我实际踩过的坑是用户时区问题。服务器默认使用 UTC 时区而考生的“今天”可能是东八区的时间。如果服务器时区配置不对打卡接口就会出现在凌晨的时候对应不到任务日期看到的现象是“我明明打卡了却显示没有任务”。解决办法是统一在 PHP 入口文件设置时区date_default_timezone_set(Asia/Shanghai);同时 MySQL 链接层也放开时区限制在 ThinkPHP 的数据库配置里加上datetime false 让框架不自动转换时间格式。这个问题排查了我大半天最后定位到时区设置希望后来者不要再踩。4. 前后端环境搭建、部署上线和常见问题排查系统的开发流程除了写代码本身环境搭建和部署上线也是非常重要的一环。下面把开发环境、服务器部署和一些高频的报错排查经验一次性整理出来这些都是我在实际搭建这套系统时攒下的实操记录。4.1 本地开发环境准备清单开发环境推荐直接用 PHPStudy 或者 Docker 搭建Windows 和 Linux 都可以。需要准备的工具列表PHP 7.4 及以上MySQL 5.7 及以上Nginx 或 ApacheComposerPHP 依赖管理工具Node.js 14 及以上用于前端构建Vue CLI 5.xThinkPHP 6 框架对 PHP 版本的要求是 7.2.5 以上但实际开发中我建议直接用 7.4兼容性和性能都更理想。MySQL 5.7 就够用了5.5 或者更老版本会存在一些索引和 JSON 函数支持的问题。用 Composer 安装依赖composer create-project topthink/think tp 6.*后端代码结构采用的常见thinkphp结构tp/ ├── app/ │ ├── controller/ │ ├── model/ │ └── middleware.php ├── config/ ├── public/ │ └── index.php ├── route/ └── .env前端在独立目录创建项目vue create postgra-frontend cd postgra-frontend npm install axios element-ui vue-router前端构建和联调有个关键的配置点在 Vue CLI 的vue.config.js里需要配置代理转发避免开发环境跨域module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8888, changeOrigin: true } } } }这里的target指向 PHP 项目的运行地址。前端跑在 8080 端口后端跑在 8888 端口浏览器请求的是 8080 的/api地址代理层会自动转发到后端。这样开发阶段完全不用处理跨域头上线部署时再用 Nginx 统一转发。4.2 生产环境服务器部署全流程生产环境我选择的是经典的 CentOS 7 服务器部署流程从装环境开始走一遍。# 安装 PHP 7.4 和扩展 yum install -y epel-release yum install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum install -y php74-php-fpm php74-php-mysqlnd php74-php-json # 安装 Nginx yum install -y nginx # 安装 Composer curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer后端项目部署到/data/www/tp后执行依赖于环境实际安装的扩展。前端项目准备上线构建cd postgra-frontend npm run build构建完成后dist 目录下会生成index.html、js、css等静态文件把这些文件复制到/data/www/tp/public目录下。Nginx 配置的核心思想是静态文件直接由 Nginx 返回PHP 动态请求转发给 PHP-FPM前端路由使用 history 模式的话需要做重写将所有请求指向 index.html。server { listen 80; server_name your_domain; root /data/www/tp/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?s$uri; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { expires 7d; } }这里有个关键的部署细节ThinkPHP 的入口文件在public/index.php所以在 Nginx 配置中root要指向项目的 public 目录。如果按照默认的 README 把 root 指向项目根目录就会出现路由全部 404 或者直接下载 PHP 源码的危险情况。如果你的前端路由用了 HTML5 History 模式Nginx 还要再加一个 fallback 配置location / { try_files $uri $uri/ /index.html; }这行配置的意思是如果请求的资源文件不存在就返回前端的index.html让 Vue Router 自己根据 URL 来匹配对应的前端路由。不加这个规则的话用户直接访问某个子页面链接会报 404。4.3 上线前的系统安全检查清单系统上线之前除了功能测试安全检查绝对不能少。有几个位置我建议重点看任何一个漏了都可能出问题。数据库层面ThinkPHP 的查询构造器默认是参数绑定的通常能避开 SQL 注入风险。真正出问题的高发点在搜索功能。比如按题目名称搜索时使用字符串拼接到where条件中的操作方式风险极高。正确做法是全部使用构造器的 where 方法传参数。密码安全。上面已经说过所有用户密码必须用password_hash()存储。管理员账号的基础密码在创建角色时尽量设置成强密码避免后台被暴力破解。XSS 防护方面前端在渲染用户生成的内容时比如用户昵称、评论区内容一定要做转义。Element UI 的组件默认对文本内容有转义防御但如果用v-html渲染接口返回的富文本就存在被注入脚本的风险。后端的输出层也要处理ThinkPHP 提供了htmlspecialchars()过滤函数。Nginx 层安全配置建议加这三行# 不返回服务器版本信息 server_tokens off; # 限制上传包大小 client_max_body_size 10m; # 防跨站请求伪造 add_header X-Frame-Options SAMEORIGN;同时生产环境在.env配置文件中要把APP_DEBUG设为false开启调试模式后出现报错会直接输出文件路径和代码行号暴露服务器目录结构是安全大忌。4.4 高频报错和排查经验实录开发这套系统期间我记录了一些经常出现的报错场景在这里整理成速查表方便后来者直接定位问题。接口请求返回 404出现 404 先排查路由是否匹配。ThinkPHP 6 的路由默认开启auto_multi_app后域名入口或虚拟目录还需要检查伪静态是否配置好。Nginx 下最常见问题是根目录配错导致 index.php 没有被正确加载。执行php think route:list查看当前加载的路由列表直接看路由是否注册。前端请求跨域报错本地开发尽量用 Vue CLI 代理不要自己去配 CORS。如果确实要用 CORS后端的解法是通过中间件给所有响应加上header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS); header(Access-Control-Allow-Headers: Content-Type, Authorization);但注意 OPTIONS 请求要单独放行否则前端预检请求会卡住。ThinkPHP 项目里建议写一个CorsMiddleware挂在全局中间件里统一处理。登录后请求返回 401排查 Token 是否成功传递。先看浏览器 DevTools 的 Network 面板请求头中是否带有Authorization字段。如果没有检查前端 Axios 拦截器里的 localStorage 获取逻辑是否是在登录之前请求发送时 Token 尚未写入。如果请求头有 Token 但后端还是 401检查 JWT 的过期时间设置和服务器时间是否一致时间偏移会导致签名验证失败。分页数据不准确内存翻页偏移问题。ThinkPHP 的分页器和前端的表格分页组件使用的是页码加每页条数这种形式但有些场景使用的是 offset 加 limit 模式。如果混合使用第二页数据就会重叠或者漏掉。实际排查过一次发现是前端把page参数传错成了pageNum后端取不到。确保前后端分页参数名一致用page和limit最简单直观。上传资料附件失败这类问题大多出在 PHP 的upload_max_filesize和post_max_size两个配置上。默认配置都只有 2M上传 PDF 很容易超限。改配置后记得重启 PHP-FPMupload_max_filesize 20M post_max_size 25M max_execution_time 60另外一个隐蔽的坑是 Nginx 层也有client_max_body_size限制默认 1M也要同步调大。5. 性能优化、安全加固和学习计划扩展思路系统上线跑了一段时间后就要考虑性能优化和后续迭代的问题了。没有哪个系统是一次性做到完美的功能和性能都需要根据真实的用户反馈持续调整。5.1 数据库索引和缓存层的思路系统初始的数据量不大的时候SQL 性能问题不明显。但是题库量到达几千道、答题记录达到几万条的时候查询就会出现明显的延迟。优化第一步是检查最常用的查询语句有没有走索引。答题记录表直接给user_id和question_id建立普通索引联合查询时优先使用它们。错题本表的查询条件是user_id加is_mastered所以也应该建一个联合索引。优化第二步是给热点数据加缓存。题目列表和学科列表这种很少变化的接口适合用 ThinkPHP 内置的缓存机制以学科列表举例$list Cache::get(subject_list); if (!$list) { $list SubjectModel::where(status, 1)-order(sort_order asc)-select(); Cache::set(subject_list, $list, 3600); }缓存时间设置为 3600 秒管理员修改学科信息后缓存最长延迟一小时生效。对学习系统而言这个延迟是可以接受的。后面如果做大可以考虑接入 Redis但初期缓存到文件就足够了。5.2 防刷接口和安全隔离的处理办法系统上线后一定会遇到的问题是有人用脚本刷接口最常见的是频繁请求登录接口或者刷题接口。这两种情况的处理方式不同。登录接口防刷可以设计一套简单的频率控制规则同一个 IP 在 5 分钟内连续失败 5 次就封锁该 IP 10 分钟。这种简单的逻辑用Cache::set()就能实现不需要专门的防火墙插件。刷题接口的防刷逻辑不能简单地封 IP因为很多考生刷题是正常行为。可以采用一个折中的方式限制单用户一分钟内最多提交 30 次答案。超出就返回请求频率过高的提示防止脚本无限拉取题目接口。管理员后台的操作也需要保护尤其是资料上传和删除的接口这些接口除了权限校验外还要加上日志记录。我在系统里给所有管理员的操作写入了操作日志记录操作人、操作时间和操作内容。后续审计问题时有据可查。5.3 系统的横向扩展方向这套系统的结构虽然简单但它是一个完整可用的学习平台骨架。基于现在的代码后续可以在多个方向做扩展。比如增加一个模拟考试模块按照真实考研的科目和时间配置模拟试卷考生限时作答提交后自动判分并生成成绩报告。这需要把题库中的题目按照试卷模板抽题组卷并增加计时器逻辑。从本题库的架构看这个功能可以直接复用现有的question表和answer_record表改动成本在可控范围内。比如增加一个学习社区功能考生可以发帖讨论题目、分享备考笔记、互相答疑。需要的表是帖子表和评论表帖子关联用户和科目。社区功能本质上就是一个轻量的 CMS现有框架完全够用。还可以接入 AI 能力做一个智能测题推荐基于考生的历史作答记录分析知识薄弱点重点推荐对应科目的题目。这属于在现有数据基础上增加一层算法逻辑模型训练不是重点重点是读取answer_record表的数据做统计分析。提示学习系统这类项目最大的价值不是代码本身而是学习数据的持续沉淀和对用户学习行为的正向引导。后续任何新功能的设计都应该围绕“如何让考生更高效地学习和复习”这个核心目标展开避免为了堆功能而堆功能。6. 这套系统开发中的几点实在心得从项目立项到系统上线整套过程下来有几点感触比较深写出来给正在做类似系统的同学参考。第一点是设计阶段一定要想清楚业务闭环。以“刷题-答错-进错题本-标记掌握”为例子这套闭环一开始在设计表结构的时候就要留好对应的表和关联字段。如果只想着做一个题库管理系统忽略了错题本和学习计划这两个下游模块后期再补的话改动会成倍放大。我就是一开始就把所有表和核心逻辑建模完成才做到按照既定计划推进开发。第二点是前端页面实现的节奏控制。我习惯先用 Element UI 把框架页面的布局、路由、登录态处理跑通然后再一个一个功能模块填充。先保证“骨架”稳定再加“肌肉”最后再处理“细节”。如果一开始就陷入每一个按钮的样式细节进展会很慢而且容易发现自己对整体页面设计还没想清楚。第三点是异常情况的处理要早做。我自己曾经忽略了对后端返回的特殊情况的处理比如用户长时间没有操作导致 Token 过期首次跳转时所有请求同时失败。后来在 Axios 响应拦截里做了统一处理遇到 401 就清理本地存储并强制跳转登录页这个问题才算彻底解决。建议在做任何一个页面前先把“请求超时、未登录、无权限、服务器错误”这四种基础提示处理好再开始做页面业务。第四点是部署上线的经验要在开发中期就要准备。本地开发没问题不代表线上没问题。服务器环境、路径配置、伪静态、跨域、文件上传限制这些问题一整套排下来大概率要花掉一整天。提前写好部署清单避免夜深人静的时候对着 Nginx 配置发呆。今天回头来看这套系统技术难度不算高但配合数据库设计、接口设计、前端联调、安全配置、性能优化这完整的一整套流程对一个前后端分离项目的理解提升还是很有帮助的。编码能力是在这种真实的项目推进中慢慢积累出来的希望这篇复盘对你搭建类似系统有所帮助。

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

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

免费获取报价 →
↑