8.8 KiB
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 的 VITE_APP_MODE 区分两种模式:
| 模式 | 环境值 | 识别方式 | 入口示例 |
|---|---|---|---|
| app_id | appid |
URL ?app_id=1 |
可猜测自增 id |
| domain | domain |
location.host |
管理端配置域名 |
核心链路:
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/App.vue、tenant-vue/src/utils/http/index.ts - 后端入口:
server/plugin/saimulti/app/controller/LoginController.php→SystemOrganizationLogic::appInfo() - 租户隔离:全站仍依赖
App-Id请求头(TenantController、TenantModel),内部继续使用数字机构 id,不改为 tenant_code
目标架构
将 appid 模式替换为 code 模式(domain 模式不动):
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 已支持 hash 路由),登录页无需新增输入框(避免与验证码字段 code 混淆)。
1. 数据库变更
在 sm_system_organization 增加字段:
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 建表语句与 seed 数据。
存量机构回填:新增独立迁移脚本(如 db/migrate_tenant_code.sql 或由一次性 PHP 命令生成),为已有记录生成不重复 6 位码;规则与新建一致。
2. Server 后端
2.1 Model
SystemOrganization.php 补充类 PHPDoc(表说明 + @property string $tenant_code 租户访问码)。
可选:新增 findEnabledByTenantCode(string $tenantCode) 查询方法。
2.2 Logic — 核心改动
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
- 读取
tenant_code查询参数(code 模式) mode=code时传入appInfo($tenantCode, 'code')- domain 模式保持
appid=host+mode=domain
不改动 App-Id 头解析逻辑 — 租户 API 仍接收机构数字 id。
3. tenant-vue 租户端
3.1 环境配置
# 模式1: code 通过 URL ?tenant_code= 区分租户
# 模式2: domain 通过域名区分
VITE_APP_MODE = code
3.2 siteStore 重构
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 路由监听
route.query.app_id→route.query.tenant_code- 相应函数重命名(如
getRouteTenantCode)
3.4 HTTP 拦截器
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:siteInfoResponse 增加 tenant_code?: string。
4. admin-vue 总后台
4.1 机构列表
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:
- 新增时:不展示 tenant_code(后端自动生成)
- 编辑时:只读展示
tenant_code(不可修改,保证链接稳定) - 不在表单提交中包含 tenant_code(防篡改)
5. 文档
更新 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 链接 |
不再支持(按你的替换需求) |
手动验证路径:
- 总后台新建机构 → 列表出现 tenant_code
- 租户端
http://localhost:16888/?tenant_code=XXXXXX→ Logo/标题正确 - admin/admin 登录 → 租户 API 正常(菜单、用户信息)
- 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 保持不变)。