# 后端开发与设计规范

本文档约定 HRO Admin Backend 在 fixture 联调阶段和后续生产化迁移阶段的后端设计规则。目标是让接口兼容、数据库迁移、业务规则补齐和测试验证保持同一套边界。

## 当前阶段

- 当前项目已具备登录鉴权、旧 EHR 接口响应壳、Flyway migration、H2/MySQL 配置、Docker 部署预览和模块化接口文档。
- 大多数考勤菜单仍使用内存 fixture 支撑前端联调，服务重启后恢复默认数据。
- 现有数据库表只覆盖生产化地基：考勤方案、方案负责人、方案范围、原始打卡记录、汇总任务。
- 新增业务能力时，必须明确该能力是 fixture 兼容、接口层校验、真实数据库持久化，还是业务引擎能力。

## 分层约定

```text
Controller
  -> Service Facade
    -> Fixture Module 或 Repository-backed Module
      -> DB / 外部服务 / 内存 fixture
```

- `AttendanceLegacyController` 只负责旧接口路由和响应壳，不写业务分支。
- `AttendanceFixtureService` 作为 facade，保留公共能力和模块委托，不继续无限堆业务逻辑。
- 新菜单或新业务链优先封装成独立 `*FixtureModule` 或后续 `*Service`，避免回到单大类。
- 真实落库时，优先给一个业务模块提供 fixture/DB 两套实现，再迁移下一个模块。

## 实现优先级与复用规范

实现新能力时，按以下顺序选择方案：

1. 复用项目已有接口、模块、工具方法和数据结构。
2. 复用当前技术栈已有能力，例如 Spring MVC、Jackson、Validation、Flyway、HikariCP、Redis、MockMvc。
3. 使用成熟第三方库解决通用问题。
4. 封装小而稳定的项目内组件。
5. 最后才手写业务专用实现。

### 复用优先

- 优先复用 `AttendanceFixtureSupport` 中的分页、Map 解析、ID/编码生成、选项工厂和通用字段工具。
- 优先复用已有 `*FixtureModule` 的写法：内存集合、`save/update/delete/list/index` 对称方法、旧接口兼容字段、MockMvc 覆盖。
- 优先复用已有公共接口：`oursContext`、主题色、组织选择、人员选择、编码生成、大任务桩。
- 同类模块新增字段时，先看已有菜单是否已有字段命名和响应结构，不重新发明一套。
- 同一业务规则被多个模块使用时，抽成 helper 或模块内私有方法，不复制粘贴。

### 第三方库优先

通用能力不要手写复杂实现：

- Excel 导入导出优先使用 Apache POI 或成熟封装，不手写二进制/CSV 解析替代真实 Excel。
- JSON 解析、对象映射、日期处理优先使用 Jackson 和 `java.time`。
- 数据库 migration 使用 Flyway，不手写启动时建表逻辑。
- HTTP、鉴权、过滤器、参数校验优先使用 Spring 已有机制。
- 复杂排班、规则表达式、工作流、任务调度等领域能力，落地前先评估成熟库或独立模块，不在 controller/service 中临时拼算法。

新增第三方依赖前必须确认：

- 依赖维护活跃，许可证可接受。
- 能减少明显代码量或风险。
- 不引入重型框架替代当前架构。
- 有测试覆盖核心用法。

### 少写代码

- 能用配置、枚举、表驱动或数据驱动表达的逻辑，不写多层 if/else。
- 新增分支前先确认是否能通过已有 helper 或结构化数据解决。
- 不为一个调用点提前抽象；出现真实重复或业务概念稳定后再抽象。
- 不写暂时无调用方的“未来扩展”代码。
- 不把真实业务引擎塞进 fixture；fixture 只做接口形状、字段回显和低成本校验。
- 保持方法短小，复杂流程拆成命名清晰的私有方法。

### 注释规范

注释要解释“为什么”，不要重复“做了什么”。

应写详细注释的场景：

- Feishu 文档或旧 EHR 截图中的业务规则，在代码里不直观。
- 为兼容旧接口保留的字段别名、响应结构或异常请求形态。
- fixture 与真实业务引擎的阶段边界。
- 日期区间、排班刷新、人事变动、删除保护等容易误改的业务约束。
- 临时实现、降级实现或后续迁移点。

不应写的注释：

- `// 设置 name` 这类逐行翻译代码的注释。
- 与代码不一致或无法验证的愿景。
- 大段粘贴需求文档，导致代码难读。

推荐写法：

```java
// 旧 EHR 在新增后续档案时会关闭上一条永久档案；fixture 需要模拟这个日期闭合，避免同一员工档案区间重叠。
```

不推荐写法：

```java
// 设置结束日期
```

## Fixture 与真实业务边界

fixture 可以做：

- 返回旧系统兼容的接口形状。
- 保存和回显页面字段。
- 模拟低成本、无外部依赖的业务校验。
- 维护少量内存关系，支持前端完成新增、编辑、删除、列表刷新。
- 用 MockMvc 和 curl 验证前端联调关键路径。

fixture 不应做：

- 真实考勤计算、排班刷新、假期余额发放、薪资联动。
- 外部硬件下发、云端人脸比对、地图/GPS 距离计算。
- 跨模块事件监听，如入职、离职、退休、返聘、部门调动自动同步。
- 大体量 Excel 导入导出和异步任务调度的真实实现。

如果 Feishu 文档中的规则依赖真实引擎，应在模块文档中标为“阶段边界”，不要塞进 fixture。

## 接口响应规范

旧 EHR 兼容接口统一返回：

```json
{
  "code": 0,
  "costTimes": 0,
  "data": {},
  "message": "请求成功"
}
```

普通新接口统一返回：

```json
{
  "code": 0,
  "message": "请求成功",
  "data": {}
}
```

业务错误：

- 使用 `BusinessException`。
- HTTP 状态保持 200。
- `code` 使用非 0，当前优先使用 `400` 表示业务校验失败。
- `message` 使用前端可直接展示的中文提示。

## 请求兼容规范

- 对旧接口要兼容空 body。
- 对录制中出现过的 `pageIndex/pageNo`、`dataList/records`、`id/ids/eids` 等差异形态，应在模块内吸收兼容。
- 对前端可能从 index 返回 URL 的接口，不要在前端硬编码时再改后端路径；后端应保持 index 字段稳定。
- 对下拉枚举使用 `{id,value,display}` 结构，必要时保留旧字段别名。

## 字段命名规范

Java/JSON 字段：

- 使用 camelCase。
- 旧系统字段保持原名，例如 `eId`、`mgrOrgId`、`checkTemplateGroupId`。
- 日期字符串统一使用 `YYYY-MM-DD`。
- 时间戳落库使用数据库 `TIMESTAMP`。

数据库字段：

- 表名使用模块前缀，例如 `atd_`。
- 列名使用 snake_case。
- 主键统一 `id VARCHAR(64)`，兼容旧系统长 ID。
- 业务编码字段使用 `code`，来源类型使用 `source_type/source_type_name`。
- 软删除字段使用 `deleted INTEGER NOT NULL DEFAULT 0`。
- 复杂配置在生产化早期可用 `config_json TEXT` 承载，但核心查询条件必须拆列。

## 数据库迁移规范

- 所有表结构变更通过 Flyway migration 管理。
- migration 文件命名：`V{序号}__{说明}.sql`。
- 新表必须包含 `id`、`created_at`、`updated_at`，关系表可按实际需要省略 `updated_at`。
- 常用查询条件必须建索引。
- 幂等请求需要唯一约束，例如打卡记录的 `request_id`。
- 不在 Java 代码里自动创建生产表。

## 生产化迁移规范（fixture → DB）

本章约定模块从内存 fixture 迁移到真实数据库时的统一边界。迁移一个模块时，保持 Controller 路由和接口契约不变，只替换数据来源。DB 访问层统一采用 `JdbcTemplate`，不引入 JPA/MyBatis 等额外 ORM。

### 数据模型与强类型

- fixture 阶段允许用 `Map<String,Object>` 承接旧接口形状；迁移到 DB 的模块，内部必须改用强类型：
  - 用 Java `record` 定义领域模型（如 `OvertimeRuleGroup`）和出入参 DTO（如 `OvertimeRuleGroupSaveRequest`）。
  - Repository 与 Service 之间只传强类型，不传 `Map`。
- 与前端的边界仍保持旧接口的 JSON 形状：在 Controller 或专门的 assembler 里做 `record ↔ 旧字段 Map` 的转换，兼容字段（`eId`、`mgrOrgId` 等）在转换层处理，不污染领域模型。
- 数据库行 ↔ 领域对象的映射集中在 Repository 的 `RowMapper`，不散落到 Service。

### 数据访问层（JdbcTemplate）

- DB 访问统一使用 `NamedParameterJdbcTemplate`；每个落库模块提供一个 `*Repository`（`@Repository`），封装该模块全部 SQL，Service 只调 Repository，不直接写 SQL。
- SQL 一律用具名参数（`:id`），禁止把用户输入拼进 SQL，防注入。
- 表 ↔ 对象映射用显式 `RowMapper`，对齐 `snake_case ↔ camelCase`。
- 动态可选过滤用 `MapSqlParameterSource` + 条件拼接 helper，不写一个塞满 `OR :x IS NULL` 的巨型 SQL。

### fixture / DB 双实现与切换

- 迁移期同时保留两套实现，用接口隔离，参照 `auth` 的 `TokenStore` 范式：

```text
interface OvertimeRuleStore       // 模块数据能力接口
  -> OvertimeRuleFixtureStore     // 内存实现（默认，联调）
  -> OvertimeRuleJdbcStore        // JdbcTemplate 实现（生产）
```

- 切换统一用 `@ConditionalOnProperty(prefix = "hro.attendance.<module>.persistence", name = "enabled", havingValue = "true", matchIfMissing = false)`，默认仍走 fixture，联调不受影响。
- 落库模块纳入 Spring 容器（`@Repository`/`@Service` 注入），不再像 fixture 子模块那样 `new`。
- 迁移验证稳定后，删除 fixture 实现和开关，避免长期双轨；两套实现在迁移期都要能跑过同一份 MockMvc 契约测试。

### 事务规范

- 写操作涉及多于一张表时，Service 方法必须加 `@Transactional`（如考勤方案要同时写 `atd_scheme`/`atd_scheme_manager`/`atd_scheme_scope`）。
- 只读批量查询可用 `@Transactional(readOnly = true)`。
- 事务边界放在 Service 层，不放 Controller、不放 Repository。
- 事务方法内不做远程调用、长耗时计算或大 Excel 处理，避免长事务占用连接。

### ID 生成规范

- fixture 的进程内 `AtomicLong` 自增不得带入生产（多实例会冲突）。
- DB 阶段新主键统一由应用层生成 64 位雪花 ID，返回字符串，兼容 `id VARCHAR(64)` 和旧长 ID 形态，不依赖数据库自增。
- 保存旧系统已有数据时沿用其原始 ID，不重新生成。
- ID 生成封装成单一组件复用，不在各模块各写一套。

### DB 分页规范

- DB 分页用 SQL `LIMIT/OFFSET` + 独立 `COUNT`，禁止把整表读进内存再 `subList`。
- 返回结构复用 fixture 同款 `{dataList, total, pageIndex, pageSize, ...}`，保证前端契约不变。
- 过滤、排序字段必须在白名单内，排序字段禁止直接拼接前端传入串。
- 列表查询默认限制最大 `pageSize`。

### Controller 拆分规范

- `AttendanceLegacyController` 不再新增端点；按业务域逐步拆为 `OvertimeController`、`LeaveController`、`ArchiveController` 等，各自 `@RequestMapping` 对应旧路径前缀。
- 拆分只搬运路由与响应壳，不改 URL、不改请求/响应结构。
- 一个 Controller 对应一个业务域 Service，不跨域调用其它模块私有方法。
- 迁移某模块时，顺手把它的端点从大 Controller 挪到对应新 Controller。

## 配置规范

- 可变配置走 `application.yml` + `${ENV:default}`，代码不硬编码环境相关值。
- 配置项统一前缀 `hro.*`，用 `@ConfigurationProperties` 绑定成强类型，不散用 `@Value`。
- 每个配置项给安全的开发默认值，保证 `mvn spring-boot:run` 零配置可起。
- 敏感配置（密钥、密码、token secret）生产必须由环境变量注入，仓库只保留占位默认，并同步更新 `.env.example`。
- 环境差异放 `application-prod.yml`，用 profile 覆盖，不在主配置写死生产值。

## 安全与数据权限规范

- 新增接口默认需要鉴权；放行端点集中维护在 `SecurityConfig` 白名单，新增放行须在评审中说明理由。
- 取当前登录用户统一用 `@AuthenticationPrincipal`，不自行解析 token，不信任前端传入的用户 id。
- 涉及组织范围的数据（`mgrOrgId`/`orgId`）必须做数据权限过滤：用户只能访问其管辖组织范围内的数据，过滤条件下推到 SQL `WHERE`，不在内存事后筛。
- 写接口要校验目标数据归属当前用户的权限范围，防止越权修改他人组织数据。
- 日志和响应不返回 token、密码、密钥等敏感信息。

## 日志与可观测规范

- 统一用 SLF4J（`LoggerFactory.getLogger`），不用 `System.out`。
- 级别约定：`error` 仅用于需人工介入的异常；`warn` 用于可降级的异常；`info` 用于关键业务动作（登录、落库、删除）；`debug` 用于联调细节，生产关闭。
- 日志不打印密码、token、身份证号等敏感字段，必要时脱敏。
- 关键写操作（删除、批量编辑、导入）记录操作人、目标 id 和结果，便于审计。
- `actuator` 暴露范围保持最小（当前 health/info），新增暴露端点需评估安全。

## 错误码与参数校验规范

- 迁移到 DB 的接口，入参必须是带 `jakarta.validation` 注解的 DTO（`@NotBlank`、`@NotNull`、`@Size` 等），不再用裸 `Map` 收参。
- 校验失败由 `GlobalExceptionHandler` 统一处理，并把当前笼统的“请求参数不正确”增强为返回首个字段级错误信息，便于前端定位。
- 业务错误码集中定义（建议 `ErrorCode` 枚举），避免散落魔法数。沿用现有约定：`400` 业务校验失败、`401` 登录过期、`403` 无权限、`404` 资源不存在、`500` 内部错误。
- 错误响应 `message` 必须是前端可直接展示的中文。

## 部署与运维纪律

部署（尤其向已有业务的共用服务器部署）前逐项确认，完整可执行清单见[部署文档](deployment.md)的“部署前检查清单”。下列规则来自一次真实事故（未查端口占用 + 配了失败自动重启 → 服务反复重启耗尽内存 → 整机含现网被拖到 SSH 失联）：

- **先查端口**：`ss -tlnp` 确认要用的每个端口空闲，绝不假设。
- **先看内存**：`free -h` 确认 available 足够（JVM 的 RSS 约 1.5×`-Xmx`）；满载的小内存机不要再塞 JVM。
- **盘点共用机**：`systemctl list-units --state=running` 看现有业务，端口 / 库 / 内存全隔离（独立库 + 独立账号 + 独立端口）。
- **留带外退路**：确认能用云控制台 VNC，SSH 失联时才有救。
- **慎用自动重启**：首次 / 不确定环境别配无限 `Restart`；要配必须带 `StartLimitBurst` 快速失败，否则服务反复重启会拖垮内存。
- **逐步验证**：启动后立即查健康 + 内存 + 现网存活；健康检查要核对响应确实来自本服务（别被别的服务的 200 假阳性骗了）。

## 测试规范

新增或修改后端行为时：

- 先补 MockMvc 测试覆盖接口契约。
- 对 bugfix 或规则补齐，优先写能在旧实现下失败的测试。
- 运行 targeted 测试，再运行 `mvn test`。
- 若影响 8080 运行态，重新打包、重启服务，并用 curl 做真实接口冒烟。
- 测试中创建的 fixture 数据要清理，避免影响同类测试。

## 文档规范

每个菜单或模块至少说明：

- 页面路由。
- 基本接口和请求/响应结构。
- 默认 fixture 数据。
- 保存/更新/删除的关键行为。
- 当前阶段已实现的规则。
- 阶段边界和后续真实业务归属。

新增对外可访问的 Markdown 文档时：

1. 新增 `docs/**.md` 文件。
2. 更新 `docs/index.md`。
3. 必要时更新 `README.md`。
4. 如果要通过本地服务访问，更新 `DocsController` 和 `DocsControllerTest`。

## 迁移优先级

建议按以下顺序从 fixture 迁移到真实 DB：

1. 考勤方案和方案适用范围。
2. 打卡规则、地址、WIFI、考勤机、区域、人脸底库等规则配置。
3. 考勤档案。
4. 班次、排班规则、排班周期和实际排班数据。
5. 请假、公出、补卡、加班等规则配置。
6. 原始打卡写入和异步汇总计算。
7. 人事变动事件同步、排班刷新、假期余额发放等业务引擎。

## 评审清单

提交前至少确认：

- 是否明确 fixture/DB/业务引擎边界。
- 是否优先复用了已有模块、helper、接口和响应结构。
- 是否评估了成熟第三方库，避免手写通用复杂能力。
- 是否减少了重复代码，没有引入无调用方的未来扩展。
- 注释是否解释业务原因、兼容原因和阶段边界，而不是逐行翻译代码。
- 是否保留旧接口兼容字段。
- 是否新增或更新文档入口。
- 是否有测试覆盖新增行为。
- 是否运行过 targeted 测试和必要的全量测试。
- 是否记录了无法在当前阶段实现的真实业务边界。

迁移类改动额外确认：

- 是否保持 Controller 路由与接口契约不变。
- 是否使用强类型 record/DTO，Repository 与 Service 间不传 `Map`。
- 多表写操作是否加了事务。
- 是否使用雪花 ID，未把 `AtomicLong` 带入生产。
- DB 分页是否走 SQL，未在内存分页大表。
- 涉及组织数据是否做了数据权限过滤。
- 入参是否使用带校验注解的 DTO。
- 敏感配置是否由环境变量注入。
