SpringBoot3+Vue3 用户多组织:默认任职、关联组织和顶栏切公司怎么配
🌐文档地址:https://ruoyioffice.com
📦源码1·GitHub:https://github.com/yuqing2026/ruoyi-office
📦源码2·GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office
📦源码3·Gitee:https://gitee.com/yqzy1688/ruoyi-office
💬微信:17156169080(备注「RuoYi Office」)
集团里一个人同时在深圳研发和长沙分公司任职,最常见的错法是再开一个账号、或者让他退出当前租户再登录。RuoYi Office 把这件事做成同一账号、多条任职、会话里只生效当前这一条。部署开关和租户开关要同时打开;默认组织必须有角色;顶栏切换会关页签、重拉菜单。关掉多组织时任职记录不删,运行时回到用户主档。
▲ 左是顶栏当前公司/部门,中是 Redis 会话里的 deptId,右是 PRIMARY / CONCURRENT 任职表;切的是任职,不是租户
引言:一人多公司难在「切的是什么」
把部门下拉框做成多选,看起来像「多组织」,上线后会碰到这些坑:
| 痛点 | 常见做法 | 后果 |
|---|---|---|
| 两个公司两套账号 | 让员工记两套密码 | 待办拆开,消息对不齐 |
| 切公司等于换租户 | 退出再登录另一个租户 | 集团共享主数据被切成两套库 |
| 兼职没有独立角色 | 全局角色带到所有部门 | 长沙只能看报销,却看见了深圳合同菜单 |
| 关掉开关就删任职 | DROP 或清空任职表 | 下次再开,全部要重配 |
| 只改前端显示 | Token 里 deptId 仍是主档 | 新开的用车申请单还挂在默认部门 |
一句话:多组织是「同一租户内、同一账号、多条任职,会话只认当前 deptId」;多租户是「另一套客户、另一套库」。两件事不要配成同一个按钮。
下面按能点的页面讲:用户列表的「关联组织」、用户档案上的默认部门/角色、租户表单的「用户多组织」、工作台顶栏的公司切换器。
一、先给出可直接抽取的定义
1.1 什么是用户多组织
用户多组织,是一个登录账号可以关联多个部门(通常分属不同公司),每条任职独立存角色和岗位;当前会话只激活其中一条,菜单、数据范围、新开单据都只认这一条。
它不是:
- 再注册一个用户
- 切换到另一个租户
- 把用户档案上的部门改成多选(多选无法表达「当前正在哪家办公」)
RuoYi Office 的任职表是system_user_org,角色在system_user_org_role,岗位在system_user_org_post。用户档案system_users.dept_id只同步默认任职那一条。
1.2 什么是默认任职
默认任职,是每用户有且仅有一条isDefault = true的记录,关系类型写成PRIMARY,角色不能为空。它同时干三件事:
- 回写用户档案的部门、默认角色、默认岗位
- 关掉多组织开关后的兜底身份
- 登录时若会话还没切过,当前组织就是它
前端弹窗里点「默认」单选,保存时把该行标成 PRIMARY,其余行标成CONCURRENT。不要让客户在表单上同时维护「主职/兼职」和「是否默认」两套口径——对外只露出「默认」这一个词。
1.3 什么是会话当前组织
会话当前组织,是这次登录(跟 refresh token 走)正在使用的deptId,存在 Redis,并回写 Access Token 的 userInfo。顶栏显示成「公司名/部门名」,切换成功后关闭全部页签,整页跳回首页,重新拉权限。
新开的审批单、用车单会带当前公司,而不是永远带用户主档部门。这才是客户能感知到的「切公司」。
二、两道开关必须同时开
运行时是否走任职表,不是看某一个布尔值,而是两道门:
| 开关 | 配在哪 | 关了会怎样 |
|---|---|---|
部署级multi-org.enable | 应用配置,默认false | 整站不读不写任职表,行为与升级前一致 |
租户级multiOrgEnabled | 租户管理 → 编辑租户 | 该租户用户列表不出现「关联组织」,顶栏不出现切换器 |
后端入口是MultiOrgSupport.isEnabled():配置未开直接 false;当前线程没有 tenantId(定时任务、MQ)也视为关闭,避免 Job 误读任职表把数据范围切乱。
只开部署、不开租户:集团里只有部分客户要用一人多公司,其它租户保持「一用户一部」。只开租户、不开部署:表单上能勾,运行时仍当没开——这是最容易让实施以为「坏了」的组合,先对这两项。
无 tenantId 的后台线程一律当关闭,这是有意的:考勤汇总、合同到期提醒不应该因为某次顶栏切换,把统计口径切到兼职部门。
菜单怎么算:多组织开启后,鉴权、菜单、数据范围都只认当前任职的roleIds。人在深圳研发、角色带合同菜单;切到长沙且长沙任职只配了 OA 角色,合同模块会从侧栏消失。这不是 bug,是任职隔离生效。若客户期望「菜单永远全集、只过滤数据」,不要开多组织,改用数据权限的「全部 / 自定义部门」。
数据权限口径跟会话deptId:角色配「本部门」时,过滤条件用的是当前部门,不是用户档案部门。验收用例写成三步更清楚——切到 A 部门能看见 A 的用车单;切到 B 看不见 A 的「本部门」数据;默认组织仍能在关联组织里改回去。
三、用户列表:关联组织才是多条任职的入口
路径:/system/user。租户已开多组织时,行内操作除「编辑 / 删除」外多一列「关联组织」。超级管理员行会锁,避免演示账号被改乱。
▲ 左树是组织,右表每行有编辑、关联组织、删除;列表只配这一张,后面看弹窗和顶栏
点开后是独立弹窗,不是把用户编辑表单拉长。弹窗标题是「关联组织 · {账号}」,例如演示账号demomulti。每一条任职一张卡片:
| 字段 | 必填 | 说明 |
|---|---|---|
| 部门 | 是 | 树选择,同一用户不能重复选同一部门 |
| 角色 | 默认行必填 | 该任职独立授权;切换后菜单只认这一组 |
| 岗位 | 否 | 同样按任职隔离 |
| 默认 | 全局唯一 | 同步至用户档案;去掉当前默认时自动把第一条顶上去 |
底部有「+ 新增任职」。保存前前端会拦:至少一条、部门不能空、默认必须恰好一条、默认必须至少一个角色。后端再拦一遍部门重复和默认角色,避免绕过弹窗的脚本把用户存成「关开关后没菜单」。
▲ 任职 1 勾了默认,部门是研发部门,角色按业务线多选;说明文字写清「切换组织只激活该任职的权限」
弹窗底部那句不要删:客户经常问「编辑用户里的角色和这里的角色会不会打架」。答案是——开启多组织后,档案上的角色/岗位只代表默认组织;兼职的授权只在这张弹窗里配。
接口:
GET /system/user-org/list?userId=管理端查某人GET /system/user-org/my-list当前登录人自己的任职(给顶栏)PUT /system/user-org/save整表覆盖保存POST /system/user-org/switch只切会话,不改任职表
权限码:system:user-org:query/system:user-org:update。查询、更新走「任一任职拥有即可」的鉴权,避免人在兼职组织时改不了自己的关联。
四、用户档案:这里只配默认组织
「编辑」打开的仍是经典用户表单:用户名、昵称、部门、角色、岗位。开启多组织后,部门/角色/岗位的帮助文案会改成:
开启多组织时,此处为「默认组织」的岗位 / 角色;其它任职请到「关联组织」逐条配置。
不要在这张表上做部门多选。默认部门被改时,后端应同步 PRIMARY 那一行,而不是再插一条 CONCURRENT。批量「分配角色」也只追加到默认组织,文案会写明:其它任职请到关联组织配置。
这是有意的产品切割:档案管人,任职管「人在哪家公司以什么身份干活」。混在一个表单里,实施会把兼职角色覆盖掉默认角色。
演示账号demomulti(昵称「业务骨干」)的编辑弹窗如下。部门仍是「研发部门」,角色是一组业务线标签。请把它理解成默认组织的身份,不要在这张弹窗里找「第二条公司」。
▲ 标题是「修改用户」;兼职公司、兼职角色不在这里,而在行内「关联组织」
关联组织弹窗底部还有一句实施常忽略的话:关闭多组织,以及薪酬、考勤、员工档案,都认默认组织。兼职能切菜单、能开单据,不等于工资和考勤日历跟着兼职部门走。算薪、排班、人事主档继续锚定 PRIMARY,避免一个人在长沙兼一周就把深圳的假期额度切走。
前端保存时也会把默认行标成 PRIMARY、其余标成 CONCURRENT,避免两套口径:
if(rows.value.length===0){message.warning('至少保留一条任职');return;}if(rows.value.some((item)=>!item.deptId)){message.warning('请选择部门');return;}constdefaultRows=rows.value.filter((item)=>item.isDefault);if(defaultRows.length!==1){message.warning('必须且只能指定一个默认组织');return;}if((defaultRows[0]!.roleIds??[]).length===0){message.warning('默认组织必须至少配置一个角色');return;}awaitsaveUserOrgList({userId:userId.value,orgs:rows.value.map((item,index)=>({...item,sort:index,relationType:item.isDefault?'PRIMARY':'CONCURRENT',})),});改的是自己的任职时立刻重拉权限,否则顶栏orgList还是旧的。兼职行未配角色会显示橙色提示:「未配角色时,切换到此组织将看不到业务菜单」。
五、租户表单:用户多组织是租户级能力
路径:/system/tenant,编辑某个租户,表单靠下有「用户多组织」单选。帮助文案的要点:
- 开启后,用户可关联多个部门,顶栏和移动端可切换(会刷新菜单与权限)
- 部署侧还要把
multi-org.enable打开 - 关闭后任职记录保留,运行时回到默认组织
▲ 开关与租户状态、演示保护排在一起;每个租户独立,图中该演示租户为关闭。当前登录租户若已开启,顶栏仍可切组织
从开到关,前端会先打预览接口,弹确认框,内容大致包括:
- 已配置任职用户多少人、其中多少人有多条任职
- 其它任职上单独配的角色条数将不再生效
- 默认组织角色与用户档案不一致的人数(关闭后菜单会变)
- 尚未配角色的任职条数
- 「本部门」数据权限按默认部门过滤,其它任职部门下的单据可能看不见
- 相关账号请重新登录
确认后才提交。不要静默关掉——兼职角色会立刻失效,客服电话会响。
六、顶栏:切公司会关页签
工作台右上角,头像左侧,格式是公司名/部门名,例如「深圳总公司/研发部门」。只有multiOrgEnabled且orgList非空才渲染。任职只有一条时下拉禁用,避免点开一个不能点的列表。
▲ 切换器跟搜索、通知、头像在同一条顶栏;切完会回到首页并重拉待办口径
点开后标题是「切换组织」,可搜公司或部门,当前项打勾。确定前有确认框:
- 目标任职一个角色都没有:警告「切换后将看不到任何业务菜单,只能再切回其它组织」
- 否则:说明角色与岗位按任职独立生效,会关闭全部页面并重新生成菜单
确认后前端顺序是:
POST /system/user-org/switch,body 只有{ deptId }- 本地 userInfo 立刻改
companyId/deptId/ 名称(避免闪一下旧部门) closeAllTabs()location.href跳到该用户的首页路径
全屏遮罩文案是「正在切换组织,请稍候…」。不要只改 Pinia 不刷新路由——动态菜单、数据权限指令都是登录后算出来的,半刷新会出现「菜单还在、接口 403」。
七、后端:会话跟 refresh token,不跟短 Access Token
切换接口从请求头取出当前 Access Token,查到对应的 refresh token,再写 Redis。Access Token 往往十几分钟过期,组织偏好必须活过「静静刷了一次 token」。
publicvoidswitchCurrentDept(LonguserId,LongdeptId,StringaccessToken){if(!multiOrgSupport.isEnabled()){throwexception(USER_ORG_DISABLED);}if(!userOrgService.hasOrg(userId,deptId)){throwexception(USER_ORG_NOT_EXISTS);}OAuth2AccessTokenDOtoken=oauth2AccessTokenMapper.selectByAccessToken(accessToken);longttl=Math.max(remainingSeconds(token.getExpiresTime()),Duration.ofDays(30).getSeconds());userOrgSessionRedisDAO.set(token.getRefreshToken(),deptId,ttl);Map<String,String>userInfo=newHashMap<>(token.getUserInfo());userInfo.put(LoginUser.INFO_KEY_DEPT_ID,String.valueOf(deptId));token.setUserInfo(userInfo);oauth2AccessTokenMapper.updateById(token);oauth2AccessTokenRedisDAO.set(token);}要点:
- 没有任职关系的
deptId直接拒绝,防止改请求体切到别的部门 - Redis key 形态是
user_org_session:{refreshToken},TTL 至少 30 天 - Access Token 的 userInfo 同步改
deptId,网关/资源服务器读 Token 就能带上当前部门 - 当前请求的
LoginUser也立刻 overlay,避免本请求后半段仍用旧部门
微服务下网关可能把用户信息缓存约 1 分钟。拦截器会读请求头current-dept-id,校验任职后覆盖LoginUser,填这个窗口。前端在多组织开启时带上该头即可。
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){if(!multiOrgSupport.isEnabled()){returntrue;}LoginUserloginUser=SecurityFrameworkUtils.getLoginUser();if(loginUser==null){returntrue;}Stringheader=request.getHeader("current-dept-id");if(StrUtil.isBlank(header)||!NumberUtil.isLong(header)){returntrue;}userOrgSessionService.overlayLoginUserDept(loginUser.getId(),Long.valueOf(header));returntrue;}overlay 同样会hasOrg校验:请求头被改成未任职的部门时直接忽略,不会把数据范围切到别人的公司。这是防篡改,不是信任前端。
八、保存任职:默认角色是关开关后的救命绳
保存不是「有几条插几条」那么简单。后端按部门去重,缺省部门报错,默认必须恰好一条。默认行的角色若显式传了空数组,直接失败;roleIds == null表示这次只调部门、不改授权,此时沿用档案上的全局角色——这是给老客户端留的兼容口。
if(defaultCount!=1||defaultDeptId==null){throwexception(USER_ORG_DEFAULT_REQUIRED);}if(defaultItem.getRoleIds()!=null?defaultItem.getRoleIds().isEmpty():CollUtil.isEmpty(permissionService.getGlobalRoleIds(userId))){throwexception(USER_ORG_DEFAULT_ROLE_REQUIRED);}StringrelationType=Boolean.TRUE.equals(item.getIsDefault())?UserOrgDO.RELATION_PRIMARY:UserOrgDO.RELATION_CONCURRENT;删除某条任职时,级联清掉该任职的角色和岗位,避免下次同一部门重建时继承旧授权。兼职行的roleIds == null同样表示不改,避免只改部门就把角色清空。
公司编号不手填:由部门向上解析「组织类型 = 公司」的祖先,写入companyId。顶栏展示的「深圳总公司」来自这里,不是用户自己填的文本。
九、三张表怎么拆
不要把角色塞进任职表的 JSON 字段。切换组织时菜单计算要按userOrgId精确查角色,JSON 无法走关联、也无法做「关闭多组织时统计兼职角色条数」。
| 表 | 一行代表什么 | 关键字段 |
|---|---|---|
system_user_org | 用户在某部门的一条任职 | userId、deptId、companyId、isDefault、relationType、status、sort |
system_user_org_role | 该任职拥有的一个角色 | userId、userOrgId、companyId、roleId |
system_user_org_post | 该任职拥有的一个岗位 | userId、userOrgId、companyId、postId |
角色表、岗位表要按userOrgId/userId/roleId收敛查询,不要误加租户列过滤。任职编号本身跨租户唯一;在「访问其它租户」这类上下文里,如果拦截器按当前 tenant_id 去滤关联表,会把角色查成空集,全站变成「没有该操作权限」。这是实施时最像权限 bug、其实是过滤条件套错的一类问题。
用户档案仍保留一份全局sys_user_role:它和默认任职对齐。关闭多组织后,运行时只读档案,不读任职角色——所以默认行必须有角色,否则关开关等于把人锁在空白工作台。
十、和多租户、数据权限分别对齐
三件事经常被配到同一个需求里,表格拆开更好讲:
| 概念 | 隔离的是什么 | 用户怎么操作 |
|---|---|---|
| 多租户 | 客户与客户,库或 schema 或tenant_id | 登录时选租户,或域名绑定 |
| 多组织 | 同一客户内的公司/部门任职 | 顶栏切换,不退出登录 |
| 数据权限 | 当前身份能看哪些行 | 角色上的「全部 / 本部门 / 本部门及以下 / 仅本人 / 自定义」 |
切组织之后,「本部门」的口径跟着当前deptId走。人在深圳研发,本部门数据权限就只看研发;切到长沙分公司,同一套角色定义会作用在长沙的部门树上。不要指望切组织却仍按用户主档过滤——那是没 overlayLoginUser。
新开单据的公司、部门默认值也取当前会话,而不是档案。客户验收时最直观的用例:切到长沙 → 发起用车 → 单据抬头是长沙。
十一、关闭多组织之后
关闭不是删数据。任职表、任职角色、任职岗位都还在。运行时:
isEnabled()为 false,顶栏切换器消失- 用户列表「关联组织」隐藏
- 菜单和数据范围回到用户档案的部门、角色、岗位
- Redis 里的会话 deptId 不再被读取(开关关了 overlay 直接 return)
预览框里已经写了副作用:兼职上单独配的角色失效;档案和默认任职不一致时菜单会变;其它任职部门下的单据按「本部门」可能看不见。实施清单建议:关之前导出任职,确认默认行角色与档案一致,再通知相关账号重新登录。
重新打开时,旧任职立刻可用,不用重录——这就是「保留记录」的价值。
登录后的权限包会带上multiOrgEnabled、orgList、defaultDeptId和当前deptId。当前部门优先取会话里 overlay 过的值,没有则回用户档案:
privateLongresolveCurrentDeptId(AdminUserDOuser){LongcurrentDeptId=user.getDeptId();LongloginDeptId=SecurityFrameworkUtils.getLoginUserDeptId();if(multiOrgSupport.isEnabled()&&loginDeptId!=null){returnloginDeptId;}returncurrentDeptId;}前端顶栏不自己调my-list拼菜单,而是吃权限接口里的orgList。这样和按钮显隐用的是同一份数据,避免「列表有任职、顶栏没有」。
切换失败时不要只弹「系统错误」,产品码已经写清:
| 错误码语义 | 什么时候出现 | 用户怎么处理 |
|---|---|---|
| 当前租户未启用用户多组织 | 部署或租户开关没开仍调 switch | 先对两道开关 |
| 用户未关联该组织,无法切换 | 改了 deptId 或任职已删 | 回到关联组织检查 |
| 必须且只能指定一个默认组织 | 保存时 0 条或多条默认 | 弹窗里只留一个「默认」 |
| 同一部门不能重复任职 | 两条卡片选了同一个部门 | 删掉重复行 |
| 无法识别当前登录会话 | Access Token 对不上 refresh | 重新登录再切 |
| 默认组织必须至少配置一个角色 | 默认行角色空 | 先给 PRIMARY 配角色 |
十二、设计对照:传统方案 vs 本方案
| 决策点 | 传统 / 错法 | 本方案 | 理由 |
|---|---|---|---|
| 一人两公司 | 两个账号 | 一条用户、多条任职 | 待办、消息、人事主数据不分裂 |
| 当前公司存在哪 | 只放 Vuex | Redis 跟 refresh token + Token userInfo | 刷新 Access Token 不丢 |
| 兼职角色 | 共用全局角色 | 每条任职独立roleIds | 长沙不能看见深圳合同菜单 |
| 关开关 | 清空任职 | 保留,运行时回主档 | 可逆,实施敢关 |
| 无角色任职 | 允许保存 | 默认行禁止空角色;切换时警告 | 防止把自己切进空白工作台 |
| 定时任务 | 读会话部门 | 无 tenantId 视为未开启 | Job 口径稳定 |
十三、技术亮点
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 双开关 | 部署multi-org.enable× 租户multiOrgEnabled | 不是所有客户都要一人多公司 |
| 默认唯一 | 前后端都校验恰好一条 PRIMARY | 关开关有兜底身份 |
| 整表保存 | PUT /save覆盖 + 级联删授权 | 不会留下孤儿角色行 |
| 会话 TTL | max(Access 剩余, 30 天) | 组织偏好活过短 Token |
| 切完硬刷新 | 关页签 +location.href | 动态菜单不会半新半旧 |
| 请求头兜底 | current-dept-id拦截器 | 网关缓存窗口内仍正确 |
| 公司冗余 | 部门向上解析写入companyId | 顶栏、统计不用每次爬树 |
| Job 隔离 | 无线程租户上下文则关闭 | 避免兼职污染批处理 |
十四、FAQ
Q1:能不能只开租户开关、部署保持 false?
不能当生产用法。表单能勾、运行时isEnabled()仍是 false,客户会以为功能坏了。演示或灰度必须两道门一起开。
Q2:切换组织后,已经打开的用车详情还算哪家公司?
页签会被关掉。请重新从列表进。不要在切换后指望旧页签里的表单公司字段自动变——那是另一份已加载的数据。
Q3:兼职没有角色,为什么还允许保存?
默认行不允许空角色。兼职行允许先建部门、后补角色,但顶栏切换会二次确认「菜单会空」。给实施一个「先挂上部门」的窗口,同时不让用户无提示地切进去。
Q4:这和 SaaS 多租户套餐有什么关系?
套餐决定租户能用哪些菜单;多组织决定同一租户里一个人能以几个部门身份工作。可以只开套餐不开多组织,反之则两道开关都要开。
Q5:移动端怎么切?
同一套my-list+switch接口。切完同样要清掉本地路由缓存并重拉权限,不要只改展示用的部门名称。
Q6:薪酬和考勤会不会跟着兼职部门走?
不会。弹窗说明写了:薪酬、考勤、员工档案认默认组织。兼职只影响「当前能点哪些菜单、新单算哪家公司」。要把工资发到长沙,应走人事调动或改默认,而不是顶栏切一下。
Q7:网关缓存 1 分钟,切组织后接口还用旧部门怎么办?
请求头带current-dept-id,后端拦截器校验任职后覆盖本次LoginUser。这是填缓存窗口,不是第二套会话存储。
十五、快速体验
在线演示:https://ruoyioffice.com/web/(账号 admin / admin123)
建议路径:
- 系统管理 → 租户列表 → 编辑当前租户,确认「用户多组织」为开启,且部署已打开
multi-org.enable - 系统管理 → 用户管理,找一个非超管账号,点「关联组织」,加一条其它公司的部门,给独立角色
- 用该账号登录,看顶栏是否出现「公司/部门」
- 切到兼职组织,确认菜单变化、新开单据的抬头公司变了
- 再切回默认,页签应全部关掉并回到首页
- (可选)关掉租户开关,确认顶栏消失、任职弹窗不再展示,重新打开后旧任职还在
实施上线前再对一次清单,避免「功能开了但菜单空」:
| 检查项 | 通过标准 |
|---|---|
部署multi-org.enable | 与要启用的环境一致,默认 false 不要误以为「配了租户就够」 |
租户multiOrgEnabled | 目标租户为开启;其它租户保持关闭也可以 |
| 每个用户恰好一条默认 | 关联组织弹窗里默认单选唯一 |
| 默认行至少一角色 | 关开关后仍能进系统 |
| 兼职角色按需 | 空角色会在切换时警告,不是保存时报错 |
| 顶栏能搜到兼职公司 | 权限接口orgList非空 |
| 切完新单抬头 | 用车/报销所属单位变成当前公司 |
| 薪酬考勤仍认默认 | 不要用顶栏切换当人事调动 |
源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office
结语
一人多公司的正确抽象是任职,不是第二个账号,也不是换租户。默认行保证关开关后还能进系统;兼职行保证菜单和数据范围按当前公司收紧;会话跟 refresh token 走,顶栏切完硬刷新,客户才不会遇到「界面上换了公司、单子还开在原来的部门」。
集团型 OA、连锁门店、项目型组织(人挂多个项目部)都可以复用这套「任职表 + 会话 deptId」。先把两道开关和默认角色配明白,再谈移动端和数据权限口径。
💡想要体验 RuoYi Office 的强大功能?
🌐在线演示:https://ruoyioffice.com/web/(账号 admin / admin123)
📦源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office
💬技术咨询:添加微信17156169080,备注「RuoYi Office」
⭐如果觉得不错,请给个 Star 支持一下!