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(前端常把 namecompanyName)。

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 YiTenantfl_th_tenant_admin_credentialfl_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_permissionmenuPermissionKeys 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 外,平台管理接口需 平台 Tokenaccount/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(host admin),公司用 th-web-auth/login
  • [ ] company-list 能看到目标公司 Id 与加密凭据
  • [ ] company-admin 后,公司端可用新密码登录;主库凭据表有对应行
  • [ ] company-menus 只改 host;company-role-menus 只改目标租户库
  • [ ] antis-foodlabeling-us 的 user/role 数量未因平台种子脚本变化
  • [ ] 未对 Default / database=us 执行 DROP

九、小结

平台端操作公司级账号的本质是:

  1. 元数据与回显凭据放在主库 host;
  2. 真正能登录的公司账号在各公司业务库 User
  3. 平台通过 tenantId + 直连连接串跨库改公司 admin / 角色菜单;
  4. SaaS 模块开关与公司内 RBAC 是两层模型;
  5. 永远不要把美国版库当成泰额平台种子库或随意操作 Default 租户。