-- ===================================================================================== -- S8-P0-4-API-AUTHORIZATION-1 · 权限种子(WIP,尚未取正式版本号) -- -- 作用域:**仅当前客户 UAT 租户** -- TenantId = 838257186181189 -- FactoryId = 838257186320453 -- -- 本脚本刻意**不做**全平台 S8 RBAC migration: -- · 不创建其它租户的 ROLE_S8_CONFIG_ADMIN -- · 不给其它 8 个租户的 54 个业务角色批量授权 -- · 不向其它租户开放新能力码的 SysTenantMenu -- 非 UAT 租户在新鉴权上线后会出现 S8 403,已作为 KNOWN RISK 登记,本轮不消除。 -- -- ⚠️ WIP 占位(CLAUDE.md 第二节第 7 条)。提交时 rename 为 1.0..sql 并注册 csproj Copy。 -- -- ⚠️⚠️ 部署顺序硬约束 —— 禁止先 restart 新代码再补权限: -- S8PermissionAttribute 是 fail-closed,未被授予能力码即 403。 -- snapshot → apply → DB verify → restart → **重新登录** → Gate 1→2→3→4 -- (重新登录是必需项:GetOwnBtnPermList 有 7 天缓存 CacheConst.KeyUserButton+userId) -- -- 幂等:所有 INSERT 带 NOT EXISTS;DELETE 谓词精确且可重复执行。 -- Id 段:1329008900000–1329008999999(已实测该段在 SysRole / SysRoleMenu / -- SysUserRole / SysTenantMenu 上全部为空)。 -- -- ── 预计影响行数(窗口期实测不符即停止)──────────────────────────────────── -- §1 SysMenu INSERT 9 新能力码 -- §2 SysTenantMenu INSERT 9 仅 UAT 租户 × 9 -- §3 SysRole INSERT 1 仅 UAT 租户的 ROLE_S8_CONFIG_ADMIN -- §4 SysRoleMenu INSERT 27 见下方逐角色明细 -- §5 SysRoleMenu DELETE 12 UAT 租户 UAT_FUNCTION_USER,15 → 只剩 3 个只读 -- §6 SysUserRole INSERT 1 仅 UATAdminA -- ──────────────────────────────────────────────────────────────────────── -- 合计 = 59 -- -- ── 执行事实(2026-09-03 已在 aidopdev 应用)───────────────────────────── -- 以事务 + 逐段「预期影响行数」断言执行,任一不符即整体 ROLLBACK。 -- 实测:§1=9 §2=9 §3=1 §4=27 §5=12 §6=1,合计 59,与上表逐项一致 → COMMIT。 -- 事务内前置校验全过:UAT_FUNCTION_USER 剩余 s8 权限 3(只读三项); -- 其非 S8 授权 231 前后不变;另一同名角色(租户 838257212780613)15 行不变; -- ROLE_S8_CONFIG_ADMIN 全库恰 1 个(仅 UAT)。 -- ────────────────────────────────────────────────────────────────────────── -- -- ── §4 授权依据(每条都对应可查的 UAT 事实,不能证明会用到的一律不授予)────── -- ROLE_S8_CONFIG_ADMIN → 10 config + debug + exception:create = 12 -- 依据:UATAdminA 需 config/debug;timeline 实测 CREATE 由 UATAdminA 执行 -- ROLE_EQUIP_MAINT → claim,start,verification:submit,create = 4 -- 依据:UATExceptionA 实测执行 CREATE / START_PROGRESS / VERIFY_SUBMITTED, -- 且名下 GEX-019-EQ(IN_PROGRESS) 与新异常 1329909430019(NEW) 待处理 -- ROLE_PURCHASER → claim,start,verification:submit = 3 -- 依据:emp 3183(梁军/UATPurchaseA) 为 GEX-002-MP / GEX-002-ETA / GEX-012-MP 处理人 -- ROLE_WH_INBOUND → claim,start,verification:submit = 3 -- ROLE_WH_OUTBOUND → claim,start,verification:submit = 3 -- 依据:emp 3179(章立坤/UATWarehouseA) 名下 3 条**未闭**: -- GEX-019-KIT(ASSIGNED) / GEX-020-SHIP(IN_PROGRESS) / GEX-020-PEND(ASSIGNED) -- ROLE_QC → verification:approve, verification:reject = 2 -- 依据:UATQualityA 实测执行 VERIFY_APPROVED / VERIFY_REJECTED -- ROLE_ORDER_PLANNER / ROLE_PRODUCTION_PLANNER → 0 -- 依据:UATPlanA 在 UAT 数据中**从未**作为任何异常的处理人/检验人; -- 只读能力经保留的 UAT_FUNCTION_USER 获得,无需额外授权 -- -- 刻意未授予(无 UAT 证据,需要时再单独加,勿"保险式"补齐): -- s8:exception:assign(转派) · s8:exception:reject · s8:exception:upgrade -- · s8:exception:comment · Purchase/Warehouse 的 s8:exception:create -- ────────────────────────────────────────────────────────────────────────── -- ===================================================================================== -- ===================================================================================== -- §0 执行前只读快照(窗口期第 1 步,留档后再执行写入) -- ===================================================================================== -- (0.1) UAT 租户 UAT_FUNCTION_USER 当前 s8:* 权限(预期 15 行) SELECT m.`Id` AS MenuId, m.`Permission` FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND m.`Permission` LIKE 's8:%' ORDER BY m.`Permission`; -- (0.2) 6 个验收账号当前角色 SELECT u.`Account`, r.`Code` AS RoleCode FROM `SysUser` u JOIN `SysUserRole` ur ON ur.`UserId` = u.`Id` JOIN `SysRole` r ON r.`Id` = ur.`RoleId` WHERE u.`TenantId` = 838257186181189 ORDER BY u.`Account`, r.`Code`; -- (0.3) ROLE_S8_CONFIG_ADMIN 是否已存在(预期 0) SELECT COUNT(*) AS existing_config_admin FROM `SysRole` WHERE `Code` = 'ROLE_S8_CONFIG_ADMIN'; -- (0.4) §5 预计删除行数(预期 12) SELECT COUNT(*) AS expect_delete FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND m.`Permission` LIKE 's8:%' AND m.`Permission` NOT IN ('s8:exception:read','s8:config:read','s8:dashboard:read'); -- (0.5) 该角色非 S8 授权数(§5 前后必须完全相等;实测基线 = 231) SELECT COUNT(*) AS non_s8_rows FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND (m.`Permission` IS NULL OR m.`Permission` NOT LIKE 's8:%'); -- (0.6) 另一个同名角色(租户 838257212780613)基线,§5 后必须不变(预期 15) SELECT COUNT(*) AS other_tenant_s8_rows FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257212780613) AND m.`Permission` LIKE 's8:%'; -- ===================================================================================== -- §1 新增 9 个 S8 能力码(SysMenu Type=3 Btn)。菜单本身是全局元数据, -- 租户可见性由 §2 的 SysTenantMenu 控制,故此处不带租户维度。 -- ===================================================================================== INSERT INTO `SysMenu` (`Id`,`Pid`,`Title`,`Name`,`Type`,`Permission`,`OrderNo`,`Status`, `IsHide`,`IsKeepAlive`,`IsAffix`,`IsIframe`,`Remark`,`CreateTime`) SELECT src.* FROM ( SELECT 1329008000115 AS Id, 1329008000016 AS Pid, '维护监控规则' AS Title, 'aidopS8Btn15' AS Name, 3 AS Type, 's8:config:watch-rule' AS Permission, 240 AS OrderNo, 1 AS Status, 0 AS IsHide, 1 AS IsKeepAlive, 0 AS IsAffix, 0 AS IsIframe, 'S8 权限点 s8:config:watch-rule' AS Remark, NOW() AS CreateTime UNION ALL SELECT 1329008000116, 1329008000015, '维护数据源', 'aidopS8Btn16', 3, 's8:config:data-source', 250, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:data-source', NOW() UNION ALL SELECT 1329008000117, 1329008000017, '维护异常类型', 'aidopS8Btn17', 3, 's8:config:exception-type', 260, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:exception-type', NOW() UNION ALL SELECT 1329008000118, 1329008000012, '维护通知分层', 'aidopS8Btn18', 3, 's8:config:notification', 270, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:notification', NOW() UNION ALL SELECT 1329008000119, 1329008000011, '维护场景配置', 'aidopS8Btn19', 3, 's8:config:scene', 280, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:scene', NOW() UNION ALL SELECT 1329008000120, 1329008000019, '维护KPI目标值', 'aidopS8Btn20', 3, 's8:config:kpi', 290, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:kpi', NOW() UNION ALL SELECT 1329008000121, 1329008000018, '维护大屏卡片', 'aidopS8Btn21', 3, 's8:config:dashboard', 300, 1, 0, 1, 0, 0, 'S8 权限点 s8:config:dashboard', NOW() UNION ALL SELECT 1329008000122, 1329008000010, '补充说明', 'aidopS8Btn22', 3, 's8:exception:comment', 90, 1, 0, 1, 0, 0, 'S8 权限点 s8:exception:comment', NOW() UNION ALL SELECT 1329008000123, 1329008000004, '运维调试接口', 'aidopS8Btn23', 3, 's8:debug:run', 900, 1, 0, 1, 0, 0, 'S8 权限点 s8:debug:run', NOW() ) AS src WHERE NOT EXISTS (SELECT 1 FROM `SysMenu` m WHERE m.`Id` = src.Id OR m.`Permission` = src.Permission); -- ===================================================================================== -- §2 租户可见性 —— **仅 UAT 租户**。 -- GetOwnBtnPermList 对非超管 INNER JOIN SysTenantMenu,不建这些行则取不到新能力码。 -- 其它租户刻意不建:非本轮验收作用域(见文件头 KNOWN RISK)。 -- ===================================================================================== INSERT INTO `SysTenantMenu` (`Id`, `TenantId`, `MenuId`) SELECT 1329008900000 + ROW_NUMBER() OVER (ORDER BY s.MenuId), 838257186181189, s.MenuId FROM ( SELECT m.`Id` AS MenuId FROM `SysMenu` m WHERE m.`Id` BETWEEN 1329008000115 AND 1329008000123 AND NOT EXISTS (SELECT 1 FROM `SysTenantMenu` x WHERE x.`TenantId` = 838257186181189 AND x.`MenuId` = m.`Id`) ) s; -- ===================================================================================== -- §3 仅为 UAT 租户创建 ROLE_S8_CONFIG_ADMIN -- ⚠️ 不加入 S8CanonicalRole.BusinessRoles —— 那是 CFG_NOTIFY 角色候选的唯一来源, -- 已被 Runtime Certification 钉死「恰好 7 个」,加进去会当场改掉已认证契约。 -- ===================================================================================== INSERT INTO `SysRole` (`Id`,`Name`,`Code`,`OrderNo`,`DataScope`,`Remark`,`Status`,`TenantId`,`CreateTime`) SELECT 1329008910001, 'S8配置管理员', 'ROLE_S8_CONFIG_ADMIN', 810, 1, 'S8-P0-4:S8 配置类接口的授权载体;能力经 SysRoleMenu 授予,业务代码不判 RoleCode。仅 UAT 租户。', 1, 838257186181189, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `SysRole` r WHERE r.`TenantId` = 838257186181189 AND r.`Code` = 'ROLE_S8_CONFIG_ADMIN'); -- ===================================================================================== -- §4 授权(全部 additive;仅 UAT 租户的角色;每条对应文件头列出的 UAT 依据) -- 只读能力(exception:read / config:read / dashboard:read)由保留的 UAT_FUNCTION_USER -- 统一提供,6 个账号都有,故此处不重复授予。 -- ===================================================================================== INSERT INTO `SysRoleMenu` (`Id`, `RoleId`, `MenuId`) SELECT 1329008920000 + ROW_NUMBER() OVER (ORDER BY s.RoleId, s.MenuId), s.RoleId, s.MenuId FROM ( -- §4.1 ROLE_S8_CONFIG_ADMIN → 10 个 config + debug + exception:create (12) SELECT r.`Id` AS RoleId, m.`Id` AS MenuId FROM `SysRole` r CROSS JOIN `SysMenu` m WHERE r.`TenantId` = 838257186181189 AND r.`Code` = 'ROLE_S8_CONFIG_ADMIN' AND m.`Permission` IN ('s8:config:read','s8:config:operator:bind','s8:config:role:write', 's8:config:watch-rule','s8:config:data-source','s8:config:exception-type', 's8:config:notification','s8:config:scene','s8:config:kpi','s8:config:dashboard', 's8:debug:run','s8:exception:create') UNION ALL -- §4.2 ROLE_EQUIP_MAINT(UATExceptionA)→ operator 最小集 + 提报 (4) SELECT r.`Id`, m.`Id` FROM `SysRole` r CROSS JOIN `SysMenu` m WHERE r.`TenantId` = 838257186181189 AND r.`Code` = 'ROLE_EQUIP_MAINT' AND m.`Permission` IN ('s8:exception:claim','s8:exception:start','s8:verification:submit', 's8:exception:create') UNION ALL -- §4.3 采购 / 仓储(UATPurchaseA、UATWarehouseA)→ operator 最小集 (3 × 3 = 9) SELECT r.`Id`, m.`Id` FROM `SysRole` r CROSS JOIN `SysMenu` m WHERE r.`TenantId` = 838257186181189 AND r.`Code` IN ('ROLE_PURCHASER','ROLE_WH_INBOUND','ROLE_WH_OUTBOUND') AND m.`Permission` IN ('s8:exception:claim','s8:exception:start','s8:verification:submit') UNION ALL -- §4.4 ROLE_QC(UATQualityA)→ 仅质检复核,**不含**任何 operator 能力 (2) SELECT r.`Id`, m.`Id` FROM `SysRole` r CROSS JOIN `SysMenu` m WHERE r.`TenantId` = 838257186181189 AND r.`Code` = 'ROLE_QC' AND m.`Permission` IN ('s8:verification:approve','s8:verification:reject') ) s WHERE NOT EXISTS (SELECT 1 FROM `SysRoleMenu` x WHERE x.`RoleId` = s.RoleId AND x.`MenuId` = s.MenuId); -- ===================================================================================== -- §5 收口 UAT 租户的 UAT_FUNCTION_USER:只保留 3 个公共只读,其余 S8 能力全部撤销 -- (config 写 / debug / operator / verification 由各自专用角色重新提供) -- -- 约束逐条落实: -- · RoleId 按 (Code, TenantId) 精确定位 —— 该 Code 在 838257212780613 也存在, -- §0.6 / §7.4 前后对比确保另一租户不受影响; -- · 只删 s8:* 且不在保留名单内的行,**不触碰任何非 S8 权限**(基线 231 行,前后相等); -- · 不重建菜单、不覆盖 SysRoleMenu。 -- 预计 12 行(15 - 3)。含已废弃的 s8:exception:close(无对应 Action,属 operator 类,一并收回)。 -- ===================================================================================== DELETE rm FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND m.`Permission` LIKE 's8:%' AND m.`Permission` NOT IN ('s8:exception:read','s8:config:read','s8:dashboard:read'); -- ===================================================================================== -- §6 仅把 ROLE_S8_CONFIG_ADMIN 绑给 UATAdminA(预计 1 行) -- 明确不绑:UATExceptionA / UATQualityA / UATPlanA / UATPurchaseA / UATWarehouseA -- ===================================================================================== INSERT INTO `SysUserRole` (`Id`, `UserId`, `RoleId`) SELECT 1329008990001, u.`Id`, r.`Id` FROM `SysUser` u JOIN `SysRole` r ON r.`TenantId` = u.`TenantId` AND r.`Code` = 'ROLE_S8_CONFIG_ADMIN' WHERE u.`Account` = 'UATAdminA' AND u.`TenantId` = 838257186181189 AND NOT EXISTS (SELECT 1 FROM `SysUserRole` x WHERE x.`UserId` = u.`Id` AND x.`RoleId` = r.`Id`); -- ===================================================================================== -- §7 执行后 DB verify(窗口期第 3 步;任一不符即停止,不得 restart) -- ===================================================================================== SELECT COUNT(*) AS new_menus FROM `SysMenu` WHERE `Id` BETWEEN 1329008000115 AND 1329008000123; -- 期望 9 SELECT COUNT(*) AS uat_tenant_menus FROM `SysTenantMenu` WHERE `TenantId`=838257186181189 AND `MenuId` BETWEEN 1329008000115 AND 1329008000123; -- 期望 9 SELECT COUNT(*) AS config_admin_roles FROM `SysRole` WHERE `Code`='ROLE_S8_CONFIG_ADMIN'; -- 期望 1(仅 UAT) SELECT COUNT(*) AS new_grants FROM `SysRoleMenu` WHERE `Id` BETWEEN 1329008920001 AND 1329008920999; -- 期望 27 -- (7.3) UAT_FUNCTION_USER 只剩 3 个只读;非 S8 授权数不变 SELECT m.`Permission` FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND m.`Permission` LIKE 's8:%' ORDER BY m.`Permission`; -- 期望恰好 3 行:s8:config:read / s8:dashboard:read / s8:exception:read SELECT COUNT(*) AS non_s8_rows_after FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257186181189) AND (m.`Permission` IS NULL OR m.`Permission` NOT LIKE 's8:%'); -- 期望 231 -- (7.4) 另一租户同名角色未被误伤 SELECT COUNT(*) AS other_tenant_s8_rows FROM `SysRoleMenu` rm JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE rm.`RoleId` = (SELECT `Id` FROM `SysRole` WHERE `Code`='UAT_FUNCTION_USER' AND `TenantId`=838257212780613) AND m.`Permission` LIKE 's8:%'; -- 期望 15(不变) -- (7.5) 6 个账号的有效 S8 能力(Gate 4 逐项预期) SELECT u.`Account`, m.`Permission` FROM `SysUser` u JOIN `SysUserRole` ur ON ur.`UserId` = u.`Id` JOIN `SysRoleMenu` rm ON rm.`RoleId` = ur.`RoleId` JOIN `SysMenu` m ON m.`Id` = rm.`MenuId` WHERE u.`TenantId` = 838257186181189 AND m.`Permission` LIKE 's8:%' GROUP BY u.`Account`, m.`Permission` ORDER BY u.`Account`, m.`Permission`; -- 期望: -- UATAdminA : 3 只读 + 10 config + debug:run + exception:create (15) -- UATExceptionA : 3 只读 + claim/start/verification:submit/create (7) -- UATQualityA : 3 只读 + verification:approve/reject (5) -- UATPurchaseA : 3 只读 + claim/start/verification:submit (6) -- UATWarehouseA : 3 只读 + claim/start/verification:submit (6,两角色并集去重后同) -- UATPlanA : 3 只读 (3)