2026-07-23泰额版平台端操作公司级账号逻辑.md
11.7 KB
2026-07-23 泰额版:平台端操作公司级账号逻辑说明
本文档说明 泰额版 平台管理端如何操作「公司级」账号与权限:架构、接口、写库路径、与公司端登录的关系,以及不得影响美国版的隔离约束。
相关文档:
2026-07-21代码优化.md:公司列表 / 改密 / SaaS 菜单与角色菜单接口细节2026-07-23泰额版当前登录账号菜单接口.md:平台 vs 公司「当前登录人菜单」th-tenant-menu-seed.sql/th-platform-user-role-seed.sql:平台主库菜单与 User/Role 种子
一、角色与库分层
| 端 | 登录 | 用户库 | 典型能力 |
|---|---|---|---|
| 平台端 | POST /api/app/account/login |
主库 antis-foodlabeling-host.user |
公司列表、改公司管理员、分配 SaaS 菜单、改公司角色菜单、开通/删除公司 |
| 公司端 | POST /api/app/th-web-auth/login(须 tenantId) |
该公司业务库 user |
业务功能;GET /api/app/auth-session/my-menus |
公司 = 租户:主库表 YiTenant(前端常把 name 当 companyName)。
flowchart LR
subgraph platform [平台端]
PLogin[account/login]
PHost[(antis-foodlabeling-host)]
PLogin --> PHost
end
subgraph company [公司端]
CLogin[th-web-auth/login]
CBiz[(租户业务库)]
CLogin --> CBiz
end
PHost -->|YiTenant.TenantConnectionString| CBiz
PHost -->|改 admin / 角色菜单| CBiz
1.1 库职责
| 库 | 配置来源 | 平台操作相关表 |
|---|---|---|
主库 antis-foodlabeling-host |
DbConnOptions.Url |
YiTenant、fl_th_tenant_admin_credential、fl_th_tenant_menu_permission、平台 user/role/menu/userrole/rolemenu |
租户业务库 antis-foodlabeling-{key} |
YiTenant.TenantConnectionString |
公司 User(登录哈希)、Role/RoleMenu/Menu、业务表 |
美国版库 antis-foodlabeling-us |
美版 appsettings;泰额 Default 租户连接串也指向此库 |
禁止当作泰额新公司业务库去写平台种子;对 Default 改 admin/角色会动到美版数据 |
1.2 两层「菜单」勿混淆
| 层 | 存储 | 接口 | 语义 |
|---|---|---|---|
| SaaS 公司开通模块 | 主库 fl_th_tenant_menu_permission(menuPermissionKeys) |
menu-permission-tree / company-menus |
该公司「能开哪些产品模块」 |
| 公司 RBAC 菜单 | 租户库 Menu + RoleMenu |
company-roles / company-role-menus;公司端 auth-session/my-menus |
该公司内角色能看哪些业务菜单 |
平台主库种子菜单(th-menu-*)服务于平台端自身路由,不是公司业务库菜单。
二、平台端操作公司账号:能力总览
实现类:ThMultiTenancyAppService(路由前缀 /api/app/th-multi-tenancy/)。
开通公司:ThTenantProvisioningAppService(/api/app/th-tenant-provisioning/)。
| 能力 | 方法 / 路径 | 主要读写 |
|---|---|---|
| 登录页租户下拉(匿名) | GET .../tenant-select |
读主库租户 + 凭据(无 Id) |
| 公司分页列表 | GET .../company-list |
读主库租户 + 凭据 + SaaS keys |
| 改公司管理员账号/密码 | PUT .../company-admin |
写租户库 User + 写主库凭据表 |
| SaaS 权限树 | GET .../menu-permission-tree |
代码目录,无库 |
| 查/设公司 SaaS 菜单 | GET/PUT .../company-menus |
读写主库 fl_th_tenant_menu_permission |
| 查公司角色 | GET .../company-roles |
读租户库 Role/RoleMenu |
| 设公司角色菜单 | PUT .../company-role-menus |
写租户库 RoleMenu |
| 开通公司 | POST /th-tenant-provisioning/provision |
写主库 YiTenant + 建业务库/Init |
| 删除公司 | DELETE .../company/{tenantId} |
软删主库租户;可选 DROP 业务库 |
鉴权:除 tenant-select 外,平台管理接口需 平台 Token(account/login)。
三、核心数据流
3.1 开通公司(产生「公司级账号」)
平台管理员
→ POST /api/app/th-tenant-provisioning/provision
→ 主库写入 YiTenant(Name + TenantConnectionString)
→ EnsureDatabaseCreated(物理建库)
→ 后台 InitAsync:业务库 CodeFirst + Seed
└─ 默认 User.UserName = admin,密码 = RbacOptions.AdminPassword(哈希)
要点:
- 开通不会自动写
fl_th_tenant_admin_credential。 - 列表回显密码:无凭据行时用配置默认密码现场 AES 加密;平台首次
PUT company-admin后才持久化可回显密文。 - Init 异步未完成时,立刻改 admin / 公司登录可能失败。
3.2 平台改公司管理员(重点)
接口:PUT /api/app/th-multi-tenancy/company-admin
1. CurrentTenant=null(主库 scope)
读 YiTenant、fl_th_tenant_admin_credential(当前 LoginAccount,默认 admin)
2. 若有 password + passwordSalt:AES 解密得明文
3. 用 YiTenant.TenantConnectionString 建独立 SqlSugarClient(直连业务库,不靠 Change(tenantId) 切仓储)
4. 在业务库 User 上:
- 可选改 UserName(查重)
- 可选 ApplyPlainPassword → 写 Password/Salt 哈希
5. Upsert 主库 fl_th_tenant_admin_credential(LoginAccount + AES 密文 + IV)
| 写入位置 | 字段语义 |
|---|---|
租户库 User.Password/Salt |
登录校验用哈希(不可逆) |
主库 PasswordCipher/PasswordIv |
列表回显用 AES 密文(可逆,密钥 FoodLabeling:TenantSelectCrypto) |
前端改密必须先按约定 AES 加密再提交,禁止明文密码直传。
3.3 平台分配公司 SaaS 菜单
GET menu-permission-tree → 静态 Key 树(ThSaasMenuPermissionCatalog)
GET company-menus?tenantId= → 读主库已分配 keys
PUT company-menus → 主库先删后插 fl_th_tenant_menu_permission
不写租户库 Menu/RoleMenu。非法 key 会校验失败。
3.4 平台配置公司内角色菜单
GET company-roles?tenantId= → 直连租户库读 Role + RoleMenu.menuIds
PUT company-role-menus → 直连租户库覆盖 RoleMenu(校验 Menu 存在)
这与公司员工登录后看到的 auth-session/my-menus 同源(租户库 RBAC)。
3.5 删除公司
DELETE /api/app/th-multi-tenancy/company/{tenantId}?dropDatabase=true
→ 禁止删除 Default 租户
→ DROP 业务库时禁止库名 host / us(及受保护库)
→ 软删 YiTenant
→ 删凭据表、SaaS 菜单权限、租户缓存
四、跨库访问约定(实现要点)
| 约定 | 说明 |
|---|---|
| 主库表 | 实体带 [DefaultTenantTable];读写前 CurrentTenant.Change(null),连接走 DbConnOptions.Url(host) |
| 租户业务库 | TenantBusinessDatabaseAccessor:按 YiTenant 连接串 独立 SqlSugarClient,避免空连接串误连主库 |
| 不信任 JWT 租户 | 平台改公司数据时以入参 tenantId + 主库登记的连接串为准,不以「当前登录租户」替代目标公司 |
五、与美国版隔离(强制)
| 规则 | 说明 |
|---|---|
| 平台种子只写 host | menu / user / role 等平台数据禁止写入 antis-foodlabeling-us |
| Default 租户 = US 业务库 | host 中 Default 的 TenantConnectionString 指向 antis-foodlabeling-us;对该租户执行 company-admin / company-role-menus 会改美版库 |
| 新公司必须独立库 | Provision 使用 antis-foodlabeling-{tenant} 模板;勿把新公司指到 us |
| 删除保护 | 不可删 Default;不可 DROP host/us |
| Token 隔离 | 平台 Token 调公司 my-menus 会因无租户失败;公司 Token 勿当平台管理凭证 |
建议:平台运营界面操作公司时,只选择泰额独立业务库租户(如 中国麦当劳公司 → antis-foodlabeling-t46d56528),避免点选 Default。
六、接口速查(含 curl)
Base:http://127.0.0.1:19002(以环境为准)。
# 平台登录
curl -X POST "http://127.0.0.1:19002/api/app/account/login" \
-H "Content-Type: application/json" \
-d "{\"userName\":\"admin@example.com\",\"password\":\"123456\"}"
# 公司列表(含加密账号密码)
curl -G "http://127.0.0.1:19002/api/app/th-multi-tenancy/company-list" \
--data-urlencode "skipCount=0" --data-urlencode "maxResultCount=20" \
-H "Authorization: Bearer <platform-token>"
# 改公司管理员(password 为前端 AES 密文)
curl -X PUT "http://127.0.0.1:19002/api/app/th-multi-tenancy/company-admin" \
-H "Authorization: Bearer <platform-token>" \
-H "Content-Type: application/json" \
-d "{\"tenantId\":\"<guid>\",\"loginAccount\":\"admin\",\"password\":\"<cipher>\",\"passwordSalt\":\"<iv>\"}"
# 公司 SaaS 菜单
curl -G "http://127.0.0.1:19002/api/app/th-multi-tenancy/company-menus" \
--data-urlencode "tenantId=<guid>" \
-H "Authorization: Bearer <platform-token>"
# 公司角色
curl -G "http://127.0.0.1:19002/api/app/th-multi-tenancy/company-roles" \
--data-urlencode "tenantId=<guid>" \
-H "Authorization: Bearer <platform-token>"
公司端自测登录(验证平台改密是否生效):
curl -X POST "http://127.0.0.1:19002/api/app/th-web-auth/login" \
-H "Content-Type: application/json" \
-d "{\"tenantId\":\"<guid>\",\"userName\":\"admin\",\"password\":\"新明文密码\"}"
七、关键代码路径
| 模块 | 路径 |
|---|---|
| 平台多租户服务 | FoodLabeling.Th.Application/Services/ThMultiTenancyAppService.cs |
| 开通租户 | FoodLabeling.Th.Application/Services/ThTenantProvisioningAppService.cs |
| 业务库直连 | FoodLabeling.Th.Application/MultiTenancy/TenantBusinessDatabaseAccessor.cs |
| AES 凭据 | FoodLabeling.Th.Application/MultiTenancy/TenantSelectCredentialCipher.cs |
| SaaS Key 目录 | FoodLabeling.Th.Application/MultiTenancy/ThSaasMenuPermissionCatalog.cs |
| 主库凭据实体 | FoodLabeling.Th.Domain/Entities/ThTenantAdminCredentialEntity.cs |
| 主库 SaaS 菜单实体 | FoodLabeling.Th.Domain/Entities/ThTenantMenuPermissionEntity.cs |
| 公司 Web 登录 | FoodLabeling.Th.Application/Services/ThWebAuthAppService.cs |
| 平台登录 | Yi.Framework.Rbac.Application/Services/AccountService.cs |
八、风险与自检
| 风险 | 应对 |
|---|---|
| 对 Default 改密 / 改角色 → 写 US | 运营禁操作 Default;删除已有保护,改密无同等硬拦 |
| 凭据表可逆 AES | 保护 TenantSelectCrypto:SecretKey;仅平台管理员可见列表 |
| 开通未写凭据表 | 改密走平台接口后才会与列表回显一致 |
| Init 未完成就改 admin | 等「后台初始化完成」或调 initialize-tenant-database |
| SaaS keys ≠ RBAC 菜单 | 配置完 company-menus 后,公司内角色仍需 company-role-menus / 公司端角色管理 |
自检清单:
- [ ] 平台用
account/login(hostadmin),公司用th-web-auth/login - [ ]
company-list能看到目标公司 Id 与加密凭据 - [ ]
company-admin后,公司端可用新密码登录;主库凭据表有对应行 - [ ]
company-menus只改 host;company-role-menus只改目标租户库 - [ ]
antis-foodlabeling-us的 user/role 数量未因平台种子脚本变化 - [ ] 未对 Default /
database=us执行 DROP
九、小结
平台端操作公司级账号的本质是:
- 元数据与回显凭据放在主库 host;
- 真正能登录的公司账号在各公司业务库
User; - 平台通过
tenantId+ 直连连接串跨库改公司 admin / 角色菜单; - SaaS 模块开关与公司内 RBAC 是两层模型;
- 永远不要把美国版库当成泰额平台种子库或随意操作 Default 租户。