ADDED Requirements
Requirement: R1 三档分档矩阵
mjava-ai 接入新客户时按以下三档分流,三档互斥(一个客户同时只属一档):
| 档 |
名称 |
物理落点 |
部署形态 |
命名 |
| A |
公共托管(多租户) |
mjava-pro/(mjava-ai 仓内共享 jar) |
一个 jar 跑 N 客户,端口 9010 |
仅在宜搭应用表加一行 tenantId |
| B |
独立子模块 |
mjava-ai/mjava-{客户}/(仓内独立子模块,复制 mjava-mcli) |
一客户一 jar,独立端口 |
mjava-{客户拼音/英文短名}(如 mjava-akds) |
| C |
独立 git 仓 |
cur/mjava-{客户}/(不在 mjava-ai 仓内) |
客户自管部署 |
mjava-{客户}(仓 root artifactId) |
历史先例:cur/mjava-guangming/ 与 akds 为 C 档;当前无落地 A/B 档客户实例。
Scenario: 新客户接入第一步
- WHEN 有新客户接入需求
- THEN 必须先按 R2 决策流程选档
- AND 在客户接入会议纪要 / OpenSpec change proposal / commit message 中显式记录所选档位
- AND 不允许跳过分档判定直接动手建子模块或拉新仓
Scenario: 单客户跨档归属
- WHEN 一个客户的不同业务模块似乎需要不同档位(如某客户审批流走 A 档共享、某独立报表服务走 B 档)
- THEN 整个客户按最高档归属(取 B),不允许跨档混编
- AND 若坚持拆分,必须视为「客户名下两个客户实体」分别走 R2
Requirement: R2 决策流程(按硬指标升档)
判定按「触发即升档」原则,逐档自上而下匹配,匹配到最高档位停止:
- 从 A 起评:默认所有新客户先评估 A 档
- A→B 触发条件(任一即触发升档):
- (a) 客户需要写自定义 Java Controller / Service / Entity
- (b) 客户需要私有数据库 schema(不能共享 mjava-pro 的 DB)
- (c) 客户需要独立部署节点以隔离故障域
- B→C 触发条件(任一即触发升档):
- (a) 客户提供自己的部署环境(私网 / 客户机房 / 客户 K8s)
- (b) 客户要求源代码进客户 git 仓
- (c) 客户要求 mjava 基座做大幅分叉(≥3 个文件改基座)
升档单向:A→B→C 不可逆。原因:升档后物理资产已经独立(独立 jar / 独立 git 仓),回退需要数据迁移 + 部署变更 + 历史 git 不可回收,成本远高于继续维护。
Scenario: 客户初次接入按硬指标走判定
- WHEN 评估新客户「思库文化」时,确认其无需写自定义 Java 代码、共享数据库即可、可部署在我们机房
- THEN A→B 三个触发条件均不命中
- AND 选 A 档:宜搭应用表加一行
tenantId=sikuwh,无新增 Java 代码
Scenario: 客户初次接入命中 B 档
- WHEN 评估「阿科德斯」时,发现需要写自定义工时审批 Controller(命中 A→B(a))
- THEN 升 B 档:在 mjava-ai 仓内
cp -r mjava-mcli mjava-akds,独立 artifactId / 端口
- AND 部署仍在我们机房(B→C 不触发)
Scenario: 客户初次接入命中 C 档
- WHEN 评估「光明集团」时,客户要求源代码进客户 git 仓(命中 B→C(b))
- THEN 升 C 档:拉独立 git 仓
cur/mjava-guangming/,fork mjava 基座到客户仓内
- AND 客户仓与 mjava-ai 解耦演进
Scenario: 误选低档需升档
- WHEN 已落地 A 档的客户业务长大,开始要求私有 schema(命中 A→B(b))
- THEN 走 R3 升档迁移路径,不继续凑合在 A 档
- AND 升档迁移期间双轨运行(A 档读写转只读,B 档新实例并行启动),数据迁移完成后下线 A 档租户配置
Scenario: 试图降档
- WHEN 已升 B 档的客户业务收缩,看似可退回 A 档
- THEN 不降档;继续以 B 档运行
- AND 若长期闲置(≥6 个月无业务变更),评估是否归档下线整个子模块(属另一类决策,不属降档)
Requirement: R3 升档迁移路径
升档触发后按以下步骤执行迁移:
A → B 迁移步骤:
- 新建 B 档子模块:
cp -r mjava-mcli mjava-{客户},改 artifactId / port / context-path
- 配置迁移:把宜搭应用表该 tenantId 的凭据复制到
application-{profile}.yml
- 数据迁移:把 mjava-pro DB 内该 tenantId 的行级数据导出 → 导入 B 档独立 schema(脚本一次性,归档备份)
- 双轨期(1~2 周):A 档租户配置
enabled=false(拒新请求),B 档接管;监控异常
- 下线 A 档配置:宜搭应用表删除该 tenantId 行
- commit message:
refactor: 客户 {名} 升档 A → B(trigger: {条件})
B → C 迁移步骤:
- 新建独立 git 仓:
cur/mjava-{客户}/,初始化为多模块 pom 结构
- fork 基座:把 mjava-ai 当前
mjava/ 目录全量复制进客户仓 mjava/,版本号保持上游一致
- 迁移 B 档子模块:把
mjava-ai/mjava-{客户}/ 全量复制进客户仓
- 删除 mjava-ai 仓内的 B 档子模块:根
pom.xml 移除 <module>,删除目录
- 客户仓 README:写明分叉自 mjava-ai 哪个 commit
- commit message:mjava-ai 侧
refactor: 客户 {名} 升档 B → C(迁出独立仓 {仓库 URL})
Scenario: A → B 双轨期出错回退
- WHEN B 档启动后发现严重 bug,3 天内无法修复
- THEN 临时把 A 档租户配置
enabled=true 恢复 A 档接管,B 档进入修复期
- AND 不视为「降档」(B 档子模块继续保留,等待修复后再切)
Scenario: B → C 客户仓 fork 基座的版本号
- WHEN 把 mjava-ai 的
mjava/ fork 到客户仓
- THEN
mjava/pom.xml 的 <version> 保持与 mjava-ai 当前一致(如 0.0.3)
- AND 客户仓内对基座的后续分叉改动不改版本号,通过 git 历史追溯
- AND 客户仓 README 写明 fork 来源 commit hash
Requirement: R4 A 档(mjava-pro)入驻清单
客户走 A 档时,宜搭应用表(mjava-pro.yml 配置的 formUuid)必填字段:
| 字段 |
含义 |
必填 |
备注 |
tenantId |
业务唯一标识 |
✅ |
全局唯一,禁用中文 / 特殊字符 |
tenantName |
中文展示名 |
✅ |
仅用于日志 / 监控可读性 |
vendor |
第三方平台 |
✅ |
dingtalk / aliwork / feishu 等枚举 |
appKey |
产品方 appKey |
✅ |
— |
appSecret |
产品方 appSecret |
✅ |
应在宜搭加密字段存储 |
corpId |
钉钉系 corp ID |
钉钉系必填 |
— |
extraJson |
vendor 特定额外参数 |
可选 |
JSON 字符串,由 vendor 适配器解析 |
enabled |
启用标志 |
✅ |
false 时拒绝该 tenant 请求 |
隔离边界:
- DB:A 档客户共享 mjava-pro 的 DB schema;所有业务表必须含
tenant_id 列;查询条件强制带 tenant_id(由 JPA Interceptor 注入,不可绕过)
- 缓存:
UtilToken key 命名空间扩为 {tenantId}:{vendor}:{appKey}(由 add-mjava-pro 的 tenant-registry capability 实现)
- 日志:MDC 自动注入
tenantId,traceId 日志格式追加 [T:{tenantId}]
入驻 SOP:
- 校验:确认 R2 决策中 A 档判定通过(不命中任一 A→B 条件)
- 宜搭应用表新增一行
- 凭据热验证:调 mjava-pro
/api/pro/_diag/tenant/{tenantId} 探活
- 邮件通告运维 + 客户对接人
Scenario: A 档入驻缺必填字段
- WHEN 宜搭应用表新增行时漏填
appKey 或 corpId(钉钉系)
- THEN mjava-pro 启动该 tenant 路由时必须显式拒绝,返回明确错误(如
400 TENANT_CONFIG_INCOMPLETE: appKey is required)
- AND 不允许「字段缺失但允许跑」的兼容兜底
Scenario: A 档客户业务表漏 tenant_id
- WHEN A 档客户的 Java Repository 查询 SQL 缺
tenant_id 条件
- THEN JPA Interceptor 应强制注入
WHERE tenant_id = :currentTenant
- AND 若 Repository 故意绕过(如原生 SQL 字符串),code review 必须打回
Requirement: R5 mjava-com 对外暴露白名单规则
mjava-com 作为对外 BaaS 网关,不自动暴露所有 mjava Client/Service 方法。暴露遵循:
白名单制:
- 在
mjava-com/application.yml 显式声明 gateway.expose: [{vendor}.{action}, ...]
- 未列入即返 404(不返 405,避免泄露内部能力清单)
危险动作默认禁开:
- 凡
action 名含 delete / remove / drop / truncate 的方法
- 凡
action 名含 batch + 写动作(update / create / delete)的方法
- 凡涉及租户管理(
tenant.create / tenant.delete / tenant.update)的方法
上述方法即使配置在 gateway.expose 也必须由主仓维护者在 PR 中显式 ACK 才能合并。
限流默认值:
- 每调用方默认 60 QPS(本地
RateLimiter,进程级,不跨实例)
- 在
gateway.rateLimit.{callerId}: {qps} 按调用方覆盖
审计强制:
- 每次调用打全字段审计日志:
{callerId, vendor, action, paramSummary, resultSummary, latencyMs, success}
- 审计日志不可关闭,不可降级到 DEBUG 级别
暴露审批流程:
- 新增白名单条目 → PR + 主仓维护者 review
- commit message 标
feat(com): 开放 {vendor}.{action} 暴露
- 危险动作额外要求 PR 描述写明业务场景 + 调用方授权范围
Scenario: 配置中含危险动作
- WHEN PR 把
dingtalk.user.delete 加入 gateway.expose
- THEN code review 必须打回,要求拆开作为「危险动作专项 PR」
- AND 专项 PR 必须含业务场景说明 + 主仓维护者显式 ACK
Scenario: 调用方未配限流
- WHEN 新调用方
callerId=xyz 首次调用 mjava-com
- THEN 默认走 60 QPS 限流
- AND 若实际业务需要更高 QPS,走 PR 改
gateway.rateLimit.xyz
Requirement: R6 C 档独立仓与 mjava-ai 解耦边界
C 档客户仓不通过 maven 依赖引入 mjava-ai 发布的 jar,而是 fork 基座源码到客户仓内(如 cur/mjava-guangming/mjava/)。
fork 基座规则:
- 客户仓
mjava/pom.xml 的 version 保持与上游同步(fork 时刻的上游版本号),后续分叉改动不改版本号
- 客户仓 README 写明 fork 来源 commit hash + 上游仓库 URL
- 客户仓 mjava 包结构保持
com.malk.*(不改 package),便于 git diff 跨仓比较
演进解耦:
- 上游 mjava-ai 的基座演进不自动同步到 C 档客户仓
- C 档客户仓的基座分叉不自动回流到上游
- 双向同步由各仓工程师按需手动 cherry-pick
R4(接口变更确认)跨仓 grep 范围:
- mjava-ai 仓内 R4 grep 范围 = mjava-ai 本仓(基础建设期)
- C 档客户仓内 R4 grep 范围 = 客户仓本仓
- 两侧不互相联动:mjava-ai 改基座接口签名时,不需要扫 C 档客户仓;C 档客户仓改自己基座副本时,不需要扫 mjava-ai
回流通道(可选,不强制):
- C 档客户仓的工程师若认为某改动有上游回流价值(修 bug / 加产品方接口),鼓励提交 PR 回 mjava-ai
- 回流走标准 OpenSpec change 流程(不绕开)
Scenario: C 档客户仓 fork 时刻
- WHEN 升档 B → C 时把 mjava-ai 的
mjava/ 复制进客户仓
- THEN 客户仓
mjava/pom.xml 保留上游版本号(如 0.0.3)
- AND 客户仓 README 记录 fork 来源 commit hash
- AND 不在客户仓建「上游同步」cron / CI
Scenario: 上游 mjava-ai 改 DDClient 接口签名
- WHEN mjava-ai 内
DDClient.foo(String) 改签名为 DDClient.foo(String, Boolean)
- THEN R4 grep 仅扫 mjava-ai 本仓
- AND 不扫
cur/mjava-guangming/ 或 akds 等 C 档客户仓
- AND C 档客户仓的同名接口(若已 fork)由客户仓工程师按需自主升级,不阻塞上游
Scenario: C 档客户仓回流改动
- WHEN
cur/mjava-guangming/mjava/ 内修了一个上游也存在的 bug
- THEN 鼓励但不强制提 PR 回 mjava-ai
- AND 回流走 OpenSpec change 流程,不绕开
Requirement: R7 README / CLAUDE.md 同步更新
本 change archive 后,mjava-ai 仓内文档必须同步:
README.md:
- 「子项目速览」表追加「档位」列:
mjava-mcli 标 B 模板、mjava-pro 标 A 容器、mjava-com 标 —
- 「新客户接入」段改写为「先按 customer-tiering 决策树选档 → 按档执行 SOP」,详细判定不再展开(避免与 spec 重复)
CLAUDE.md:
- 「快速操作」加一行:「新客户接入:先查
openspec/specs/customer-tiering/spec.md R2 决策树选档 → 按档执行 SOP」
共享后端规范(/Users/malk/Desktop/Tech/claude/后端/CLAUDE.md):
- 在「子项目接入流程」段补 customer-tiering 锚点(与本 spec R2/R3 互锚)
Scenario: README 子项目速览表更新
- WHEN 本 change archive
- THEN README.md 「子项目速览」表必须含「档位」列
- AND 各模块标记与本 spec R1 一致
Scenario: 后续新增 B 档子模块
- WHEN 任何 B 档客户子模块(如
mjava-akds)合入 mjava-ai
- THEN README.md 「子项目速览」表必须追加该子模块行,「档位」列标
B
- AND 不允许新子模块合入但表内不登记