Files
cursor/plans/tenant_code-9333b12a.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

231 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 9333b12a-cba7-4c83-9a56-b9ca81467dd0 -->
---
todos:
- id: "db-tenant-code"
content: "新增 tenant_code 字段、唯一索引,更新 saimulti.sql 与存量回填脚本"
status: pending
- id: "server-generate-resolve"
content: "SystemOrganizationLogic 实现码生成、add 自动写入、appInfo code 分支LoginController 接收 tenant_code"
status: pending
- id: "tenant-vue-code-mode"
content: "tenant-vue siteStore/App.vue/http/.env/types 从 app_id 模式切换为 tenant_code 模式"
status: pending
- id: "admin-vue-display"
content: "admin-vue 机构列表与编辑弹窗展示 tenant_code只读及可选登录链接"
status: pending
- id: "docs-verify"
content: "更新 README跑 verify 脚本与手动登录联调"
status: pending
isProject: false
---
# tenant_code 租户登录改造方案
## 现状
当前租户端通过 [`tenant-vue/.env`](tenant-vue/.env) 的 `VITE_APP_MODE` 区分两种模式:
| 模式 | 环境值 | 识别方式 | 入口示例 |
|------|--------|----------|----------|
| app_id | `appid` | URL `?app_id=1` | 可猜测自增 id |
| domain | `domain` | `location.host` | 管理端配置域名 |
核心链路:
```mermaid
sequenceDiagram
participant Browser
participant TenantVue
participant Server
participant DB
Browser->>TenantVue: ?app_id=1 或域名访问
TenantVue->>Server: GET /saimulti/appInfo
Server->>DB: 按 id 或 domain 查 sm_system_organization
Server-->>TenantVue: 返回 id/title/logo...
TenantVue->>Server: 后续请求 Header App-Id=机构id
```
关键文件:
- 前端站点识别:[`tenant-vue/src/store/modules/site.ts`](tenant-vue/src/store/modules/site.ts)、[`tenant-vue/src/App.vue`](tenant-vue/src/App.vue)、[`tenant-vue/src/utils/http/index.ts`](tenant-vue/src/utils/http/index.ts)
- 后端入口:[`server/plugin/saimulti/app/controller/LoginController.php`](server/plugin/saimulti/app/controller/LoginController.php) → [`SystemOrganizationLogic::appInfo()`](server/plugin/saimulti/app/logic/system/SystemOrganizationLogic.php)
- 租户隔离:全站仍依赖 `App-Id` 请求头([`TenantController`](server/plugin/saimulti/basic/TenantController.php)、[`TenantModel`](server/plugin/saimulti/basic/TenantModel.php)**内部继续使用数字机构 id不改为 tenant_code**
## 目标架构
`appid` 模式替换为 `code` 模式domain 模式不动):
```mermaid
sequenceDiagram
participant Browser
participant TenantVue
participant Server
participant DB
Browser->>TenantVue: /auth/login?tenant_code=A3K9M2
TenantVue->>Server: GET /saimulti/appInfo?tenant_code=A3K9M2&mode=code
Server->>DB: WHERE tenant_code=A3K9M2
Server-->>TenantVue: id, title, logo, tenant_code...
Note over TenantVue: 持久化 tenant_code + 解析后的 org id
TenantVue->>Server: POST /tenant/login, Header App-Id=1
```
**安全收益**:对外 URL 只暴露 6 位随机码(大写+数字,排除 0/O、1/I/L不再暴露可枚举的自增 id。
**tenant_code 获取方式**:与现有 app_id 一致,从浏览器 URL 查询参数自动读取([`getUrlQueryValue`](tenant-vue/src/store/modules/site.ts) 已支持 hash 路由),**登录页无需新增输入框**(避免与验证码字段 `code` 混淆)。
---
## 1. 数据库变更
在 [`sm_system_organization`](db/saimulti.sql) 增加字段:
```sql
ALTER TABLE `sm_system_organization`
ADD COLUMN `tenant_code` varchar(6) NOT NULL COMMENT '租户访问码6位随机' AFTER `domain`,
ADD UNIQUE INDEX `uk_tenant_code` (`tenant_code`);
```
同步更新 [`db/saimulti.sql`](db/saimulti.sql) 建表语句与 seed 数据。
**存量机构回填**:新增独立迁移脚本(如 `db/migrate_tenant_code.sql` 或由一次性 PHP 命令生成),为已有记录生成不重复 6 位码;规则与新建一致。
---
## 2. Server 后端
### 2.1 Model
[`SystemOrganization.php`](server/plugin/saimulti/app/model/system/SystemOrganization.php) 补充类 PHPDoc表说明 + `@property string $tenant_code 租户访问码`)。
可选:新增 `findEnabledByTenantCode(string $tenantCode)` 查询方法。
### 2.2 Logic — 核心改动
[`SystemOrganizationLogic.php`](server/plugin/saimulti/app/logic/system/SystemOrganizationLogic.php)
- **`generateUniqueTenantCode()`**private
- 字符集:`ABCDEFGHJKLMNPQRSTUVWXYZ23456789`32 字符)
- 循环生成 6 位 + 查重,直到唯一
- **`add($data)`** 重写/扩展:保存前若未传 `tenant_code`,自动调用生成器写入
- **`appInfo($identifier, $mode)`** 扩展分支:
- `mode === 'domain'` → 现有逻辑不变
- `mode === 'code'``where('tenant_code', $identifier)` 查询
- 移除或废弃 `mode !== 'domain'` 时按自增 id 查询的路径(原 appid 模式)
- **`appInfo` 返回字段**增加 `tenant_code`(供前端展示/持久化)
### 2.3 Controller
[`LoginController::appInfo()`](server/plugin/saimulti/app/controller/LoginController.php)
- 读取 `tenant_code` 查询参数code 模式)
- `mode=code` 时传入 `appInfo($tenantCode, 'code')`
- domain 模式保持 `appid=host` + `mode=domain`
**不改动** `App-Id` 头解析逻辑 — 租户 API 仍接收机构数字 id。
---
## 3. tenant-vue 租户端
### 3.1 环境配置
[`tenant-vue/.env`](tenant-vue/.env)
```env
# 模式1: code 通过 URL ?tenant_code= 区分租户
# 模式2: domain 通过域名区分
VITE_APP_MODE = code
```
### 3.2 siteStore 重构
[`tenant-vue/src/store/modules/site.ts`](tenant-vue/src/store/modules/site.ts)
- 常量 `TENANT_CODE_QUERY_KEY = 'tenant_code'`
- `SiteInfoParams` 改为 `{ tenantCode: string; mode: string }`(替代 `appid`
- `loadSiteInfo``/saimulti/appInfo``tenant_code` + `mode`
- 解析成功后:`info.id` 用于 `App-Id` 头;`tenant_code` 持久化到 localStorage刷新后无需 URL 参数也能识别租户)
- domain 模式分支逻辑保持不变
### 3.3 路由监听
[`tenant-vue/src/App.vue`](tenant-vue/src/App.vue)
- `route.query.app_id``route.query.tenant_code`
- 相应函数重命名(如 `getRouteTenantCode`
### 3.4 HTTP 拦截器
[`tenant-vue/src/utils/http/index.ts`](tenant-vue/src/utils/http/index.ts)
- `appMode === 'code'`:优先 `siteStore.info.id` 作为 `App-Id`;未加载时用已持久化的 org id
- 移除 `appid` 模式分支
### 3.5 类型定义
[`tenant-vue/src/types/api/api.d.ts`](tenant-vue/src/types/api/api.d.ts)`siteInfoResponse` 增加 `tenant_code?: string`
---
## 4. admin-vue 总后台
### 4.1 机构列表
[`admin-vue/src/views/admin/panel/organization/index.vue`](admin-vue/src/views/admin/panel/organization/index.vue) 增加列:
- `tenant_code` — 标签「租户码」
- 可选:展示租户登录链接模板 `租户端地址?tenant_code=xxx`(方便复制发给客户)
### 4.2 编辑弹窗
[`admin-vue/src/views/admin/panel/organization/modules/edit-dialog.vue`](admin-vue/src/views/admin/panel/organization/modules/edit-dialog.vue)
- **新增**时:不展示 tenant_code后端自动生成
- **编辑**时:只读展示 `tenant_code`(不可修改,保证链接稳定)
- 不在表单提交中包含 tenant_code防篡改
---
## 5. 文档
更新 [`README.md`](README.md) 租户前端配置章节:
- 模式 1`code` + `?tenant_code=A3K9M2`
- 模式 2`domain`(不变)
- 删除 app_id 相关说明
---
## 6. 兼容与测试要点
| 场景 | 预期 |
|------|------|
| 新机构创建 | 自动生成唯一 6 位 tenant_code |
| `?tenant_code=xxx` 访问 | 加载站点信息,登录成功 |
| 刷新页面(无 URL 参数) | localStorage 中 tenant_code + org id 仍可识别 |
| 错误/不存在 tenant_code | appInfo 报错「未找到该应用」 |
| domain 模式部署 | 行为与改造前一致 |
| 旧 `?app_id=1` 链接 | **不再支持**(按你的替换需求) |
手动验证路径:
1. 总后台新建机构 → 列表出现 tenant_code
2. 租户端 `http://localhost:16888/?tenant_code=XXXXXX` → Logo/标题正确
3. admin/admin 登录 → 租户 API 正常(菜单、用户信息)
4. domain 模式 `.env` 切换后仍可用
---
## 改动范围摘要
| 层 | 文件 | 动作 |
|----|------|------|
| DB | `db/saimulti.sql` + 迁移脚本 | 加字段、唯一索引、存量回填 |
| Server | `SystemOrganizationLogic.php` | 生成码、appInfo code 分支、add 钩子 |
| Server | `LoginController.php` | 接收 tenant_code 参数 |
| Server | `SystemOrganization.php` | PHPDoc |
| tenant-vue | `site.ts`, `App.vue`, `http/index.ts`, `.env`, `api.d.ts` | appid → code |
| admin-vue | `organization/index.vue`, `edit-dialog.vue` | 展示 tenant_code |
| Docs | `README.md` | 更新模式说明 |
**刻意不改**`App-Id` 请求头机制、TenantModel 全局 scope、登录接口参数验证码 `code` 保持不变)。