# 后端基础结构与分页规范

## 1. 文档定位

本文记录后端基础分层、通用实体、ID 策略、分页、Mapper、Service 和 Controller 的开发习惯。

本文适用于 Java + Spring Boot + MyBatis-Plus 项目。具体业务项目可以在此基础上补充自己的包名、模块边界、认证方式和接口响应格式。

## 2. 分层结构

后端业务代码按以下结构组织：

```text
server/src/main/java/<base-package>/
├── platform/
│   ├── common/
│   │   ├── domain/
│   │   ├── mapper/
│   │   ├── page/
│   │   ├── response/
│   │   └── security/
│   ├── message/
│   ├── ai/
│   └── system/
├── workflows/
│   └── <business-domain>/
└── integrations/
```

每个业务对象建议按以下文件拆分：

```text
<domain>/
├── domain/
│   └── Xxx.java
├── mapper/
│   └── XxxMapper.java
├── service/
│   ├── XxxService.java
│   └── impl/XxxServiceImpl.java
├── control/
│   └── XxxController.java
├── repository/
│   └── XxxRepository.java
└── common/
    ├── request/
    │   ├── XxxCreateRequest.java
    │   ├── XxxUpdateRequest.java
    │   └── XxxQueryRequest.java
    ├── result/
    │   └── XxxResult.java
    ├── dto/
    │   └── XxxSnapshot.java
    └── enums/
        └── XxxStatus.java
```

规则：

- `platform` 放通用能力。
- `workflows` 放业务流程。
- `integrations` 放外部系统适配器；外部协议适配类建议放在 `integrations/<capability>/<provider>/adapter`。
- 平台层不得依赖具体业务流程。
- 业务层不得直接依赖外部系统 DTO。
- Controller 不直接访问 Mapper。
- 前端不直接调用外部系统。
- 外部协议到内部命令或 DTO 的转换类使用 `Adapter`、`Converter` 等后缀，不使用 `Mapper` 命名，避免和 MyBatis Mapper 混淆。

## 3. 低耦合与可维护性

后端模块必须保持低耦合、高内聚和高可维护性。设计新功能时，应先确认模块归属、依赖方向和公开契约，再创建类和接口。

落地要求：

- 一个业务模块只表达一个清晰业务能力，避免在同一个 Service 中混入多个业务流程。
- `platform` 只能提供部门中立能力，不依赖 `workflows`。
- `workflows` 可以依赖 `platform`，但不同业务流程之间不得随意互相调用内部实现。
- `integrations` 只保存外部系统适配器和外部 DTO，不让外部 DTO 进入领域模型。
- 跨模块调用通过 Service、Port、API 契约或事件完成，不直接访问对方 Mapper、Entity 或私有工具类。
- Controller 只调用本模块或明确授权的应用服务，不跨层访问 Mapper 或 Adapter。
- Mapper 只负责本聚合或本表的数据访问，不承载跨业务编排。
- 不为了重命名 MyBatis-Plus 自带方法而在 Mapper 中写薄 default 包装；语义化查询优先放在 Repository 或确有必要的自定义 Mapper 方法中。
- 公共能力先放在业务模块内，出现真实重复和稳定语义后再提升到 `platform`。
- 不为少量相似代码提前抽象大而全的 `CommonService`、`CommonUtil` 或通用模型。
- 当修改一个模块会牵动多个无关模块时，应优先检查依赖方向和接口边界。
- 测试优先覆盖模块公开行为、跨模块契约和关键业务边界。

## 4. ID 策略

### 4.1 内部主键

默认使用数据库内部主键：

```text
数据库类型：BIGINT
Java 类型：Long
生成方式：MyBatis-Plus IdType.ASSIGN_ID
算法类型：Snowflake 风格 64 位 ID
常见长度：18 到 19 位数字
```

推荐实体写法：

```java
@TableId(type = IdType.ASSIGN_ID)
private Long id;
```

规则：

- 内部主键用于数据库主键、关联关系和内部查询。
- 不依赖数据库自增。
- 不使用 UUID 作为默认数据库主键。
- 不把业务含义塞进主键。
- 不根据主键大小判断业务时间顺序，除非明确验证过生成器语义。

### 4.2 API 与前端 ID

后端返回给前端的 ID 必须按字符串处理。

原因：JavaScript `number` 安全整数上限是 `9007199254740991`，Snowflake 这类 18 到 19 位 ID 可能丢精度。

规则：

- API JSON 中 ID 建议返回字符串。
- TypeScript 中 ID 类型使用 `string`。
- 前端不使用 `number` 保存后端 Long ID。
- 前端不对 ID 做数学运算。

示例：

```json
{
  "id": "1890123456789012345"
}
```

### 4.3 公开 ID

如果需要不可枚举、适合公开传播的 ID，不要替代内部主键，另加字段：

```text
publicId
externalId
businessKey
```

可选方案：

- ULID：`VARCHAR(26)`
- UUID：`VARCHAR(36)`
- 业务编号：按业务规则生成

规则：

- 内部主键负责关联。
- 公开 ID 负责对外展示、分享、回调或跨系统引用。
- 是否需要公开 ID 由具体业务决定，不默认添加。

## 5. BaseEntity 规范

推荐统一基础实体：

```java
public abstract class BaseEntity {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;

    private String createdBy;

    private String updatedBy;
}
```

规则：

- `id` 使用 `Long` + `ASSIGN_ID`。
- 数据库字段使用 `BIGINT`。
- `createdAt` / `updatedAt` 由后端自动填充。
- `createdBy` / `updatedBy` 由后端当前用户上下文自动填充。
- 前端不得传入审计字段。
- 业务代码不得散落手动填写审计字段。
- 不把查询参数、分页参数、`params` Map 放进 `BaseEntity`。
- Request 对象不继承 `BaseEntity`。

## 6. createdBy / updatedBy 来源

`createdBy` / `updatedBy` 来自后端当前用户上下文，不来自前端请求体。

建议定义统一接口：

```java
public interface CurrentUserProvider {

    String getCurrentUserId();
}
```

初期没有登录系统时，可以返回：

```text
system
local-dev
```

未来接入登录后，可以从以下来源获取：

- Spring Security `SecurityContext`
- JWT subject
- Session 用户
- 网关注入的用户 Header
- 内部系统任务身份

推荐通过 MyBatis-Plus `MetaObjectHandler` 自动填充：

```text
insert 时填充：
- createdAt
- updatedAt
- createdBy
- updatedBy

update 时填充：
- updatedAt
- updatedBy
```

字段类型建议：

```text
createdBy: VARCHAR
updatedBy: VARCHAR
```

原因：操作者可能是内部用户、系统任务、外部集成、本地开发身份或未来 IAM subject，不一定永远是数据库用户表的 Long ID。

如果后续有稳定用户表，可以额外添加：

```text
createdByUserId BIGINT
updatedByUserId BIGINT
```

但不建议第一阶段默认添加。

## 7. Domain / Entity 规范

`Domain` 或 `Entity` 对应数据库表结构，只表达持久化字段和基础业务含义。

规则：

- 可以继承统一 `BaseEntity`。
- 不直接作为 Controller 入参。
- 不直接作为 API 响应返回给前端。
- 不混入外部系统 DTO 字段。
- 金额使用 `BigDecimal`。
- 时间使用 `LocalDateTime` 或明确时区语义的类型。
- 状态字段使用稳定英文代码或枚举，不使用中文展示文案。
- 字段注释说明业务含义，不只复述字段名。

## 8. Request / QueryRequest 规范

请求对象用于接收前端入参。

建议拆分：

```text
XxxCreateRequest
XxxUpdateRequest
XxxQueryRequest
```

规则：

- 不使用一个大而全的 `Bo` 承载所有场景。
- Request、Command、Query 条件建议放在模块内 `common/request`。
- 创建、更新、查询参数分开。
- 使用 Bean Validation 做基础校验。
- 查询对象可以包含分页、排序、时间范围等条件。
- Request 不继承 `BaseEntity`。
- Request 不包含 `createdBy`、`updatedBy`、`createdAt`、`updatedAt`。
- 不把前端展示文案作为业务参数。

## 9. Response / VO 规范

响应对象用于返回给前端。

规则：

- 不直接返回 Entity。
- 不暴露内部字段、Secret、外部系统原始响应或敏感数据。
- 字段名稳定。
- 前端不能依赖中文或英文文案判断业务。
- Long ID 返回给前端时按字符串处理。
- 时间统一按 API 规范格式化。
- 列表接口使用统一分页响应。
- Result、分页结果、操作结果建议放在模块内 `common/result`；跨层 DTO、Snapshot、Draft 建议放在模块内 `common/dto`。

## 10. Mapper 规范

Mapper 基于 MyBatis-Plus。

建议提供统一基础接口：

```java
public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {

    V selectVoById(Serializable id);

    List<V> selectVoList(Wrapper<T> wrapper);

    PageResult<V> selectVoPage(PageQuery pageQuery, Wrapper<T> wrapper);
}
```

规则：

- Mapper 只负责数据库访问。
- 复杂业务判断不放在 Mapper。
- 优先使用 MyBatis-Plus Wrapper。
- MyBatis-Plus 自带的 `selectById`、`insert`、`updateById` 等继承方法不需要额外 default 包装。
- 自定义 default 方法只有在表达明确业务语义且能减少真实重复时才添加，不能只是改名转调。
- 手写 SQL 必须使用 `#{}` 参数绑定。
- 禁止使用 `${}` 拼接业务参数。
- 手写 SQL 要避免返回过宽字段。
- Mapper 不直接依赖 Controller Request。
- Mapper 不调用外部系统。

## 11. Service 规范

Service 分接口和实现：

```text
XxxService
XxxServiceImpl
```

职责：

- 编排业务流程。
- 管理事务边界。
- 调用 Mapper、Repository 或外部端口。
- 做业务校验、幂等、并发版本、审计和失败恢复。
- 完成 Entity 与 Response 的转换。

规则：

- Controller 不直接调用 Mapper。
- Service 不返回数据库 Entity 给 Controller。
- Service 不吞异常。
- 失败要转换为受控业务异常。
- 日志只记录必要上下文，不输出敏感信息。
- 不在 Service 中硬编码中文展示文案作为业务判断。
- 外部系统调用通过端口或 Adapter 隔离。

## 12. Controller 规范

Controller 只做 HTTP 入口。

职责：

- 路由定义。
- 参数校验。
- 权限入口。
- 调用 Service。
- 返回统一响应结构。

规则：

- Controller 不直接访问 Mapper。
- Controller 不直接调用外部系统 Adapter。
- 方法名必须和业务语义一致。
- 不在 Controller 写复杂业务逻辑。
- 不返回 Entity。
- 不暴露内部异常栈。
- 不接收或信任前端传入的审计字段。

## 13. 分页规范

统一分页请求对象：

```java
public class PageQuery {

    private Integer pageNum = 1;

    private Integer pageSize = 20;

    private String orderBy;

    private String orderDirection;
}
```

规则：

- `pageNum` 从 1 开始。
- `pageSize` 默认 20。
- `pageSize` 必须有最大值限制，例如 100。
- 不允许默认 `Integer.MAX_VALUE` 查询全部。
- 排序字段必须做白名单映射。
- 不能直接拼接前端字段进 SQL。
- `orderDirection` 只允许 `asc` 或 `desc`。

统一分页响应：

```java
public class PageResult<T> {

    private List<T> items;

    private long total;

    private int pageNum;

    private int pageSize;
}
```

推荐 JSON：

```json
{
  "items": [],
  "total": 0,
  "pageNum": 1,
  "pageSize": 20
}
```

不建议使用 `rows/total` 这种偏表格组件的命名，除非前端组件强依赖。

## 14. 统一响应规范

可以使用统一响应包装，例如：

```java
public class ApiResponse<T> {

    private String code;

    private String message;

    private T data;
}
```

规则：

- `code` 使用稳定英文代码。
- `message` 只用于展示，不用于前端业务判断。
- 错误响应不返回 Secret、Token、SQL、内部堆栈或个人敏感信息。
- 业务错误、参数错误、系统错误要有清晰边界。
- 前端业务判断依赖 `code` 和结构化字段，不依赖 `message`。

## 15. 从旧项目借鉴但不照搬

可以借鉴：

- `Domain / Mapper / Service / Controller` 分层。
- `BO / VO` 分离思想。
- `BaseMapperPlus` 减少 Entity 到 VO 转换重复代码的思想。
- `PageQuery + 分页结果` 的统一封装。
- `BaseEntity` 统一审计字段的思想。

不照搬：

- Mapper 中使用 `${}` 拼 SQL。
- catch 后空处理。
- 默认 `pageSize = Integer.MAX_VALUE`。
- 在日志里打印完整业务对象。
- Controller 方法名和业务语义不一致。
- 创建、更新、查询共用一个大 `Bo`。
- Request 继承包含持久化审计字段的 `BaseEntity`。
- 业务代码手动散落填写 `createdBy` / `updatedBy`。
- 前端按 number 处理后端 Long ID。

## 16. 与 Alibaba Java Coding Guidelines 的关系

本规范默认遵守 Alibaba Java Coding Guidelines，尤其是：

- 命名清晰。
- 不写魔法值。
- 异常不吞掉。
- 日志不泄露敏感信息。
- 集合、空值、字符串、时间、金额和 `BigDecimal` 使用安全写法。
- 复杂流程、幂等、事务、外部字段映射和脱敏逻辑写必要中文注释。

项目自身业务边界优先级高于通用 Java 风格规范。
