## ADDED Requirements ### Requirement: R1 原子接口分层(server / service 两层) mjava 基座对每个产品方接入按两层组织: - **`com.malk.server.{product}/`**:产品方原始数据契约。允许放 POJO(如 `*R` 响应、`*Param` 入参、`*Dto` 字段映射)、`{Prod}Conf` 配置类、枚举、常量、签名/加密工具类。禁业务逻辑。 - **`com.malk.service.{product}/{Prod}Client*.java`**:1:1 对应产品官方接口文档,**一接口一方法**,不做组合,不做错误码翻译,仅做参数填充 + HTTP 调用 + 反序列化。 - **`com.malk.service.{product}/{Prod}Service.java`**:可选。仅当存在跨客户共用的二次封装(自动续 token、分页拼装、错误码翻译、跨接口编排)时建立。准入条件见 R7。 实现类一律落在 `com.malk.service.{product}.impl/` 子包。 #### Scenario: 新增一个产品方接入 - **WHEN** 需要在 mjava 基座新增对某产品方(如 "腾讯会议")的接入 - **THEN** 必须建 `server/txmeeting/` 与 `service/txmeeting/` 两个目录 - **AND** `server/txmeeting/` 下至少有 `TXMConf` 或 `TXMR` 至少一个数据契约类 - **AND** `service/txmeeting/TXMClient.java` 接口 + `impl/TXMClientImpl.java` 实现各一个文件 - **AND** 如无跨客户复用场景,**禁建** `TXMService.java` 空壳 #### Scenario: Client 方法定义 - **WHEN** 实现 `{Prod}Client*.java` 接口的方法 - **THEN** 方法签名应与产品方官方接口文档 1:1 对应(每个 OpenAPI 一个 Java 方法) - **AND** 方法体只做:构造 headers / params / body → 调 `UtilHttp` → 反序列化 → 返回;**不调其他 Client 方法、不写 if-else 业务分支** #### Scenario: 集成平台 INTP 当前现状与演进 - **WHEN** 当前 `service/integration/` 仅 `INTPClient_User`(单一域) - **THEN** **不**预建空壳 `INTPClient` 主入口或 `INTPService`(服从 R7 防空壳准入) - **AND** 未来扩多域时(例:补 `INTPClient_Org` / `INTPClient_Permission` 等)按 R2 触发: - 若总方法数 > 15 或跨 2+ 域,必须建 `INTPClient` 主入口聚合 - 若 ≥2 客户子项目复用相同的二次编排(如统一鉴权续约),按 R7 准入条件补 `INTPService` --- ### Requirement: R2 板块拆 client 当单一 `{Prod}Client.java` 接口方法数 > 15,或跨 2+ 产品板块(例:钉钉的「通讯录」+「考勤」),必须按板块拆分为 `{Prod}Client_{Module}.java`。 参照范式: - 钉钉 12 子 client:`DDClient_Attendance` / `DDClient_Contacts` / `DDClient_Group` 等 - 宜搭 2 子 client:`YDClient_Form` / `YDClient_Process` - 北森 2 子 client:`BSClient_Attendance` / `BSClient_Employee` 主 `{Prod}Client.java` 保留作为总入口或聚合(含 token 获取、通用工具方法),不消失。 #### Scenario: Client 方法数超阈值 - **WHEN** 给某 Client 增加新方法导致总数 > 15 - **THEN** 必须在同次 PR 中拆出至少 1 个 `{Prod}Client_{Module}.java` 子文件,按业务板块归类 - **OR** 如无法立即拆分,必须在 PR 描述里明确「**技术债**:超阈值 N 方法,跟踪 issue/change #xxx」 #### Scenario: 跨板块新方法 - **WHEN** 新方法所属产品板块(如「考勤」)与现有 Client 主体(如「通讯录」)不同 - **THEN** 必须建新的 `{Prod}Client_{Module}.java` 而不是塞进现有 Client --- ### Requirement: R3 调用优先级(子项目复用 mjava) 所有 mjava-ai 仓内子项目(mcli / pro / com / 未来新建客户模块)和**外部独立客户仓**(akds / 光明独立仓 / 其他客户仓)调产品方接口时遵循优先级: 1. **优先调** `mjava.service.{product}.{Prod}Client*` / `{Prod}Service` 2. mjava 未提供但**确属产品方原子接口** → 按 R1/R2 在 mjava 新增 Client → 再被子项目调 3. mjava 未提供且**仅为该客户独有业务编排**(非产品方原子接口)→ 留在客户子项目本地 子项目**禁止**自建 HTTP 直调(绕过 `UtilHttp`)、禁止自建产品方鉴权/Token 逻辑(绕过 `UtilToken`)、禁止引入产品方三方 SDK。 #### Scenario: 子项目发现 mjava 缺接口 - **WHEN** 客户子项目需要某产品方接口,mjava 当前未提供 - **THEN** 先判断该接口是否为「产品方原子能力」 - **AND** 是 → 起 mjava 侧 change 新增 Client → mjava 发版 → 子项目升级 mjava 版本调用 - **AND** 否(客户独有编排)→ 在子项目本地写,不进 mjava #### Scenario: 子项目存量 HTTP 直调 - **WHEN** 巡检发现客户子项目内有 `UtilHttp.doXxx` 直接调产品方域名 - **THEN** 标记为待迁移技术债 - **AND** 下次触碰该代码段时按 R3 上提到 mjava Client --- ### Requirement: R4 变更确认(核心) 对 `com.malk.service.{product}.{Prod}Client*.java` 或 `{Prod}Service.java` 的**接口签名**做以下任一变更前,必须执行变更确认流程: - 删除方法 - 改名方法 - 改方法参数列表(增/减/换类型/换顺序) - 改方法返回类型 - 删除/改名接口本身 - 删除/改名 `server/` 层 POJO 类、字段、方法 变更确认流程: 1. **grep 引用扫描**: - **基础建设阶段(当前默认)**:扫 mjava-ai 本仓全量 - **有客户子项目依赖时**:用户在 ACK 流程中显式列出需扩扫的下游仓库路径(如未来 akds / 光明独立仓 / 新客户仓) 2. **生成引用清单**:file:line + 调用上下文 3. **报告给用户**:影响面 + 拟改动方案 + 兼容路径(如有) 4. **等待用户 ACK**:用户回复"OK / 同意 / 改吧"等明确肯定后方可执行 5. **变更后回写**:commit message 注明 `BREAKING: {Class}.{method} 签名变更`,受影响调用方列表写入 commit body **仅扩展**变更(新方法 / 新重载 / 新子 client 文件 / 新 server 层 POJO)**不需要**确认,但 commit message 注明 `feat(service): 新增 {Class}.{method}`。 #### Scenario: 改 Client 方法签名 - **WHEN** 计划修改 `DDClient_Contacts.getUserDetail(String userid)` 改为 `getUserDetail(String userid, Boolean includeExt)` - **THEN** 必须先执行 grep `\.getUserDetail\(` 跨仓扫描 - **AND** 生成引用清单提交给用户 - **AND** 在用户 ACK 前**不修改源代码** - **AND** ACK 后才动手,commit message 标 `BREAKING` #### Scenario: 仅扩展(加新方法) - **WHEN** 给 `DDClient_Contacts` 新增 `listDepartmentTree()` 方法 - **THEN** 直接实现,无需扫引用 - **AND** commit message 写 `feat(service/dingtalk): DDClient_Contacts 新增 listDepartmentTree` --- ### Requirement: R5 命名一致(Impl 后缀) 所有实现类命名风格统一为 `XxxImpl` **后缀**: - 正:`DDClientImpl` / `DDClient_AttendanceImpl` / `DDServiceImpl` / `YDClient_FormImpl` / `BSServiceImpl` - 负:`DDImplClient` / `DDImplClient_Attendance` / `DDImplService`(中缀风格,禁止新增) R5 对**新增代码**强制生效。钉钉现有 14 个中缀文件(`DDImplClient` + `DDImplClient_X×12` + `DDImplService`)作为存量技术债,拆到独立 change `rename-dingtalk-impl-suffix` 推进,本 change 不动。 #### Scenario: 新增实现类 - **WHEN** 给某 Client 接口新增实现 - **THEN** 文件名必须用 `XxxImpl` 后缀,禁用 `XxxImplXxx` 中缀 #### Scenario: 钉钉新增子 client 实现 - **WHEN** 给 `service/dingtalk/impl/` 新增子 client 实现 - **THEN** 即使周围 12 个文件是 `DDImplClient_X` 中缀风格,新文件必须用 `DDClient_XImpl` 后缀 - **AND** 不为「对齐周围风格」回退 R5 --- ### Requirement: R6 server/ 层定位(数据契约纯净性) `com.malk.server.{product}/` 包内的类必须满足: - **允许**:POJO(含 `*R` `*Param` `*Dto` `*Conf`)、枚举(`enum`)、常量类(`public static final`)、产品方签名/加密工具类(如 `DingCallbackCrypto`、`DigestUtil`) - **禁止**: - `@Service` / `@Component` / `@Repository` / `@RestController` 等 Bean 注解 - `@Autowired` / `@Resource` / 构造器注入其他 Bean - 直接调 `UtilHttp` / `UtilToken` 发起网络请求 - 含业务编排逻辑(if-else 业务分支、跨产品组合) `{Prod}Conf` 是 `@ConfigurationProperties` 的允许例外(标了 `@Component` 注解但仅为配置绑定,不参与业务)。 #### Scenario: 误把业务类放进 server/ - **WHEN** 代码评审发现 `server/dingtalk/` 下出现 `@Service` 注解的类 - **THEN** 必须移到 `service/dingtalk/impl/` 或客户子项目对应位置 #### Scenario: server/ 类的依赖 - **WHEN** 写 `server/{product}/` 下的 POJO 类 - **THEN** import 范围限定在:JDK / Lombok / fastjson / `com.malk.server.common.*` / 同包内类 - **AND** 不得 import `com.malk.service.*` / `com.malk.utils.*`(除常量工具如 `UtilMap` 静态方法) --- ### Requirement: R7 Service 层准入(防空壳) `{Prod}Service.java` 仅当满足以下任一条件时才建: - 当前有 ≥2 个客户子项目(包括 mjava-ai 内 mcli/pro/com 与外部独立客户仓)使用相同二次编排逻辑 - 该编排涉及非业务的通用横切(自动续 token、统一分页拼装、产品方错误码 → `McException` 翻译) - 该编排为新接入产品的「试水期占位」,提案中明确承诺 3 个月内补复用方,否则归零 不满足上述条件时,二次编排留在客户子项目内 `com.malk.{customer}.service.{product}.{Prod}LocalService.java`,命名带 `Local` 前缀以示区分。 未满足条件强行预建 Service 文件(空 interface / 仅一个 default 方法 / 仅一个未被任何客户调用的方法)视为违反 R7,code review 必须打回。 #### Scenario: 新接入产品评估 Service 层 - **WHEN** 在 mjava 接入新产品(如「腾讯会议」) - **THEN** 默认**只建 Client**,不建 Service - **AND** 第一个客户的二次编排放在客户子项目本地 - **AND** 第二个客户来要复用时再上提 #### Scenario: 误建空壳 Service - **WHEN** PR 中出现 `XxxService.java` 接口仅有 1~2 个未被任何子项目调用的方法 - **THEN** code review 必须打回 - **AND** 编排迁回客户子项目