多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot+Vue全栈在线考试系统源码实战详解

SpringBoot+Vue全栈在线考试系统源码实战详解 1. 这个项目到底是什么为什么值得做很多准备毕业设计或者课程设计的同学都会面临同一个问题题目看起来都差不多但真正动手做的时候才发现坑一个接一个。今天我想复盘一个非常经典、也特别适合拿来当毕设或课设的完整源码项目——SpringBoot Vue 的在线考试与学习交流网页平台。这个项目我在实际带学生的过程中反复推荐过自己也完整走通过一套从数据库设计到前后端联调踩了不少坑积累了不少经验这里一次性整理出来。先一句话说清楚这个东西是什么它是一套基于 Java 技术栈的全栈 Web 管理系统后端用 SpringBoot 搭 RESTful API前端用 Vue 框架做单页应用数据存储用 MySQL通过 MyBatis-Plus 做持久层操作。系统核心解决三个问题在线考试创建试卷、答题、自动判分、学习交流发帖、回帖、互动、后台管理用户、题库、试卷、公告、权限。如果你正在准备毕设这套系统的功能量、技术深度、扩展空间都卡在了一个非常合适的档位上——不会简单到让答辩老师觉得没含量也不会复杂到让新手在一个月内做不完。适合谁来参考我建议这几类人可以重点关注一是正在选毕设题目的大四学生二是做 Java 课程设计的在校生三是想从前端或者纯后端转向全栈的初学者。你会从这套源码里学到的不只是一个“能跑的系统”而是 SpringBoot 项目结构怎么划分、Vue 路由和状态管理怎么组织、MySQL 表结构怎么满足业务需求、前后端联调时常见的坑有哪些。和那些纯 CRUD 的管理系统不同在线考试平台有两个比较有代表性的业务难点一个是试卷生成的随机策略一个是自动评分的判定逻辑。这两个功能做扎实了整个系统的含金量立刻不一样。而且这个题目天然带有“学习交流”模块比单纯考试系统多了一层互动性这也符合现在很多高校对线上教学平台的现实需求。我在梳理这套源码的时候有一个很深的体会它的技术选型非常“主流”。SpringBoot Vue MySQL 这套组合在当前 Java 就业市场里几乎算是入门标配面试时候聊项目面试官大概率不会陌生。换句话说写完这个项目你不仅交了毕业设计还顺带给自己攒了一个能写进简历的项目经历。2. 整体设计和模块拆解先想清楚再动手2.1 角色权限是地基一开始就要定好在线考试平台最少要区分三种角色管理员、教师、学生。有些系统会再拆出“超级管理员”但我建议初期三种就够。管理员负责全局配置包括用户管理、公告发布、数据统计教师负责题库维护、试卷创建、阅卷与成绩查看学生负责在线考试、查看成绩、参与学习交流。权限设计直接决定路由和接口的拦截策略如果一开始不规划好后面会改得非常痛苦。前端路由需要按角色做动态权限控制我的做法是登录成功之后后端返回当前用户的角色标识前端根据角色动态生成可访问的路由表而不是把全部路由一股脑注册进去。比如管理员才有用户管理页面教师才有题库管理页面学生只有考试中心、成绩查询、交流社区这些入口。后端的拦截器负责二次校验每个接口都校验当前登录用户的角色是否匹配防止有人绕过前端直接调接口。角色权限这块我建议用一个简单的 RBAC 模型不要搞太复杂。三张表用户表、角色表、用户角色关联表。虽然 SpringSecurity 或者 Sa-Token 都能做细粒度权限但毕设项目里用拦截器 注解结合的方式完全够用而且代码更好理解。答辩时老师问你“权限是怎么实现的”你能把拦截和角色判断讲清楚比甩一个框架配置更有说服力。2.2 核心业务流程考前考中考后三个环节在线考试的业务流程可以拆成三段来理解每一段都有它要处理的特殊情况。考前的核心是组卷。教师从题库里按题型和难度选题系统要支持手动选题和自动抽题两种模式。手动选题就是教师一道一道勾选自动抽题需要设计规则比如“单选题 20 道、每题 2 分”“多选题 10 道、每题 3 分”“判断题 10 道、每题 1 分”系统从题库里随机抽取对应数量的题目组成一张试卷。这里的关键点是抽过的题和没抽过的题要区分清楚还要防止同一张试卷里出现重复题目。考中的核心是答题和计时。学生进入考试页面后前端开始倒计时后端在发起考试时生成一条考试记录记录开始时间。交卷方式有两种主动交卷和到时自动交卷。主动交卷前端把答题数据一次性提交给后端自动交卷需要后端定时任务扫描超时的考试记录把未交卷的强制置为交卷状态。这个逻辑看起来简单但涉及并发场景后面我会专门说坑在哪。考后的核心是判分和成绩分析。客观题单选、多选、判断由系统自动判分主观题需要教师手动阅卷。自动判分的逻辑不复杂就是拿学生的答案和标准答案比对但多选题的比对有一些细节处理全对才得分漏选、错选都不得分或者漏选得一半分具体规则要根据题目设置来定。成绩分析就是简单的统计平均分、及格率、分数段分布这些可以用 SQL 聚合查询直接做。2.3 学习交流模块别做得太复杂但要有互动性学习交流模块本质是一个轻量论坛功能上只需要四个东西帖子列表支持分页和关键词搜索、发帖、回帖、帖子详情。如果能再加一个“我的帖子”和“帖子分类”体验会更好。回复里可以设计一个楼层概念展示“第几楼”这个对新手理解 Map 或 List 的排序逻辑有点帮助。为什么我建议学习交流模块不要做太重因为这个模块的定位是辅助功能它的存在意义是让平台不只是“做题工具”还能承载学习氛围。你把发帖、回帖、搜索做扎实已经完全能满足毕设的功能要求。如果时间充裕可以再加点赞或置顶但优先级排在考试核心功能后面。2.4 数据库表设计体现实力的关键部分数据库设计是整个项目里最体现基本功的地方。我建议核心表不少于十张用户表、角色表、用户角色关联表、公告表、题目表可以按题型分表也可以单表加题型字段、试卷表、试卷题目关联表、考试记录表、答题详情表、帖子表、回复表。这里有一个设计决策值得展开讲讲题目类型用单表加字段还是每种题型单独建表。我测试过两种方案单表加类型的做法在毕设级别是完全够用的。你把字段设计齐全题目类型单选、多选、判断、简答、难度等级、所属科目、题目内容、选项内容用 JSON 字符串存储四个选项、标准答案、分值。用 JSON 存储选项的好处是前端渲染灵活坏处是查询时如果想按选项内容搜索会麻烦一点但在我们这个业务场景下几乎没有那种搜索需求所以取 JSON 是划算的。试卷表的核心设计思路是“一表一试卷一表一关联”。试卷表只存试卷的基本信息比如标题、总分、考试时长、创建人试卷题目关联表存的是这份试卷包含了哪些题目每个题目的分值在关联表里单独记录。为什么要单独记录因为同一个题目可以被不同试卷引用而且在不同试卷里分值可能不同。这个设计直接体现你有没有理解关系型数据库的建模思路。考试记录表和答题详情表是联动的。考试记录表一条数据代表学生的一次考试进程包含学生 ID、试卷 ID、开始时间、交卷时间、最终得分、状态答题详情表每条数据对应一道题目的学生作答记录包含考试记录 ID、题目 ID、学生答案、是否得分。成绩查询的时候先查考试记录再按记录 ID 去查答题详情这样数据的层次感就出来了。3. SpringBoot 后端核心实现从项目搭建到业务落地3.1 项目分层和依赖选型SpringBoot 项目的目录结构我比较推荐这样分controller 层接收请求、service 层处理业务逻辑、mapper 层操作数据库、entity 实体类、dto 传输对象、vo 视图对象、config 配置类、common 存放通用返回结果和异常处理。这个分层方式非常标准答辩时老师一看就知道你受过正规训练。很多新手喜欢把所有代码写进 controller当时看起来快后面维护任何一个功能都要改动 controller项目越改越乱。依赖方面我和大家说一下我实际用的版本组合这是踩过坑之后固定下来的SpringBoot 2.7.x、MyBatis-Plus 3.5.x、MySQL 8.0、JWT 用 jjwt 0.9.1或者 java-jwt 3.x 也行、Hutool 工具包、Lombok。SpringBoot 2.7 是目前比较稳妥的版本网上资料多遇到问题很容易搜到解决方案。MyBatis-Plus 帮我们把单表 CRUD 全部省掉了你不用再写基础的增删改查 XML只需要写复杂 SQL。Lombok 用 Data 注解省掉 getter/setter代码量肉眼可见地减少。3.2 统一返回格式和全局异常越早做越省事如果你不想所有接口的返回结构乱七八糟一定要在第一行代码之前定义统一返回对象。我的做法是一个 Result 类里面三个字段code、msg、data。成功时 code 是 200失败时是 4xx 或 5xx。前端 axios 拦截器判断 code 来决定是走成功回调还是弹出错误提示。这个类虽然简单但它统一了整个项目的信息交换格式所有接口的返回都是同一个模板。全局异常处理用 RestControllerAdvice 注解实现。它的作用是当 service 层抛出业务异常时不需要每个 controller 都写 try-catch而是集中到一个地方处理。比如用户注册时用户名已存在service 层直接抛一个 BusinessException(用户名已存在)全局异常处理器捕获后返回给前端一个 code 为 400 的 Result。这样代码干净逻辑也统一。3.3 JWT 登录认证自己动手实现一次就懂了登录认证我强烈建议用 JWT不要用 Session。虽然 Session 更简单但前后端分离项目里 JWT 是更合理的方案。流程是这样的用户提交用户名密码后端校验通过后生成一个 token 字符串把它返回给前端前端存在 localStorage 里之后每次请求都在请求头带上 Authorization 字段。后端有一个拦截器在请求进入 controller 之前先取 token解析 token 得到用户 ID 和角色如果解析失败就返回 401 未登录。这里有很多新手容易犯的错误是拦截器写好了但忘记放行登录接口结果用户还没登录就被拦截了登录接口本身都访问不了。所以白名单一定要配好比如 /api/auth/login、/api/auth/register 这些接口要放行前端静态资源如果有的话也要放行。我在源码里还会顺手实现一个注解叫 RequireRole可以加在 controller 的方法上用来标记这个接口需要什么角色才能访问。拦截器在 JWT 解析完成之后再检查当前用户角色是否满足注解要求不满足就返回 403。这个设计比每个接口自己写 if 判断要优雅很多。3.4 自动组卷的随机策略核心代码拆开讲自动组卷是我认为最能体现逻辑思维的模块。教师提交一个组卷规则比如单选题 20 道每题 2 分、多选题 10 道每题 3 分。后端要做的事情是从题库中把符合“科目 题型”条件的题目全部查出来然后随机抽取指定数量最后把选中的题目写入试卷题目关联表。随机抽取我建议用 MyBatis-Plus 的 LambdaQueryWrapper 配合 ORDER BY RAND() 实现。简洁的做法是这样ListQuestion questionList questionMapper.selectList( new LambdaQueryWrapperQuestion() .eq(Question::getSubjectId, subjectId) .eq(Question::getType, type) .orderByAsc(RAND()) .last(limit count) );这里有两个需要注意的细节第一个是.last(limit count)的写法MyBatis-Plus 提供了 last 方法拼接 SQL 片段但如果有用户输入的 count 参与拼接必须要做好类型校验防止 SQL 注入第二个更关键如果题库里符合条件的题目数量少于要求数量程序要提前处理抛出“题库数量不足”的异常而不是让 MySQL 返回一个奇怪的错误。抽完题之后还有一个隐藏步骤——校验总分。组卷规则里写的分数加起来应该等于试卷总分但老师在配置时可能粗心大意配错了所以后端在创建试卷前一定要做一遍总分校验如果不一致直接返回提示当前配置总分是 X应为 Y。这个校验能避免学生在考试时看到的试卷总分是错的。3.5 在线考试自动评分逻辑细节决定成败自动评分看起来就是比对两个字符串但实际上有几个细节很容易被忽略。单选题和判断题是最简单的学生提交的答案和标准答案完全一致即得分。多选题要复杂一些我的实现是先判断学生答案的字符集合和标准答案是否完全一致一致满分如果不一致检查学生答案是否是标准答案的子集如果是就按规则给一半分。这里我用了一个简单函数来比对字符串集合public int judgeMulti(String studentAnswer, String correctAnswer, int score) { if (correctAnswer.equals(studentAnswer)) { return score; } SetString studentSet new HashSet(Arrays.asList(studentAnswer.split(,))); SetString correctSet new HashSet(Arrays.asList(correctAnswer.split(,))); if (correctSet.containsAll(studentSet)) { return score / 2; } return 0; }注意字符串答案的分隔符前后端要严格统一我这边统一用英文逗号分隔前端在多选题拼接答案时也是用英文逗号join(,)。这个统一约定看似是小事情但出现全对判零分的 bug 时检查这里通常能发现问题。简答题不能走自动判分它的审核状态要标记为“待人工阅卷”教师可以从后台看到待阅卷列表手动打分。3.6 考试过程中“防作弊”和异常处理能加分的设计考试过程中常见的异常情况有三个学生考试中途刷新页面、考试期间断网、到时未交卷。刷新页面会导致前端状态丢失所以我的做法是学生每次进入考试页面时先查询后端如果当前用户对该试卷已经存在未交卷的考试记录就恢复到那条记录继续答题而不是重新创建。这个设计叫“断点续考”答辩的时候讲出来是加分项。到点未交卷的处理方式有两种一种是前端倒计时结束后自动提交另一种是后端启动一个定时任务扫描超时记录强制交卷。我建议两个一起做前端自动提交保证正常情况下的体验后端定时任务保证容错。定期任务我用的 Spring 自带的 Scheduled 注解每 30 秒扫描一次考试记录表找到超过截止时间但状态还是“进行中”的记录把答案置为“超时未答”状态改为“已交卷”。3.7 MySQL 连接配置和分页插件数据库连接配置我建议把关键参数都写清楚尤其是时区这一项。MySQL 8.0 的连接串如果不加 serverTimezone 参数大概率会报时区错误。我的 JDBC 连接串是这样的spring.datasource.urljdbc:mysql://localhost:3306/exam_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password你的密码 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver分页插件用的是 MyBatis-Plus 自带的分页插件在 config 里声明一个 MybatisPlusInterceptor Bean添加 PaginationInnerInterceptor。之后所有分页查询只需要在 service 层传入 pageNum 和 pageSize配合 Page 对象不用手动写 LIMIT 了非常省事。4. Vue 前端实现从页面搭建到考试交互4.1 项目搭建和目录组织前端我用 Vue CLI 创建的 Vue 2 项目。为什么不用 Vue 3说实话Vue 3 的组合式 API 确实更现代但很多毕设参考代码还是 Vue 2 的写法而且 Vue 2 的生态资料丰富遇到问题好搜。但如果你已经会 Vue 3直接用 Vue 3 也完全没问题核心思路是一样的。我这里的源码是 Vue 2 的写法但把它改成 Vue 3 的 setup 语法也不难。src 目录建议这样分api 目录放所有接口请求方法每个模块单独一个文件比如 exam.js、user.js、post.jsrouter 目录放路由配置要做动态路由store 目录用 Vuex 管理登录状态和用户信息views 目录放页面组件按角色分子目录 admin、teacher、studentcomponents 目录放公共组件比如顶部导航栏、侧边栏、分页组件。4.2 Axios 封装和请求拦截让接口调用有统一入口Axios 一定要做封装不要每个页面直接 import axios 就发请求。封装的核心是两件事设置 baseURL 以及请求拦截器里自动加 token。我的封装思路大概是这样import axios from axios import router from /router const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.msg)) } return res }, error { return Promise.reject(error) } )这样封装之后每个业务接口只需要关心成功的数据不需要每个页面都处理 401 跳转登录这些逻辑。特别是毕业论文里的代码展示环节你把这种“统一处理”的设计讲清楚老师会觉得你有工程意识而不仅仅是会调用 axios。4.3 前端路由和菜单权限前端动态路由的做法是登录成功后把后端返回的角色信息存在 Vuex 里然后根据角色的不同调用不同的路由注册方法。具体思路是routes 里先只放公共路由比如登录页、注册页登录后前端判断角色再通过 router.addRoutes 方法把该角色对应的业务路由动态加上。菜单栏的显隐也是根据角色控制学生登录看不到题库管理入口教师登录看不到用户管理入口。这里有个我去实验室帮人调过的常见bug动态路由加上了但页面一刷新就 404。原因很简单路由是 JS 动态加的刷新后 Vuex 状态重新初始化动态路由就没了页面自然找不到。解决办法是在路由守卫里做一个“已登录但路由还没初始化”的状态判断每次刷新后重新拉取用户信息并且重新 addRoutes。这个坑几乎每个做动态路由的人都会遇到提前知道可以省下很多时间。4.4 考试页面核心交互设计考试页面是整个前端最复杂的页面在实现的时候要考虑到防误触和实时计时。我的布局是顶部显示试卷标题和倒计时左侧是题目列表可以快速跳转到任意题目中间是题目内容和作答区域。这样分栏的好处是考生能看到整体答题进度哪些题没做一目了然。倒计时用 setInterval 实现每秒钟减一秒剩余时间显示为“分:秒”格式。时间到了自动触发交卷确认弹窗然后调用后端交卷接口。这里要注意一个问题setInterval 在页面被切换或浏览器标签页切走时定时器的精度会下降所以前端倒计时可能不准。我的方案是后端在考试记录里存了开始时间和考试时长前端在拿到试卷信息时算出截止时间戳然后用截止时间戳减当前时间戳来刷新倒计时不依赖 setInterval 的次数累加。这样即使定时器偶尔延迟倒计时依然是准的。交卷前一定要做二次确认弹窗防止学生手抖点了交卷。交卷按钮触发后弹一个 Dialog提示“你还有 X 道题未作答确认交卷吗”确认后再提交数据。这个防误触设计在答辩演示时很有用可以当作一个用户体验的亮点来讲。4.5 考试数据提交方式一次性提交还是逐题保存答题数据的保存有两种常见方案。方案一是每答一道题就调一次接口保存优点是不会丢数据缺点是频繁请求后端压力大方案二是所有答案在前端暂存交卷时一次性提交优点是接口调用少缺点是中途系统崩溃数据不保。如果你的项目想要兼顾两者可以做一个中间态答题时把数据存在 localStorage每 30 秒自动同步一次到后端交卷时再提交一次。但我实测下来毕设级别用方案二就够了把最终提交的数据结构设计好即可。提交的数据结构我定义成 JSON 数组每个元素包含题目 ID 和学生答案。交卷接口改成接收一个 List后端循环判断。要注意的是数组中的题目 ID 必须是这张试卷里的题目后端要做一个校验防止学生篡改请求把其他题目的答案塞进去。这个“前后端数据可信度”的问题可以在文档里当做一个安全点来说也是答辩亮点。5. 常见问题排查与避坑指南5.1 跨域问题新手必遇的拦路虎前后端分离项目百分之百会遇到跨域问题。表现是前端请求后端接口浏览器控制台报错 CORS接口完全不返回数据。解决方式有两种一种是后端配置全局跨域另一种是前端通过 devServer 的 proxy 代理转发。我推荐开发阶段用前端代理因为配置 proxy 之后你只需要理会 baseURL而且浏览器看起来还是“同源”请求更干净。// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }如果你一定要后端跨域用 CrossOrigin 注解或者写一个 CorsFilter 也行。但我建议毕设项目直接用代理方案因为部署到服务器后你用 Nginx 反代也能复用这个思路学一次能顶两处用。5.2 时区问题和日期显示MySQL 和 Java 的时区不一致会导致两个典型问题一个是存入数据库的时间比实际时间少 8 小时另一个是查询出来的时间在前端显示不对。解决办法是连接串加serverTimezoneAsia/Shanghai同时实体类里的日期字段统一用LocalDateTime前端拿到时间后格式化显示。另外要注意如果你用了 Jackson 的日期格式化配置记得确认一下spring.jackson.date-format和time-zone配置不然接口返回的日期格式可能不符合前端预期。5.3 并发交卷和超时自动交卷的冲突考试平台在毕设答辩场景下并发量不高但代码逻辑上还是要处理极端情况学生在最后几秒手动交卷同时后台定时任务也发现这条记录超时了两个操作同时去修改考试记录状态就可能导致状态混乱。我的处理方式很直接修改考试记录状态时加一个条件判断比如用 MyBatis-Plus 的 UpdateWrapper 设置eq(status, 进行中)这样两个操作只有一个能成功更新。这其实就是乐观锁思想你不用真去配 Version 也能达到目的但把这个思路写进文档是加分的。5.4 JWT 过期和用户状态异常JWT 默认有个有效期但有时候学生考试时间超过了 token 有效期中途就会因为 token 过期被拦截器踢回登录页。这个问题在考试类系统里很现实。解决方法有两个方向一个是把 token 有效期设置得比最长考试时长更长简单但不够优雅另一个是后端放宽考试相关接口的拦截比如只校验 token 是否存在不校验是否过期。我还是建议做成“刷新 token”机制拦截器发现 token 快过期时在响应头里返回一个新 token前端拦截器检测到后替换本地存储。这种实现有助于你理解 token 的工作原理放在简历上也是亮点。5.5 源码学习建议拿到一套源码应该怎么看这套平台源码在我手里但我还是要提醒你拿到源码不是跑起来就完事了你要能讲清楚每一块是干什么的。我的建议是先跑起来看整体功能再追核心链路。整体功能就是注册登录、创建试卷、参加考试、查看成绩这一条线走通。核心链路是指你从点击“开始考试”这个按钮开始前端发送什么请求、后端哪个 controller 接收、调用了哪些 service、查了哪些表、返回了什么数据这整条链路追踪下来你对项目的理解就基本到位了。然后再去改一些细节比如把多选题判分规则改成“漏选不得分”改完跑测试验证这种小改造会让源码真正变成你的东西。6. 部署上线和扩展方向6.1 本地打包和部署项目开发完最终要能打包部署。前端在项目根目录执行npm run build会在 dist 目录生成静态文件后端用 Maven 打包执行mvn clean package -DskipTests生成一个 jar 文件。如果你有服务器把 dist 里的文件交给 Nginx 托管把 jar 用java -jar运行然后 Nginx 里配置一个反向代理把/api路径转发到后端端口就完成了最简单的部署。有些同学可能觉得部署是“加分项”不想做但我建议至少自己在本地用前后端分离方式跑通一次因为答辩演示时你可能要用到部署后的系统。6.2 在线考试平台的合理扩展方向毕设做完不是终点如果你想把它继续发展成一个更有竞争力的项目可以往三个方向扩展。第一个是统计分析当前成绩分析只是简单汇总可以加 ECharts把分数分布、科目对比、班级对比做成图表视觉效果立刻提升也显得更贴合“智慧教学”概念。第二个是导入导出用 EasyExcel 或者 POI 把题库批量导入 Excel把考试成绩导出成 Excel这是企业里非常现实的需求顺手也能在简历里写“熟悉 EasyExcel 的操作”。第三个是消息通知考试创建后自动通知学生、成绩发布后通知学生用 WebSocket 或者简单的站内信功能都可以实现。6.3 文档和答辩资料准备别忽略软实力最后提醒一句源码和项目本身只是一个部分毕业设计还有文档和答辩这些环节。写文档的时候重点把数据库设计的 E-R 图、核心业务流程的时序图、接口设计的表格放进去图多字少是技巧。答辩的时候不要全程念 PPT而是先现场演示系统功能演示完再讲技术点老师一般会对你实现过程中遇到过的难点更感兴趣。你在 JWT 认证、自动组卷、自动判分、断点续考这几个点上的经验就是最好的答辩素材。我个人在实际带项目的过程中最深的体会是这类 SpringBoot Vue 的全栈项目真正难的不是某一个单独的技术而是把前后端两套体系串起来时出现的各种“连接性”问题。你只要完整走完一遍组卷、考试、判分这条链路踩过一遍这些坑后面再遇到其他全栈项目基本都能触类旁通。如果时间充裕建议在跑通源码之后自己动手从零写一遍后端核心接口哪怕只是照着源码的流程仿写收获都会非常大。
返回列表