# abacus.springboot.example — 架构文档（ARCHITECTURE）

> 模块、依赖、契约、目录布局的**唯一权威**（本仓库视角）。新增/重命名/移动模块或调整依赖方向，必须先改本文，再改代码（同一个 commit）。
> 上游：`PRD.md`（行为）。前后端共享约束见工作区 `docs/`（`../../../../docs/coding-standards.md` §5 后端规范、§6 接口契约），本文不复制其正文。

## 0. 阅读指引

本文描述后端模板的模块划分与依赖方向；§2 模块清单与 §6 目录一一对应，§3.2 是跨模块接口契约。新会话先读 §2 + §6 定位任务归属，再动手。

## 1. 领域模型（Domain Model）

### 1.1 实体

模板自带示例纵切面（稽查 JcBill），供复制改写：

| 实体 | 字段 | 关系 | 持久化？ |
|---|---|---|---|
| jc_bill（稽查主单） | id(@Id)/version(@Version)/status/… | 1:N 明细 | 是 |
| jc_bill_item / jc_bill_photo / jc_bill_hg / jc_bill_hg_pay / jc_source_item / jc_bill_in_item / jc_bill_source_item | id/主单关联字段 | N:1 主单 | 是 |

字段细节见 `dao/*.java` 与 `sql/upgrade.xml`（建表 DDL）。

### 1.2 派生概念（非持久化）

- `api/view/*`：请求/响应 DTO（ApiReq*/ApiResp*），编排层与 Controller 之间传递。
- `@Transient` 字段：实体上的非持久化附加信息（如 `recsource`）。

### 1.3 不变量（任何模块都必须维护）

- I1. 业务主键一律由 `DaoIdGenerator` 生成（prefix+yyyyMMdd+serialLength），禁止手拼/UUID。
- I2. 原生 SQL 全量 `?` 参数化，禁止字符串拼接。
- I3. 分层依赖单向：api/controller → esb/wsi → {pi/impl, repository} → dao；禁止反向依赖。
- I4. Controller 直接 return 业务对象，禁止手拼统一响应体。
- I5. 多租户隔离：所有数据访问必须带 `tenant_id` 过滤（JPA @Filter / Repository 基类 / 原生 SQL `?`），缓存 key 前缀 `tenant:{tenantId}:`；跨租户操作须 `@CrossTenant` + 审批。详见 `../../../../docs/architecture.md §7` 与 `coding-standards.md §9`。

## 2. 模块清单

按依赖层级从低到高排列。

### Layer 0 — dao（实体层）
```
## 模块名：dao
- 职责：JPA 实体（表映射、状态常量、@Version 乐观锁）
- 不负责：查询逻辑、业务规则
- 输入：无（被 repository/impl 引用）
- 输出：实体类型
- 关键类型/接口：@Entity 类（如 JcBill）
- 持有状态：无（纯数据）
```

### Layer 0 — config（基础设施配置）
```
## 模块名：config
- 职责：@Configuration（DaoIdGenerator、AbacusConfigNote 默认值）+ 配置 key 常量
- 不负责：业务规则
- 输入：application.yml / Nacos 配置
- 输出：基础设施 Bean
- 持有状态：无
```

### Layer 1 — repository / pi+impl（数据访问）
```
## 模块名：repository（Spring Data JPA）
- 职责：标准 CRUD 与派生查询（findByXxx）
- 不负责：复杂查询（走 pi/impl）
- 输出：实体查询结果

## 模块名：pi + impl（原生 SQL 访问）
- 职责：复杂查询/批量写（pi 定义接口，impl 实现，SQL 参数化）
- 不负责：业务编排
- 输出：DTO/实体结果
```

### Layer 2 — esb / wsi（服务编排与接口）
```
## 模块名：esb + wsi
- 职责：业务用例编排（esb/*BizService，@Service）、对外服务接口（wsi/*ServiceIF）
- 不负责：直接 SQL、Controller 层参数解析
- 输入：Controller 传入的 DTO/参数
- 输出：编排结果（DTO/实体）
- 持有状态：事务边界（@Transactional 按需）
```

### Layer 3 — api / controller（接口层）
```
## 模块名：api + controller
- 职责：对外接口（api/，springdoc 扫描）与前端/内部接口（controller/，abacus.controllerPackages 扫描）
- 不负责：业务编排与 SQL
- 输入：HTTP 请求（@RequestParam/@RequestBody）
- 输出：业务对象（由 ResultResponseBodyWrapper 包装）
- 持有状态：无
```

## 3. 依赖图（DAG）

```mermaid
graph TD
    api_controller[api / controller] --> esb_wsi[esb / wsi]
    esb_wsi --> pi_impl[pi / impl]
    esb_wsi --> repository[repository]
    pi_impl --> dao[dao]
    repository --> dao
    esb_wsi --> dao
    config -.注入.-> esb_wsi
    config -.注入.-> pi_impl
```

### 3.1 依赖方向规则

- 高层依赖低层，单向；**禁止** dao 依赖 repository、esb 依赖 controller 等反向依赖。
- 跨层调用走接口（wsi ServiceIF / pi AccessIF），不 reach into 实现。

### 3.2 跨模块接口契约（写代码时必须遵守的签名）

```
// module: controller
//   @RequestMapping(value, method) -> 业务对象（throws Exception）   // 直接 return，I4

// module: esb/wsi
//   XxxServiceIF.method(dto...) throws Exception                   // 编排用例，事务边界

// module: pi/impl
//   XxxAccessIF.method(params...) -> List<DTO>                     // 原生 SQL，I2 参数化

// module: repository
//   XxxRepository extends JpaRepository<Entity, String>, JpaSpecificationExecutor<Entity>

// module: config
//   @Bean DaoIdGenerator（prefix/useDateFormat/yyyyMMdd/serialLength）  // I1
```

## 4. 持久化与边界

- 数据源：Druid 连接池 + JPA；多数据源由 Nacos `abacus-database.yml` 提供（enableMultiSource / multidb0~9 / defaultDB / readDB / dynamicRelation）。
- 外部边界：Nacos（注册+配置）、Kafka（消息）、XXL-JOB（调度）、Feign（服务间调用）、springdoc（接口文档）、Actuator（监控）。

## 5. 测试边界

- 无自动化单测（PRD 决策）；接口人工冒烟。
- **必须真机验证**：多数据源路由、Kafka 消费、XXL-JOB 调度、Nacos 配置拉取、真实数据库迁移——mock/模拟器不算数。

## 6. 目录结构（与 §2 模块清单一一对应）

```
src/main/java/abacus/springboot/<应用名>/
├── Application.java                     # 入口（注册 ResultResponseBodyWrapper）
├── api/                                 # 对外 Controller（springdoc 扫描）；api/view/ 放 DTO
├── controller/                          # 前端/内部 Controller（abacus.controllerPackages 扫描）
├── dao/                                 # JPA 实体（abacus.entitypackages 扫描）
├── esb/                                 # Service 编排层（@Service）
├── wsi/                                 # 服务接口 *ServiceIF
├── pi/                                  # 数据访问接口 *AccessIF
├── impl/                                # 数据访问实现（原生 SQL）
├── repository/                          # Spring Data JPA Repository
├── config/                              # @Configuration + 配置常量
├── util/ vo/                            # 既有兼容（可引不可增）
src/main/resources/
├── config/{dev,test,prod}/              # 三套同构配置
├── sql/{table,data}/ + upgrade.xml      # 建表双份/初始化/版本化升级
├── html/vue/<应用名>/                   # 前端构建产物（打包入 *-bin.zip）
└── doc/升级日志.md                       # 功能更新记录
```

## 7. 自检（架构健康度三问）

1. 出 bug 了，能否 30 秒内指出是哪个包（controller/esb/impl/repository）的责任？
2. 换数据库/换 JPA 实现，改动能否控制在 impl/repository + 配置内？
3. 加新业务接口，能否立刻说出它落在 api 还是 controller、esb 是否要新增 BizService？

## 8. 待办与已知技术负债

- `springdoc-openapi-maven-plugin` 的 `apiDocsUrl` 硬编码 `http://localhost:8080/v3/api-docs`，与应用实际端口（9060）不一致，生成 openapi.json 时需手工修正。
- `prod/application.yml` 引用 `abacus.springboot.example.springdoc` 扫描路径但该包不存在（模板未创建 springdoc 包）——新系统以实际包为准。

## 9. 文档变更协议

- 加/删模块或改依赖方向 → 改本文 §2 + §3，同一 commit。
- 改对外签名 → 先改 §3.2，再改代码。
- 与 `PRD.md` 冲突 → 以 `PRD.md` 为准，回改本文；与共享 `docs/coding-standards.md` 冲突 → 以 docs/ 为准并回改本文。

## 10. SaaS 多租户架构（指针）

> 完整设计（隔离策略、租户识别、缓存/异步隔离、前后端协作、扩展性、落地映射）见工作区 `../../../../docs/architecture.md §7`；编码强制约束见 `../../../../docs/coding-standards.md §9`。本文仅记录后端特有约束：

- 租户识别以 JWT `tenantId` 为唯一真相源；`X-Tenant-Id` 头仅透传/审计。
- `TenantContextHolder`（ThreadLocal）由 `TenantInterceptor` 写入、`afterCompletion` 清除。
- 数据访问（JPA / `pi` 原生 SQL / `esb` 编排）一律带 `tenant_id`；缓存 key 前缀 `tenant:{tenantId}:`；`@Async` 经 `TenantTaskDecorator` 传播、Kafka/XXL-JOB 携带 `tenantId`。
- 后端新增 `tenant/` 相关类（`config/TenantConfig`、`dao/Tenant*`、`api/TenantController`、`esb/TenantBizService`/`TenantProvisioningService`、`pi/TenantAccess`），包归属见 §2 / §6，纳入统一扫描。
