2026-07-13泰额版多租户-前端优化.md 19.2 KB

2026-07-13 泰额版多租户 — 前端优化说明

本文档供 泰额版 Web 管理端 / App 对接多租户后端时使用。后端已完成独立库多租户改造,前端尚未改动

后端架构说明见 项目相关文档/5-19泰额版.md;本次后端结构变更见下文「后端已完成项」。


一、多租户对前端的核心影响

变更点 说明
数据库 每个租户独立业务库;平台主库仅存 yitenant
业务 API 必须在租户上下文下调用,否则连主库、列表为空或报错
登录 须先选租户,再登录;Token 须含 TenantId Claim
旧接口 POST /api/app/account/login 不能单独用于泰额多租户 Web 登录
旧 App 登录 us-app-auth/login 在多租户模式下已禁用(返回友好错误)

租户上下文传递方式(二选一)

方式 说明
JWT 使用泰额登录接口返回的 Token(已含 TenantId
请求头 __tenant: {租户Guid}(与 Token 中租户一致)

二、登录流程改造(必做)

2.1 Web 管理端推荐流程

1. GET  /api/app/th-multi-tenancy/tenant-select   → 租户下拉
2. POST /api/app/th-web-auth/login                → Web 登录(含 RBAC Token)
3. 后续业务 API 带 Authorization: Bearer {token}

租户下拉(匿名可访问)

GET /api/app/th-multi-tenancy/tenant-select

出参示例:

[
  { "id": "11111111-1111-1111-1111-111111111111", "name": "Default" }
]

Web 管理端登录(新接口,替代 account/login)

POST /api/app/th-web-auth/login
Content-Type: application/json
{
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "userName": "admin@example.com",
  "password": "YourPassword1!",
  "uuid": null,
  "code": null
}

出参:

字段 说明
token JWT(含 TenantId + 角色/菜单权限 Claims)
refreshToken 刷新令牌
tenantId 当前租户 Id
tenantName 租户名称

不要再使用 POST /api/app/account/login 作为泰额 Web 主登录入口(无 tenantId,会查主库无用户)。

2.2 泰额 App 登录(已有,保持不变)

POST /api/app/th-app-auth/login
{
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "email": "user@example.com",
  "password": "YourPassword1!"
}

2.3 默认租户(迁移期)

tenantId 11111111-1111-1111-1111-111111111111
name Default
业务库 antis-foodlabeling-us(现有数据)

开发联调可先用 Default 租户。


三、HTTP 客户端改造(必做)

3.1 Axios / fetch 封装

登录成功后:

  1. 持久化 tokenrefreshTokentenantIdtenantName
  2. 所有业务请求 Header 带 Authorization: Bearer {token}
  3. (可选)同时带 __tenant: {tenantId},与 Token 保持一致
// 伪代码示例
api.interceptors.request.use((config) => {
  const token = getToken();
  const tenantId = getTenantId();
  if (token) config.headers.Authorization = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
  if (tenantId) config.headers["__tenant"] = tenantId;
  return config;
});

3.2 须改造的现有服务文件(泰额 Web)

文件 改动
src/services/accountService.ts 登录改为 th-web-auth/login,入参增加 tenantId
新建 src/services/tenantService.ts 封装 tenant-selectcurrent-tenant
src/lib/apiClient.ts 请求拦截器附加 __tenant
src/lib/authStorage.ts 存储 tenantId / tenantName
登录页组件 增加租户下拉(在邮箱密码之前)

3.3 退出登录

  • 清除 tokenrefreshTokentenantId
  • 跳转登录页并重新选择租户

四、业务 API 调用(无路径变更)

业务接口路径与美国版一致,例如:

模块 示例
标签 GET /api/app/label?SkipCount=1&MaxResultCount=10
产品 GET /api/app/product?SkipCount=1&MaxResultCount=10
门店 GET /api/app/location?...
菜单权限 GET /api/app/auth-session/my-menus

分页SkipCount1 起(与美国版一致)。

未带租户上下文时,auth-session/my-menus 等接口返回:

未识别租户上下文。请使用泰额登录接口(th-web-auth / th-app-auth)或请求头 __tenant 携带租户 Id。


五、页面级优化清单

P0 — 阻塞联调

页面/功能 优化内容
登录页 租户下拉 + th-web-auth/login
全局 API 客户端 Token + __tenant 注入
路由守卫 tenantId 时跳转登录

P1 — 体验

页面/功能 优化内容
登录页 记住上次选择的租户(localStorage)
顶栏 展示当前 tenantName
错误提示 401/租户相关错误引导重新登录
Swagger 联调说明 文档注明须带 __tenant

P2 — 平台管理(可选)

页面/功能 优化内容
平台管理员 租户开通页对接 th-tenant-provisioning/provision
租户管理 对接 /api/app/tenant CRUD

六、禁止 / 废弃用法

用法 原因
POST /api/app/account/login 作为泰额 Web 主登录 无租户,查主库
POST /api/app/us-app-auth/login 多租户模式下后端已拒绝
业务 API 仅带 Token、无 TenantId JWT 须来自泰额登录接口
fl_* 业务传 tenantId 字段 独立库模式,无行级 TenantId

七、泰额 Web 与美国版 Web 差异对照

美国版 Web 泰额版 Web(待改)
API Base 19001 19002(以 appsettings 为准)
登录接口 account/login th-web-auth/login + tenantId
租户选择 登录前必选
业务 API 路径 相同 相同
Token Claims 无 TenantId 含 TenantId

八、联调检查清单

  • [ ] 登录页能拉取 tenant-select 列表
  • [ ] 选择 Default 租户后能 th-web-auth/login 成功
  • [ ] 登录后 auth-session/my-menus 有数据
  • [ ] 标签列表 GET /api/app/label?SkipCount=1&MaxResultCount=10 有数据
  • [ ] 刷新页面后 Token + tenantId 仍有效
  • [ ] 退出后业务 API 返回未授权/无租户提示

九、后端已完成项(供前端参考)

Swagger 分组:泰额版-食品标签FoodLabeling.Th.Application)。
基础路径:/api/app/(ABP 动态 API,以下路径以 Swagger 为准)。
泰额本地联调 Base URL 一般为 http://localhost:19002

9.1 模块总览

模块 应用服务 路径/接口 鉴权
租户解析 JwtClaimTenantResolveContributor + HeaderTenantResolveContributor 请求头 __tenant 或 JWT TenantId Claim
Web 登录 ThWebAuthAppService POST /api/app/th-web-auth/login 匿名
App 登录 ThAppAuthAppService POST /api/app/th-app-auth/login 匿名
App 我的门店 ThAppAuthAppService GET /api/app/th-app-auth/my-locations Bearer
租户列表 ThMultiTenancyAppService GET /api/app/th-multi-tenancy/tenant-select 匿名
当前租户 ThMultiTenancyAppService GET /api/app/th-multi-tenancy/current-tenant Bearer
租户开通 ThTenantProvisioningAppService POST /api/app/th-tenant-provisioning/provision Bearer(平台管理员)
补建租户库 ThTenantProvisioningAppService POST /api/app/th-tenant-provisioning/initialize-tenant-database Bearer
业务守卫 TenantContextGuard GET /api/app/auth-session/my-menus Bearer + 租户上下文
UsAppAuth 禁用 UsAppAuthAppService POST /api/app/us-app-auth/login 多租户下直接拒绝
JWT TenantId AccountManager Web 登录签发 Token 时写入 Claim

9.2 租户上下文解析(JwtClaimTenantResolveContributor

解析顺序YiAbpWebModule 配置):

  1. 请求头 __tenant: {租户Guid}
  2. JWT Claim:TenantIdTokenTypeConst.TenantId)或 AbpClaimTypes.TenantId

前端约定

说明
登录后 持久化 tenantId,业务请求带 Authorization: Bearer {token}
可选 同时带 __tenant: {tenantId},须与 Token 内租户一致
无租户时 连接平台主库,业务 API 列表为空或报「未识别租户上下文」

9.3 GET /api/app/th-multi-tenancy/tenant-select

说明:登录页租户下拉,匿名可访问;数据来自平台主库 yitenant

请求:无 Query / Body。

出参ThTenantSelectDto[](JSON 数组,非分页包装)

字段 类型 说明
id Guid 租户 Id(登录时传 tenantId
name string 租户名称(顶栏展示)

响应示例

[
  { "id": "11111111-1111-1111-1111-111111111111", "name": "Default" }
]

curl

curl -X GET "http://localhost:19002/api/app/th-multi-tenancy/tenant-select"

9.4 GET /api/app/th-multi-tenancy/current-tenant

说明:调试当前请求是否已解析到租户;登录后调用。

请求头

Header 必填 说明
Authorization Bearer {token}
__tenant 与 Token 租户一致时可附带

出参ThCurrentTenantDto

字段 类型 说明
tenantId Guid? 当前租户 Id;未解析时为 null
tenantName string? 当前租户名称

响应示例

{
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "tenantName": "Default"
}

9.5 POST /api/app/th-web-auth/login(Web 管理端登录)

说明

  1. 平台主库校验 tenantId 存在且已配置 TenantConnectionString
  2. 切换到该租户业务库,按 userName(邮箱或用户名)+ 密码校验
  3. 通过 AccountManager.GetTokenByUserIdAsync 签发含 RBAC 角色/菜单权限的 JWT(含 TenantId Claim)

入参 ThWebLoginInputVo(JSON Body)

字段 类型 必填 说明
tenantId Guid 平台主库 yitenant.Id,不可为 00000000-0000-0000-0000-000000000000
userName string 登录账号:优先匹配 user.Email,亦可为 user.UserName
password string 密码
uuid string 图形验证码 UUID(RbacOptions.EnableCaptcha=true 时必填)
code string 图形验证码

出参 ThWebLoginOutputDto

字段 类型 说明
token string JWT 访问令牌(含 TenantId、角色、权限 Claims;部分环境已带 Bearer 前缀)
refreshToken string 刷新令牌
tenantId Guid 当前租户 Id
tenantName string 租户名称

请求示例

POST /api/app/th-web-auth/login
Content-Type: application/json
{
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "userName": "admin@example.com",
  "password": "YourPassword1!",
  "uuid": null,
  "code": null
}

出参示例

{
  "token": "Bearer eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "...",
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "tenantName": "Default"
}

常见错误

提示 原因
请输入租户、邮箱与密码! tenantId / userName / password 缺失
租户不存在或已停用 主库无该 yitenant
租户未配置业务库连接串 TenantConnectionString 为空
Sign-in failed: account not found. 租户库中无匹配用户
Invalid captcha. 验证码错误或过期
用户名或密码错误 密码校验失败

9.6 POST /api/app/th-app-auth/login(泰额 App 登录)

说明:在租户库校验用户,签发 App 专用 JWT(Claim 含 TenantIdclient_kind=th_app),并返回绑定门店。

入参 ThAppLoginInputVo(JSON Body)

字段 类型 必填 说明
tenantId Guid 平台主库 yitenant.Id
email string 登录邮箱(user.Email / 邮箱形 UserName
password string 密码
uuid string 图形验证码 UUID(开启验证码时必填)
code string 图形验证码

出参 ThAppLoginOutputDto

字段 类型 说明
token string JWT(含 TenantId RBAC 菜单 Claims)
refreshToken string 刷新令牌
tenantId Guid 当前租户 Id
tenantName string 租户名称
locations array 绑定门店列表,元素为 UsAppBoundLocationDto

locations[] 子项

字段 类型 说明
id string 门店 Guid
locationCode string 门店编码
locationName string 门店名称
fullAddress string 拼接地址
state bool 是否启用

请求示例

{
  "tenantId": "11111111-1111-1111-1111-111111111111",
  "email": "user@example.com",
  "password": "YourPassword1!"
}

JWT Claims(节选)

Claim 说明
TenantId / tenantid 租户 Guid
client_kind 固定 th_app
sub / UserId 用户 Id

9.7 GET /api/app/th-app-auth/my-locations

说明:在当前 JWT 租户上下文下查询 userlocation + location

请求头

Header 必填 说明
Authorization Bearer {token}(须为 th-app-auth/login 签发)
__tenant 与 Token 租户一致

出参UsAppBoundLocationDto[](字段同 9.6 locations

常见错误

提示 原因
用户未登录 Token 无效或未传
未识别租户,请重新登录或携带 __tenant JWT 无 TenantId 且未传 __tenant

9.8 POST /api/app/th-tenant-provisioning/provision(租户开通,P2 可选)

说明:平台管理员在主库登记租户并生成独立业务库连接串;可选立即 CodeFirst 建库建表。

入参 ThProvisionTenantInputVo(JSON Body)

字段 类型 必填 默认值 说明
name string 租户名称(用于生成库名,如 antis-foodlabeling-{tenant}
tenantConnectionString string 自定义 MySQL 连接串;空则按 FoodLabeling:TenantDatabase 模板生成
dbType int 0 SqlSugar DbType0 = MySql
initializeDatabase bool true 是否立即调用 InitAsync 建库 + 业务表

出参 ThProvisionTenantOutputDto

字段 类型 说明
tenantId Guid 新租户 Id
name string 租户名称
databaseName string 业务库名
tenantConnectionString string 完整连接串
databaseInitialized bool 是否已执行 Init

请求示例

POST /api/app/th-tenant-provisioning/provision
Content-Type: application/json
Authorization: Bearer {token}
{
  "name": "acme",
  "initializeDatabase": true
}

9.9 POST /api/app/th-tenant-provisioning/initialize-tenant-database

说明:对已有租户补执行业务库 CodeFirst(建库 + 业务表,不含 yitenant)。

说明
入参 tenantId(Guid,路由或 Query,以 Swagger 为准)
鉴权 Bearer(平台管理员)
出参 无业务体(204 / 空对象,以实际为准)

9.10 业务守卫 TenantContextGuardauth-session/my-menus

说明:泰额 Web 登录后,须带租户上下文才能拉取菜单;AuthSessionAppService.GetMyMenusAsync 首行调用 TenantContextGuard.EnsureTenantResolved

GET /api/app/auth-session/my-menus

请求头

Header 必填 说明
Authorization Bearer {token}(须为 th-web-auth/login 签发)
__tenant 建议与 Token 租户一致

出参CurrentUserMenuPermissionsOutputDto(与美国版字段一致)

字段 类型 说明
user object 用户简要信息
roleCodes string[] 角色编码
permissionCodes string[] 权限码
accessPermissionCodes string[] 访问权限(如 manage_people
menus array 可见菜单树
lastUpdated datetime? 资料/全局编辑时间戳
role string 角色展示名(逗号拼接)
fullName string 全名

无租户时错误

获取菜单权限:未识别租户上下文。请使用泰额登录接口(th-web-auth / th-app-auth)或请求头 __tenant 携带租户 Id。

POST /api/app/auth-session/logout

说明
鉴权 Bearer
出参 bool,是否清除服务端用户缓存

9.11 UsAppAuth 多租户禁用

DbConnOptions.EnabledSaasMultiTenancy = true 且请求无租户上下文时:

接口POST /api/app/us-app-auth/login

入参(美国版 UsAppLoginInputVo无 tenantId):

字段 类型 必填 说明
email string 邮箱
password string 密码
uuid string 验证码 UUID
code string 验证码

返回错误(HTTP 400)

多租户模式下请使用泰额 App 登录接口 /api/app/th-app-auth/login(须传 tenantId),勿使用 us-app-auth。

前端处理:泰额 App 统一改用 th-app-auth/login,入参必须包含 tenantId


9.12 JWT TenantId 写入(AccountManager

登录方式 Token 签发 TenantId Claim
Web th-web-auth/login AccountManager.GetTokenByUserIdAsync(在 CurrentTenant.Change 内调用) 写入 AbpClaimTypes.TenantId + TokenTypeConst.TenantId
App th-app-auth/login ThAppAuthAppService.CreateAppAccessToken 同上,另含 client_kind=th_app

条件:仅在 CurrentTenant.Id 有值时写入(泰额登录流程已在租户上下文中执行校验与签发)。


9.13 前端对接速查

场景 接口 关键入参
登录页拉租户 GET tenant-select
Web 登录 POST th-web-auth/login tenantId, userName, password
App 登录 POST th-app-auth/login tenantId, email, password
登录后拉菜单 GET auth-session/my-menus Header: Authorization +(可选)__tenant
业务列表 GET /api/app/label 同上;SkipCount1
调试租户 GET current-tenant Header: Authorization

十、相关文档

  • 项目相关文档/5-19泰额版.md — 多租户架构与泰额专用接口
  • 项目相关文档/5-27代码优化.md — th-app-auth 登录 500 修复说明
  • 项目相关文档/2026-07-09代码修改.md — 标签批量导入等业务接口

变更记录

日期 内容
2026-07-13 泰额版后端多租户结构整理完成;新增 Web 登录 th-web-auth;整理前端优化清单
2026-07-13 第九节补充泰额专用接口完整入参/出参、请求头、错误说明与前端速查表