Request → TenantInterceptor (读 X-Tenant-Id) → TenantContext.set(tenantId) → Controller → Service → Client
↓
Client 从 TenantContext 拿 appKey/appSecret
TenantContext 用 ThreadLocal<TenantProfile>;TenantProfile 包含:
tenantId(宜搭应用表主键)vendorCredentials: Map<String, VendorCredential>(钉钉/aliwork 等)extraJson(tenant 级自定义配置)数据源:宜搭"应用表"(formUuid 配置)。字段约定:
| 宜搭字段 | 含义 |
|---|---|
textField_tenantId |
租户唯一标识(建议用客户代号如 guangming) |
textField_vendor |
第三方平台 key(dingtalk / aliwork / ...) |
textField_appKey |
该 vendor 的 appKey |
textField_appSecret |
该 vendor 的 appSecret(写入时走宜搭加密字段) |
textField_corpId |
可选(钉钉需要) |
textareaField_extraJson |
JSON 字符串,扩展参数 |
radioField_enabled |
on / off |
加载策略:
Map<tenantId, TenantProfile>tenant.registry.ttlSeconds)过期后按需刷新/pro/_admin/reloadTenant?tenantId=xxx(仅 dev profile 开放)做热刷基座 DDClient 签名不变(仍接受 appKey/appSecret 参数),mjava-pro 提供一层 DynamicDDService 组合:
// Service 层内部
DDConf conf = TenantContext.current().credential("dingtalk").toDDConf();
ddClient.listDepartmentUserDetail(conf, deptId, cursor, size);
不做 AOP 反射修改签名 — 保持显式、可读、可调试。
UtilToken 当前 key 是裸 {vendor}:{appKey}。多租户环境下两个客户可能共用同一 appKey(极少,但要防撞)。建议基座支持 namespace:
UtilToken.put(tenantId, "dingtalk:" + appKey, token, expireSec);
UtilToken.get(tenantId, "dingtalk:" + appKey);
若基座改动太大,回退方案:mjava-pro 约束 key 为 {tenantId}:{vendor}:{appKey},不改基座。
401 TENANT_REQUIRED403 TENANT_NOT_FOUND500 TENANT_VENDOR_MISCONFIGURED + 审计日志mjava-baseline.md 全部规范application-prod.yml 只配宜搭应用表 formUuid / systemToken(访问宜搭自身的凭据仍是静态),不配任何客户凭据tenantId 字段(在 §3.5 审计字段基础上扩展)