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 封装
登录成功后:
- 持久化
token、refreshToken、tenantId、tenantName - 所有业务请求 Header 带
Authorization: Bearer {token} - (可选)同时带
__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-select、current-tenant |
src/lib/apiClient.ts |
请求拦截器附加 __tenant |
src/lib/authStorage.ts |
存储 tenantId / tenantName |
| 登录页组件 | 增加租户下拉(在邮箱密码之前) |
3.3 退出登录
- 清除
token、refreshToken、tenantId - 跳转登录页并重新选择租户
四、业务 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 |
分页:SkipCount 从 1 起(与美国版一致)。
未带租户上下文时,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 配置):
- 请求头
__tenant: {租户Guid} - JWT Claim:
TenantId(TokenTypeConst.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 管理端登录)
说明:
- 在平台主库校验
tenantId存在且已配置TenantConnectionString - 切换到该租户业务库,按
userName(邮箱或用户名)+ 密码校验 - 通过
AccountManager.GetTokenByUserIdAsync签发含 RBAC 角色/菜单权限的 JWT(含TenantIdClaim)
入参 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 含 TenantId、client_kind=th_app),并返回绑定门店。
入参 ThAppLoginInputVo(JSON Body)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| tenantId | Guid | 是 | 平台主库 yitenant.Id |
| 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 DbType,0 = 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 业务守卫 TenantContextGuard(auth-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):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| 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 等 |
同上;SkipCount 从 1 起 |
| 调试租户 | 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 | 第九节补充泰额专用接口完整入参/出参、请求头、错误说明与前端速查表 |