14 KiB
架构总览(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 package(assembly.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) |
| 注册/配置中心 | Nacos(bootstrap.yml,extension-configs 引入 abacus-database / discovery / acas / redis / actuator / xxljob 公共配置) |
| 数据库 | MySQL / SQL Server(jtds+mssql) / Oracle / 达梦,Druid + JPA;多数据源由 Nacos abacus-database.yml 提供 |
| 定时任务 | XXL-JOB 2.3.0(application-xxljob.yml) |
| 消息队列 | Kafka(配置经 Nacos 公共配置) |
| 接口文档 | springdoc(springdoc.packages-to-scan) |
| 监控 | Actuator + Prometheus(base-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(useTenantStorePinia),保存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且经审批。
- JPA:在
- 缓存隔离(Redis):全部缓存 key 加前缀
tenant:{tenantId}:<原key>(TenantCacheKeyGenerator)。漏加前缀会跨租户串数据,属高危(P0)。 - 异步上下文传播:
@Async:TenantTaskDecorator(实现TaskDecorator)将父线程TenantContext传给子线程。- Kafka:Producer 打
tenantId消息头;Consumer 在处理器开头据消息头重建TenantContext。 - XXL-JOB:分片 / 参数携带
tenantId;Job Handler 处理每个租户数据前先setTenantContext,且任务本身需"多租户感知"(按租户遍历或参数指定)。
- ID 与数据源:
DaoIdGenerator全局唯一(prefix + 日期 + 流水),跨租户不冲突;tenant_id作为行属性而非主键一部分。Schema / 库级租户经dynamicRelation路由到其datasource_key对应数据源。
7.4 前后端协作方式(端到端)
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.signupKafka 事件触发,幂等 + 可重试(XXL-JOB 兜底):- 行级:插
tenant+ 种子默认管理员 + 赋默认模块订阅 + 注册菜单 / 模块数据(沿用sql/data/模式)+ 默认配置。 - Schema / 库级:经专属数据源执行建 schema / DB + 基线 DDL(mysql / 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 列(行级默认)。