# 代码格式与源码风格指南

## 文档定位

本文档集中约定基于 `ineed-core` 新增或修改后端业务源码时的代码格式、源码注释和基础风格规则。

这些规则不区分人工开发、代码生成器生成或自动化工具修改；所有业务源码都按同一套标准交付。

## 适用范围

- Java 源码与测试源码
- `package-info.java`
- `AO / Query / VO / DTO / Entity / Service / Manager / Repository / Mapper / Converter / Controller`
- 业务模块中需要与 `ineed-core` 约定保持一致的源码模板

## 源码版权头

新增 Java 源码时，文件头必须统一写入：

```java
/*
 * Copyright (c) 2026 Ineedfull.Co.Ltd. All rights reserved.
 */
```

额外约束：

- 适用范围包括 `src/main/java`、`src/test/java` 下的 `.java` 文件以及 `package-info.java`
- 已有同类版权头的文件继续保持 `2026`，不要回退成旧年份
- 第三方许可证文件、上游源码镜像、非 Java 资源文件不要机械套用这条规则

## 类级 Javadoc

类级 Javadoc 是正式交付内容，不能删除、压缩或空置。

统一要求：

- `AO / Query / VO / DTO / Entity / Service / Manager / Repository / Mapper / Converter / Controller` 都要补齐类级 Javadoc
- 类级 Javadoc 必须先说明职责边界，再按业务需要补充关键业务规则
- 类级 Javadoc 必须包含 `@author` 标签
- `@author` 的值按项目模板或既有模块约定填写
- 不要额外机械添加生成时间等易过期的生成信息
- 类级 Javadoc 除 `@author` 外，正文只描述职责边界和关键业务规则
- 不要只写“某某管理器”“某某服务类”“某某对象”这类空泛说明

默认职责口径：

| 类型 | 类级 Javadoc 应表达的职责 |
|------|------|
| `AO` | 承载新增、修改等入参数据和接口层输入校验，不承载持久化逻辑或跨对象业务规则 |
| `Query` | 只表达查询条件和过滤参数，查询组合、默认条件、权限范围由 `Manager` 统一拼装 |
| `VO / Detail` | 面向接口返回和前端展示，写入操作仍使用对应 `AO` |
| `Entity` | 只描述表结构和持久化映射，业务校验、状态流转和跨聚合规则落到 `Manager` 或领域支持类 |
| `Repository / EntityManager` | 只提供底层数据访问能力，不承接业务流程判断或事务编排 |
| `Mapper` | 只提供当前数据栈的持久化访问能力；对象转换实现不放在 `mapper` 包 |
| `Converter` | 只负责实体、入参对象和返回对象之间的结构转换 |
| `Manager` | 承载单实体规则、持久化编排、查询拼装、数据状态操作和必要领域校验 |
| `Service` | 作为事务与业务用例编排边界，只组织流程，不直接访问 `Repository`，也不与其它 `Service` 互调 |
| `Controller / API` | 只暴露访问契约、路由入口、参数校验和统一返回，不直接编排多表业务 |

如果需求中已经明确唯一性、状态流转、删除保护、级联清理、外部同步、审计或权限边界，必须把这些具体规则补充到对应源码的 Javadoc 或必要注释中，不能保留空泛模板原文作为最终交付。

类级 Javadoc 示例：

```java
/**
 * 个人会员管理器
 *
 * <p>负责个人会员单实体的创建、编辑、查询、数据状态切换和删除规则，不承接企业与会员关系的跨表编排。
 *
 * @author "PangZhiGang, xiaopanghhh@163.com"
 */
public class MemberManager {
}
```

## 字段级 Javadoc 与字段声明

字段级 Javadoc 不是可选装饰。

统一要求：

- `Entity` 所有成员变量都必须保留字段级 Javadoc，字段语义要和 SQL 及需求文档一致
- `core` 下 `contract/*` 中的 `AO / VO / Query / DTO` 以及其它普通契约对象，所有成员变量都必须保留字段级 Javadoc
- `Entity` 与 `contract/*` 对象的成员变量统一使用 `protected`
- 不要在同一模块内混用 `protected` 字段和 `private` 裸字段风格
- `@Schema(title = ...)`、Javadoc、`message.multiproperties` 中的对象名和字段名应保持一致
- 不要写空洞注释，例如“客户名称字段”“设置名称”
- 字段注释应直接表达业务语义，例如“客户名称国际化 JSON 内容”“互动下次跟进时间”
- 注解与字段声明分行书写，`@Schema(...)` 独占一行，下一行再写字段声明

正确格式示例：

```java
/**
 * 经营场所
 */
@Schema(title = "经营场所", requiredMode = RequiredMode.NOT_REQUIRED)
protected String branchKey;
```

错误格式示例：

```java
/** 经营场所。 */
@Schema(title = "经营场所", requiredMode = RequiredMode.NOT_REQUIRED) protected String branchKey;
```

## 实体数据状态与租户范围方法

继承 `AbstractTenantBase*Entity` 且需要声明数据状态能力或覆盖租户范围的实体，统一保持固定的类体布局和注解格式。

统一要求：

- `@EnableDataStatus` 必须使用多行格式，每个 `DataStatusFeature` 独占一行
- `@EnableDataStatus` 必须使用 `//@formatter:off` / `//@formatter:on` 做局部保护
- `@EnableDataStatus` 的 formatter 标记只包住该注解，不要把 `@Table`、`@Entity`、Lombok 注解或类声明包进去
- `//@formatter:on` 后必须换行再写下一个注解，不能出现 `//@formatter:on@Table(...)` 这类粘连
- 类体内先声明 `serialVersionUID`，再放 `tenantScopeMode()`，再放业务成员变量
- `tenantScopeMode()` 只表达租户范围策略，不要夹在字段声明之间，也不要放到类尾
- `tenantScopeMode()` 内部保持标准 2 空格缩进，方法体内语句再缩进 2 空格

正确格式示例：

```java
//@formatter:off
@EnableDataStatus({
  DataStatusFeature.FIXED,
  DataStatusFeature.ENABLED,
  DataStatusFeature.LOCKED
})
//@formatter:on
@Table("icfg_category_definition")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CategoryDefinition extends AbstractTenantBaseEntity {

  @Serial
  private static final long serialVersionUID = 1L;

  @Override
  public TenantScopeMode tenantScopeMode() {
    return TenantScopeMode.GLOBAL_ONLY;
  }

  /**
   * 应用标识
   */
  @Column("application_key")
  protected String applicationKey;
}
```

错误格式示例：

```java
@EnableDataStatus({ DataStatusFeature.FIXED, DataStatusFeature.ENABLED, DataStatusFeature.LOCKED })
@Table("icfg_category_definition")
public class CategoryDefinition extends AbstractTenantBaseEntity {

  /**
   * 应用标识
   */
  @Column("application_key")
  protected String applicationKey;

  @Override
  public TenantScopeMode tenantScopeMode() {
    return TenantScopeMode.GLOBAL_ONLY;
  }
}
```

## 方法 Javadoc

统一要求：

- 方法 Javadoc 必须使用标准多行格式
- 摘要与 `@param`、`@return`、`@throws` 等标签之间必须保留一个空 `*` 行
- Javadoc 文本不要机械追加中文句号 `。`
- 如果需求原文已经带标点，避免重复标点

正确格式示例：

```java
/**
 * 追加应用指标
 *
 * @param ao 应用指标提交对象
 * @return 应用指标视图对象
 */
```

## Javadoc 格式

统一要求：

- 类级、字段级和方法级 Javadoc 必须保留
- `/**` 必须独占一行，摘要从下一行 `* ` 开始
- 不要生成 `/** 摘要 */`、`/** 摘要。 */` 或 `/** 摘要` 这类压缩格式
- `package-info.java` 也必须保留包级 Javadoc 和 `@author`

## Java 类声明格式

编写 `Manager`、`Service`、`Controller`、`Repository`、`Mapper`、`Converter` 以及其他长继承或长接口组合类声明时，统一遵守：

- 当 `extends` / `implements` 后存在两个及以上父类型、fragment、能力接口，或单行声明明显过长时，继承 / 接口列表统一换行
- 使用 `//@formatter:off` / `//@formatter:on` 只包住继承 / 接口列表
- `extends` / `implements` 关键字放在类声明首行末尾，行尾不要留空格
- 每个父类型独占一行，使用 2 空格缩进，除最后一个父类型外保留逗号
- `//@formatter:on` 放在类体第一行之后，不能包住字段、构造器、方法或整个类
- 类体开始后，首个字段或成员声明前保留一个空行
- 单一父类型且声明较短时，可以保持单行，不要为了形式化强行加 formatter 标记

示例：

```java
public interface IXxxManager extends
//@formatter:off
  ICreateManager<XxxAO, XxxVO>,
  IFindByIdManager<XxxVO>,
  ISearchManager<XxxQuery, XxxVO> {
//@formatter:on
}

public class XxxManager extends AbstractJdbcManager implements
//@formatter:off
  IXxxManager,
  CreateManager<XxxEntity, XxxAO, XxxVO>,
  SearchManager<XxxEntity, XxxQuery, XxxVO> {
//@formatter:on

  private final XxxRepository xxxRepository;
}
```

## 局部 formatter 保护

自动格式化是默认规则，但有些代码块的换行、缩进和对齐本身承载了阅读结构。遇到这类块时，应使用 `//@formatter:off` / `//@formatter:on` 进行局部保护，避免 IDE 或批量格式化把结构压扁。

必须保护的典型场景：

- 长 `extends` / `implements` 接口列表
- 多行 `@EnableDataStatus` 数据状态能力声明
- 多行 fluent chain，例如 Reactor 链、Stream 链、QueryDSL / MyBatis-Flex `QueryWrapper` 链
- 多行 builder，尤其是安全用户、权限树、数据范围快照、字典项等含多个业务字段的构造
- 多行 SQL / R2DBC / JDBC 查询绑定链
- 多行 `List.of(...)`、字典构造、Comparator 链
- Manager 中 `getKeySearchPath()` / `getKeySearchColumns()` 返回的查询键数组，包括空数组
- 人工排版过、格式变化会明显降低可读性的业务规则块

统一要求：

- formatter 标记只包住需要保护的最小代码块，不包整个方法、整个类或无关字段
- `//@formatter:off` 与 `//@formatter:on` 必须成对出现
- 使用项目既有写法 `//@formatter:off`，不要写成 `// @formatter:off`
- 普通短链、单行 builder、简单 getter / setter、短 Repository 调用不需要机械保护
- 测试代码中只有确实依赖多行结构表达场景意图时才加保护，不要把所有测试 builder 都包起来
- 新增或批量修改后，至少检查 formatter 标记数量是否平衡

批量调整流程：

1. 改动前先确认当前工作区是否已有无关修改；有无关修改时不要运行会覆盖大范围文件的格式化命令。
2. 对本次修改范围内的文件执行项目默认 formatter，优先使用 IDE 或项目既有格式化配置，不手写一套新的缩进规则。
3. 对需要人工排版的结构化代码块补 `//@formatter:off` / `//@formatter:on`，再重新格式化确认保护有效。
4. 格式化后用 diff 复核，只保留本次任务需要的排版变化。无关文件被 formatter 改动时，应从本次提交中排除。
5. 提交前运行 formatter 标记平衡检查，并用 `git diff --check` 检查尾随空白、冲突标记和空白错误。

示例：

```java
//@formatter:off
return roleManager.findById(roleId)
    .switchIfEmpty(Mono.error(StatefulException.of(
        AccessControlConstant.ErrorCode.ROLE_NOT_FOUND,
        "角色不存在")))
    .flatMap(role -> Boolean.TRUE.equals(role.getDeleted())
        ? Mono.error(StatefulException.of(
            AccessControlConstant.ErrorCode.ROLE_NOT_FOUND,
            "角色不存在"))
        : Mono.just(role));
//@formatter:on
```

```java
//@formatter:off
return Comparator
    .comparing(Permission::getOrderWeight, Comparator.nullsLast(Comparator.reverseOrder()))
    .thenComparing(Permission::getCreatedTimestamp, Comparator.nullsLast(Comparator.naturalOrder()))
    .thenComparing(Permission::getId, Comparator.nullsLast(Comparator.naturalOrder()));
//@formatter:on
```

```java
@Override
public StringPath[] getKeySearchPath() {
  //@formatter:off
  return new StringPath[] {
      QMember.member.memberNo,
      QMember.member.memberName,
      QMember.member.mobile
  };
  //@formatter:on
}

@Override
public String[] getKeySearchColumns() {
  //@formatter:off
  return new String[] {
  };
  //@formatter:on
}
```

批量修改后可用以下命令做平衡检查：

```bash
awk '/formatter:off/{off++} /formatter:on/{on++} END{print "off=" off " on=" on}' $(find -L java -path '*/target/*' -prune -o -name '*.java' -print)
```

## import 与注解格式

统一要求：

- `import` 按 IDE / formatter 默认规则整理
- 同一组 import 之间不要每个 import 都插入空行
- 注解独占一行，不要和类声明、字段声明挤在同一行
- `@Schema(...)`、验证注解、Excel 注解和字段声明分行书写

## 第三方 API 使用

新增或修改代码时，不允许继续使用 JDK 或第三方依赖中已经标记为 `@Deprecated` 的 API。

统一要求：

- 编写或修改代码前，应处理 IDE / 编译器提示的 deprecation warning，不要用 `@SuppressWarnings("deprecation")` 掩盖问题
- 修改遗留代码时，如果当前方法或当前改动路径中存在过期 API，应顺手迁移到推荐替代 API
- Apache Commons Lang 3 中已过期的字符串比较和查找方法不要继续使用，例如 `StringUtils.equals(...)`、`StringUtils.equalsIgnoreCase(...)`、`StringUtils.contains(...)`、`StringUtils.containsIgnoreCase(...)`
- 大小写敏感比较和查找统一使用 `org.apache.commons.lang3.Strings.CS.equals(...)`、`Strings.CS.contains(...)`
- 大小写不敏感比较和查找统一使用 `org.apache.commons.lang3.Strings.CI.equals(...)`、`Strings.CI.contains(...)`
- `StringUtils.isBlank(...)`、`StringUtils.isNotBlank(...)`、`StringUtils.defaultIfBlank(...)` 等未过期的空白判断和默认值方法可以继续使用

示例：

```java
// 错误：StringUtils.equals(...) 已过期
StringUtils.equals(user.getTenantKey(), tenantKey);

// 正确：大小写敏感比较
Strings.CS.equals(user.getTenantKey(), tenantKey);

// 正确：大小写不敏感查找
Strings.CI.contains(message, keyword);
```

## 通用实体可用性判断

涉及关系绑定、授权对象筛选、候选数据过滤等场景时，不要在各业务模块重复手写“未删除、租户匹配、数据状态可用”判断。

统一要求：

- 基础必要检查是逻辑删除和租户归属：实体必须未删除，且 `tenantKey` 必须匹配
- 数据状态检查是可选条件：按业务场景传入 `DataStatusFilter`，为空或不包含任何字段时不检查数据状态
- 关系绑定这类只做通用可用性过滤的场景，优先直接调用 `EntityAvailabilitySupport`，不要额外抽取无业务增量的私有判断函数
- 数据状态过滤对象统一使用 `DataStatusFilter`

示例：

```java
if (!EntityAvailabilitySupport.isAvailableInTenant(user, tenantKey)) {
  continue;
}
```

## 日志注解

新增具体业务类统一使用 Lombok 的 `@Slf4j`，不要继续手写 `LoggerFactory.getLogger(...)`。

标准支持接口已有默认日志时，空壳实现不要再包一层重复日志；自定义方法按 [delete-hook-and-logging-guide.md](delete-hook-and-logging-guide.md) 补入口日志和异常日志。

## 生成与修改检查清单

提交或交付前至少检查：

1. Java 源码和 `package-info.java` 是否具备统一版权头
2. 类级 Javadoc 是否描述职责边界和关键业务规则
3. 类级 Javadoc 是否包含 `@author`
4. 是否没有机械添加生成时间等易过期信息
5. 字段级 Javadoc 是否完整，且语义与 SQL、需求和 `@Schema` 一致
6. `Entity` 与 `contract/*` 对象的成员变量是否统一使用 `protected`
7. 实体 `tenantScopeMode()` 是否位于 `serialVersionUID` 后、业务成员变量前
8. `@EnableDataStatus` 是否使用 formatter 保护，且每个 `DataStatusFeature` 独占一行
9. 是否没有单行压缩 Javadoc
10. 方法 Javadoc 的摘要与标签之间是否保留空行
11. 注解、字段声明和类声明是否分行书写
12. 长 `extends` / `implements` 是否只用局部 formatter 标记包住接口列表，且父类型列表使用 2 空格缩进
13. `import` 是否按 formatter 默认规则整理
14. 新写具体类是否统一使用 `@Slf4j`
15. 是否没有继续使用 JDK 或第三方依赖中已标记为过期的 API
16. 手写排版的链式调用、builder、SQL、字典、Comparator 等结构化代码块是否已用局部 formatter 标记保护，且 `off/on` 数量平衡
17. 批量格式化是否只影响本次任务相关文件，且无关 formatter diff 没有进入提交
18. 是否已执行 `git diff --check`，确认没有空白错误

> 最后更新：2026-06-01
