diff --git a/superpowers/specs/2026-06-07-sys-app-management-design.md b/superpowers/specs/2026-06-07-sys-app-management-design.md index defb737..befe102 100644 --- a/superpowers/specs/2026-06-07-sys-app-management-design.md +++ b/superpowers/specs/2026-06-07-sys-app-management-design.md @@ -24,8 +24,7 @@ 1. 在 `rui-service-system` 增加 `SysApp` 实体 + CRUD + 内部接口 2. 支持多租户:每条记录区分 `owner_type=PLATFORM`(平台默认)或 `TENANT`(租户自配) 3. 凭证字段完整:覆盖社交登录 + 第三方支付 + AES 对称加密 + 证书文件 -4. `OAuth2ServerConfig` 改为运行时从 `SysApp` 拉凭证,Redis 缓存 30min -5. 租户隔离按 `client_id → tenant_id` 映射 +4. `OAuth2ServerConfig` 改为运行时从 `SysApp` 拉凭证,Redis 缓存 30min,**用 `appId` 作为唯一标识**(来自请求头 `X-App-Id`) --- @@ -80,6 +79,7 @@ CREATE TABLE sys_app ( updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), PRIMARY KEY (id), UNIQUE KEY uk_tenant_owner_platform (tenant_id, owner_type, platform, status), + UNIQUE KEY uk_app_id (app_id), INDEX idx_tenant (tenant_id), INDEX idx_platform (platform), INDEX idx_status (status) @@ -139,29 +139,28 @@ com.rui.common.oauth2.cache/ ``` 1. POST /oauth2/token?grant_type=wechat&code=xxx + Header: X-App-Id: wx1234567890 ↓ 2. authorizationServerFilterChain 被调用 ↓ -3. 从 OAuth2ClientAuthenticationToken 取 clientId +3. 从请求头 X-App-Id 取 appId ↓ -4. 查 rui_auth_oauth2_client 表 → 取 tenantId - ↓ -5. AppCredentialsCache.get(platform=wechat, tenantId) +4. AppCredentialsCache.get(appId) ├─ HIT → 直接返回 - └─ MISS → Feign: GET /system/inner/app/getCredentials?platform=wechat&tenantId={tenantId} + └─ MISS → Feign: GET /system/inner/app/getCredentials?appId={appId} ↓ SysAppInnerController 查 sys_app: - - 优先 owner_type=TENANT AND tenant_id={tid} AND status=1 - - 缺省 owner_type=PLATFORM AND tenant_id=0 AND status=1 - - 都找不到 → 返回 404(throw new UsernameNotFoundException 或 BizException) + - WHERE app_id = {appId} AND status = 1 + (app_id 字段已加 UNIQUE 约束) + - 找不到 → 返回 404 ↓ 把 SysApp 转 AppCredentialsVO 返回 ↓ - oauth2 端写 Redis: SET app:creds:{platform}:{tenantId} = json EX 1800 + oauth2 端写 Redis: SET app:creds:{appId} = json EX 1800 ↓ -6. 拿到 AppCredentialsVO → 构造 WechatApiClient(appId, appSecret) 等 +5. 拿到 AppCredentialsVO → 构造 WechatApiClient(appId, appSecret) 等 ↓ -7. 走完登录流程,返回 token +6. 走完登录流程,返回 token ``` ### 5.2 租户管理自己的 SysApp @@ -171,10 +170,11 @@ com.rui.common.oauth2.cache/ 2. POST /system/app (Body: SysAppSaveDTO) - owner_type=TENANT - platform=wechat + - app_id = "wx9999999" - tenant_id = 当前用户 tenantId - - 业务校验:uk_tenant_owner_platform 唯一性 + - 业务校验:uk_tenant_owner_platform + uk_app_id 唯一性 3. 写 DB -4. 删缓存:DEL app:creds:wechat:{tenantId} +4. 删缓存:DEL app:creds:{app_id} 5. 返回成功 ``` @@ -185,22 +185,23 @@ com.rui.common.oauth2.cache/ 2. POST /system/app - owner_type=PLATFORM - platform=wechat + - app_id = "wx1234567" - tenant_id=0 -3. 删缓存:DEL app:creds:wechat:0 -4. 所有未自配的租户下次登录都会拉到这条 +3. 删缓存:DEL app:creds:wx1234567 +4. 所有未自配(uk_app_id 不冲突)的租户下次登录会用 appId=wx1234567 拉这条 ``` --- ## 6. 缓存策略 -- **Key**: `app:creds:{platform}:{tenantId}`(tenantId=0 表示 PLATFORM 默认) +- **Key**: `app:creds:{appId}`(用 `app_id` 唯一标识,不用 platform/tenantId 因为多租户都叫 platform=wechat 会冲突) - **TTL**: 30 分钟(1800 秒) - **失效时机**: - `SysApp` CRUD 写操作完成后 - `SysApp` 启/禁用操作后 - 超管强制刷新(可选接口) -- **防穿透**:缓存空对象 5 分钟(针对不存在的 tenantId) +- **防穿透**:缓存空对象 5 分钟(针对不存在的 appId) - **序列化**:用 fastjson2 序列化 `AppCredentialsVO` --- @@ -213,7 +214,7 @@ com.rui.common.oauth2.cache/ |---|---| | `OAuth2ServerConfig` | 删 `@Bean @ConfigurationProperties` 声明 wechat/alipay AppProperties | | 同上 | 注入 `SysAppFeign` + `AppCredentialsCache` | -| `authorizationServerFilterChain` | 根据 grant_type 动态选 platform,调用 `appCredentialsCache.get(platform, tenantId)` 拿凭证 | +| `authorizationServerFilterChain` | 从 `request.getHeader("X-App-Id")` 拿 appId,调 `appCredentialsCache.get(appId)` 拿凭证 | ### 7.2 兼容与过渡