|
|
@@ -0,0 +1,280 @@
|
|
|
+## 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 决策流程(按硬指标升档)
|
|
|
+
|
|
|
+判定按「**触发即升档**」原则,逐档自上而下匹配,匹配到最高档位停止:
|
|
|
+
|
|
|
+1. **从 A 起评**:默认所有新客户先评估 A 档
|
|
|
+2. **A→B 触发条件**(任一即触发升档):
|
|
|
+ - (a) 客户需要写自定义 Java Controller / Service / Entity
|
|
|
+ - (b) 客户需要私有数据库 schema(不能共享 mjava-pro 的 DB)
|
|
|
+ - (c) 客户需要独立部署节点以隔离故障域
|
|
|
+3. **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 迁移步骤**:
|
|
|
+
|
|
|
+1. **新建 B 档子模块**:`cp -r mjava-mcli mjava-{客户}`,改 artifactId / port / context-path
|
|
|
+2. **配置迁移**:把宜搭应用表该 tenantId 的凭据复制到 `application-{profile}.yml`
|
|
|
+3. **数据迁移**:把 mjava-pro DB 内该 tenantId 的行级数据导出 → 导入 B 档独立 schema(脚本一次性,归档备份)
|
|
|
+4. **双轨期**(1~2 周):A 档租户配置 `enabled=false`(拒新请求),B 档接管;监控异常
|
|
|
+5. **下线 A 档配置**:宜搭应用表删除该 tenantId 行
|
|
|
+6. **commit message**:`refactor: 客户 {名} 升档 A → B(trigger: {条件})`
|
|
|
+
|
|
|
+**B → C 迁移步骤**:
|
|
|
+
|
|
|
+1. **新建独立 git 仓**:`cur/mjava-{客户}/`,初始化为多模块 pom 结构
|
|
|
+2. **fork 基座**:把 mjava-ai 当前 `mjava/` 目录全量复制进客户仓 `mjava/`,版本号保持上游一致
|
|
|
+3. **迁移 B 档子模块**:把 `mjava-ai/mjava-{客户}/` 全量复制进客户仓
|
|
|
+4. **删除 mjava-ai 仓内的 B 档子模块**:根 `pom.xml` 移除 `<module>`,删除目录
|
|
|
+5. **客户仓 README**:写明分叉自 mjava-ai 哪个 commit
|
|
|
+6. **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**:
|
|
|
+
|
|
|
+1. 校验:确认 R2 决策中 A 档判定通过(不命中任一 A→B 条件)
|
|
|
+2. 宜搭应用表新增一行
|
|
|
+3. 凭据热验证:调 mjava-pro `/api/pro/_diag/tenant/{tenantId}` 探活
|
|
|
+4. 邮件通告运维 + 客户对接人
|
|
|
+
|
|
|
+#### 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** 不允许新子模块合入但表内不登记
|