231 lines
8.8 KiB
Markdown
231 lines
8.8 KiB
Markdown
<!-- 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` 保持不变)。
|