在线考试系统源码实战:从数据库设计到自动判分避坑指南

📅 发布时间:2026/9/23 14:20:30
在线考试系统源码实战:从数据库设计到自动判分避坑指南
简介这份在线考试管理系统源代码基于Java技术开发面向需要完成课程设计或毕业设计的初学者与开发者可解决传统考试流程繁琐、成绩统计耗时等问题。系统覆盖试题库管理、智能组卷、在线答题、成绩统计与权限控制等环节代码应用了分层架构、依赖注入、对象关系映射、请求处理、动态页面、数据库设计、SQL优化、安全加密与防注入等多项技术并将业务逻辑与界面展示解耦完整呈现软件工程流程。压缩包共125个文件大小约1.06MB包含动态页面、静态页面、演示动图、依赖库、配置文件、编译字节码、数据库文件及部署包等其中动图可直观演示操作数据库文件存储试题与用户信息。目前已有1369人学习下载适合作为毕业设计、课程设计或Java Web入门实践的参考对理解工程结构、权限控制与数据库交互有直接帮助。1. 这套系统到底是什么别把它当成又一个 CRUD 作业在线考试管理系统在 Java 项目里出现频率极高不是因为技术多新而是它天然覆盖了一门 Web 开发课该有的全部要素用户角色权限、核心业务状态机、复杂查询、并发控制、事务边界。你拿到的源代码如果是单体 JSP/Servlet 版本重点看 Session 管理和 JDBC 封装如果是 Spring Boot Vue 版本重点看前后端分离下的接口设计和 Token 鉴权。这套系统的业务主链路非常清晰——管理员出卷、考生答题、系统判分、成绩归档——一条链路走完基本把 Java Web 开发的主干技术过了一遍。适合两类人正在找课程设计/毕业设计题目的在校生以及公司内部要做培训考核、不想买 SaaS 产品的一线开发。前者关心代码结构和文档后者关心能不能改、好不好部署。下面我按最常见的 SSM/Spring Boot 单体架构来讲这也是大多数源码包的默认形态。2. 技术选型和系统拆分先看懂骨架再动手改代码2.1 为什么绝大多数源码选 SSM 或 Spring Boot 单体而不是微服务你下载的源码十有八九是 Spring Boot MyBatis/MyBatis-Plus或者老一点的 SSMSpring Spring MVC MyBatis。选单体是合理的这套系统的并发量根本到不了需要微服务的程度。就算一个学校几千人同时在线考试单体应用加个 Redis 缓存和数据库连接池调优就够用了。微服务在这里只会增加无意义的复杂度——你还要处理服务发现、配置中心、分布式事务而这些对考试系统没有任何业务收益。Spring Boot 相对 SSM 的最大优势是零 XML 配置内嵌 Tomcatjava -jar 就能跑这对学生党部署和演示是实打实的省事。MyBatis 在这类系统里的占比高于 JPA原因是考试系统的 SQL 普遍偏复杂多表联查试卷表、试题表、考试记录表、动态条件按题型、难度、知识点筛选、批量插入批量导入试题、批量提交答案。MyBatis 的 XML 映射文件写动态 SQL 非常顺手而 JPA 在这种场景下要么写 Query 注解里拼接 JPQL要么用 Specification可读性反而差。如果你是拿源码改优先确认持久层用的是 MyBatis 还是 MyBatis-Plus——后者自带 BaseMapper单表 CRUD 不用写 SQL能省不少事。2.2 标准模块划分七个模块一条考试主流程考试系统无论前端界面长什么样后端模块基本跑不出下面这张表。拿到源码第一步不是跑起来而是对照这张表找到对应代码位置。模块核心实体关键技术点典型代码位置用户认证User, RoleSession/Token、MD5/BCrypt、拦截器controller/LoginController, interceptor/题库管理Question, QuestionType批量导入、多条件查询、题目去重controller/QuestionController, service/试卷管理Paper, PaperQuestion手工组卷/随机组卷、题目分值分配controller/PaperController, service/impl/在线考试ExamRecord, ExamAnswer倒计时、答案暂存、交卷事务controller/ExamController, service/自动判分无独立实体客观题比对、主观题人工评分service/GradeService成绩管理Score, GradeStatistic成绩导出、及格率统计controller/ScoreController系统管理Menu, Log菜单权限、操作日志controller/SystemController拿到源码后按这张表去定位代码能节省至少两小时的摸索时间。判分逻辑不要在 Controller 里一层层找直接在 service 包下搜 Grade 开头的类最省事。2.3 拿到源码后的第一步快速确认它能不能跑不管源码是从哪里下的先做三件事确认项目能启动再谈改代码。第一看 pom.xmlMaven或 build.gradleGradle里的依赖版本确认 JDK 版本匹配——Spring Boot 2.x 要 JDK 8 或 11Spring Boot 3.x 必须 JDK 17版本不匹配你连编译都过不了。第二找 application.yml 或 application.properties看数据库连接配置连的是 MySQL 还是 PostgreSQL端口是多少Redis 有没有。第三看有没有 sql 目录或 db 目录数据库初始化脚本在不在。这三个确认完项目能不能跑你心里就有数了。# 第一步检查项目依赖和 JDK 版本 cd exam-system cat pom.xml | grep -E (java.version|spring-boot-starter-parent) # 输出示例java.version1.8/java.version说明需要 JDK 8 # 第二步查看数据库配置文件 cat src/main/resources/application.yml | grep -A 10 datasource # 确认 url、username、password 三个关键项 # 第三步确认数据库脚本存在 ls -la sql/ 或者 find . -name *.sql -type f这一步不是玄学是血泪经验。我遇到过不止一次源码包里的 SQL 脚本是空文件或者数据库名和配置文件里对不上导致项目启动成功但登录时报Table doesnt exist。先确认这些比急着点 Run 按钮靠谱得多。3. 数据库设计与初始化考试系统的地基六个关键表不能少3.1 核心表结构从用户到成绩的完整数据链路考试系统的数据库表设计是所有代码的基础。表设计得烂后面写再漂亮的代码也补不回来。最常见的表结构包含六张核心表用户表、角色表、试题表、试卷表、考试记录表、答案表。其中最容易出问题的不是用户表而是试卷表和试题表的关系以及考试记录表和答案表的关系。前者是多对多需要中间表后者是一对多考试记录表作为主表答案表存每题作答明细。下面是最常见的一个建表 SQL 片段对应的是 MySQL 8.x。如果你的源码用的是 5.7把 utf8mb4_0900_ai_ci 改成 utf8mb4_general_ci 就行。-- 用户表 CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role_id bigint(20) NOT NULL COMMENT 角色ID 1管理员 2教师 3学生, status tinyint(4) DEFAULT 1 COMMENT 1启用 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 试卷表 CREATE TABLE exam_paper ( id bigint(20) NOT NULL AUTO_INCREMENT, paper_name varchar(100) NOT NULL COMMENT 试卷名称, subject_id bigint(20) DEFAULT NULL COMMENT 科目ID, total_score int(11) DEFAULT 100, duration_minutes int(11) DEFAULT 60 COMMENT 考试时长分钟, pass_score int(11) DEFAULT 60 COMMENT 及格分, create_by bigint(20) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, status tinyint(4) DEFAULT 0 COMMENT 0草稿 1已发布 2已结束, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试卷表; -- 试卷-试题关联表核心中间表 CREATE TABLE exam_paper_question ( id bigint(20) NOT NULL AUTO_INCREMENT, paper_id bigint(20) NOT NULL COMMENT 试卷ID, question_id bigint(20) NOT NULL COMMENT 试题ID, question_score int(11) NOT NULL COMMENT 该题分值, sort_order int(11) DEFAULT 0 COMMENT 题目顺序, PRIMARY KEY (id), KEY idx_paper_id (paper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试卷题目关联表;3.2 考试记录表和答案表事务与并发控制的关键考试记录表和答案表是这套系统里事务最密集的地方。考生点击交卷按钮的那一刻后端要做的事包括校验考试时间是否超时、计算客观题得分、关联主观题给分、更新考试记录状态、写入每道题的答案明细、更新成绩表。这一串操作必须在一个事务里完成否则可能出现成绩表更新了但答案明细没写入的数据不一致问题。-- 考试记录表 CREATE TABLE exam_record ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_code varchar(32) NOT NULL COMMENT 考试编号UUID, paper_id bigint(20) NOT NULL, student_id bigint(20) NOT NULL COMMENT 考生用户ID, start_time datetime DEFAULT NULL, submit_time datetime DEFAULT NULL, objective_score decimal(5,2) DEFAULT 0.00 COMMENT 客观题得分, subjective_score decimal(5,2) DEFAULT 0.00 COMMENT 主观题得分, total_score decimal(5,2) DEFAULT 0.00, status tinyint(4) DEFAULT 0 COMMENT 0未开始 1考试中 2已交卷 3已判分 4缺考, PRIMARY KEY (id), UNIQUE KEY uk_exam_code (exam_code), KEY idx_paper_student (paper_id, student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试记录表; -- 答题明细表 CREATE TABLE exam_answer_detail ( id bigint(20) NOT NULL AUTO_INCREMENT, record_id bigint(20) NOT NULL COMMENT 考试记录ID, question_id bigint(20) NOT NULL, student_answer text COMMENT 考生作答内容, correct_answer text COMMENT 正确答案冗余存储判分用, is_correct tinyint(4) DEFAULT 0 COMMENT 0错误 1正确主观题为0, score decimal(5,2) DEFAULT 0.00 COMMENT 本题得分, PRIMARY KEY (id), KEY idx_record_id (record_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试答题明细表;考试系统的性能瓶颈几乎都在这两张表上。索引要加在 record_id 上因为判分和成绩查询都以 record_id 为入口。另外exam_answer_detail 里的 correct_answer 字段是冗余存储这在设计上是有意为之——考试结束后如果要改题库正确答案变了已考过的试卷成绩不能被影响所以判分那一刻的正确答案必须快照到明细表里。这个细节如果你要改源码千万别优化掉。3.3 数据库初始化脚本的顺序问题拿到 SQL 脚本后导入顺序很重要。先建库再建表最后插入初始化数据尤其管理员账号。很多源码自带的 SQL 脚本是混在一起的你需要自己理清顺序。如果你用的是 Navicat 或 DataGrip直接执行整个 .sql 文件通常没问题因为脚本顶部一般有 CREATE DATABASE IF NOT EXISTS 语句。但有个高频坑字符集。如果脚本里的中文注释乱码十有八九是连接数据库时的字符集没指定 utf8mb4需要在 JDBC 连接串上加上 characterEncodingutf8mb4。# JDBC 连接串参考application.yml 里的 datasource.url jdbc:mysql://localhost:3306/exam_system?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue参数说明serverTimezone 必须指定MySQL 8.x 默认时区是 UTC不指定的话你存进去的时间和本地时间差 8 小时allowPublicKeyRetrieval 是 MySQL 8.x 用 caching_sha2_password 认证时需要的不加会报 Public Key Retrieval is not allowed 错误。这两个参数是新手最容易卡住的点。4. 核心功能落地登录鉴权、随机组卷和自动判分怎么写4.1 登录鉴权Session 还是 Token源码里两种都有老一点的 JSP/Servlet 版本清一色用 SessionSpring Boot 前后端分离版本基本用 TokenJWT。如果你拿到的是前者登录接口在 LoginServlet 或 LoginController 里逻辑非常简单查用户表 → 比对密码 → 把 user 对象塞进 Session → 跳转到首页。这种做法的缺点是集群部署时 Session 不共享需要引入 Spring Session Redis但对单体应用来说足够了。// 基于 JWT 的登录鉴权核心代码Spring Boot 版 PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 1. 根据用户名查询用户 LambdaQueryWrapperSysUser wrapper new LambdaQueryWrapper(); wrapper.eq(SysUser::getUsername, dto.getUsername()); SysUser user userMapper.selectOne(wrapper); // 2. 用户名不存在直接返回错误 if (user null) { return Result.error(用户名或密码错误); } // 3. BCrypt 密码比对注意不能用 MD5 直接比较 if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } // 4. 检查账号状态 if (user.getStatus() 0) { return Result.error(账号已被禁用请联系管理员); } // 5. 生成 JWT Token有效期 2 小时 String token JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRoleId()); return Result.success(token); }这段代码解决了两个常见问题为什么用 BCrypt 而不是 MD5——MD5 撞库太容易加盐BCrypt 内置随机盐是底线很多网上的老源码还在用 MD5你要改的第一件事就是这个为什么登录失败统一返回用户名或密码错误——避免通过返回信息差异来枚举用户名。4.2 随机组卷从题库抽题的核心算法组卷是这个系统的灵魂功能面试时也常考。随机组卷需求非常简单从指定科目和题型下随机抽取 N 道题每道题分值固定。但实现上有三个边界条件要处理题目数量不够时要提示而不是抽到一半报空指针不同题型要分开查不能一条 SQL 搞定抽题后要校验总分和设定的总分一致。// 随机组卷核心逻辑按题型分组抽题 public boolean generateRandomPaper(PaperDTO dto) { // dto 里包含科目ID、每种题型的数量和分值、试卷总时长 ListPaperQuestion paperQuestions new ArrayList(); int totalScore 0; // 1. 遍历题型配置逐个题型抽题 for (QuestionTypeConfig config : dto.getTypeConfigs()) { // 按题型和科目从题库随机抽取 ListQuestion questions questionMapper.selectRandomByType( dto.getSubjectId(), config.getQuestionType(), config.getCount() ); // 2. 边界校验题库数量不足要抛异常 if (questions.size() config.getCount()) { throw new BizException( 题型[ config.getQuestionTypeName() ]题库数量不足需要[ config.getCount() ]道实际只有[ questions.size() ]道 ); } // 3. 组装试卷-题目关联关系 for (int i 0; i questions.size(); i) { PaperQuestion pq new PaperQuestion(); pq.setPaperId(dto.getPaperId()); pq.setQuestionId(questions.get(i).getId()); pq.setQuestionScore(config.getScore()); pq.setSortOrder(i); paperQuestions.add(pq); totalScore config.getScore(); } } // 4. 总分校验防止组卷后总分不等于 100 if (totalScore ! dto.getTotalScore()) { throw new BizException(组卷总分异常配置总分[ dto.getTotalScore() ]实际总分[ totalScore ]); } // 5. 批量插入关联表 paperQuestionMapper.batchInsert(paperQuestions); return true; }这里的关键不是抽题 SQL而是第 2 步和第 4 步的两个校验。题库数量不足时如果没有提示生成的试卷就会缺题考生交卷时判分逻辑也会出错总分不校验的话会出现考生明明全做对了成绩却是 98 分而不是 100 分的诡异情况。前者是开发阶段必踩的坑后者是对外演示时最容易翻车的细节。SQL 实现上随机抽题的写法也有讲究-- 按题型随机抽取 N 道题MySQL SELECT * FROM exam_question WHERE subject_id #{subjectId} AND question_type #{questionType} ORDER BY RAND() LIMIT #{count}ORDER BY RAND() 在数据量小几千题时没有问题但题库超过十万道时性能会明显下降。高并发内部系统的替代方案是先 SELECT COUNT(*) 拿总数再用随机偏移量查。但对于课程设计级别的源码RAND() 够用了不用过度优化。4.3 自动判分客观题秒判主观题走人工判分逻辑的代码位置在不同源码里差别很大但核心思路大致是交卷时遍历考试记录关联的所有答题明细对选择题、判断题、填空题逐一比对答案。选择题和判断题用 equals 比填空题要考虑学生答案的首尾空格——这个细节踩坑率极高很多学生的答案后面带个空格导致正确答案也被判错。// 客观题自动判分核心逻辑 public void gradeObjectiveQuestions(Long recordId) { // 1. 查询该考试记录的所有答题明细 ListExamAnswerDetail details answerDetailMapper.selectByRecordId(recordId); // 2. 遍历判分 double objectiveScore 0.0; for (ExamAnswerDetail detail : details) { // 跳过主观题简答题/论述题 if (detail.getQuestionType() 3) { // 假设 3主观题 continue; } // 3. 答案比对前先 trim去首尾空格 String studentAnswer detail.getStudentAnswer() null ? : detail.getStudentAnswer().trim(); String correctAnswer detail.getCorrectAnswer() null ? : detail.getCorrectAnswer().trim(); // 4. 比对并打分 boolean isCorrect studentAnswer.equalsIgnoreCase(correctAnswer); detail.setIsCorrect(isCorrect ? 1 : 0); detail.setScore(isCorrect ? detail.getQuestionScore() : 0.0); objectiveScore detail.getScore(); // 5. 标记主观题待人工评分 detail.setIsCorrect(0); // 主观题不判对错只标 0 answerDetailMapper.updateById(detail); } // 6. 更新考试记录表的客观题得分 ExamRecord record examRecordMapper.selectById(recordId); record.setObjectiveScore(objectiveScore); record.setStatus(2); // 已交卷待判分 examRecordMapper.updateById(record); }有几个源码常见的坑比较答案用了 而不是 equals导致字符串内容相同但判错没处理 null考生没作答时 studentAnswer 为 null直接调用 trim() 抛空指针主观题没跳过把文本答案和参考答案用 equals 比对结果全判错。拿到源码后优先检查这三处比通读全部代码更有价值。5. 避坑指南在线考试系统最容易翻车的五个地方5.1 时不时的 404前端路由刷新就白屏现象用前后端分离版本部署时考生在考试页面按 F5 刷新直接变成 404 页面。原因前端是 Vue Router 的 history 模式路由由前端管理但 Nginx 没有配置 try_files 回退到 index.html。解决在 Nginx 的 location / 块里加上如下配置。location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这个配置让所有找不到的后端路径都回退到 index.html由前端路由接管。注意/api 开头的请求不能走这个规则要单独配置 proxy_pass 转发到后端服务。5.2 交卷后成绩一直是 0事务提交顺序惹的祸现象考生交卷后考试记录状态显示已交卷但客观题得分为 0成绩没算出来。原因判分逻辑先更新了考试记录表再逐条更新答题明细表但答题明细更新失败或没有在同一个事务里导致记录表更新了、分数却丢了。解决确认判分的入口方法上有 Transactional 注解并且 Service 层所有对数据库的写操作都在这个事务内。// 正确写法交卷和判分必须在一个事务里 Transactional(rollbackFor Exception.class) public void submitExam(SubmitExamDTO dto) { // 1. 校验考试时间 // 2. 保存答题明细 // 3. 判分 // 4. 更新考试记录状态和分数 }rollbackFor Exception.class 是必须的不然 Spring 默认只在遇到 RuntimeException 时回滚某些自定义异常会导致事务不生效。这是老源码里非常隐蔽的问题。5.3 考试中途 Session 过期答完的题全丢了现象考生答了 40 分钟点提交时提示登录已过期跳回登录页回来发现答题内容全没了。原因Session 默认超时时间是 30 分钟考试时长 60 分钟考生答题中途 Session 就失效了。解决在 application.yml 里调大 Session 超时时间或者改用 JWTToken 有效期拉长到考试时长加缓冲。server: servlet: session: timeout: 120m # 单位是分钟m 不能漏如果用的是 JWT生成 Token 时把过期时间设成当前时间 考试时长 30 分钟缓冲。另外考试页面要做答案自动暂存——每隔 60 秒把已答内容通过 AJAX 发送到后端草稿表防止浏览器崩溃或断电导致答案丢失。这个功能叫防断网答题在源码里一般对应 answer_draft 表或 Redis 缓存。5.4 时间校验客户端时间能改别信它现象考生把电脑系统时间调慢两小时考试倒计时就变多了可以慢慢百度答案。原因倒计时逻辑写在前端用的是 new Date() 读取本地时间。解决倒计时要由后端下发服务器时间前端只做展示和每秒减一交卷时后端再校验一次实际耗时是否超过考试时长。// 后端交卷时校验考试时长 public void checkExamDuration(ExamRecord record) { long startTime record.getStartTime().getTime(); long submitTime System.currentTimeMillis(); int durationMinutes record.getDurationMinutes(); long maxAllowedMinutes durationMinutes 2; // 2 分钟缓冲 if ((submitTime - startTime) maxAllowedMinutes * 60 * 1000) { throw new BizException(考试超时系统已自动提交); } }这里的 2 分钟缓冲不是多余的要考虑网络延迟和考生提交时的正常操作耗时。没有缓冲的话考生 59 分 59 秒点交卷后端一校验超时就报错体验很差。5.5 数据库连接池耗尽一次考试全部卡死现象200 人同时交卷系统直接卡死后台日志报 Connection is not available, request timed out。原因连接池最大连接数配得太小默认 HikariCP 是 10200 个并发请求同时占用连接连接池被占满后续请求全部排队超时。解决估算并发量调大连接池同时优化持有连接的时间。spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000 max-lifetime: 1800000不要无脑调到 100连接数越多数据库压力越大反而更慢。建议按实际并发量估算公式是峰值并发数 × 单请求平均耗时秒 / 数据库平均响应时间秒。内部系统通常会高估30 到 50 足够。6. 部署与验证用一套完整流程确认源码可用再谈二次开发源码能跑通只是第一步真正要上线给考生用还要过一遍部署和验证流程。我一般会在本地先跑通开发环境再切到生产环境配置本文不涉及网络代理相关的内容最后做一次全流程功能回归。这套方法对任何考试系统源码都适用。先说部署参数。生产环境要把 application.yml 里的几个配置改掉数据库密码换成强密码不能用 root 账号连接数据库JWT 密钥换掉源码自带的默认值否则任何人都能伪造 Token日志级别从 DEBUG 改成 INFO考试系统的日志量非常大DEBUG 级别一天能写几个 G 磁盘。代码混淆工具在项目上线前值得考虑尤其是题库和判分逻辑是核心资产时。# 打包命令跳过单元测试 mvn clean package -DskipTests # 启动 Java 程序 java -jar target/exam-system-1.0.0.jar --spring.profiles.activeprod # 查看启动日志确认端口和数据库连接正常 tail -f logs/exam-system.log验证流程我建议按主链路 → 异常链路 → 边界场景三层来测全部过了再放给真实用户。主链路是管理员登录创建试卷、导入试题、发布考试考生登录参加考试、答题交卷教师登录判主观题、发布成绩。异常链路是考生交卷超时、断网重连、重复提交。边界场景是零题量的空试卷、考试时长只有 10 分钟的极限配置、题库只有 1 道题时随机组卷还能不能出卷。# 模拟 200 并发同时交卷的简单压测用 wrk 或 ab ab -n 200 -c 20 -p submit_payload.json -T application/json http://localhost:8080/api/exam/submit压测关注两个指标请求失败率为 0平均响应时间在 2 秒内。如果超时优先看数据库连接池和慢 SQL 日志而不是急着加服务器配置。大部分考试系统卡顿不是硬件不够是 SQL 没走索引。最后一件事是给这套源码做备份。数据库脚本、源码、部署配置、压测结果放在一起打压缩包写一个 README 记录部署步骤。这不是形式主义而是我吃过亏——项目跑了一个学期期中的时候数据库被误删恢复数据花了一天一夜。备份文件别放在 Tomcat 的 webapps 目录下万一被当成静态资源下载就麻烦了放到专门的备份目录权限收紧。如果你拿到的源码里前端路由配置和后端跨域配置对不上——这种情况还挺常见的优先看后端有没有加 CORS 配置没有的话在 Spring Security 或拦截器里加上。希望这篇笔记能帮你在一堆代码里少走点弯路。本文还有配套的精品资源点击获取