十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue在线考试与学习交流系统开发实战指南

SpringBoot+Vue在线考试与学习交流系统开发实战指南 我们先把话放在前面这种“在线考试学习交流”的玩意儿网上搜一圈要么是烂大街的学生管理系统要么是只给前端页面没有后端逻辑的半吊子。之所以拿SpringBootVue这套组合来做不是因为它新而是因为它足够稳、资料足够多更重要的是——对于绝大多数要做毕设或者个人项目的人来说这套技术栈最容易出活踩坑也最容易找到解决方案。这篇文章不打算从零开始贴每一行代码那没有意义。我会把整个系统的骨架、核心功能的实现逻辑、数据库设计的关键点、以及你在实际开发中一定会遇到的坑全部拆开讲清楚。不管你是拿它做课程设计、毕业设计还是想自己搞一个类似的教育类平台练手这篇文章都能让你少走很多弯路。1. 为什么还用SpringBootVue做教育类系统选型逻辑与真实成本先把最核心的问题说清楚为什么是SpringBootVue不是SpringCloud不是Go不是Python说白了这套系统的体量决定了你不需要微服务那套复杂的东西。在线考试和学习交流平台本质上是三个核心场景用户管理、考试流程管理、内容交互。这些场景用单体应用加前后端分离完全能撑住。从后端角度看SpringBoot的价值在于“约定大于配置”。你不需要像传统SSH那样写一堆XML配置一个启动类加上几个注解项目就能跑起来。SpringMVC负责路由和控制层Spring负责业务对象的生命周期管理MyBatis负责数据库操作这一套组合在中小型项目中几乎是黄金搭档。尤其是MyBatis它比JPA更容易控制SQL考试系统中那些复杂的联表查询、条件筛选、分数统计用MyBatis写起来更直观。从前端角度看Vue的价值在于组件化和响应式数据绑定。考试页面需要实时更新答题状态讨论区需要动态渲染用户回复这些用jQuery那套手动操作DOM的方式会写得让人崩溃。Vue的双向绑定和虚拟DOM能让你把精力集中在业务逻辑上而不是花大把时间处理DOM更新。还有一个很现实的原因是招人和参考资料的生态。Java这行有个特点你不一定需要最前沿的技术但你需要遇到问题时能搜到答案。SpringBoot和Vue在社区里的资料密度是其他技术栈没法比的。你随便搜一个报错信息基本上都能在CSDN、掘金或者Stack Overflow上找到对应的解决方案。这一点在实际开发中极其重要尤其是你自己一个人搞项目的时候没有同事可以问只能靠搜索引擎。另外要说一下这套组合在实际运行中的性能表现。在线考试系统的并发量不会特别高一个班可能就几十人同时考试撑死一个年级几百人。MySQL用默认配置就能扛住加上MyBatis的二级缓存完全不用担心性能瓶颈。真正需要注意的反而是索引设计和SQL写法这个后面会专门讲。2. 系统功能模块拆解从需求到数据库表的落地过程2.1 用户体系设计不止是登录注册那么简单用户体系是整个系统的地基这个设计不好后面的考试模块和交流模块都会出问题。这里不是简单地建一张user表就完了而是要仔细思考角色的区分和权限的控制。在这个平台里用户角色至少要分成三种管理员、教师、学生。管理员负责系统配置和全局管理教师负责出题、阅卷、管理课程和班级学生是考试和学习的参与者。从技术层面看这里涉及两个关键点一是用户表的设计二是权限控制的粒度。用户表的字段设计除了常规的id、username、password、email以外我建议一定加上一个role字段用整数类型存比如0代表学生1代表教师2代表管理员。不要用字符串存因为字符串在查询和比较时会增加不必要的开销而且容易写错。密码存储方面千万不要用明文。用BCrypt加密Spring Security里自带这个工具即使数据库泄露了密码也不会被轻易破解。这个是非常基础但又特别容易被忽略的点。权限控制这块JWT是当前最主流的方案。用户登录成功后后端返回一个token前端把它存在localStorage里每次请求都带上这个token后端通过拦截器或过滤器验证token的合法性并取出用户信息。这个方案的好处是无状态服务器不需要保存会话信息方便系统水平扩展。JWT本身有几个需要注意的坑。第一个是密文长度JWT默认使用HS256算法加盐签名这个盐要硬编码在配置文件里不要让前端控制。第二个是token过期时间一般设置成2小时是合理的。考试场景比较特殊如果学生在考试过程中token过期了他的答题数据提交不了这是他非常痛苦的体验。我的建议是考试接口单独做处理只要学生进入考试页面就刷新一次token的有效期。2.2 数据库表结构设计考试表、班级表、成绩表的关系该怎么处理数据库设计直接决定了系统能不能撑住复杂的业务场景。这里我挑几张核心表来讲它们之间如何关联是考试系统能否稳定运行的关键。第一张是exam表。字段包括id、exam_name、course_id、teacher_id、start_time、end_time、duration、status、total_score、pass_score。其中start_time和end_time是用来控制考试时间窗口的duration是用来做倒计时的。第二张是exam_question表。这张表用来关联考试和题目字段包括id、exam_id、question_id、question_order、score。为什么要单独建一张关联表因为一张试卷里可以有不同分值的题目而且同一道题可以被多个考试引用。直接把题目挂在exam表下面的话当你修改一道题目时所有引用这道题的考试都会受到影响这个风险太大了。第三张是student_answer表。这张表存储每个学生在每道题上的作答情况字段包括id、exam_id、student_id、question_id、answer_content、is_correct、score。这张表的查询量比较大特别是考试结束后教师需要查看所有学生的答题详情所以要注意在exam_id和student_id上建立联合索引。第四张是score表。这张表存储学生每场考试的总分字段包括id、exam_id、student_id、total_score、submit_time。为什么要把总分单独拎出来一张表因为如果每次都通过计算student_answer表来获取总分数据量大了以后查询会非常慢而且成绩排名、统计通过率这些操作都需要频繁访问总分数据。班级表class_info和学生表user之间的关联也需要注意。一个学生可能分属多个班级比如跨专业选修课程所以需要一张中间表class_student来维护多对多关系。教师和班级之间也是多对多关系同样需要一张中间表class_teacher。这里特别提示一个问题创建表的时候一定要用utf8mb4字符集不要用utf8。因为utf8在MySQL里其实最多只支持3个字节而学生端如果粘贴一个生僻字或者某些特殊符号比如emoji会直接报错。utf8mb4才是真正的4字节完整Unicode支持这个问题在交流模块中暴露得尤为明显。3. 试卷生成与在线答题判分的核心逻辑最容易写崩的地方3.1 自动组卷策略按规则抽题比手动选题更实用试卷生成是考试系统中第一个真正的技术难点。很多学弟做这个模块时就是简单的做个列表教师手动一题一题挑这种做法虽然在功能上是通的但使用体验很差而且不符合真实的考试场景。我建议实现两种组卷方式一种是手动选题适合小规模测验另一种是按规则自动抽题适合正式考试。自动抽题的核心就是写一个随机抽题算法。可以根据题目的难度系数、题型、所属知识点这几个维度从题库中随机抽取符合数量要求的题目。在MyBatis中实现这个逻辑可以通过ORDER BY RAND()把题目打乱然后LIMIT取出指定数量的记录。但这个方法有个前提就是题库不能太大超过几万道题时性能会下降明显。对于学校内的在线考试平台题目量一般不会超过这个规模所以直接用ORDER BY RAND()是没问题的。抽题的时候还要注意分值分配的问题。假设试卷总分是100分单选题20道每题2分共40分多选题10道每题3分共30分判断题10道每题1分共10分简答题4道每题5分共20分。这个分值比例要在抽题时动态计算避免抽完才发现分数对不上。3.2 在线答题与倒计时前端交互是关键体验在线答题页面的体验直接影响学生的考试状态。这个页面包含几个核心组件题目列表导航、答题区域、倒计时、答题卡状态栏。这里有一个特别关键的细节答题卡状态栏。学生需要在页面顶部或侧边看到一个透明的答题卡上面显示所有题目的编号做过的标记为绿色没做的标记为灰色跳过的标记为黄色。这个功能看似简单但对于考试体验的提升非常明显学生不需要来回翻页来确认自己哪些题没做。倒计时功能建议前端用setInterval实现同时在后端存储一个考试截止时间前端定时向后端同步时间。如果只靠前端控制时间学生可以通过修改浏览器的系统时间来绕过限制这个问题以前出现过不止一次。为了保险起见后端每次收到提交请求时都要校验当前时间是否超出考试截止时间超出的话直接拒绝接收答案。3.3 自动判分逻辑客观题和主观题的分开处理自动判分是考试系统的核心竞争力。客观题单选、多选、判断可以完全自动判分主观题简答、论述则需要教师人工批改。先讲客观题的判分逻辑。单选题最简单学生答案和正确答案完全匹配就得分否则不得分。多选题要复杂一些通常有两种策略严格模式——选项和标准答案一模一样才得分多选、少选、错选都不得分部分得分模式——少选但是不含错误选项时给一半分数或按比例给分。我建议在试卷配置里增加一个字段来控制判分模式这样灵活性更高。判断题为两种答案对错或者正确错误学生答案与标准答案相同即可得分这个没什么好说的。主观题的判分需要教师人工操作在系统里有一个专门的阅卷页面教师可以看到学生的主观题答案然后输入得分可以选择是否添加评语。这里可以探讨一个把多个学生同一道题的答案放到同一张表格里方便教师统一批改。这个设计能显著提高阅卷效率因为教师不需要不停切换用户。判分完成后的总分统计要特别注意事务一致性阅卷整体完成之后系统需要自动汇总成绩并更新到成绩表。这里事务的边界要处理好——如果几个学生同时在这个时间点提交答案绝对不能出现成绩张冠李戴的情况。4. 学习交流模块与信息流设计被忽略的性能隐患学习交流模块是很多类似系统做得最单薄的部分。很多案例里只是简单搞了一个留言板学生能发帖和回复仅此而已。但在真实使用中这个模块往往是用户量最大、数据量增长最快的地方如果设计不好很容易成为性能瓶颈。数据库层面帖子表和回复表的设计是比较常规的重点要注意的是关联查询的性能。在帖子列表页通常需要显示每个帖子的回复数量以及最后回复人的信息和时间。这里搜索出来的查法是使用LEFT JOIN加上GROUP BY查询效率低每次打开列表都要扫描整张帖子表。改进方案是设计一个stats字段来加速parent表在发布新帖时先插入回复数量统计字段在每次有用户回复时递增更新最后一条回复信息也用额外的字段同步存储——用空间换时间。帖子内容的存储也要小心。最常见的是XSS攻击也就是用户在帖子里插入恶意脚本其他用户打开这个帖子时脚本就执行了。后端要对用户的输入做过滤推荐使用Jsoup解析器在存储之前把HTML里的script标签全部去除只保留纯文本和一些基础的格式化标签。另外使用Vue的{{}}插值或v-text指令默认会转义HTML内容但如果你用了v-html就要特别注意过滤问题。5. SpringBoot后端架构中的技术选型问题整理后端架构看起来只是把SpringBoot、MySQL、MyBatis拼在一起但实际开发中会遇到很多细节问题这里挑几个关键点来分析。5.1 拦截器或过滤器不能用错在权限验证上要注意区分Filter过滤器和Interceptor拦截器的适用场景。Filter是基于Servlet的能够在请求进入Servlet之前和响应离开Servlet之后进行处理它拿不到SpringMVC的Handler信息。Interceptor则是SpringMVC内部的可以拿到进入的是哪个Controller的哪个方法。对于这个项目权限验证用拦截器更合适因为拦截器可以拿到访问的URL和HTTP方法可以方便的实现细粒度权限控制。而全局异常处理则用过滤器因为过滤器在处理CORS跨域问题、XSS过滤时更靠前。5.2 MyBatis分页插件和动态SQL分页查询是后端开发中最常见的需求之一帖子的列表页、学生的成绩列表都有了分页。手动编写LIMIT ? OFFSET ?的做法不仅繁琐而且容易在多表关联时出错。推荐使用MyBatis官方推荐的分页插件PageHelper它基于拦截器原理改写SQL使用起来非常方便一行代码就能完成分页查询。PageHelper.startPage(pageNum, pageSize); ListQuestion questions questionMapper.findByExamId(examId); PageInfoQuestion pageInfo new PageInfo(questions);这段代码会自动为查询添加LIMIT语句并且通过PageInfo可以很方便地获取总记录数、总页数等分页信息。动态SQL是MyBatis的另一大杀器。比如在题目列表的筛选查询中需要根据题目类型、难度、所属课程这些可选条件动态拼接SQL。用MyBatis的where和if标签可以轻松解决这个问题不需要为了应对不同条件组合写多个SQL。5.3 事务管理与回滚考试相关操作对数据一致性要求极高典型的场景是学生提交试卷时需要同时写入学生答案表、更新考试状态、累计成绩和更新成绩表。如果其中一个环节失败整个流程都必须回滚绝不能出现“答案为空但是成绩为0”的脏数据。在SpringBoot中使用事务非常简单只要在Service层的方法上添加Transactional注解Spring就会在方法执行前开启事务在方法正常结束后提交在抛异常时自动回滚。不过要注意几个坑。第一个是把事务注解加在Controller上这种写法会导致事务边界过大把不必要的操作也纳入了事务影响性能。第二个是Transactional默认只会回滚RuntimeException如果要让它回滚受检异常需要显式地设置rollbackFor属性。第三个是自调用时事务注解不会生效同一个类内部A方法调用B方法B上的Transactional不会生效解决方法是把事务方法拆分到不同的Service类中。5.4 数据库连接池的配置SpringBoot 2.x默认使用HikariCP作为数据库连接池性能是几大连接池里最好的。但是有一点要注意默认的maximum-pool-size是10对于部署在学校测试机上的小项目这可能不够用。如果在线考试时并发量大建议调整为20或30。spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000另外一个容易被忽视的问题是connection-timeout设置过短。如果数据库偶尔出现慢查询连接池可能需要排队等待空闲连接如果超时时间太短客户端就会直接报连接错误。这个值可以根据实际请求高峰调整到30000毫秒大多数情况下够用了。6. Vue前端工程化与核心页面交互解析6.1 路由权限控制用户不能通过改URL越权Vue Router有一个核心功能叫导航守卫。在项目里我强烈建议在路由配置中为每个页面指定meta元信息标注它需要的权限层级然后在全局前置守卫中做校验。// 全局前置守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(token) const userRole parseInt(localStorage.getItem(role)) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.role to.meta.role ! userRole) { next(/403) } else { next() } })这样做的好处是就算学生手动在浏览器地址栏输入了/admin/users这种管理后台的URL也会被前端的路由守卫拦截根本进不去。虽然后端接口同样需要做权限校验但前端先拦截掉一部分既能减少无效请求也能提升用户体验。6.2 状态管理考试状态、用户信息不能全靠localStorage如果项目规模稍微大一点就不要把所有状态都塞进localStorage里。我建议考试模块的状态放到Vuex或Pinia中来管理因为考试页面中至少有这些状态需要全局共享当前用户信息登录后拉取全局使用当前考试的剩余时间考中状态学生做题的遍历所有题目的当前进度答题卡上每一题的完成状态如果用localStorage存储每次操作都要读取和解析JSON性能开销大不同类型的数据难以统一管理。用Pinia的话可以直接在store中定义state、actions、getters组件之间共享状态不再需要一层层$emit和props。例如答题状态的store可以这样写export const useExamStore defineStore(exam, { state: () ({ examId: null, questions: [], answers: {}, remainingTime: 0 }), actions: { setAnswers(questionId, answer) { this.answers[questionId] answer } } })6.3 组件库选型与页面布局前端页面不推荐从零手写全部CSS效率太低。Element UIVue 2或Element PlusVue 3是当前最成熟的中后台组件库它提供了表格、表单、时间选择器、弹窗等开箱即用的组件能节省至少30%的页面开发时间。页面布局上后台管理端推荐使用侧边栏顶栏的结构。侧边栏放导航菜单顶栏放用户信息和全局搜索。学生端的学习页面建议设计成卡片式信息流一门课程一张卡片点击进去就能看到相关的考试、资料和讨论帖。移动端适配也是一个值得考虑的方面。虽然大部分学校里的使用场景都是电脑但不排除学生用手机和平板浏览学习资料、查看成绩。需要注意的细节是增加viewport标签并且对表格类组件做响应式处理或者在移动端隐藏一些不关键的列。7. 部署上线与源码使用建议7.1 如何从零开始跑通这套源码拿到一套完整源码之后第一步不是急着在IDE里打开而是要按照正确顺序进行环境准备。如果用的Java版本太高或太低Maven依赖就拉不下来SpringBoot版本不同启动方式也会有差异。这里按照最常见的组合来给出一份配置建议组件推荐版本说明JDK1.8 或 11太老的教程多是JDK8如果代码里用了新版API建议升到11Maven3.6用来管理后端依赖Node.js14Vue项目的构建环境MySQL5.7 或 8.0字符集选utf8mb4前端构建npm vue-cli或vite根据项目实际包结构选择后端启动步骤很简单先建立数据库把项目里提供的SQL脚本导入MySQL然后修改application.yml中的数据库账号密码最后运行主类上的main方法即可。Vue前端需要单独启动进入前端目录执行npm install安装依赖然后npm run dev启动开发模式的服务器。这里要注意后端接口的跨域配置比较方便的做法是使用Vue的代理服务器方式配置在vue.config.js里面module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端访问/api/user/login时代理会把请求转发到后端地址localhost:8080上规避了浏览器跨域的限制。7.2 部署到Linux服务器上的关键点开发环境跑通只是第一步最终还要部署到服务器上这个问题在实际交付时经常出问题。后端打包使用Maven的package命令会生成一个可执行的jar文件。上传到服务器之后用nohup java -jar xxx.jar log.log 21 命令在后台启动即可。注意生产环境的数据库连接不要再用root账号而是建立一个专用账号并限制IP访问范围。前端部署最简单的方式是把构建产物就是dist目录中的文件交给Nginx托管然后配置反向代理把/api的请求转发到后端端口server { listen 80; server_name your-domain.com; root /opt/app/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; } location / { try_files $uri $uri/ /index.html; } }try_files这一步非常关键因为Vue使用History模式的路由后前端页面刷新时如果直接请求服务器的某个路径Nginx会去查找这个路径对应的文件找不到就会404。加上try_files $uri $uri/ /index.html之后Nginx会在找不到文件时回退到index.html由Vue接管路由这个问题就解决了。8. 线上踩坑实录五个让你半夜改代码的隐藏问题上面讲了架构和实现的大方向接下来分享几个我在实际开发和学生代码评审中遇到的隐藏问题这些问题教科书上很少会写但遇上一个就能折腾掉半天时间。8.1 MySQL的SQL Mode问题某次有一个学弟的判分逻辑怎么跑都不对后端不报错前端显示的成绩和实际算出来的成绩不一致。排查到晚上才发现是MySQL的ONLY_FULL_GROUP_BY模式在作祟。MySQL 5.7以上默认开启ONLY_FULL_GROUP_BY这意味着如果在SQL中使用GROUP BYSELECT中出现的非聚合字段必须也出现在GROUP BY后面。一旦查询里写了GROUP BY e.id但SELECT了s.student_id就会报错或返回异常数据。解决方案是在连接字符串中加一个参数或者在MySQL配置文件里关闭这个模式spring: datasource: url: jdbc:mysql://localhost:3306/exam_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaisessionVariablessql_modeSTRICT_TRANS_TABLES8.2 前端接口返回的时间差问题数据库里使用的是datetime类型而Java端使用LocalDateTime接收JSON序列化后默认是带T的ISO格式类似于2024-05-20T14:30:00。前端拿到这样的字符串直接展示体验非常不友好。而且跨时区时还要注意如果服务器设置的时区和中国标准时间不一致所有时间都会显示为偏差8小时。在两极个月前一份代码里服务器部署在香港机房时区设成了UTC结果所有考试时间都显示成凌晨学生常说完全没办法安排。解决方法是统一时区设置后端在application.yml中配置spring.jackson.time-zoneGMT8同时前端用dayjs或moment对时间串做格式化。8.3 前端数据响应式丢失数组和对象的坑Vue的双向绑定并不是所有数据操作都是响应式的尤其是以下几种情况// 通过索引修改数组元素直接赋值的方式不会触发更新 this.answers[index] A // 新增一个不存在的对象属性 this.examInfo.newField xxx这两种操作在Vue 2里不会触发视图更新数据变了但页面不重新渲染学生答题时选中一个选项但界面上的高亮颜色不变化。解决方案有两个一是使用this.$set()方法二是从设计上一开始就把所有要用到的字段在data()中声明好不依赖运行时动态添加。Vue 3使用Proxy重构了响应式系统这个坑没有那么明显了但使用reactive时仍需要注意不能直接使用解构赋值否则同样会丢失响应式。8.4 文件上传大小限制如果学习交流模块里允许用户上传图片或附件那么后端一定要修改SpringBoot的默认上传大小限制。默认值只有1MB一张手机照片都传不上去。spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB同时Nginx这边也要配置client_max_body_size否则走到Nginx层就会被拦下来。另外提示一下上传目录要使用服务器上的绝对路径不要放到项目打包后的临时目录里那个目录会在重启时自动清理文件到时候用户上传的头像和作业全部会消失。8.5 MyBatis的#{ }和${ }区别不能搞错这是我在代码评审时几乎每次都会揪出来的问题。#{ }是预处理占位符MyBatis会把它编译成?使用PreparedStatement执行能有效防止SQL注入${ }是字符串拼接直接替换SQL语句中的内容存在注入风险。正确的做法是值条件一律使用#{ }只有表名、排序字段这种不能参数化的场景才用${ }而且要确保传入的字段在服务端进行合法值校验避免拼入恶意内容。9. 这套系统后续可以如何扩展如果一个项目做完上线了就算结束那进步速度就太慢了。基于这套架构有几个方向可以继续扩展难度依次递增根据你的时间来选择。第一个方向是引入Redis做缓存。目前题库和帖子列表的查询频率很高如果每次都打MySQL压力会越来越大。把热点数据缓存到Redis中查询速度能快上一个数量级。考试会话也可以并存Redis来管理学生进入考试时生成一个带有效期的会话缓存有效期设置为考试时长加上缓冲时间过期自动清理。第二个方向是增加数据可视化功能。成绩发布后管理员和教师需要看一眼全局统计信息比如各班平均分、分数段分布、知识点掌握情况。前端集成一个图表库比如ECharts后端提供统计接口也就能得到一组非常直观的分析结果。这个模块做出来后整个系统的档次会提升不少。第三个方向是使用WebSocket实现实时通知和在线监考能力。WebSocket能实现双端实时通信学生进入考场时页面和服务器维持长连接教师端可以实时看到在场状态学生提交试卷后立即通知教师。另外还可以做一个低频的屏幕截图或摄像头抓拍如果学校允许的话应对线上考试防作弊的需求。从技术难度来说SpringBoot整合WebSocket是成熟方案前端用new WebSocket()原生API也能直接实现。第四个方向是引入消息队列。目前系统规模不必上Kafka或RabbitMQ但如果你打算把整个平台做成全校通用考试高峰期会有大量并发提交试卷的场景——每个学生提交试卷时需要写几十上百条答案记录。消息队列可以把这些写操作削峰填谷让系统在极端流量下依然平稳运行。不过这个方向如果只是毕业设计就不推荐做了复杂度会增加很多对评审老师来说也不一定能体现出更多价值。说回这套系统本身。技术栈都是基础款没有高深莫测的算法也没有多精妙的架构但它覆盖了一个真实业务系统的完整链路用户、权限、内容、流程、统计。把任何一个模块做精都能写出一篇很扎实的项目总结。在线考试系统最有价值的地方不在于技术上的不可替代性而在于你通过这个项目完整经历了一遍“从需求分析到数据库设计再到前后端联调和部署上线”的全过程。至于那份源码拿到手后不要急着改业务逻辑先把核心表结构和几个关键接口的调用链路走通再考虑往里面加自己的东西。别人写的代码未必最优但往往有它自己的逻辑习惯磨合清楚后再动手你会节省掉大量改bug的时间。
返回列表