news 2026/10/16 6:50:31

SpringBoot+Vue+MySQL编程训练系统设计:从判题到权限控制的全栈实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue+MySQL编程训练系统设计:从判题到权限控制的全栈实战

最近连续好几个学弟学妹问我毕设选题的事,聊下来发现一个共同点:都想做一个“看起来有技术含量、工作量适中、又不至于烂大街”的系统。编程训练管理平台恰好是这类项目里最稳的选择之一——它既有用户管理、权限控制这类常规业务,又有代码提交、自动判题这种能体现技术深度的模块,而且前后端分离的架构天然适合写进简历。这篇文章就把基于 SpringBoot + Vue + MySQL 的编程训练系统管理平台从需求设计、数据库建模、后端接口、前端联调到部署上线的完整链路拆开讲一遍,尤其是判题模块和权限控制这两块,很多教程都一笔带过,我这边把关键逻辑和踩过的坑都说清楚。

这套系统适合三类人参考:一是拿它当毕设或课设的在校学生,二是想完整走一遍全栈项目流程的初学者,三是需要给学校或培训机构搭一个轻型刷题平台的开发者。无论你属于哪一类,核心思路都是一样的——先把业务边界划清楚,再把数据模型建稳,最后才是堆功能。

1. 项目整体设计与技术选型思路

1.1 需求定位:这套系统到底解决什么问题

编程训练系统说白了就是一个让学习者刷题、让老师布置作业、让管理员管理资源的在线平台。市面上的开源OJ(在线判题系统)很多,但大部分功能冗余、部署复杂,不适合拿来当课程设计级别的项目。自己做一套轻量化的训练平台,重点不是做得多大,而是把链路走通:学生登录 → 选择题目 → 编写代码 → 提交运行 → 系统判题 → 返回结果 → 老师查看统计数据。

这个闭环如果完整实现了,项目的深度就有了。我在实际做的时候把系统拆成三个端:管理员端负责用户管理、题目审核、公告发布;教师端负责题目创建、训练任务布置、查看学生提交情况;学生端负责刷题、提交代码、查看判题结果和个人统计。

三个角色的权限边界是这套系统的灵魂,如果这里不清晰,后面的接口设计一定会乱。我的做法是在设计阶段就画清楚一个矩阵:哪些操作是管理员独有,哪些是教师可用,哪些是学生只读。比如题目表的设计就要区分“草稿”和“已发布”状态,老师创建的题目必须先经过管理员审核或者老师自己确认发布,学生只能看到已发布的题目。

1.2 技术栈选型:为什么是 SpringBoot + Vue + MySQL

这个组合几乎是目前国内Java全栈学习路线的事实标准,选它不是为了跟风,而是它确实在“学习成本”和“生产可用”之间找到了最佳平衡点。

SpringBoot负责后端,核心优势是自动配置和生态成熟。做这种管理系统,Spring Data JPA或者MyBatis-Plus都能快速搞定数据访问层,Spring Security配合JWT做认证授权,跑题不大。我实际用的是MyBatis-Plus,理由只有一个字:快。分页插件、代码生成器都是现成的,做毕设最怕时间耗在重复的CRUD上,用它能省下一大半时间。

Vue负责前端,我用的是Vue 2 + Element UI的组合。虽然现在Vue 3 + Element Plus已经很成熟了,但考虑到很多课设要求里老师自己更熟悉Vue 2的生态,而且网上的现成案例、组件库示例更多,遇到问题容易搜到答案。如果你是从零开始学,选Vue 3配Element Plus也完全没问题,底层思路都一样,只是API写法差异。

MySQL做数据存储是标准操作。这里我特别强调一个点:表结构设计一定要先于编码。很多人上来就写代码,写到一半发现数据表字段不够用再回头改,这种返工最浪费时间。我后面会给出完整的建表SQL,照着拆就能理解整个业务的数据流转。

2. 核心功能模块拆解与数据库建模

2.1 场景驱动:从用户故事倒推功能清单

做管理平台不能一上来就想着“我要做哪些页面”,而是要问“用户进来要完成什么动作”。我列了十几个核心场景,每个场景对应一个或一组接口:

  • 学生注册登录后,能看到训练列表和题目列表,点进某道题能查看题目描述、难度标签和样例输入输出。
  • 学生在代码编辑器里写完代码,选择语言提交,系统编译运行并返回结果,结果状态分为:通过、答案错误、编译错误、运行超时、内存超限。
  • 教师创建题目时,需要配置题目描述、输入输出样例、测试点数据和判题脚本。测试点数据在课程设计阶段可以用简单文本比对实现,不需要上沙箱容器。
  • 教师发布一场训练或考试,设定起止时间,学生只能在时间窗口内提交。
  • 管理员查看整体数据统计,包括活跃用户数、提交次数、题目通过率等。

这五个场景基本覆盖了系统的核心价值。剩下的什么公告、站内信、个人中心都属于锦上添花,建议放到第二阶段再做,先保证主线闭环跑通。

2.2 数据库表结构设计与关键字段说明

数据库我设计了一共十张表,核心的七张表是:用户表、角色表、用户角色关联表、题目表、提交记录表、训练任务表、训练题目关联表。零散的还有公告表和分类表。

先看用户表,我故意把用户名和密码拆开,密码存储用的是BCrypt加密,绝对不要存明文,这一点在答辩时经常被老师问到。

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密码', nickname VARCHAR(50) COMMENT '昵称', email VARCHAR(100), avatar VARCHAR(255), status TINYINT DEFAULT 1 COMMENT '1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );

角色表不需要多解释,就三个角色编码:ADMIN、TEACHER、STUDENT。用户角色关联表是一张中间表,用来支持一个用户多角色的可能,虽然目前业务上不需要,但保留扩展性。

题目表是核心中的核心,字段设计直接决定后续功能好不好写:

CREATE TABLE exercise ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL COMMENT '题目标题', description TEXT COMMENT '题目描述', input_desc TEXT COMMENT '输入说明', output_desc TEXT COMMENT '输出说明', sample_input TEXT COMMENT '样例输入', sample_output TEXT COMMENT '样例输出', difficulty TINYINT COMMENT '1简单 2中等 3困难', type VARCHAR(20) COMMENT '题目类型:编程题/选择题', judge_type VARCHAR(20) DEFAULT 'text' COMMENT '判题方式:text精确匹配/hidden隐藏测试点', test_case_url VARCHAR(255) COMMENT '测试数据文件路径', creator_id BIGINT COMMENT '创建人ID', status TINYINT DEFAULT 0 COMMENT '0草稿 1已发布 2下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );

提交记录表是整个系统数据量增长最快的表,一定要给关键字段加索引,否则数据量上来之后查询会越来越慢:

CREATE TABLE submission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, exercise_id BIGINT NOT NULL, language VARCHAR(20) COMMENT 'JAVA/PYTHON/C/CPP', code TEXT COMMENT '提交的源码', status TINYINT COMMENT '0待判 1通过 2答案错误 3编译错误 4运行超时 5内存超限 6系统错误', execute_time INT COMMENT '运行耗时ms', execute_memory INT COMMENT '运行内存KB', judge_detail TEXT COMMENT '判题详细结果', submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_exercise (exercise_id), INDEX idx_status (status) );

训练任务表设计时要注意,一个训练包含多道题,题目和训练是多对多关系,所以需要训练题目关联表。训练本身有起止时间、参与班级或面向所有用户、发布状态这几个核心字段。

这套表结构够用在课程设计答辩时讲很久了,因为每张表都能对应到一个功能点,每个字段都能解释出“为什么这样设计”。当初我把训练统计表单独拆出来,就是考虑到参赛要展示“排行榜、正确率、完成人数”这些聚合数据,单独建表做定时统计比每次都现算SQL高效得多。

3. 后端核心实现与关键代码解析

3.1 统一响应结构与全局异常处理

后端项目的第一个基础工程是统一响应体。我见过很多半成品项目的接口返回格式五花八门,有的返回Map,有的直接返回实体类,导致前端解析异常时根本无从下手。我的做法是定义一个Result泛型类,所有接口统一返回这个结构:

public class Result<T> { private Integer code; // 200成功,其他为失败 private String message; // 提示信息 private T data; // 业务数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

配合@RestControllerAdvice做全局异常处理,这样Controller层就不用每个方法都写try-catch了,代码干净很多。

3.2 JWT认证与权限控制

认证方案我选择JWT,原理很简单:用户登录成功后,后端生成一个包含用户ID和角色信息的签名令牌返回给前端,前端存在本地,每次请求通过Authorization请求头带上,后端用一个拦截器验证令牌合法性并从令牌中解析用户身份。这样做的好处是服务端不需要保存会话状态,对前后端分离架构特别友好。

这里有一个我在实际项目中踩过的坑:JWT Token不能放用户密码等敏感信息,因为Token虽然签名不可篡改,但载荷部分是Base64编码的,任何人都能解码看到内容。我见过有人把明文密码放进Token的,这是非常危险的做法。

权限控制我用Spring Security + 自定义过滤器实现。核心逻辑是在过滤器中解析出用户角色,然后用注解@PreAuthorize("hasRole('TEACHER')")标注在需要教师权限的接口上。这里要注意:Spring Security的角色判断默认会加上ROLE_前缀,自定义角色编码时要区分好,不然会出现明明用户是TEACHER却始终没有权限的问题。

3.3 判题模块的实现思路

判题模块是整套系统里最容易被问“你是怎么实现的”的部分,也是区分项目含金量的关键。

课程设计级别的判题没必要引入Docker沙箱,那套方案涉及容器隔离、资源限制,工作量大且部署复杂。我的实现分两步:

第一步是代码保存与编译。后端收到提交请求后,把源码保存到服务器指定目录,文件名为submission_id.扩展名,然后调用本机JDK或GCC的编译命令。这里必须用ProcessBuilder,不能直接用Runtime.exec,因为前者能更好地控制工作目录和环境变量。

第二步是运行测试用例。对于公开样例,直接把样例输入写入输入文件,程序运行后读取标准输出,和期望输出做文本比对。文本比对有一个经验:一定要先忽略行尾空格和末尾换行,否则正常能过的代码会因为多一个空格被判错。我当时用了一个简单的归一化函数,去掉所有空白字符再比对,虽然宽松了点,但课程设计阶段完全够用。

判题时还要设置超时控制,否则提交一个死循环代码会直接把服务器拖垮。我用Future+ 超时机制的思路:把运行任务提交到线程池,通过get(timeout)控制等待时间,超时就强制杀掉进程并把状态置为运行超时。

下面这段是判题运行的核心逻辑骨架:

public JudgeResult runTestCase(String compileDir, String testInput) { JudgeResult result = new JudgeResult(); ExecutorService executor = Executors.newSingleThreadExecutor(); Future<RunOutcome> future = executor.submit(() -> { ProcessBuilder pb = new ProcessBuilder("java", "-cp", compileDir, "Main"); pb.redirectErrorStream(false); Process process = pb.start(); try (OutputStream os = process.getOutputStream()) { os.write(testInput.getBytes(StandardCharsets.UTF_8)); } // ... 读取输出 process.waitFor(); return outcome; }); try { RunOutcome outcome = future.get(5, TimeUnit.SECONDS); result.setOutput(outcome.getOutput()); result.setTimeUsed(outcome.getTimeUsed()); } catch (TimeoutException e) { future.cancel(true); result.setStatus(4); // 运行超时 } return result; }

这里有个容易忽略的细节:给子进程发送输入后一定要关闭OutputStream,否则子进程读不到EOF会一直阻塞。很多人在这一步卡了半天,就是因为输入流没关闭。

4. 前端Vue实现与前后端联调实录

4.1 前端路由与状态管理设计

前端我用Vue Router做页面路由,分为静态路由和动态路由两部分。静态路由是登录页、注册页、首页这种所有用户都能访问的;动态路由是在用户登录后根据角色动态添加的,比如管理员的用户管理页面,学生的训练列表页面,教师的题目管理页面。

状态管理用了Vuex,主要存三类数据:用户登录信息、角色权限标识、当前训练或答题的临时状态。有一个教训是:不要把大段的题目内容或代码提交内容放进Vuex,页面刷新后Vuex数据会清空,会造成体验问题。正确做法是刷新后从后端重新拉取,所以接口设计时提交记录和题目详情必须支持按ID查询。

4.2 代码编辑器接入与判题结果展示

前端最出效果的地方是代码编辑器。我接入的是CodeMirror,相比Monaco Editor(VS Code的内核),CodeMirror体积小、集成简单、对Vue2的兼容好,做课程设计完全够用。接好之后用户可以选择语言(Java/Python/C++),编辑器会根据语言切换高亮语法。

判题结果展示我用了一个结果状态组件,把后端返回的状态码映射成不同的颜色和文案——通过用绿色,编译错误用橙色,运行超时用红色。同时展示运行耗时和内存,旁边放一个“查看详情”的抽屉,把判题过程中的错误堆栈或者输出内容展示出来。这个交互细节在答辩演示的时候很加分,因为直观地展示了“平台真的在判题”而不是假数据。

Axios请求封装同样是重点。我统一封装了一个request模块,在请求拦截器里添加Token,在响应拦截器里统一处理401跳转登录页、500弹错误提示。代码不长但非常关键:

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => Promise.reject(error)) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { MessageBox.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response.status === 401) { router.push('/login') } return Promise.reject(error) } )

4.3 前后端联调踩过的坑

前后端联调是我整个开发过程中耗时最长也最烦的阶段,因为很多问题只有两边真正连起来才暴露。我在这里总结三个高频问题,提前知道能省不少时间。

第一个是跨域问题。开发环境下我用脚手架的代理功能解决,在vue.config.js里配置devServer.proxy把/api前缀的请求转发到后端端口。上线部署阶段,我用Nginx把/apilocation 反向代理到后端Java进程,前端只负责静态资源,这样生产环境也没有跨域问题。后端同时开启CORS配置兜底,但原则上能不用跨域就不用跨域。

第二个是时间格式化问题。Java后端返回的LocalDateTime默认序列化后是2025-01-08T12:30:00这种带T的格式,前端直接展示很难看。解决办法是在后端统一配置JSON序列化格式,把日期格式化为yyyy-MM-dd HH:mm:ss。这个问题虽然小,但在提交记录列表、公告列表这些涉及时间显示的地方到处都会出现,统一处理一次省心很多。

第三个是Long类型精度丢失。后端表主键用的BIGINT,转成JSON后超过JavaScript安全整数范围的数字会被截断,导致前端拿到的ID和实际不一致。解决办法是在实体主键字段上加@JsonSerialize(using = ToStringSerializer.class)把ID转成字符串返回,或者在全局配置里处理。这个问题不遇到一次根本想不到,但一旦遇到就很折腾,我第一次联调时查了快两小时才发现是精度问题。

5. 部署上线、常见问题与毕设答辩建议

5.1 本地开发到服务器部署的完整流程

项目完成后的部署同样是老师爱问的环节。我把流程梳理成了四步,照着操作基本不会出问题。

第一步是准备环境。服务器安装JDK 8或11、MySQL 5.7或8.0。注意MySQL 8的密码加密方式默认是caching_sha2_password,老版本的JDBC驱动会连不上,需要在MySQL里调整加密方式,或者直接用新版JDBC驱动。

第二步是初始化数据库。把建表SQL在服务器MySQL里执行一遍,还要注意修改数据库连接的时区配置。我遇到过一个问题:本地连数据库一切正常,服务器上一直报The server time zone value is unrecognized,最后发现是数据库连接URL少加serverTimezone=Asia/Shanghai参数。

第三步是后端打包运行。使用Maven的package命令打成Jar包,然后用nohup java -jar xxx.jar > app.log 2>&1 &后台启动。每个环境多多少少有点不一致,所以强烈建议把数据库连接、文件上传路径这些配置放在application-prod.yml里,用spring.profiles.active=prod启动时指定。

第四步是前端构建。执行npm run build生成dist目录,然后配置Nginx把root指向dist目录,把/api反向代理到http://127.0.0.1:8080。这里有一个注意点:前端路由如果用了history模式,刷新会出现404,需要Nginx配置try_files $uri $uri/ /index.html;来做前端路由兜底。

5.2 常见问题速查表

我把自己开发过程中遇到并解决的典型问题整理成了表格,按“现象—原因—解决”的方式列出,方便以后排查:

现象可能原因解决方式
登录接口报401,Token无效前端没在请求头携带Token在Axios请求拦截器统一添加Authorization头
有权限但接口仍然拒绝访问Security权限校验顺序问题,匿名请求被拦截检查配置类中放行路径是否覆盖了登录和注册接口
提交代码后编译错误服务器未安装JDK/GCC安装对应编译环境,并确认PATH配置正确
判题结果一直显示待判中判题模块异步线程池或时间太长没返回检查是否调用了判题服务,查看日志确认线程执行情况
前端请求接口跨域端口不一致或未配置代理开发环境用脚手架代理,生产环境用Nginx反代
数据库连接超时空闲连接被MySQL主动关闭Druid连接池配置空闲回收策略,或降低wait超时时间
分页数据不对MyBatis-Plus分页插件未注册在MyBatis-Plus配置中增加分页插件拦截器,注意版本兼容

排查问题一定要养成看日志的习惯。后端启动后用tail -f app.log跟踪日志输出,前端浏览器F12看重网络请求的响应体和状态码,问题定位能快一半以上。

5.3 给准备拿这套系统做毕设的同学几个实操建议

第一,工作量要提前规划。核心五个功能模块(用户角色、题目管理、训练任务、代码提交、判题结果)往往就能满足答辩要求,不要一上来就想做得又多又全。功能多少不是关键,主线闭环是否通了才是重点,把一个模块做扎实比十个模块都半吊子更有价值。

第二,答辩演示前一定要准备预演数据。题目列表要有几道不同难度的题,提交记录要有状态为通过、答案错误、编译错误的不同样例,这样演示时不会出现冷场。我在真实项目演示时,一般是提前准备好两道题:一道简单题现场秒过,一道中等题故意留个常见错误现场演示失败,这样反而能展示判题的真实性。

第三,关于代码审查我有个经验:毕设查重和老师问问题的时候,重点会放在你对核心代码的解释能力上。建议把Spring Security认证过滤器、判题模块子进程调度这两块代码反复读几遍,确保能清楚地讲出每一步在做什么、为什么这么做,这比能背出一万个技术名词有用得多。

6. 这套系统后续还能怎么扩展

项目做完交付了,不代表思路就结束了。从学习和展示的角度,这套系统的扩展方向其实非常多,而且每个方向都对应一个可以往简历上写的技能点。

最实用的扩展是把判题模块升级成Docker容器隔离方案。基于容器的隔离是真实生产环境里在线判题系统的标准做法,每个提交启动一个短暂容器,限制CPU和内存。改动量不大,但技术深度立刻提了一个档。我当时把这个版本作为进阶目标,就是把原有的子进程运行方式封装成一个接口,具体实现从本地进程切换成调用Docker SDK,后端其余逻辑不用改动。

第二个扩展方向是增加数据统计功能。目前我只做了简单的用户提交次数和通过率统计,如果引入ECharts做可视化大屏,展示每日活跃用户、题目通过率热力图、训练参与趋势,这类功能在答辩现场会非常亮眼。数据统计不是光靠一个SQL就能做完的,需要有统计访问量的设计,比如用独立表记录每个提交事件,或者定时聚合任务。

第三个方向是支持更多题型。目前的判题只支持编程题,可以扩展选择题、填空题、简答题。选择题这一块很值得做,因为很多入门训练都以选择题为主,做起来本身比较简单,却能拓展系统的适用场景。判题模块不需要大改,新增一个judge_type即可,选择题的答案是字符串比对,简答题则用相似度算法做参考评分。

我个人在做完这套系统后的体会是:平台本身不难,难的是把每个环节都打通并且讲清楚。很多同学的项目经验少,不是因为没写过代码,而是缺少从一个完整的业务场景出发去理解系统的视角。这套系统的价值不只是“能用”,而是它覆盖了从数据库建模到前后端分离、从权限控制到进程调度的完整链路,认真做一遍,会对完整项目的运作方式有一个非常立体的认识。最后再提醒一点:如果你是用它做毕设,请务必自己把代码过一遍,不要抱着“能跑就行”的心态去答辩,老师们最看重的是你对每一行核心逻辑的真正理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 6:49:43

Java手写ArrayList与LinkedList:从扩容到哨兵节点全解析

最近帮一位朋友梳理 Java 基础时&#xff0c;他随口说了句&#xff1a;"ArrayList 和 LinkedList 我天天用&#xff0c;但你要我现在手写一个&#xff0c;我估计得想半天。" 这话我太熟悉了&#xff0c;几乎每个靠背 API 活着的开发者都会有这个症状。能调用现成容器…

作者头像 李华
网站建设 2026/10/16 6:49:28

宠物购买管理系统毕设全攻略:从数据库到答辩一次讲透

每年到了毕设选题的时候&#xff0c;"宠物购买管理系统"这类题目都会在选题清单里占据一席之地。很多同学觉得它"太普通"、"没技术含量"&#xff0c;但真正动手之后才发现&#xff0c;一个能跑通完整业务流程、经得起答辩追问的系统&#xff0c;…

作者头像 李华
网站建设 2026/10/16 6:48:52

Spring Boot高铁售票系统毕业设计:并发控制与业务建模实战

高铁售票系统这个题目&#xff0c;在计算机毕业设计里属于那种看起来门槛不高&#xff0c;但真正做完的人没几个的经典款。你要是去看这几年本科毕设的选题清单&#xff0c;"基于Spring Boot的XXX系统"基本占了大半壁江山&#xff0c;其中围绕车票、机票、电影票、酒…

作者头像 李华
网站建设 2026/10/16 6:48:51

Aria2+AriaNg:全平台下载工具原理与搭建实践

下载文件速度慢、任务中断只能从头再来、不同系统要用不同下载软件——这些场景很多同学都遇到过。市面上的下载工具数量不少&#xff0c;但真正能做到“全平台、多协议、多线程分段加速、自带浏览器扩展、轻量低占用”的并不算多。本文就围绕这一类下载工具&#xff0c;拆解其…

作者头像 李华
网站建设 2026/10/16 6:48:37

Nacos接入人大金仓KingbaseES实战避坑指南

简介&#xff1a;本资源是一套面向Java微服务开发者与国产化数据库运维人员的Nacos人大金仓数据源集成实践方案&#xff0c;聚焦国产数据库在配置中心场景下的适配落地&#xff0c;解决服务治理中动态管理Kingbase数据源的核心痛点。压缩包共15个文件&#xff0c;含4个关键SQL脚…

作者头像 李华
网站建设 2026/10/16 6:48:16

全球省市数据JSON处理:结构体检、转换SQL与避坑指南

简介&#xff1a;《全球各国省市数据.json》是一份面向地理信息开发与数据分析人群的全球行政区划数据集。数据采用JSON格式组织&#xff0c;以“国家-省份-城市”三层结构存储&#xff0c;并为每个节点提供唯一ID&#xff0c;便于在地图应用、区域统计、位置服务等场景中快速定…

作者头像 李华