Blame view

项目相关文档/2026-07-13泰额版多租户-前端优化.md 19.2 KB
2893c050   李曜臣   2026-07-13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
  # 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}
  ```
  
  #### 租户下拉(匿名可访问)
  
  ```http
  GET /api/app/th-multi-tenancy/tenant-select
  ```
  
  出参示例:
  
  ```json
  [
    { "id": "11111111-1111-1111-1111-111111111111", "name": "Default" }
  ]
  ```
  
  #### Web 管理端登录(新接口,替代 account/login)
  
  ```http
  POST /api/app/th-web-auth/login
  Content-Type: application/json
  ```
  
  ```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 登录(已有,保持不变)
  
  ```http
  POST /api/app/th-app-auth/login
  ```
  
  ```json
  {
    "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. 持久化 `token`、`refreshToken`、`tenantId`、`tenantName`
  2. 所有业务请求 Header 带 `Authorization: Bearer {token}`
  3. (可选)同时带 `__tenant: {tenantId}`,与 Token 保持一致
  
  ```typescript
  // 伪代码示例
  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` 配置):
  
  1. 请求头 `__tenant: {租户Guid}`
  2. 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 | 租户名称(顶栏展示) |
  
  **响应示例**
  
  ```json
  [
    { "id": "11111111-1111-1111-1111-111111111111", "name": "Default" }
  ]
  ```
  
  **curl**
  
  ```bash
  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? | 当前租户名称 |
  
  **响应示例**
  
  ```json
  {
    "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 | 租户名称 |
  
  #### 请求示例
  
  ```http
  POST /api/app/th-web-auth/login
  Content-Type: application/json
  ```
  
  ```json
  {
    "tenantId": "11111111-1111-1111-1111-111111111111",
    "userName": "admin@example.com",
    "password": "YourPassword1!",
    "uuid": null,
    "code": null
  }
  ```
  
  #### 出参示例
  
  ```json
  {
    "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` |
  | 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 | 是否启用 |
  
  #### 请求示例
  
  ```json
  {
    "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 |
  
  #### 请求示例
  
  ```http
  POST /api/app/th-tenant-provisioning/provision
  Content-Type: application/json
  Authorization: Bearer {token}
  ```
  
  ```json
  {
    "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**):
  
  | 字段 | 类型 | 必填 | 说明 |
  |------|------|------|------|
  | 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` 等 | 同上;`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 | 第九节补充泰额专用接口完整入参/出参、请求头、错误说明与前端速查表 |