spec.md 10 KB

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/ 下至少有 TXMConfTXMR 至少一个数据契约类
  • 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: 仅扩展(加新方法)

  • WHENDDClient_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 实现

  • WHENservice/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)、产品方签名/加密工具类(如 DingCallbackCryptoDigestUtil
  • 禁止
    • @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/ 类的依赖

  • WHENserver/{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 编排迁回客户子项目