news 2026/10/19 17:27:01

SpringBoot+Vue高校就业招聘系统:从设计到上线全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue高校就业招聘系统:从设计到上线全流程实战

如果你带过一个和高校就业相关的开发项目,一定会碰到类似场景:就业办的老师手里攥着十几份Excel表格,一遍遍核对就业率和签约信息;学生抱着简历不知道往哪儿投;企业HR在双选会上翻纸质简历翻到手腕发酸。我当时就是被这个场景推着,去设计了一套基于 SpringBoot + Vue 的高校就业招聘系统。这篇文章不聊选题怎么包装,直接盘一套可参考、可复现的完整实现思路:从需求拆解、技术选型、数据库设计,到后端接口、前端页面、部署上线和一堆实测踩坑记录。适合需要从零做同类型项目的开发者,也适合正在准备高校就业类系统毕业设计的人对照着调整自己的方案。

1. 这类系统在设计前必须先想清楚的三件事

很多同学拿到“高校就业招聘系统”这个题目,第一反应就是画一张“用户管理 + 岗位管理 + 投递管理”的页面清单,然后埋头写代码。这样做的结果往往是:代码写得不少,但一到对接真实业务场景就到处卡壳。我建议先停下来,把下面三件事想透,再动手。

1.1 角色不是“用户”两个字,而是权责和流程

高校就业招聘系统和普通招聘网站最大的区别在于:它不是一个单纯的双边市场,中间还夹着一个“学校管理方”。在我设计的系统里,角色最终拆成了五类:

  • 学生(求职者):注册登录、完善简历、浏览岗位、投递简历、接收面试通知、确认录用、签订三方。
  • 企业端账号(HR或招聘专员):发布岗位、查看简历、发起面试、录入面试结果、发放录用通知。
  • 辅导员(院系审核角色):对学生基本信息、就业意向进行核实,对学院层面的就业数据进行查看。
  • 校级就业办管理员(超级管理员):管理所有院系、企业账号,审核企业资质、发布校级公告,维护就业数据字典。
  • 系统维护角色(可选):日志查看、数据备份、参数配置。

这个角色划分直接决定了权限模型。最省事的做法是给用户表加一个role字段,简单粗暴,但后续你会发现“辅导员想查看本学院学生数据”“校级管理员想跨院系查看”这类需求根本没法用单一字段表达。我当时采用的是RBAC(基于角色的访问控制)模型,用sys_user_role做关联,把权限点细化到“按钮级别”,比如“就业办管理员可以导出统计报表”“辅导员只能导出本院学生名单”。这个设计后面前端动态路由、后端接口鉴权都用得上,很值得一开始就花时间搭好。

1.2 招聘流程的状态流转是系统的灵魂

就业招聘不是简单的“投递成功/投递失败”两句状态就能描述完的。一套完整的招聘流程应该是:

企业发布岗位 → 学生投递简历 → 辅导员确认就业意向(可选) → 企业筛选简历 → HR发起面试 → 录入面试结果 → 企业发放录用offer → 学生接受/拒绝offer → 签署三方协议 → 纳入就业统计

这里每一步都可能出现分支:简历被淘汰、面试未通过、学生拒绝offer、学生同时收到多个offer后选择一家。处理这类场景,我强烈建议不要在业务代码里到处写if (status == 1) { status = 2; }之类的散弹代码,而是把这套状态流转做成一个独立的状态机模块。我在项目中用DeliveryStatusEnum定义了从WAIT_REVIEW到SIGNED的完整状态枚举,并配置了“合法迁移表”(哪个状态能流向哪个状态,哪个角色能触发这个迁移)。这样做的好处是:产品经理后面提需求“录用前必须加一个HR复核环节”,你只需要在枚举和迁移表里加一个状态,而不需要去几十个Controller里翻找逻辑。

1.3 数据统计口径决定了后台报表怎么写

“就业率”这三个字,看似简单,背后全是口径问题。同一个学生,签了三方算就业,签了劳动合同算不算就业?灵活就业算不算?升学算不算就业?考研成功的学生,系统里状态是“待就业”还是“已就业(升学)”?

如果不提前定义,到写统计报表的时候一定会陷入无尽的返工。我在项目设计阶段就和就业办的老师把统计口径定了下来:“已就业”包括签订三方协议、劳动合同、录用函、升学、入伍、自主创业六种情况;院系就业率 = 已就业人数 / 有就业意愿的毕业生总数。数据库里专门加了一张employment_dict字典表,记录“就业类型”和“统计口径说明”,后面所有的报表都从这个字典出发,避免每个地方写死不同的字符串。这一点对系统能否真正落地上线非常关键,也算是这个项目里最有业务价值的一步。

2. 技术骨架:SpringBoot + Vue 到底该怎么搭

搞清楚了业务边界,接下来聊技术选型。这个题目本身限制得很明确——SpringBoot + Vue。我在确认技术栈时没有盲目追新,而是按照“当前主流稳定版本 + 生态成熟度 + 学习资料丰富度”来选。

2.1 为什么前端用 Vue 不用 React

高校就业招聘系统这类管理后台+门户站混合体,Vue 的优势非常明显:上手门槛低、模板语法直观、配合 Element Plus 这类组件库可以快速搭建出表格和表单密集的后台页面。我不是说 React 不行,而是对大多数开发者和毕业设计场景来说,Vue 生态里的vue-router、Pinia、Element Plus组合起来“开箱即用”的程度更高,你把精力留给业务逻辑会更划算。

我最终采用的版本组合如下,这些都是当前能稳定跑通的搭配:

技术版本说明
JDK1.8+兼容性最好,很多服务器还是 JDK8
Spring Boot2.7.x稳定且资料多,避免用过度追求新版
MyBatis-Plus3.5.x单表 CRUD 省代码,分页插件好用
MySQL8.0支持 JSON 字段,方便存拓展信息
Redis6.x缓存验证码、Token 黑名单、热点数据
Vue3.4.x组合式 API 写起来更清晰
Element Plus2.6.x后台组件库,表格表单很全
Vite5.x前端构建,比 Webpack 快不少

这套组合的另一个好处是社区问答非常丰富,几乎每个报错都能搜到解决方案,对需要独立完成的开发者来说,这能省下一大半查资料的时间。

2.2 后端工程结构的分层策略

后端我不建议搞那种“一个 Controller 一个 Service 一个 Mapper”的机械分层,而是按照业务模块纵向切开,同时保留横向的公共层。项目里最终的结构是这样的:

com.campus.recruit ├── common # 统一响应体、全局异常、工具类 ├── config # 安全配置、Redis配置、文件上传配置 ├── module │ ├── auth # 登录鉴权模块 │ ├── user # 用户、角色、权限 │ ├── student # 学生档案、简历 │ ├── enterprise # 企业信息、岗位 │ ├── delivery # 投递、面试、offer、三方 │ └── report # 就业统计报表 └── JobApplication.java

这样切的好处是:每新增一个模块,比如“校园宣讲会”,可以独立添加module/schoolfair,不影响已有模块。而在每个模块内部,仍然保持Controller → Service → Mapper的基础分层,避免事务边界和职责混乱。

2.3 统一响应体与异常处理,省掉一半联调时间

我第一次写这个项目的时候,每个接口返回格式都不一样,有的返回{code: 0, data: ...},有的直接返回data,前端同学调到崩溃。后来我做了两件事:

  • 定义统一响应体Result<T>:code(200成功,其他为业务异常码)、message、data三个字段。
  • 使用@RestControllerAdvice做全局异常捕获,把参数校验异常、业务异常、未知异常统一封装成响应结构。

前端在 axios 拦截器里只需要判断一次code,等于把联调时间压缩了一大半。这里有几个细节值得留意:业务异常建议自定义BizException,通过throw new BizException("岗位已停招")的方式抛出;不要把NullPointerException之类的细节直接暴露给前端,统一转成“系统开小差了”这种描述。这样设计的用户体验和可维护性都会明显更好。

3. 数据库设计:从学生、企业到三方协议的表结构拆解

数据库是整个系统里最不该省时间的部分。我第一版设计得比较随意,上线前改了三次表结构,教训很深。现在把最终版的核心表拆开讲讲,你写自己项目的时候可以拿来对照。

3.1 学生与企业的主数据设计

先看两张业务主表。student_profile不只是用户表字段的平铺,它要存储学号、学院、专业、班级、毕业年份、生源地、就业意向等字段。我选择把它和sys_user分表,而不是把所有东西塞进一张表。原因很简单:认证信息(账号密码)和业务档案(学号学院)的读写频率不一样,分表后登录鉴权不用每次把整条档案捞出来,后续扩展“校友信息登记”也很方便。

enterprise_info表需要审核字段。企业注册后会有HR添加企业资料,但不能马上发布岗位,必须由就业办管理员审核通过后才能开通招聘权限。所以表里必须有一个audit_status字段,以及audit_remark(审核拒绝原因)。这看起来只是“多一个字段”,实际上是把线下“企业资质审核”这个真实业务场景搬进了系统。

另外我强烈建议给每张业务表都加create_time、update_time、deleted字段,并用 MyBatis-Plus 的自动填充功能统一维护。后面做数据统计、回收站逻辑、问题排查,这几个字段都特别好用。

3.2 岗位、简历、投递记录的核心关联

接下来是最关键的几张关联表。job_position存储企业发布的岗位信息,包括岗位名称、招聘人数、薪资范围、学历要求、工作城市、岗位描述等。这里的enterprise_id和recruiter_id都要建索引,因为列表页最常见的查询就是“某个企业下的岗位列表”。

resume表建议采用“主表 + JSON 扩展”的设计。每个学生的基础信息、教育经历、实习经历、项目经历、技能标签等字段非常多。如果全部设计成字段列,表会非常宽,而且不同学生的简历结构差异很大。我的做法是:resume主表存学生id、简历标题、完整度评分、是否默认等固定字段,同时用一个resume_contentJSON 字段存整体履历数据。查询列表时只查主表,进入详情页再解析 JSON。这在大部分场景下完全够用,也避免了做通用 EAV(实体-属性-值)模型的过度复杂。

delivery_record是投递记录表,也是整个系统数据量增长最快、并发压力最大的表之一。字段至少要包括:学生id、岗位id、投递时间、当前状态、状态变更历史(JSON数组)、企业是否已读。这里的student_id + job_position_id需要建唯一索引,防止同一个学生对同一个岗位重复投递。状态变更历史用JSON数组存,每次状态流转都往里追加一条{from, to, time, operator},后续做流程回溯特别方便。

3.3 三方协议与就业统计:容易被忽略的扩展表

很多同类项目做到“学生接受了offer”就结束了,但这套高校就业招聘系统不能停在这里,因为就业办的老师真正关心的是后续三方协议和就业率。agreement(三方协议)表需要记录:学生id、企业id、岗位id、协议编号、填写的薪资和档案接收单位、辅导员是否审核、就业办是否盖章、状态(待上报/审核中/已归档)。

就业统计报表则依赖一张employment_statistics汇总表。每天晚上用定时任务,把当天的就业数据按院系、专业、就业类型聚合成一条统计记录。报表和日志接口直接查汇总表,而不是实时去delivery_record做全量计算。这个“空间换时间”的思路,让我的统计报表接口响应时间从原来的3秒多降到了300毫秒以内,在数据量变大之后体会尤其明显。

4. 后端核心模块:鉴权、消息中心与简历解析

后端模块里,我认为对系统体验影响最大的不是那些CRUD接口,而是鉴权、消息实时性和简历文件的处理。这三个模块直接决定了一个系统从“能用”到“好用”的距离。

4.1 JWT + RBAC:一个能撑住多角色的方案

鉴权方案我在Spring Security和“拦截器 + JWT + 注解鉴权”之间犹豫过。后来考虑到这个项目需要支持的角色类型多、但是并发量不大,最终选择了更可控的“JWT + 自定义拦截器 + 自定义注解”方案。核心逻辑是:

  • 登录成功后,生成 JWT Token,里面只存用户id和角色编码列表,设置过期时间(默认2小时)。
  • 前端把 Token 放在请求头Authorization: Bearer xxx中。
  • 后端拦截器解析 Token,把用户信息放入ThreadLocal上下文。
  • 在 Controller 方法上用@RequirePermission("report:export")这样的注解标记需要的权限,通过 AOP 做权限校验。

我觉得这个方案比直接上Spring Security更适合这类不够复杂的业务系统,因为逻辑透明、调试方便,也不容易踩到 Security 过滤器链配置的各种坑。注意JWT密钥要用足够长的随机字符串,不要硬编码在普通常量里,放到配置文件并加入随机盐。

// 精简后的JWT工具类核心代码 public class JwtUtils { // 建议从配置中心或环境变量读取,避免写在代码里 private static final String SECRET = "configurable-secret-please-override-in-prod"; private static final long EXPIRE = 2 * 60 * 60 * 1000L; public static String generateToken(Long userId, List<String> roles) { long now = System.currentTimeMillis(); return Jwts.builder() .claim("uid", userId) .claim("roles", roles) .setIssuedAt(new Date(now)) .setExpiration(new Date(now + EXPIRE)) .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SignatureAlgorithm.HS256) .compact(); } }

4.2 招聘流程状态机:从投递到录用的状态迁移

前面提到状态机是整个系统的灵魂,这里具体说落地。我把状态机和枚举放在一起,定义在一个配置类里:

public enum DeliveryStatus { // 状态编码,当前状态可流向的目标状态 WAIT_REVIEW("待筛选", "ACCEPTED", "REJECTED"), ACCEPTED("简历通过", "INTERVIEWING", "REJECTED"), INTERVIEWING("面试中", "OFFERED", "REJECTED"), OFFERED("待接受offer", "ACCEPTED_OFFER", "REJECTED_OFFER"), ACCEPTED_OFFER("已接受offer", "SIGNING", "CANCELED"), SIGNING("签署三方中", "SIGNED"), SIGNED("已签约", ""), REJECTED("已淘汰", ""), REJECTED_OFFER("已拒绝offer", ""), CANCELED("已取消", ""); private final String label; private final List<String> allowedTargets; }

Service 层做状态迁移时,统一走一个transition(currentStatus, targetStatus, userId, roleCode)方法,内部检查目标状态是否在合法迁移列表里,并且记录一条状态历史。如果不合法,直接抛BizException("非法状态流转")。这个设计让流程变得非常可追溯,也让产品经理后面加“撤回投递”“HR转推荐其他岗位”这些需求变得简单很多——本质都是在迁移表里增加一个目标状态。

4.3 简历OCR解析与附件上传的落地姿势

学生上传PDF简历后,系统应能解析出基本信息,方便企业端在列表页直接预览。这里需要明确一点:不要一上来就做重型OCR。我实际能稳定跑通的方案是“三级解析”:第一级,如果能拿到 PDF 内部的文本流,直接用文本解析库(比如PDFBox)抽取内容;第二级,如果是扫描版图片,再调用OCR服务;第三级,实在解析不了就让用户手动补充结构化的“在线简历”。

真正的坑在于文件上传。高校场景下,学生用的电脑可能是一台老笔记本,浏览器版本也参差不齐;我一开始用multipart/form-data直接上传,文件稍微大一点就报 413。后来在前端做了分片上传,单个分片定为 5MB,逐片上传到临时目录,最后合并;同时后端接口里限制了文件大小(PDF简历上限10MB,企业营业执照上限5MB),这样既保证了体验,又避免了服务器磁盘被写满。nginx 层也要同步设置client_max_body_size,否则后端限制得再严格,请求也到不了后端。

5. 前端 Vue 实现的几个关键点

前端同样是这个系统里工作量很大的一环。我用 Vue 3 + TypeScript + Element Plus 重新写过一版,几个关键点你可能也会遇到。

5.1 基于角色的动态路由与菜单

不同角色登录后,看到的菜单应该完全不同:学生端是“我的简历、岗位浏览、投递记录、我的offer”;企业端是“岗位管理、简历筛选、面试日程”;管理端是“用户审核、就业统计、公告管理”。

实现思路不复杂:登录成功后,后端返回当前用户拥有的菜单/权限编码列表;前端用router.addRoute动态注册路由。这里我最想提醒的是:必须在 vue-router 的全局守卫里做“无权限页面重定向”,不能只靠隐藏菜单。否则用户手敲 URL 照样能进到没权限的页面,后端权限拦得住接口,但前端页面会出现各种空白报错。

5.2 招聘流程页面的数据联动

招聘流程页面是前端交互最复杂的一块。企业HR在“简历筛选”列表里点击“通过”,这条投递记录的状态会变成“面试中”,并自动给学生生成一条站内信通知;学生端刷新后,“投递记录”列表里的状态标签要实时更新。

为了保证一致性,我前端做了三层配合:

  • 列表页和详情页统一用 Pinia 管理“当前投递记录”的状态。
  • 调状态流转接口成功后,不重新刷整个列表,而是更新本条记录的状态字段,并刷新对应的消息中心红点数。
  • 每次页面切换时,通过 useRoute 监听路由参数变化,重新拉取详情数据。

这些小细节决定了前端交互是否顺滑。如果你只是机械地“调完接口就全页面 reload”,体验会显得很粗糙。

5.3 文件上传组件的进度与回显

Element Plus 的el-upload组件自带进度条,但要注意它内部的action属性是必填项,而且会向这个地址提交文件。如果后端接口还需要带 Token,就不能直接用字符串形式的action,而是通过:http-request自定义上传逻辑。

我封装了一个FileUploader组件,接收uploadUrl、fileType、limitSize三个 props,内部用XMLHttpRequest直接发请求,这样能完整拿到上传进度;上传成功后回显文件列表,支持下载、删除、预览图片。对PDF简历,我加了一个“打开预览”的按钮,用window.open(blobUrl)实现,避免强制下载打断用户浏览体验。

<script setup lang="ts"> // 自定义上传逻辑中,手动附带 Token,并实时更新进度 const uploadFile = async (options: UploadRequestOptions) => { const formData = new FormData() formData.append("file", options.file) const xhr = new XMLHttpRequest() xhr.open("POST", uploadUrl.value) xhr.setRequestHeader("Authorization", `Bearer ${token}`) xhr.upload.onprogress = (e) => { options.onProgress({ percent: (e.loaded / e.total) * 100 }) } xhr.onload = () => { ... } xhr.send(formData) } </script>

6. 我踩过的坑与最后的优化

代码写完只是第一步,真正让项目“能跑起来、敢上线”的,是环境配置和细节调优。我整理几个我踩过、也比较典型的坑,希望对你有实际帮助。

6.1 时间字段的时区问题

第一次自测时,我发现学生投递简历的时间慢了8个小时。查了一圈,根因有三个:MySQL 连接串没加serverTimezone=Asia/Shanghai;服务器系统时区是 UTC;前端dayjs默认按本地时区解析。单看任何一个环节都没问题,合在一起就出现了时间错乱。

最终的配置方案是统一在字符串层面用LocalDateTime,MySQL 连接串明确加serverTimezone=Asia/Shanghai,Jackson 配置spring.jackson.time-zone=GMT+8。我建议后端接口的时间字段统一返回yyyy-MM-dd HH:mm:ss字符串,不在 JSON 里序列化时间对象,前端不再额外做时区转换,省掉一大类问题。

6.2 大数据量下的列表查询变慢

当投递记录超过几万条时,最明显的卡顿出现在“就业统计报表”和“投递记录筛选”。我通过 MySQL 的EXPLAIN发现,问题出在两张表关联查询时没有走索引,以及状态统计的COUNT(*)扫描了整张表。

优化分了三步:

  • 给delivery_record.enterprise_id、delivery_record.job_position_id、student_profile.college_id等高频查询字段建立联合索引。
  • 列表查询默认只查最近三个月的数据,历史数据进入归档表;学生端默认只查“求职进行中”的记录。
  • 所有统计接口优先走employment_statistics汇总表,而不是实时全表扫描。

优化后,最慢的管理员首页从 3.2 秒降到了 0.4 秒左右,尽管数据量还在增长,这个表现已经足够了。

6.3 文件存储路径与部署配置

开发阶段直接把文件存在项目目录下没问题,但部署到线上,一个不匹配的绝对路径就会导致文件404。我最后用配置中心变量来隔离环境:

# 开发环境 file.storage-path=D:/temp/recruit-files/ # 生产环境(示例路径) file.storage-path=/data/recruit/files/

部署时,前端用 nginx 托管静态资源,接口通过反向代理转发到 Spring Boot 服务;文件上传目录通过/data/recruit/files/映射到静态资源地址。还有一个小坑:Linux 下第一次上传文件后,目录的属主如果是root,服务运行的账号可能没有写权限,我在这上面卡了快半天。最简单的办法是用chown -R 服务账号 /data/recruit/files提前把权限处理好。

问题根因解决方式
时间少8小时MySQL/系统/前端时区不一致统一时区配置,接口返回时间字符串
列表查询慢3秒缺索引 + 全表统计加联合索引 + 汇总表
大文件上传失败nginx、后端、前端三方限制不一致统一大小限制,前端分片上传
文件上传后404存储路径不一致配置化路径 + 静态资源映射

最后再分享一点关于文档和代码交付的体会。这个题目标题里提到的“万字文档+源码+视频讲解”,我自己整理时最大的感触是:文档别写成说明书,要把设计的“为什么”写清楚,比如为什么多角色要拆权限表、为什么就业统计要建汇总表;源码要留一个README.md,从数据库初始化到前后端启动命令,一步步写清楚;视频讲解不用录长,把“系统演示 + 核心代码位置 + 设计思路”讲透就够了。这些配套材料对别人真正理解项目价值、快速跑起来,远比冷冰冰的代码重要。我后来再看这套系统时,最感谢的就是当时花时间把踩坑记录写成了文档——很多坑半年后又有人踩了一遍,翻出来就能解决。

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

Golang并发编程:深入理解锁机制与性能优化

1. 为什么需要关注Golang锁机制刚接触Go语言并发编程时&#xff0c;我曾在面试中被问到一个看似简单的问题&#xff1a;"说说sync.Mutex和sync.RWMutex的区别"。当时只能支支吾吾地回答"一个读写都能锁&#xff0c;一个只能读锁"&#xff0c;结果自然是被面…

作者头像 李华
网站建设 2026/10/19 17:27:01

有序数组去重必学:双指针原地修改算法详解

这道题在刷题圈里几乎无人不知&#xff1a;删除有序数组中的重复项。它是经典中的经典&#xff0c;不少人第一次在题库里刷它&#xff0c;是在刚接触数据结构的阶段&#xff0c;好多人以为两分钟就写完了&#xff0c;结果一提交就被边界用例教做人。也有不少人背下了答案&#…

作者头像 李华
网站建设 2026/10/19 17:26:33

MySQL REPLACE() 函数:数据清洗与批量修正的实战指南

如果你在某个项目的存量数据里翻过Bug&#xff0c;大概率会被一类问题缠住&#xff1a;值本身没错&#xff0c;就是格式烂、内容脏、符号乱。最近我处理一批历史数据修正任务时&#xff0c;就撞上了这种场景——路径分隔符混用、富文本里塞满换行和全角空格、业务标识里多了一段…

作者头像 李华
网站建设 2026/10/18 8:57:23

context-mode:从隐式状态到显式上下文模式的设计与实践

1. 从"context-mode"这个词说起&#xff1a;它到底指什么第一次看到"context-mode"这个标题&#xff0c;很多人会愣一下——它不像"XX管理系统"或"XX爬虫"那样一眼能看出用途。我当初也是被这个词吸引&#xff0c;琢磨了半天才反应过来…

作者头像 李华
网站建设 2026/10/18 9:00:38

Object → Method Call:WSaiOS-SI 中方法调用的结构运行理论

Object → Method Call&#xff1a;WSaiOS-SI 中方法调用的结构运行理论作者&#xff1a;东塬一老翁单位&#xff1a;WSaiOS 多模态智能日期&#xff1a;2026 年 10 月摘要本文基于 WSaiOS-SI 中 Class、Object、Attribute、Value、Method 等基本理论&#xff0c;系统讨论“方法…

作者头像 李华
网站建设 2026/10/18 0:31:48

CoDeSys运动控制:Gantry3pi坐标变换与插补器参数调试

简介&#xff1a;CoDeSys运动控制资料包面向工业自动化领域的PLC编程、运动控制与机器人调试人员&#xff0c;系统讲解CoDeSys软PLC中的运动控制实现方案。文档以SMC_TRAFO_Gantry3pi模块和SMC_Interpolator功能块为核心&#xff0c;逐一说明目标位置向量、x/y偏移量、移动范围…

作者头像 李华