# 业务模块骨架与目录结构约定

## 文档定位

本文档定义基于 Ineed-Core 开发业务模块时，推荐采用的：

- 聚合模块骨架
- 子模块命名规则
- 子模块物理目录模板
- `main/java` 下的语义包结构
- 创建模块前必须执行的一致性检查

本文档面向两类对象：

- 人工创建新业务模块的开发者
- 基于 SQL / 需求说明自动生成代码的代码生成器或自动化工具

如果你要判断的是“普通 Java 模块、聚合模块、BOM 模块该不该有 `src`”，先读 [java-module-directory-guide.md](../core/java-module-directory-guide.md)。
本文档只负责业务聚合模块及其子模块骨架，不替代通用 Java 模块物理目录规范。

## 适用范围

本文档描述的是“业务模块约定”，不是 `ineed-core` 自身的真实 Maven 聚合清单。

真实稳定模块清单仍以 [module-inventory.md](../core/module-inventory.md) 为准。

这里定义的是业务代码在接入 Ineed-Core 时应遵守的标准骨架，适用于类似以下目录：

- `java/ineed-modules/ineed-security/ineed-security-authorization/ineed-security-authorization-access-credential`
- `java/ineed-modules/ineed-security/ineed-security-authorization/ineed-security-authorization-system-identity`

## 命名规则

### 聚合模块命名

聚合模块目录名统一使用：

`ineed-{domain}-{capability}`；存在实际能力分组时可继续使用 `ineed-{domain}-{group}-{capability}`

要求：

- 全部小写
- 使用短横线连接
- 必须体现业务域和能力；只有领域确有中间分组时才增加该层，不能为满足固定段数虚构分组
- 不允许使用含糊目录名，如 `common`、`core2`、`temp`、`demo`

示例：

- `ineed-config-category`（域 + 能力）
- `ineed-marketing-campaign`（域 + 能力）
- `ineed-security-authorization-access-credential`
- `ineed-security-authorization-system-identity`

### 子模块命名

子模块目录名统一使用：

`{aggregate-module}-{layer}`

示例：

- `ineed-security-authorization-access-credential-core`
- `ineed-security-authorization-access-credential-engine`
- `ineed-security-authorization-access-credential-jpa`
- `ineed-security-authorization-access-credential-webmvc`

## 标准业务模块骨架

正式业务聚合模块默认按完整骨架创建：

```text
{module}/
  pom.xml
  {module}-core/
  {module}-engine/
  {module}-jpa/
  {module}-jdbc/
  {module}-mybatis-flex/
  {module}-webmvc/
  {module}-engine-reactive/
  {module}-r2dbc/
  {module}-webflux/
```

其中各子模块职责如下：

- `core`
  承载常量定义、枚举定义、工具类、共享 contract，以及跨模块复用的共享 service 契约，如 `contract/ao`、`contract/vo`、`contract/query`、`contract/dto`
- `engine`
  承载阻塞式业务编排、`service`、`manager` 接口与业务规则
- `jpa`
  承载基于 JPA 的实体、仓储、manager 实现、映射
- `jdbc`
  承载基于 JDBC 的实体、仓储、manager 实现、映射
- `mybatis-flex`
  承载基于 MyBatis-Flex 的实体、mapper、manager 实现、映射
- `webmvc`
  承载阻塞式 Web 入口层，如 controller
- `engine-reactive`
  承载响应式业务编排、响应式 `service`、响应式 `manager` 接口与业务规则
- `r2dbc`
  承载基于 R2DBC 的实体、仓储、manager 实现、映射
- `webflux`
  承载响应式 Web 入口层，如 controller

## 最小骨架规则

### 阻塞式最小骨架

如果当前模块只接入阻塞式主链，可暂时最少创建：

```text
{module}/
  pom.xml
  {module}-core/
  {module}-engine/
  {module}-jpa/
  {module}-jdbc/
  {module}-mybatis-flex/
  {module}-webmvc/
```

### 响应式最小骨架

如果当前模块只接入响应式主链，最少创建：

```text
{module}/
  pom.xml
  {module}-core/
  {module}-engine-reactive/
  {module}-r2dbc/
  {module}-webflux/
```

### 约束

- `core` 是阻塞式与响应式共用的共享契约层
- 不再单独创建 `{module}-reactive-core`
- 正式业务模块默认优先按完整骨架预留目录和 `pom.xml`
- 即使某些子模块暂未实现，也建议先保留空骨架，避免后续模块结构失衡
- 只有在当前里程碑已明确限定为“阻塞式最小骨架”或“响应式最小骨架”时，才允许暂时省略其他子模块
- 后续补齐子模块时，必须继续沿用本文档的命名与分层，不得临时发明新的层名

## 标准子模块物理目录模板

每个业务子模块都必须先生成统一的 `src` 骨架：

```text
src/
├── main
│   ├── java
│   │   └── .gitkeep
│   └── resources
│       └── .gitkeep
└── test
    ├── java
    │   └── .gitkeep
    └── resources
        └── .gitkeep
```

规则：

- 即使当前还没有实现代码，也必须保留 `main/java`、`main/resources`、`test/java`、`test/resources`
- 空目录通过 `.gitkeep` 入库
- 不允许只创建 `main/java` 而忽略测试目录

如果模块包含模块私有 `ErrorCode`、字段国际化或对象国际化，推荐继续补齐：

```text
src/main/resources/
  i18n/ineed/{module-segments}/
    message.multiproperties
    messages.properties
    messages_en.properties
    messages_zh_TW.properties
```

相关常量和 i18n 规则继续参考 [constant-errorcode-i18n-guide.md](../core/constant-errorcode-i18n-guide.md)。

模块自描述资源放在 `META-INF/ineed` 下，由框架通过 `classpath*:` 自动扫描聚合。不要把这类资源散落在 `resources` 根目录或业务目录下。

```text
src/main/resources/
  META-INF/ineed/permissions/
    {module-key}.json
  META-INF/ineed/security/whitelist/
    {module-key}.properties
```

规则：

- 权限清单使用 `META-INF/ineed/permissions/*.json`。
- 安全访问白名单使用 `META-INF/ineed/security/whitelist/*.properties`。
- 不要再新增根目录 `whitelist.properties`，也不要使用 `security/{module}/whitelist.properties` 这类旧路径。
- i18n 继续放在 `i18n/ineed/...`，不要迁入 `META-INF`。

权限清单 JSON 的节点类型统一使用 `catalog / menu / page / api / button`：

- `catalog` 表示目录或分组节点，不对应具体前端路由、组件或后端接口，不配置 `permissionUrl` / `component`。一级大类如平台、业务、运营、安全中心使用 `catalog`；多个独立菜单需要归组时，中间层也使用 `catalog`。
- `menu` 表示独立出现在菜单中的前端入口，必须配置前端 `permissionUrl` 和真实 `component`。只要资源是独立入口，就使用 `menu`，不要降级成 `page`。
- `page` 表示挂在某个菜单或目录下的隐藏/从属页面，不独立出现在菜单中，例如详情页、编辑页、配置页、主从资源的子列表页。典型识别信号是该页面依赖主资源 ID 进入，后端通常有 `ISearchOfMasterIdController` / `search-of-master-id` 能力。
- `api` 表达后端接口能力，`permissionUrl` 填后端接口路径；详情、批量详情、列表查询、字典查询、回收站查询、`search-of-master-id` 都应生成 `api`。
- `button` 表达页面可见操作按钮，不填 `permissionUrl`、`component`、`icon`。
- 独立入口不要套成 `menu -> page`。例如“系统身份”和“系统身份分组”都是独立入口时，结构应为 `安全中心 catalog -> 系统身份管理 catalog -> 系统身份 menu / 系统身份分组 menu`，而不是 `系统身份管理 menu -> 系统身份 page / 系统身份分组 page`。
- 权限清单必须补齐 `orderWeight`，按权限树 `DESC` 规则设计；同一资源推荐顺序为辅助查询 API、列表查询、CRUD、状态操作、回收站操作。
- `permissionDescription` 面向授权人员阅读，必须使用业务语言，不写 `idSet`、`query`、`commonParameters` 等后端参数名。

## 子模块语义包结构

以下结构描述的是 `src/main/java` 下推荐的语义包布局。

## 源码注释约定

开发者、代码生成器或自动化工具在创建业务模块源码时，Javadoc、`@author`、字段声明、注解分行、类声明换行和 import 整理统一遵守 [code-style-guide.md](code-style-guide.md)。

本文档只补充业务模块骨架要求：生成模板中的默认职责边界应保持一致，`AO` 承载入参和输入校验，`Query` 只表达查询条件，`VO / Detail` 面向展示返回，`Entity` 只表达表结构和持久化映射，`Repository` 只提供数据访问能力，`Manager` 承载单实体规则、查询拼装、状态操作和持久化编排，`Service` 只组织一次业务用例流程，`Controller / API` 只暴露访问契约。

如果生成后业务规则已经明确，开发人员应继续把模板生成的通用说明补充为更具体的业务规则、状态约束、生命周期说明或异常边界，而不是保留空泛注释。

### JPA QueryDSL Javadoc 构建约定

JPA 模块如果通过 `apt-maven-plugin` 生成 QueryDSL `Q*` 类，生成目录通常是 `target/generated-sources/java`。业务源码可以直接引用这些生成类，例如 `QLoginLog`、`QOperationLog`。编译阶段能够通过，是因为 Maven 编译会纳入生成源码；但 `maven-javadoc-plugin` 默认只扫描 `${project.build.sourceDirectory}`，即 `src/main/java`。

因此，JPA 模块只要源码 Javadoc 阶段需要解析 `Q*` 类型，就必须在该模块的 `maven-javadoc-plugin` 中补齐 `sourcepath`：

```xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-javadoc-plugin</artifactId>
  <configuration>
    <sourcepath>${project.build.sourceDirectory}${path.separator}${project.build.directory}/generated-sources/java</sourcepath>
  </configuration>
</plugin>
```

这个配置的目的不是屏蔽 Javadoc 检查，也不是跳过 `Q*` 类，而是让 Javadoc 的类型解析范围与编译阶段一致，避免 `cannot find symbol: class Q...`。

父 POM 中的 `<show>public</show>` 只控制 Javadoc 对已纳入生成范围的 public API 生成文档；它不决定哪些源码目录被纳入类型解析，也不会专门屏蔽 QueryDSL `Q*` 类。`Q*` 生成类如果被 Javadoc 实际纳入文档生成集合，可能仍会出现 `no comment` 警告；这类 generated-sources 下的 `Q*` 注释警告可接受，不应为了它们给生成源码补手写注释或关闭真实源码的 Javadoc 质量检查。

需要注意的是，`sourcepath` 中加入 `target/generated-sources/java` 后，Javadoc 可以把 QueryDSL `Q*` 类当作依赖类型解析；但这不等同于把所有 `Q*` 类都作为本模块的主文档对象输出。也就是说，`Q*` 类虽然是 public，但只要它们只是被业务源码引用的解析依赖，而不是 Javadoc 插件本次要生成文档的源码入口，就不会因为 `<show>public</show>` 自动触发 `no comment` 警告。真正需要处理的是 `src/main/java` 下的业务源码 Javadoc 警告；`target/generated-sources/java` 下生成类的注释警告可以忽略。

### MyBatis-Flex Annotation Processor 构建约定

MyBatis-Flex 模块如果使用 `@Table` 等注解生成辅助元数据，必须确保 `mybatis-flex-processor` 作为 annotation processor 参与编译。MyBatis-Flex 官方 Maven 文档建议把 `mybatis-flex-processor` 配置到 `maven-compiler-plugin` 的 `annotationProcessorPaths`；这属于当前编译模块自己的编译期处理器列表，不要假设依赖模块中的 processor 配置会自动作用到本业务模块。

Ineed 项目内可以在上层父 POM 统一维护 `mybatis-flex.version`，但 `mybatis-flex-processor` 本身应像 JPA QueryDSL APT 一样独立启用：只有需要生成 `entity.table.*TableDef` 的 `{module}-mybatis-flex` 子模块才配置该 processor。不要把 `mybatis-flex-processor` 放进全局父 POM 的通用 annotation processor path，避免所有 Java 模块都被无差别加载。

`mybatis-flex-processor` 只需要位于 annotation processor path 中，不应作为业务模块运行期依赖；如果为了 IDE 识别临时放入 `<dependencies>`，应使用 `provided`，不要把 processor 扩散为运行期依赖。

模块级核心配置示例：

```xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <annotationProcessorPaths combine.children="append">
      <path>
        <groupId>com.mybatis-flex</groupId>
        <artifactId>mybatis-flex-processor</artifactId>
        <version>${mybatis-flex.version}</version>
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>
```

这个配置属于编译期 annotation processor 配置，不是 Javadoc `sourcepath` 配置；不要用 JPA QueryDSL 的 `apt-maven-plugin` 写法替代 MyBatis-Flex processor，也不要因为业务模块依赖了 `ineed-core-data-mybatis-flex` 就额外添加 `junit-jupiter` 等测试依赖。

### `core`

新建业务模块推荐结构：

```text
com/ineed/{domain}/{object}/
  {Object}Constant.java
  enums/
  contract/dto/
  contract/ao/
  contract/query/
  contract/vo/
  service/
  service/reactive/
  util/
```

约束：

- `core` 只承载共享 contract、常量、枚举、工具类
- `core` 可以放跨模块复用的共享 `service` 契约，但不放业务 `service` 实现
- `core` 不放 ORM `entity`
- `core` 不放 `repository`、`mapper`、`controller`
- 同一共享能力如果同时存在阻塞式与响应式定义，阻塞式放 `service/`，响应式放 `service/reactive/`
- 如果接口围绕当前登录上下文工作，优先直接传 `SecuritySession` 等聚合上下文对象，不把 `identityType`、`principalId`、`tenantKey` 拆成多个零散参数
- 如果某个动作型共享契约在阻塞式侧返回 `void`，响应式侧统一返回 `Mono<Void>`；只有查询、统计等读取型方法才返回具体结果值
- 新建模块和新增代码统一使用 `contract/*` 包名，而不是直接散落在根包下，也不要生成新的 `pojo/*`

兼容说明：

- 遗留业务模块中可能仍然存在 `pojo/ao`、`pojo/query`、`pojo/vo` 一类旧命名
- `pojo/*` 只作为遗留代码识别项，不作为新模块或新增代码的生成目标
- 旧模块如果已经大面积使用 `pojo/*`，新增对象必须先迁移到 `contract/*` 或明确拆出迁移任务，不要继续扩散旧结构
- 创建新模块时，不应因为参考了历史示例目录，就回退生成 `pojo/*` 结构

### `engine`

推荐结构：

```text
com/ineed/{domain}/{object}/
  manager/
  service/
  support/
```

约束：

- `service` 是业务事务与编排边界
- `manager` 表达局部业务能力或规则
- `support` 只放少量辅助编排类
- `engine` 不放具体 ORM 实现
- 阻塞式 `service` 默认方法统一使用 `try/catch + ServiceSupport.logError(log, "...", e)` 记录异常后原样抛出，不再重复写 `if (log.isErrorEnabled())` 模板
- 阻塞式 `manager` 如果要访问可选下游能力，统一优先使用 `OperationSupport.getOperation(Class<T>)` / `requireOperation(Class<T>)` 获取 typed operation

### `jpa`

推荐结构：

```text
com/ineed/{domain}/{object}/jpa/
  entity/
  repository/
  manager/
  converter/
```

### `jdbc`

推荐结构：

```text
com/ineed/{domain}/{object}/jdbc/
  entity/
  querydsl/
  repository/
  manager/
  converter/
```

补充约定：

- `Repository` 默认生成 `interface`，不要回退生成 `class`
- `Repository` 接口直接组合 `ineed-core-data-jdbc` 提供的 fragment，例如 `CreateRepository`、`UpdateRepository`、`FindByIdRepository`、`LastModifiedRepository`、`DataStatusRepository`、`LogicDeleteRepository`、`OrderRepository`、`TreeRepository`、`AccountRepository`、`PhysicsDeleteRepository` 等；按实体能力择需组合，不机械全量继承
- `Repository` 接口统一继续继承 `GetEntityClassOperation<T>`，并在接口内保留 `default Class<T> getEntityClass()`
- 关系表、明细表或只承载少量动作的仓储，只组合真正需要的 fragment，并直接在接口中声明模块私有查询 / 删除方法
- 不再生成模块内的 `repository/support/*`、`Abstract*JdbcRepository` 或只做转发的 `RepositoryImpl`
- 只有确实存在 fragment 无法覆盖的自定义 SQL 或 Spring Data JDBC 限制时，才允许新增具体 `Impl`；此时实现只承载那部分真实差异，不重复包一层通用 CRUD
- JDBC manager 需要组装 Querydsl SQL 查询、排序、搜索字段或 join 时，必须在模块内提供手写 `querydsl/QXxx extends QuerydslSqlTable` 表路径类
- `QXxx` 类只承载 JDBC SQL 表达式元数据：表名、静态单例、业务字段 `StringPath` / `NumberPath` / `DateTimePath` 等；它是源码维护的 JDBC Querydsl SQL table path，不是 JPA APT 生成的 `Q*` 类
- manager 统一覆盖 `getSqlTablePath()` 返回对应 `QXxx` 静态单例，并在 `getKeySearchPath()`、`appendWhereOfQuery()`、`buildDefaultOrderBy()`、join 查询中直接引用 `QXxx.xxx.field`
- 已存在 `QXxx` 可表达的列，不允许在 manager 查询组装中继续散落 `"column_name"` 字符串、`JdbcQuerydslUtil.stringColumn(getSqlTablePath(), "...")` 或等价临时列对象
- 当前 JDBC 代码生成的对标样板以 `ineed-security-authorization-system-identity-jdbc` 为准，`ineed-config-*` 与 `ineed-platform-tenant-jdbc` 已按这一路线收敛

### `mybatis-flex`

推荐结构：

```text
com/ineed/{domain}/{object}/mybatisflex/
  entity/
  mapper/
  manager/
  converter/
```

约束：

- MyBatis-Flex 实现层使用 `mapper`
- `mapper/` 只保留持久化接口，不承载对象转换实现
- 对象转换实现统一放 `converter/`，类名统一使用 `XxxConverter`
- 业务 `*-mybatis-flex` 模块如果需要使用 `QueryWrapper` 拼装跨表查询，必须在本模块独立接入 `mybatis-flex-processor`；如果父级已配置 Lombok / MapStruct processor，本模块使用 `annotationProcessorPaths combine.children="append"` 追加 MyBatis-Flex processor，不要重复复制整套父级 processor 配置
- 跨表查询优先引用 processor 生成的 `entity.table.*TableDef`，例如 `XxxTableDef.XXX.as("x")` 后再使用生成列对象拼装 `select / join / where / orderBy`；不要在 `Manager` 或 `Mapper` 中手写一套散落的 `QueryTable / QueryColumn` 常量
- JPA / JDBC 实现层使用 `repository`
- 新增或重构查询时，JPA 与 MyBatis-Flex 是优先优化路径：JPA 优先使用框架约定查询 / QueryDSL，MyBatis-Flex 优先使用 `QueryWrapper` + 生成的 `TableDef`。JDBC / R2DBC 保持当前 core 支撑层和集中 SQL 实现，只做必要维护，不为了风格统一主动引入 Criteria、jOOQ 或 MyBatis 集成。
- 字典查询默认标签列统一按 `label_value` / `labelValue` 对齐；默认标签读取失败时再 fallback 到 `id`

### `webmvc`

推荐结构：

```text
com/ineed/{domain}/{object}/
  controller/
  service/
```

如确实存在明显分工差异，可按需要继续分为：

- `controller/admin/`
- `controller/internal/`
- `controller/runtime/`

但如果尚未形成稳定分层，不要提前过度拆分。

补充约束：

- `service/` 只放 `webmvc` 这一线的适配器或桥接实现，例如实现 `core` 中共享 service 契约的 `WebMvc*Service`
- `webmvc` 模块中的 `service` 不承担业务主编排，不替代 `engine` 层 `service`

### `engine-reactive`

推荐结构：

```text
com/ineed/{domain}/{object}/
  manager/
  service/
  support/
```

补充约定：

- 响应式 `service` 与 `manager` 的默认方法统一按订阅期装配，`requireManager(...)`、`requireOperation(...)`、上下文读取和能力判断都放进 reactive 链内部
- reactive `manager` 缺少下游 `operation` / `support` 时，应在订阅时发出错误信号，不保留链外同步 `throw`
- 查询型 reactive `manager` 如果要给 `QueryParameters` 补默认过滤条件，也必须放进 `Mono.defer(...)` 或链内准备步骤；不要在返回 Publisher 之前同步修改入参
- create / update / delete / status / account 这类副作用型响应式 `manager` / `service`，阻塞式语义为 `void` 时，响应式统一返回 `Mono<Void>`

### `r2dbc`

推荐结构：

```text
com/ineed/{domain}/{object}/r2dbc/
  entity/
  repository/
  manager/
  converter/
```

补充约定：

- `Repository` 默认同样生成 `interface`
- 响应式仓储与 `jdbc` 侧保持同一套路：直接组合 `ineed-core-data-r2dbc` fragment，并保留 `GetEntityClassOperation<T> + default getEntityClass()`
- 业务 `Manager` 不再实现 `getEntityClass()`；实体类型从 `getOperation().getEntityClass()` 读取，避免 `Repository` 和 `Manager` 两边返回不一致
- 没有真实自定义持久化逻辑时，不生成 `repository/support/*`、`Abstract*R2dbcRepository` 或空 `RepositoryImpl`
- 只有遇到 fragment 无法覆盖的自定义 SQL 或框架限制时，才新增具体 `Impl`，并保持实现范围最小化
- `getKeySearchColumns()` 和 `appendWhereOfQuery(...)` 中的主表列必须使用 `getQualifiedColumn("column_name")`；不要直接写裸列名。只有确实需要 join 或 VO 扩展字段时才覆盖 `buildSelect()` / `buildJoin()` / `getDefaultTableAlias()`
- 自定义 `RepositoryImpl` 一旦进入响应式链路，`entityClass`、`tableName`、`rowMapper` 等解析必须延迟到订阅期；优先复用 `getEntityClassMono()`、`getTableNameMono()` 或 `Mono.defer(...)`
- 不允许在 Publisher 装配期提前求值 `getEntityClass()`、`getTableName()`，也不要生成 `Mono.just(getEntityClass())`、`Mono.just(getTableName())`、`final Class<?> entityClass = getEntityClass()` 这类代码
- R2DBC manager 基类命名统一使用 `AbstractR2dbcManager`；旧的 `AbstractJdbcManager` 仅作为兼容别名保留，不要在新代码中继续引用

### `webflux`

推荐结构：

```text
com/ineed/{domain}/{object}/
  controller/
  service/reactive/
```

补充约束：

- `service/reactive/` 只放 `webflux` 这一线的适配器或桥接实现，例如实现 `core` 中响应式共享契约的 `WebFlux*Service`
- `webflux` 模块中的响应式适配器负责对外桥接和上下文转换，不替代 `engine-reactive` 层业务编排

补充约束：

- 转换层统一使用 `converter/` 包，不再生成 `mapstruct/`
- 转换实现类统一命名为 `XxxConverter`
- MyBatis-Flex 的 `mapper` 与对象转换层 `converter` 必须拆开，避免同一聚合中两套语义不同的 `XxxMapper`
- `XxxConverter` 继承 `AoEntityConverter` / `EntityVoConverter` 后，默认只保留继承关系和必要的 `INSTANCE`；不要重复生成 `ao2Entity`、`entityCopy`、`appleAo`、`entity2VO`
- 需要字段表达式、字典标签、ignore 规则或其他方法级 MapStruct 配置时，才显式声明对应转换方法；普通一比一转换交给父接口继承方法和 MapStruct 生成实现
- 如果业务代码需要接入 `CreateManager` / `UpdateManager` / `FindByIdManager` / `OrderManager` 一类抽象，统一实现 `getCreateEntityConverter()`、`getUpdateEntityConverter()`、`getEntityVoConverter()`
- 如果业务对象需要按稳定业务键重复创建或刷新，例如注册、同步、上报场景，显式实现 `CreateOrRefreshManager` 并实现 `findEntityByBusinessKey(ao)`；业务入口继续使用 `register` / `sync` / `importOne` 等命名并委托 `save(ao)`，不要生成 `saveOrUpdate` / `upsert`
- 业务键查询需要在各数据栈同步落地：JPA / JDBC 返回 `Optional<Entity>`，R2DBC 返回 `Mono<Entity>`，MyBatis-Flex 可由 mapper 查询实体后在 Manager 包装 `Optional`；特殊 JDBC 更新只覆盖 `persistUpdatedEntity(...)`

## Java 类声明格式约定

业务模块生成 `Manager`、`Service`、`Controller`、`Repository`、`Mapper`、`Converter` 等类时，长 `extends` / `implements` 声明统一遵守 [code-style-guide.md](code-style-guide.md)。

`ineed-develop-generator-template` 中的 Java 模板如果新增或调整 `AO / Query / VO / Detail / Entity / Repository / Manager / Controller / API / Constant` 等类模板，必须同步维护类级 Javadoc 的职责说明，避免重新生成后把业务模块注释质量打回空泛模板。

## 共享扩展契约与 Web 适配约定

当某个能力由 A 模块定义、由 B 模块实现，并且需要同时支持 `webmvc` 与 `webflux` 两条线时，统一采用以下约定：

- 共享接口定义优先放在发起方的 `core` 共享层，而不是散落在某个 `webmvc` / `webflux` 实现模块
- 阻塞式共享契约放 `service/`
- 响应式共享契约放 `service/reactive/`
- `webmvc` / `webflux` 模块只保留各自的适配实现，命名可使用 `WebMvc*Service` / `WebFlux*Service`
- 调用方如果已经拿到当前会话对象，接口入参优先直接使用 `SecuritySession` 一类聚合上下文对象，而不是继续拆分成多个基础字段
- create / update / delete / status / account 这类副作用型响应式共享契约不要向上层暴露影响行数；阻塞式为 `void` 时，响应式统一定义为 `Mono<Void>`

## 新功能跨模块对齐规则

无论人工实现、代码生成器生成还是自动化工具修改，只要需求没有明确限制范围，新增功能都必须默认同步评估并对齐：

- 数据层：`core`、`engine`、`engine-reactive`、`jpa`、`jdbc`、`mybatis-flex`、`r2dbc`
- Web 层：`webmvc`、`webflux`
- 对齐内容不只是类文件存在，还包括 `entity`、`repository / mapper`、`manager`、`service`、`controller`、映射、回调、统一返回、异常语义与测试入口
- 同一业务对象在不同模块中的目录层级、类命名、方法职责和代码风格必须保持一致，不能出现一边有 `manager / repository`、另一边缺层或风格漂移
- 阻塞式 `Service` 的异常日志收口也属于对齐范围，统一走 `ServiceSupport.logError(...)`
- 阻塞式 `Manager` 的可选能力解析也属于对齐范围，统一优先走 typed `OperationSupport`
- JDBC / JPA / MyBatis-Flex 的字典查询默认过滤语义必须一致：统一先 `handleQueryParameters(...)`，再强制 `dataStatus.enabled = true`
- 只有在需求文档已经明确限定“本次只做阻塞式”或“本次只做指定数据栈”时，才允许暂时缩小范围；否则不要只改 `jpa` 或只改 `webmvc`

## 字段生成补充约定

无论人工实现、代码生成器生成还是自动化工具修改，新增 `entity`、`contract/*` 下对象时统一遵守 [code-style-guide.md](code-style-guide.md)，并额外满足：

- `AO` 字段如果标记 `@Schema(requiredMode = RequiredMode.REQUIRED)`，必须同步增加 `jakarta.validation.constraints` 校验注解；`String` / `CharSequence` 使用 `@NotBlank`，`List` / `Set` / `Collection` / `Map` / 数组使用 `@NotEmpty`，其它对象、数字、布尔、时间、枚举类型使用 `@NotNull`
- `AO` 字段如果标记 `RequiredMode.NOT_REQUIRED` 或未明确必填，不要为了“看起来更严格”生成 `@NotBlank`、`@NotEmpty`、`@NotNull`；业务条件必填应放在 Service / Manager 规则或专门的校验逻辑中表达
- 非必填字段的格式、范围、长度等输入形态校验，优先直接放在 `AO` 字段上使用 Jakarta Validation，例如 `@Email`、`@Pattern`、`@Size`、`@DecimalMin`、`@DecimalMax`；这类校验不需要额外编写 `*ValidationSupport` 或在 `Manager.beforeSave(...)` / `Manager.beforeUpdate(...)` 中重复调用
- 不要在 Controller 方法参数上新增 `@Valid`；Ineed-Core 的 Web 入口会按框架节奏手动调度 Jakarta Validation，便于统一封装报错消息，并精确控制什么时候验证、什么时候不验证
- 自定义 create / update / assign 等方法如果需要手动调度 AO 校验，应先记录入口日志，再执行 Jakarta Validation；不要在日志前展开 AO 字段做空值兜底，也不要为 AO 上已经表达的必填规则再写 `validateRequiredFields(...)`
- `*ValidationSupport` 不承担 AO 字段级必填、格式、长度等输入形态校验；它只用于跨字段业务规则、依赖持久化状态的规则，或独立方法参数（例如 masterId、organizationId）这类不属于 AO 的入口参数
- 只有需要访问业务状态、持久化数据或跨字段业务规则时，才在 `Manager` 中覆盖 `validate(ao)`；不要为了业务校验覆盖 `save(ao)`、`update(ao)` 或仅转调 `super` 的 `beforeSave(...)`、`beforeUpdate(...)`
- `AO / VO` 默认按未来 Excel 导入导出对象考虑；凡是明确参与导入导出的字段，应直接补齐 `@ExcelProperty`
- 即使当前阶段暂未接入 Excel，也要保持 `AO / VO` 的字段命名、中文语义和注解落点可平滑接入，避免后续为导入导出返工拆类或改字段

## 跨层禁行规则

- `core` 不放 ORM `entity`
- `core` 不把共享扩展契约散落到某一条 Web 线实现模块
- `engine` / `engine-reactive` 不放 `repository` / `mapper` 实现
- `jpa` / `jdbc` / `mybatis-flex` / `r2dbc` 不放 `controller`
- `webmvc` / `webflux` 不放持久化实现
- 同一扩展点的响应式定义不与阻塞式定义混放在同一个 `service` 根包下
- `Service` 不与 `Service` 互调
- 跨表业务通过组合多个 `Manager` 完成
- 删除顺序和依赖清理按业务规则落在正确的删除钩子或业务编排中

## 创建模块前的一致性检查

创建业务模块前，至少必须检查以下内容：

1. SQL 文件、需求说明、目标模块目录三者是否一致
2. 聚合模块名称是否符合 `{domain}-{capability}-{object}`
3. 子模块名称是否符合 `{aggregate-module}-{layer}`
4. 是否已创建统一 `src` 骨架与 `.gitkeep`
5. 当前模块应走完整骨架、阻塞式最小骨架，还是响应式最小骨架
6. `core` 是否作为共享 contract 层被复用，而不是重复创建 `{module}-reactive-core`
7. `core` 下 contract 包命名是否已确定并在整个模块内保持一致
8. 如果存在共享扩展契约，是否已确定接口定义落在 `core`，并按 `service/` 与 `service/reactive/` 分包
9. 围绕当前会话上下文的接口，是否优先采用 `SecuritySession` 等聚合上下文对象作为入参
10. 是否已按 Ineed-Core 文档约定确定：
   - `Controller -> Service -> Manager -> Repository/EntityManager`
   - `Service` 不互调
   - 删除扩展与日志/异常落点
11. 如果业务涉及多对多关系、级联删除、认证/授权或多数据栈实现，是否已按 [business-integration-test-guide.md](business-integration-test-guide.md) 规划模块级主回归测试入口
12. 是否已明确本次功能要同步覆盖哪些数据栈与 Web 入口；如果没有明确限制，是否已按默认规则对齐 `jpa / jdbc / mybatis-flex / r2dbc` 以及 `webmvc / webflux`
13. 字段风格是否已统一遵守 [code-style-guide.md](code-style-guide.md)；是否没有生成新的 `pojo/*`；`AO` 中 `RequiredMode.REQUIRED` 字段是否已同步补齐 `@NotBlank` / `@NotEmpty` / `@NotNull`；需要导入导出的 `AO / VO` 是否已考虑 `@ExcelProperty`

如果以上检查未完成，不应直接开始生成代码。

## SQL / 需求 / 目标模块一致性约定

创建或补齐业务聚合模块前，至少保持三类输入一致：

- 一份 SQL 文档
- 一份需求说明
- 一个目标业务模块目录

命名应尽量保持一致，例如：

- SQL：`ineed_security_access_credential.sql`
- 需求：`ineed_security_access_credential.md`
- 目标模块：`ineed-security-authorization-access-credential`

使用代码生成器或自动化工具生成前，必须先校验这三者和目标模块是否对齐；如不一致，应先停止并指出冲突。

## 参考说明

[分类模块](../../../ineed-modules/ineed-config/ineed-config-category/pom.xml)目前已包含九层业务骨架和 `contract/*` 目录，可用于理解各层接入。逐层源码与运行步骤见[首个模块教程](../getting-started/first-module.md)。

参考实际业务时，应核对当前 POM 和源码，不能沿用早期“缺少 JDBC/响应式、仍使用 pojo”的历史描述。新模块仍需按本规范决定实体能力、权限、租户和依赖边界，不逐字照抄样例中的所有业务规则。

## 阅读建议

- 想看当前 `ineed-core` 框架真实稳定模块清单，先读 [module-inventory.md](../core/module-inventory.md)
- 想理解框架能力分层与进入路径，读 [module-capability-map.md](../core/module-capability-map.md)
- 想让代码生成器或自动化工具按固定规则建模块和写代码，结合 [development-contract.md](development-contract.md) 一起使用本文档

> 最后更新：2026-05-15
