Files
saas-mbr/docs/architecture.md
T

182 lines
14 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 动静分离微服务)
> 权威来源:`docs/architecture.md` · 编码约束见 `docs/coding-standards.md` · AI 工作流见 `docs/agent-guide.md`
## 1. 总体形态:前后端分离 + 动静同包发布
两个仓库协作,**共享一份 docs/ 文档中心**(本目录):
```
D:\workBuddySpace\member\
├── docs/ ← 团队文档单一权威(本目录)
├── abacus-static-framework/ ← Vue 3 前端底座(src/framework 只读 / src/subsystem 业务可写)
└── abacus.springboot.example/ ← Spring Boot Maven 微服务模板(业务写在 src/main/java 与 resources/sql
```
交付链路(动静分离):
```
前端源码 ──npm run build:prod──► 产物输出到后端 resources/html/vue/<应用名>/
后端 mvn packageassembly.xml
*-bin.zip = jar + doc + sql + html ← 一个发布单元,一起发版
```
- 前端产物**不打进 jar**,随 zip 的 `html/` 目录部署(Nginx/容器托管静态资源,Java 进程提供 API)。
- 开发期前端 `vite.config.ts server.proxy` 反代后端,替代 Nginx 联调。
## 2. 前端底座(abacus-static-framework
| 项 | 选型 |
|---|---|
| 框架 | Vue 3.5 + TypeScript 5 + Vite 6 |
| UI | Element Plus(通用)+ vxe-table(仅表格) |
| 状态 | Pinia |
| 代码生成 | unplugin-auto-import / unplugin-vue-components(自动引入,无需手写常见 import) |
关键机制:
- **只读/可写分区**`src/framework/**` 只读共享;业务只写 `src/subsystem/**`api / router / views / extend 及自建 components / hooks / store / styles / types / utils)。
- **请求管线**`framework/utils/request.ts`):统一响应体解包、服务别名映射、token 自动刷新、commonParam 审计头——业务无感,详见 `coding-standards.md §6`
- **动态菜单权限**`permissionStore.filterAsyncRoutes``url === path` 匹配后端菜单与 `subsystem/router/dynamicRouter.ts`,过滤后 `addRoute`
- **运行时配置**`window.frameBaseConfig`(含 `backendServices` 服务别名映射)由宿主注入,`public/data/data.js` 声明当前子系统(`currentSubsystem` / `currentSubsystemName`)。
## 3. 后端模板(abacus.springboot.example
| 项 | 选型 |
|---|---|
| 构建 | Maven,父工程 `abacus.framework:abacus.springcloud.pom:1.5.0`artifactId `abacus.springboot.<应用名>` |
| 框架 | Spring Boot + abacus 系列(config 2.1.0 / commons 1.6.5 / fundation 3.4.4 / business.service / asyncTool 1.0.0 |
| 注册/配置中心 | Nacosbootstrap.ymlextension-configs 引入 abacus-database / discovery / acas / redis / actuator / xxljob 公共配置) |
| 数据库 | MySQL / SQL Server(jtds+mssql) / Oracle / 达梦,Druid + JPA;多数据源由 Nacos `abacus-database.yml` 提供 |
| 定时任务 | XXL-JOB 2.3.0application-xxljob.yml |
| 消息队列 | Kafka(配置经 Nacos 公共配置) |
| 接口文档 | springdoc`springdoc.packages-to-scan` |
| 监控 | Actuator + Prometheusbase-path `/services/actuator` |
分层(包前缀 `abacus.springboot.<应用名>.`):`api(+api/view) / controller / dao / esb / pi / impl / repository / wsi / config / util / vo`,职责与扫描配置见 `coding-standards.md §5.1`
统一响应体 `{status, message, data, errorCode}``abacus.commons``ResultResponseBodyWrapper` 包装(`Application.java` 注册 `@Bean``abacus.responseExcludePath` 排除白名单 URI)。
## 4. 关键一致性:APP_NAME 四处
应用名(APP_NAME)必须同时一致于:① `bootstrap.yml spring.application.name`Nacos 注册)② `application.yml` 扫描包前缀(`abacus.controllerPackages` / `abacus.entitypackages` / `springdoc.packages-to-scan`)③ 前端 `api/<别名>/*.ts` 的 URL 首段 ④ `vite.config.ts build.outDir` 目录名。漏配任一处将**静默失败**,排查见 `coding-standards.md §3`
## 5. 接口契约速览
| 契约项 | 约定 |
|---|---|
| 响应体 | `{status, message, data, errorCode}``status===200` 前端自动解包 data |
| 异常码 | 业务首位 `9`、系统首位 `5` |
| URL | 首段=服务别名 → `backendServices` 映射真实服务名 |
| 分页 | 响应 `{data, total}`vxe-grid 的 result/total |
| Excel | ArrayBuffer 响应不解包 |
| 鉴权 | Bearer token<10min 自动刷新 + 并发队列 |
## 6. 治理体系
- 前端仓库:`.project.agents/`CLAUDE.md / AGENTS.md / SELF_CONSTRAINTS.md / VIBECODING_GUIDE.md / docs/context/{PRD,ARCHITECTURE,ROADMAP,CONVENTIONS,UIUX}.md
- 后端仓库:`.project.agents/`(同构,无 UIUX.md
- 单源化:共享约束只存在于 `docs/`,仓库内 CONVENTIONS 用指针引用,禁止复制正文。
## 7. SaaS 多租户架构(新增 · 默认形态)
> 所有基于本底座的业务系统**默认按多租户 SaaS 形态交付**。本节定义租户隔离策略、前后端协作方式与扩展能力;编码强制约束见 `coding-standards.md §9`。
### 7.1 多租户隔离策略与选择理由
**结论:行级隔离(`tenant_id` 列)为主,Schema/库级隔离为大型/合规租户的升级路径,统称"混合分层隔离"。**
| 隔离级别 | 实现方式 | 适用对象 | 成本 / 风险 |
|---|---|---|---|
| **行级(默认)** | 每张业务表含 `tenant_id` 列;查询统一附加 `tenant_id = :tenantId` | 绝大多数租户(试用 / 标准版) | 成本低、弹性好,单实例支撑数千租户;**需严防漏写过滤**(§9 强制) |
| Schema 级 | 同一 DB 实例下每租户独立 schema;经动态数据源路由 | 数据敏感 / 合规客户 | 隔离强、成本中;DDL 维护量上升 |
| 库级 | 每租户独立数据库实例 + 独立连接池 | 大型企业 / 集团、数据驻留要求 | 隔离最强、成本最高;运维复杂 |
**选择理由(为什么行级为主):**
- 底座已具备 **Druid + JPA + 多数据源**Nacos `abacus-database.yml``enableMultiSource` / `multidb0~9` / `defaultDB` / `readDB` / `dynamicRelation`)——该能力可直接复用作"Schema / 库级租户的路由底座",无需另起炉灶。
- 行级隔离与现有 `JpaRepository` / `JpaSpecificationExecutor`、原生 SQL`pi/impl`)天然契合,可**不动表结构范式**落地;AI 生成代码能自动带上租户过滤(见 §9)。
- 租户扩展时,通过 `tenant` 表的 `isolation_type` + `datasource_key` 字段,将"标准租户"逐步**升级**为 Schema / 库级,业务代码无需重构(查询仍走同一 `TenantContext`,仅数据源路由不同)。
**核心主数据表(新增,属平台租户,可豁免行级过滤但须标注 `@PlatformTenantOnly`):**
- `tenant`(租户主数据):`id`(业务主键 / DaoIdGenerator) / `tenant_code`(唯一,如 `acme`) / `name` / `tier`(trial / standard / enterprise) / `isolation_type`(row / schema / db) / `datasource_key`(指向 Nacos 数据源键) / `status`
- `tenant_config`(品牌 / 主题 / 模块):`tenant_id` / `logo_url` / `primary_color` / `product_name` / `theme` / `subscribed_modules`(逗号分隔功能码)。
- `tenant_quota`(配额):`tenant_id` / `max_users` / `storage_bytes` / `api_qps` / `feature_flags`
> 建表须遵循 §5.8`sql/table/{mysql,sqlserver}.sql` 双份同步 + `upgrade.xml` 追加版本段。
### 7.2 前端设计(按登录租户动态加载)
- **租户上下文来源**:登录成功后,认证返回 JWT(claims 含 `tenantId` / `tenantCode` / `roles`)与 `tenantConfig`。前端建立 `subsystem/store/tenant.ts``useTenantStore` Pinia),保存 `tenantId` + `tenantConfig`,与 token 一并持久化到 localStorage。
- **品牌与主题动态化**`framework/utils/theme.ts` 提供 `applyTenantTheme(config)`——将 `primary_color` 写入 Element Plus 运行时主题(`--el-color-primary` 及派生 6 档明度),替换 logo(`tenantConfig.logo_url`)、产品名(`product_name` 注入标题 / 页脚)。登录后调用一次,租户切换时重调。
- **功能模块与菜单**`permissionStore.filterAsyncRoutes` 在原有"权限过滤"基础上叠加**租户模块门禁**——仅当菜单所属模块的 feature code ∈ `tenantConfig.subscribed_modules` 时才保留(即"有权限但租户未订阅 → 不可见")。
- **租户切换(运营 / 平台管理员)**:`useTenantStore.switchTenant(code)` → 重新拉取 `tenantConfig``applyTenantTheme` → 重新 `filterAsyncRoutes` → 刷新菜单。
- **请求附带租户标识**:扩展 `framework/utils/request.ts`,在拦截器中自动附加 `X-Tenant-Id` 头(取自 `useTenantStore.tenantId`),与 token 一并发送;**业务代码不得手写租户头**。
### 7.3 后端设计(识别、隔离、缓存、异步全链路)
- **租户识别(token 为准)**:以 JWT 中的 `tenantId` 为数据隔离的**唯一真相源**`X-Tenant-Id` 头仅作下游服务透传 / 审计用,不单独作为隔离依据(防越权)。跨租户运营操作须走独立高权限接口(如 `@CrossTenant` 标注 + 二次鉴权)。
- **TenantContextHolder**`ThreadLocal<TenantContext>``tenantId` / `tenantCode` / `tier` / `isolationType` / `dataSourceKey`)。由 `TenantInterceptor``HandlerInterceptor.preHandle`)在请求入口从 `Authentication` 取出并写入,`afterCompletion``remove()` 防泄漏。
- **查询天然带租户过滤(核心)**:
- JPA:在 `dao` 实体启用 Hibernate `@Filter(name="tenantFilter", condition="tenant_id = :tenantId")`,由 `TenantEntityManager` / `OpenSessionInView` 拦截器统一激活并填入 `:tenantId`;或提供基类 `TenantRepository<T> extends JpaRepository<T,String>, JpaSpecificationExecutor<T>`,所有 `findByXxx` 自动叠加 `equal("tenantId", currentTenant())`
- 原生 SQL`pi/impl`):**每条查询必须** `... AND t.tenant_id = ?`,参数取自 `TenantContextHolder.getTenantId()`,沿用 §5.4 的 `?` 参数化(I2)。
- 硬规则:数据访问方法**禁止省略租户条件**;跨租户场景必须显式 `@CrossTenant` 且经审批。
- **缓存隔离(Redis)**:全部缓存 key 加前缀 `tenant:{tenantId}:<原key>``TenantCacheKeyGenerator`)。漏加前缀会跨租户串数据,属高危(P0)。
- **异步上下文传播**
- `@Async``TenantTaskDecorator`(实现 `TaskDecorator`)将父线程 `TenantContext` 传给子线程。
- KafkaProducer 打 `tenantId` 消息头;Consumer 在处理器开头据消息头重建 `TenantContext`
- XXL-JOB:分片 / 参数携带 `tenantId`Job Handler 处理每个租户数据前先 `setTenantContext`,且任务本身需"多租户感知"(按租户遍历或参数指定)。
- **ID 与数据源**`DaoIdGenerator` 全局唯一(prefix + 日期 + 流水),跨租户不冲突;`tenant_id` 作为行属性而非主键一部分。Schema / 库级租户经 `dynamicRelation` 路由到其 `datasource_key` 对应数据源。
### 7.4 前后端协作方式(端到端)
```mermaid
sequenceDiagram
participant U as 用户
participant FE as 前端(Vue)
participant Auth as 认证/登录
participant BE as 后端(微服务)
participant DB as 数据库/缓存
U->>FE: 登录(账号密码)
FE->>Auth: POST /login
Auth-->>FE: JWT(tenantId,tenantCode,roles) + tenantConfig
FE->>FE: useTenantStore 保存 + applyTenantTheme + filterAsyncRoutes(权限 ∩ 模块)
U->>FE: 业务操作
FE->>BE: 请求 + Authorization:Bearer + X-Tenant-Id
BE->>BE: TenantInterceptor → TenantContextHolder
BE->>DB: 查询自动带 tenant_id=? / Redis key=tenant:{id}:...
DB-->>BE: 仅本租户数据
BE-->>FE: {status:200, data: 本租户数据}
```
- **契约要点**:前端无需、也不应感知其他租户;所有隔离在服务端完成。URL / 业务参数中**不携带** `tenantId`tenant 来自 token / 头)。接口契约补充见 §6。
- **故障隔离**:任一层漏写租户条件由 §9 强制检查 + 评审拦截;缓存 / 异步串租户属 P0 事故。
### 7.5 扩展性(性能 / 计费 / 配额 / 自动化开通)
- **性能**:每张租户表建**复合索引 `(tenant_id, ...)`**;按 `tier` 配置连接池(企业版走独立 `datasource_key` 专属池);大表按 `tenant_id` 分区;读副本按租户流量分配。
- **计量计费**:新增 `billing` 能力(超出示例模板范围,给出设计钩子)——业务流通过 Kafka 异步发送 `tenant.usage` 事件(API 调用数 / 存储字节 / 活跃用户 / 功能用量),`usage-aggregation` 任务按月汇总生成账单。计量必须**异步、非阻塞**,不侵入主流程。
- **配额管理**`tenant_quota` 表 + `QuotaService` / `QuotaInterceptor`,在"影响配额的操作"(建用户、上传、大批量导出)前校验;超出返回 `9xxx` 业务异常(§6.2)。
- **自动化开通(Provisioning**`esb/TenantProvisioningService` 由运营接口 / `tenant.signup` Kafka 事件触发,**幂等 + 可重试(XXL-JOB 兜底)**
- 行级:插 `tenant` + 种子默认管理员 + 赋默认模块订阅 + 注册菜单 / 模块数据(沿用 `sql/data/` 模式)+ 默认配置。
- Schema / 库级:经专属数据源执行建 schema / DB + 基线 DDLmysql / sqlserver 双份)+ 种子数据;在 Nacos 注册该租户的数据源键。
- 完成后发 `tenant.provisioned` 事件。
### 7.6 落地实现建议(映射到 abacus 分层)
**后端新增(包前缀 `abacus.springboot.<应用名>.`):**
- `config/TenantConfig.java`:注册 `TenantInterceptor``TenantContextHolder``TenantTaskDecorator``TenantCacheKeyGenerator`、Hibernate tenant filter。
- `dao/``Tenant``TenantConfig``TenantQuota`(均 `@Entity`;主数据表本身由平台租户管理,可豁免过滤但需标注 `@PlatformTenantOnly`)。
- `api/TenantController.java``/tenant/config` 返回品牌 / 主题 / 模块)、`controller/` 下运营开通接口。
- `esb/TenantBizService.java``esb/TenantProvisioningService.java`
- `pi/TenantAccess.java`(原生 SQL 含 `tenant_id = ?`)。
**前端新增:**
- `subsystem/store/tenant.ts``useTenantStore`)。
- `framework/utils/theme.ts``applyTenantTheme`)。
- 扩展 `framework/utils/request.ts`:自动附加 `X-Tenant-Id`,登录后拉取并缓存 `/tenant/config`
- 扩展 `permissionStore.filterAsyncRoutes`:叠加模块门禁。
**AI 生成代码时**:涉及任何数据访问,`coding-standards.md §9` 强制要求带上租户过滤;新业务表 DDL 必须含 `tenant_id` 列(行级默认)。