status: done created: 2026-07-06
execution-log.md 记录命令、关键输出、失败原因或通过证据。execution-log.md、spec.md、tasks.md 和 code-copilot/rules/automated-testing-standard.md。| 项目 | 值 |
|---|---|
| 后端测试 | JUnit 5 / Spring Boot Test / Mockito(按现有模块测试依赖为准) |
| 前端测试 | 以 pnpm build 为 P0;如现有 Vitest 覆盖相关组件则补定向用例 |
| 数据库验证 | Flyway 静态检查 + 可用 MySQL 环境下接口/SQL 验证 |
| 权限验证 | 后端服务测试 + 登录后接口验证 |
UserLoadServiceImpl| 方法 | 场景 | 输入 | Mock/数据准备 | 预期结果 |
|---|---|---|---|---|
loadUserByUserId(userId, tenantId, preferredActiveOrgId) |
用户在组织 A/B 分别拥有不同角色,preferred 为 A | tenantId=T1, activeOrgId=A |
sys_user_org 有 A/B,sys_user_org_role A=R1、B=R2 |
LoginUser.activeOrgId=A,roleIds=[R1],不包含 R2 |
| 同上 | preferred 不属于用户组织 | activeOrgId=X |
用户组织只有 A/B,主组织 B | 当前组织回退 B |
| 同上 | 当前组织无角色 | activeOrgId=A |
A 无 sys_user_org_role |
roleIds/permissions/apiPermissions 为空,不能继承 B |
loadUserByUsername(username, tenantId, preferredActiveOrgId) |
/auth/userInfo 刷新保留当前组织 |
Session 原 activeOrgId=A |
A 仍有效 | 刷新后仍是 A |
SysOrgServiceImpl| 方法 | 场景 | 输入 | Mock/数据准备 | 预期结果 |
|---|---|---|---|---|
switchCurrentOrg |
切到当前数据中心已加入组织 | orgId=A |
用户属于 A | 返回重算后 LoginUser,Session 写入 A |
switchCurrentOrg |
切到未加入组织 | orgId=X |
用户不属于 X | 抛业务异常 |
switchCurrentOrg |
切到其他数据中心组织 | orgId=OtherTenantOrg |
组织 tenant_id != currentTenantId |
抛业务异常 |
SysUserServiceImpl| 方法 | 场景 | 输入 | Mock/数据准备 | 预期结果 |
|---|---|---|---|---|
bindUserOrgRoles |
给组织成员授权适用角色 | userId=U, orgId=A, roleIds=[R1] |
U 属于 A,R1 适用于 A | 写入 sys_user_org_role |
bindUserOrgRoles |
角色不适用于目标组织 | orgId=B, roleIds=[R1] |
R1 只适用于 A | 拒绝授权 |
bindUserOrgs |
移除组织 | 原 A/B,保存 A | B 下存在角色授权 | 删除 B 的 sys_user_org_role |
DataScopeServiceImpl| 方法 | 场景 | 输入 | Mock/数据准备 | 预期结果 |
|---|---|---|---|---|
getCurrentUserDataScope |
ORG 数据范围 |
activeOrgId=A, orgIds=[A,B] |
当前角色数据范围 ORG |
context.orgIds=[A] |
| 同上 | ORG_AND_CHILD 数据范围 |
activeOrgId=A |
A 子组织 A1/A2 | 拦截器使用 A/A1/A2,不含 B |
| 同上 | 无当前组织 | activeOrgId=null |
普通用户角色为 ORG |
后续 SQL 条件强制无数据 |
DynamicDataScopeService| 方法 | 场景 | 输入 | Mock/数据准备 | 预期结果 |
|---|---|---|---|---|
buildCondition |
低代码 FOLLOW_SYSTEM + ORG | context.orgIds=[A] |
模型配置 orgColumn=org_id |
SQL 条件只包含 A |
buildCondition |
ORG_AND_CHILD | activeOrgId=A |
A 子组织 A1 | SQL 条件包含 A/A1,不含 B |
| Mapper | 场景 | 输入 | 预期结果 |
|---|---|---|---|
SysUserOrgRoleMapper |
按用户当前组织查启用且适用角色 | tenantId,userId,orgId |
只返回该组织有效角色 |
SysRoleMapper.selectRoleUsers |
按角色和组织查询用户 | roleId,orgId |
只返回该组织授权用户 |
BusinessMessageChannelMapper.selectUserIdsByRoleIds |
按当前组织角色找用户 | tenantId,orgId,roleIds |
不返回其他组织用户 |
| 接口 | 场景 | 请求 | 预期结果 |
|---|---|---|---|
POST /system/org/switch |
用户切换到组织 A | orgId=A |
返回 activeOrgId=A,权限为 A 的角色 |
GET /system/org/current/options |
查询可切组织 | 当前登录用户 | 只返回当前数据中心已加入组织 |
POST /system/user/{id}/org-roles |
保存组织角色 | orgId + roleIds |
成功后查询回显一致 |
POST /system/role/{id}/orgs |
保存角色适用组织 | orgIds |
成功后不适用组织不能分配 |
| 页面/组件 | 场景 | 验证 |
|---|---|---|
OrgSwitcher.vue |
多组织用户切换组织 | 切换后清理菜单缓存并重新加载首页 |
system/user.vue |
用户绑定多个组织和主组织 | 保存后列表展示多个组织,重新打开回显 |
system/user.vue |
用户在组织内授权角色 | 选择组织 A/B 分别回显不同角色 |
system/role.vue |
角色适用组织配置 | 保存后可回显,并限制用户授权角色列表 |
| 模块 | 场景 | 预期结果 |
|---|---|---|
| 数据集 ACL | ACL 按 ROLE/ORG 授权 | 只匹配当前组织和当前组织角色 |
| 流程启动变量 | 发起流程时注入角色变量 | startUserRoleIds 为当前组织角色 |
| 业务消息 | 角色接收人 | 只发给当前组织或业务指定组织内拥有该角色的用户 |
git diff --check。mvn -pl forge-admin-server -am compile -DskipTests。UserLoadServiceImpl、SysOrgServiceImpl、SysUserServiceImpl、DataScopeServiceImpl 编写 P0 测试,先确认 Red。source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build。execution-log.md、tasks.md 和 spec.md。| 时间 | 范围 | 命令 | 结果 | 备注 |
|---|---|---|---|---|
| 2026-07-06 | 建档 | 未执行代码验证 | 未执行 | 本轮仅生成 SDD 文档 |
| 2026-07-06 | Apply 增量验证 | 后端编译 + 前端构建 + 静态检查 | 通过 | 未启动服务/数据库做接口实测 |
| 时间 | 变更范围 | 必跑项 | 实际命令 | 结果 | 跳过/警告 |
|---|---|---|---|---|---|
| 2026-07-06 | SDD 文档生成 | 文档结构检查 | git diff --check -- code-copilot/changes/org-scoped-role-permission |
通过 | 建档阶段轻量检查 |
| 2026-07-06 | 组织上下文权限改造 | 后端最小编译 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests |
通过 | 仅既有 deprecation/unchecked/Lombok 警告 |
| 2026-07-06 | 前端组织切换/用户角色/角色组织范围 | 前端构建 | source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build |
通过 | 仅既有 chunk/CSS/组件命名警告 |
| 2026-07-06 | Flyway 脚本 | 占位符静态扫描 | rg -n '\$\{[^}]+\}' forge-server/db/migration |
通过 | 无输出 |
| 2026-07-06 | 全量 diff | 空白检查 | git diff --check |
通过 | 无输出 |
| 2026-07-06 | 当前组织写入链路 | 自动填充修正后后端最小编译 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests |
通过 | create_dept 改为优先写 activeOrgId 后复跑;仅既有 deprecation/unchecked/Lombok 警告 |
| 2026-07-06 | 服务级验证准备 | 本地 MySQL/Redis 可用性探测 | mysqladmin --protocol=tcp -h127.0.0.1 -P3407 -uroot ping; mysqladmin --protocol=tcp -h127.0.0.1 -P3306 -uroot ping; redis-cli -h 127.0.0.1 -p 6379 ping |
跳过接口实测 | 3407 MySQL 未运行;3306 root 无密码访问被拒;Redis 探测受沙箱限制且提权未获批。未启动后端,未写数据库 |
| 2026-07-06 | Flyway/全量 diff | 占位符扫描 + 空白检查 | rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check |
通过 | Flyway 扫描无输出;diff 检查无输出 |
| 2026-07-06 | Review 修复:服务权限兜底、旧角色加人阻断、角色加人直接组织筛选、消息业务组织上下文 | 后端最小编译 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests |
通过 | Reactor BUILD SUCCESS;仅既有 deprecation/unchecked 警告 |
| 2026-07-06 | Review 修复:前端角色用户选择直接组织筛选 | 前端构建 | source ~/.nvm/nvm.sh && nvm use v20.19.0 && NODE_OPTIONS=--max-old-space-size=8192 pnpm --dir forge-admin-ui build |
通过 | Node v20.19.0 可用;仅既有 CSS // 注释、动态/静态导入和组件命名冲突提示 |
| 2026-07-06 | Review 修复后静态检查 | 占位符扫描 + 空白检查 | rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check |
通过 | Flyway 扫描无输出;diff 检查无输出 |
| 2026-07-06 | Review 修复后服务级验证 | 接口/数据库实测 | 未启动后端服务 | 跳过 | 本轮未引入新 Flyway;当前 dev 库仍不适合作为自动验证目标,服务级真实登录/组织授权/消息落库待可控本地 MySQL/Redis 环境补跑 |
| 2026-07-06 | 超级管理员组织选项边界修复 | 后端最小编译 + 空白检查 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; git diff --check |
通过 | 超管有组织绑定时只返回/允许切换绑定组织;仅无绑定超管保留全量兜底。未改前端,未重复跑前端构建 |
| 2026-07-06 | 超级管理员显式组织绑定边界二次修正 | 后端最小编译 + Flyway 占位符扫描 + 空白检查 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check |
通过 | options/switch 直接查询 sys_user_org 区分真实绑定和历史超管全量兜底;本轮未改前端,扁平下拉展示不变 |
| 2026-07-06 | 超级管理员组织边界 Mapper XML 收敛 | 后端最小编译 + Flyway 占位符扫描 + 空白检查 | env JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin:$PATH mvn -pl forge-admin-server -am compile -DskipTests; rg -n '\$\{[^}]+\}' forge-server/db/migration; git diff --check |
通过 | 新增 SysUserOrgMapper.xml 和 SysOrgMapper.selectEnabledOrgsByIds,显式绑定查询不在 Service 层拼接;本轮未改前端 |
execution-log.md:记录每次验证命令、结果、跳过项、服务启动和清理情况。/auth/login、/auth/userInfo、/auth/current/menu、/system/org/switch、/system/user/{id}/org-roles。sys_role_org、sys_user_org_role、sys_user_org、sys_role、sys_role_resource。