Files
saas-mbr/abacus.springboot.example/.project.agents/docs/context/ARCHITECTURE.md
T

189 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/*`:请求/响应 DTOApiReq*/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
- 职责:@ConfigurationDaoIdGenerator、AbacusConfigNote 默认值)+ 配置 key 常量
- 不负责:业务规则
- 输入:application.yml / Nacos 配置
- 输出:基础设施 Bean
- 持有状态:无
```
### Layer 1 — repository / pi+impl(数据访问)
```
## 模块名:repositorySpring 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 // 直接 returnI4
// module: esb/wsi
// XxxServiceIF.method(dto...) throws Exception // 编排用例,事务边界
// module: pi/impl
// XxxAccessIF.method(params...) -> List<DTO> // 原生 SQLI2 参数化
// module: repository
// XxxRepository extends JpaRepository<Entity, String>, JpaSpecificationExecutor<Entity>
// module: config
// @Bean DaoIdGeneratorprefix/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/ # 对外 Controllerspringdoc 扫描);api/view/ 放 DTO
├── controller/ # 前端/内部 Controllerabacus.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,纳入统一扫描。