删除目录「docs」
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
# 团队文档中心(docs/)
|
||||
|
||||
> 前后端统一架构、编码标准、接口契约与 agent 协作边界的**单一权威来源**。
|
||||
> 对应仓库:`abacus-static-framework`(Vue 前端底座)· `abacus.springboot.example`(Spring Boot 微服务模板)
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 内容 | 读者 |
|
||||
|---|---|---|
|
||||
| **coding-standards.md** | ★ 前后端开发约束规范(权威):架构边界、APP_NAME 四处一致、前端规范、后端规范、接口契约、AI 生成检查清单、**§9 SaaS 多租户约束** | 全体成员 + AI |
|
||||
| architecture.md | 动静分离架构总览、技术栈、交付链路、治理体系、**§7 SaaS 多租户架构** | 新成员 onboarding |
|
||||
| agent-guide.md | AI 生成代码工作流(界面→代码提示词协议)、联调纪律、Vibe Coding 协作约定 | 全体成员 + AI |
|
||||
| README.md(本文件) | 文档中心索引与交叉引用映射表 | 所有人 |
|
||||
|
||||
## 交叉引用映射表(单源化)
|
||||
|
||||
> 共享约束**只允许存在于 docs/**;各仓库 `.project.agents/docs/context/` 只写指针与仓库特有内容,禁止复制正文。维护共享内容时**先改 docs/,再同步各仓库指针**。
|
||||
|
||||
| 主题 | 权威位置 | 被引用于 |
|
||||
|---|---|---|
|
||||
| 前后端共享约束(命名/契约/工作流) | `docs/coding-standards.md` | 前端/后端 `CONVENTIONS.md`、`CLAUDE.md` |
|
||||
| 动静分离架构链路 | `docs/architecture.md` | 前端/后端 `ARCHITECTURE.md` |
|
||||
| AI 协作与界面→代码协议 | `docs/agent-guide.md` | 前端/后端 `VIBECODING_GUIDE.md`、`AGENTS.md` |
|
||||
| 前端特有约定(标准件清单/页面模式) | 前端 `.project.agents/docs/context/CONVENTIONS.md` | 前端开发 |
|
||||
| 后端特有约定(分层模板/SQL/打包) | 后端 `.project.agents/docs/context/CONVENTIONS.md` | 后端开发 |
|
||||
| 升级日志 | 后端 `src/main/resources/doc/升级日志.md` | 发布记录 |
|
||||
|
||||
## 快速上手(新成员 / 新任务)
|
||||
|
||||
1. 读 `architecture.md` 了解整体形态。
|
||||
2. 开发前通读 `coding-standards.md`(尤其 §3 APP_NAME 与 §6 接口契约)。
|
||||
3. 由 AI 生成代码时,按 `agent-guide.md §2` 提供界面与业务说明,AI 遵循 §7 工作流并回附交付说明。
|
||||
@@ -1,93 +0,0 @@
|
||||
# AI 生成代码工作流与 Vibe Coding 协作指南
|
||||
|
||||
> 权威来源:`docs/agent-guide.md` · 编码约束详见 `docs/coding-standards.md`(本文件只讲"怎么协作",不重复约束)
|
||||
|
||||
## 1. 适用场景
|
||||
|
||||
- **用户给界面 → AI 出代码**:用户提供界面原型(图片/截图/链接/文字描述)与业务说明,AI 按 `coding-standards.md §7` 的步骤序列(S1~S10)生成前后端代码。
|
||||
- **团队 Vibe Coding**:所有成员(人 + AI)在统一约束下开发,产出可互审、可合并、可交付。
|
||||
|
||||
## 2. "界面 → 代码"提示词协议
|
||||
|
||||
### 2.1 用户侧(发起开发请求时的最小输入)
|
||||
|
||||
```
|
||||
【业务说明】<要做什么,一两句话>
|
||||
【界面】<原型图 / 截图 / 链接 / 界面文字描述>
|
||||
【应用名】<APP_NAME,如 example>
|
||||
【服务别名】<前端 URL 首段,如 sms>
|
||||
【数据库】<MySQL / SQL Server,默认 MySQL>
|
||||
【子系统编码】<public/data/data.js 的 currentSubsystem,默认沿用现有>
|
||||
【租户上下文】<本系统为多租户 SaaS,默认行级隔离;AI 生成代码须自动带租户过滤,业务参数不传 tenantId>
|
||||
```
|
||||
|
||||
### 2.2 AI 侧(开工前必须明确的默认值)
|
||||
|
||||
若用户未提供以下任一项,**按下述默认值执行并在交付说明中标注**,不得中断询问:
|
||||
|
||||
| 项 | 默认值 |
|
||||
|---|---|
|
||||
| APP_NAME | 沿用后端 `bootstrap.yml` 现有 `spring.application.name`(老系统内新增功能);新系统才要求用户确认 |
|
||||
| 服务别名 | 沿用 `window.frameBaseConfig.backendServices` 中现有别名;新增服务需用户确认映射 |
|
||||
| 数据库 | MySQL(建表脚本需与 sqlserver.sql 双份同步) |
|
||||
| 页面归属 | 菜单结构按界面标题推断一级/二级菜单;无法推断时归入现有最近菜单 |
|
||||
| 租户隔离 | 默认开启(行级);AI 生成的数据访问自动叠加 `tenant_id` 过滤;不在 URL / 业务参数中传递 tenantId(来自 token,见 `coding-standards.md §9`) |
|
||||
|
||||
### 2.3 交付说明模板(AI 每次交付代码时附带)
|
||||
|
||||
```
|
||||
## 本次交付
|
||||
- 新增/修改文件清单(路径)
|
||||
- 界面元素 → 实现映射(查询区/表格列/表单字段/操作按钮分别落在哪些代码位置)
|
||||
- 接口清单(方法 + URL + 响应结构)
|
||||
- SQL 变更(表/升级版本号)
|
||||
## 已执行检查
|
||||
- APP_NAME 四联检(①~④ 一致?)
|
||||
- 统一响应体/异常码/分页契约/Excel 分支
|
||||
- 未触碰 src/framework、pom、assembly
|
||||
## 待办/风险
|
||||
- 需后端菜单数据注册才能在前端菜单可见
|
||||
- 需真机验证项(多数据源/定时任务/消息队列等)
|
||||
```
|
||||
|
||||
## 3. 联调与验证纪律
|
||||
|
||||
- **Mock 证据不足**:多数据源路由、Kafka 消费、XXL-JOB 调度、Nacos 配置拉取、真实数据库迁移等**必须真机验证**,不得以 mock/静态检查作为完成依据。
|
||||
- 前端 `npm run dev` + 后端启动的冒烟清单见 `coding-standards.md §7.4`。
|
||||
- 交付前由 AI 执行 `coding-standards.md §7.3` 必须/禁止清单自检。
|
||||
|
||||
## 4. 团队协作约定
|
||||
|
||||
### 4.1 提交
|
||||
|
||||
- 遵循 Conventional Commits:`type(scope): subject`(`feat/fix/docs/refactor/chore/test`)。
|
||||
- 提交粒度:一个功能/一个修复一个提交;先 `git add` 相关文件再提交,禁止 `git add -A` 夹带无关改动。
|
||||
- 工作区单一 git 仓库(`D:\workBuddySpace\member`),docs/ 与两项目统一管理;发布以后端 `*-bin.zip` 为单元。
|
||||
|
||||
### 4.2 评审
|
||||
|
||||
- 评审重点:`coding-standards.md §7.3` 清单项、分层依赖方向(§5.1)、SQL 双份同步、命名规范。
|
||||
- PR 描述按「变更内容 / 验证方式 / 影响范围」三段。
|
||||
|
||||
### 4.3 文档维护(单源化纪律)
|
||||
|
||||
- 共享约束只改 `docs/coding-standards.md`;仓库内 `.project.agents` 的 CONVENTIONS 只允许写指针 + 本仓库特有内容,**禁止复制正文**。
|
||||
- 功能更新同步后端 `src/main/resources/doc/升级日志.md`。
|
||||
|
||||
### 4.4 框架演进
|
||||
|
||||
- 业务自研组件/工具泛用性高 → 提交框架负责人评审后纳入 `src/framework/**`,再在新业务中复用;不得长期只存在于业务内。
|
||||
- 框架升级:不覆盖 `public`、`vite.config.ts`、`src/subsystem`。
|
||||
|
||||
## 5. 常见坑(高频返工点)
|
||||
|
||||
| 坑 | 规避 |
|
||||
|---|---|
|
||||
| 前端再取 `.data`(已自动解包) | 编码时直接使用返回值 |
|
||||
| URL 首段与后端 context-path/Controller 路径不符 → 404 | 生成后端接口后回填前端 ts,路径逐字对齐 |
|
||||
| 原生 SQL 字符串拼接 → 注入/报错 | 一律 `?` 占位参数化 |
|
||||
| 只改 mysql.sql 忘 sqlserver.sql | 建表双份同步作为 S7 固定动作 |
|
||||
| 新页面菜单不可见 | 后端菜单数据(`sql/data/`)注册 + dynamicRouter 匹配 url===path |
|
||||
| 手动拼主键/UUID | 一律 `DaoIdGenerator` |
|
||||
| 新增 Util/Helper 类 | 遵循 §8.4:可引不可增 |
|
||||
| 漏写租户过滤 → 跨租户数据泄露 | 数据访问必须带 `tenant_id`(JPA @Filter / Repository 基类 / 原生 SQL `?`),缓存 key 加 `tenant:{id}:` 前缀(§9) |
|
||||
@@ -1,181 +0,0 @@
|
||||
# 架构总览(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`(`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` 传给子线程。
|
||||
- Kafka:Producer 打 `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 + 基线 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` 列(行级默认)。
|
||||
@@ -1,497 +0,0 @@
|
||||
# 前后端开发约束规范(abacus 动静分离微服务)
|
||||
|
||||
> 版本:1.0(2026-08-19) · 权威来源:本文件(`docs/coding-standards.md`)
|
||||
> 适用范围:基于 `abacus-static-framework`(Vue 前端底座)与 `abacus.springboot.example`(Spring Boot Maven 微服务模板)开展的一切业务开发。
|
||||
> 读者:**AI 智能体**(生成代码时强制遵守)与**团队全体成员**(评审与开发时共同遵循)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 规范目的与适用范围
|
||||
|
||||
### 1.1 目的
|
||||
|
||||
1. **界面 → 代码**:用户可直接提供界面(原型图 / 截图 / 描述),由 AI 依据本规范生成前后端代码,无需逐条追问技术细节。
|
||||
2. **统一约束**:团队所有成员在相同约束下开发,代码结构与风格一致,实现可预期的 Vibe Coding 工作流。
|
||||
|
||||
### 1.2 边界(必须)
|
||||
|
||||
| 端 | 只读共享区(不得修改) | 业务可写区(唯一开发位置) |
|
||||
|---|---|---|
|
||||
| 前端 | `abacus-static-framework/src/framework/**` | `abacus-static-framework/src/subsystem/**` |
|
||||
| 后端 | `abacus.springboot.example` 的 `pom.xml` / `assembly.xml` / `config/{dev,test,prod}/` 基础结构 | `src/main/java/abacus/springboot.<应用名>/**` + `src/main/resources/sql/**` + `doc/升级日志.md` |
|
||||
|
||||
- **禁止**在 `src/framework/**` 内新增或修改代码;业务组件/工具无法复用标准件时,按 §4.6/§4.7 提交框架负责人评审后纳入框架,而非绕过。
|
||||
- 前端框架升级不覆盖清单:`public`、`vite.config.ts`、`src/subsystem`。
|
||||
|
||||
### 1.3 文档层级(单源化)
|
||||
|
||||
- 本规范是**前后端共享约束的唯一权威**;各子项目 `.project.agents/docs/context/CONVENTIONS.md` 只写本仓库特有约定,共享内容一律以 `详见 ../../docs/coding-standards.md §N` 指针引用,**禁止复制正文**(防止文档漂移)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 动静分离架构总览
|
||||
|
||||
```
|
||||
[前端] Vue 3 源码 (src/subsystem/**)
|
||||
│ npm run build:prod
|
||||
▼
|
||||
vite outDir ──► [后端] 微服务工程 src/main/resources/html/vue/<应用名>/
|
||||
│
|
||||
│ mvn package(assembly.xml)
|
||||
▼
|
||||
发布包 *-bin.zip(jar + doc + sql + html)──► 部署(Nginx / 容器托管 html,Java 进程提供 API)
|
||||
```
|
||||
|
||||
- 前后端通过 **HTTP 接口**通信;开发期由 `vite.config.ts server.proxy` 反代到后端(代替 Nginx)。
|
||||
- 后端通过 Nacos 注册/发现;接口文档 springdoc(OpenAPI);统一响应体由 abacus.commons 的 `ResultResponseBodyWrapper` 包装(`Application.java` 注册 `@Bean`,`abacus.responseExcludePath` 排除不包装的 URI)。
|
||||
|
||||
---
|
||||
|
||||
## 3. APP_NAME 四处一致性(最高优先级,漏配静默失败)
|
||||
|
||||
新系统/新应用开发时,**应用名(APP_NAME)必须在以下四个位置一致**,任一处漏配即静默失败(启动正常但页面 404 / 接口不可达 / 路由不装配):
|
||||
|
||||
| # | 位置 | 作用 | 示例 |
|
||||
|---|---|---|---|
|
||||
| ① | 后端 `bootstrap.yml` → `spring.application.name` | Nacos 注册名(须唯一) | `example` |
|
||||
| ② | 后端 `application.yml` → `abacus.controllerPackages` / `abacus.entitypackages` / `springdoc.packages-to-scan` 的**包前缀** | 框架装配 Controller / Entity / 接口文档扫描 | `abacus.springboot.example` |
|
||||
| ③ | 前端 `src/subsystem/api/<APP_NAME>/*.ts` 的 **URL 首段** | 服务别名映射(§6.3) | `/example/xxx` |
|
||||
| ④ | 前端 `vite.config.ts` → `build.outDir` 的**目录名** | 构建产物输出到后端 html 目录 | `html/vue/example/` |
|
||||
|
||||
包前缀规范:`abacus.springboot.<应用名>.<层>`(分层见 §5.1)。
|
||||
|
||||
**排查表**(页面/接口异常时按症状定位):
|
||||
|
||||
| 症状 | 优先检查 |
|
||||
|---|---|
|
||||
| 页面 404 / 菜单空白 | ④ outDir 目录名 ≠ 后端 html 实际加载目录;③ URL 首段错误 |
|
||||
| 接口 404 / 未装配 | ② 扫描包前缀未含新包;① Nacos 未注册 |
|
||||
| 接口 500 / 服务不可达 | ① 与 ③ 映射不一致(backendServices 找不到别名) |
|
||||
|
||||
**必须**:AI 生成代码完成后执行"APP_NAME 四联检"(见 §7.3 检查清单第 1 项)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端开发规范(Vue 3 + TS + Vite + Element Plus + vxe-table + Pinia)
|
||||
|
||||
### 4.1 目录结构(`src/subsystem/**` 内)
|
||||
|
||||
```
|
||||
src/subsystem/
|
||||
├── api/<服务别名>/ # 接口封装:按微服务名分目录,按 Controller 分 ts 文件
|
||||
├── router/dynamicRouter.ts # 菜单路由唯一注册点
|
||||
├── views/<一级菜单>/<二级菜单>/index.vue # 菜单页面,按一/二级菜单分目录
|
||||
├── extend/ # 框架扩展钩子(如初始化定时器、菜单徽章)
|
||||
├── components/ # (业务需要时自建)子系统内部通用组件
|
||||
├── hooks/ # (自建)业务组合式函数
|
||||
├── store/ # (自建)业务状态机(Pinia)
|
||||
├── styles/ # (自建)业务公共 css
|
||||
├── types/ # (自建)业务公共类型
|
||||
└── utils/ # (自建)业务工具函数与常量
|
||||
```
|
||||
|
||||
- 参考实现:`views/log/operationLog/index.vue`(列表页)、`views/document/dataDictionary/index.vue`(客户端分页)、`views/config/business/index.vue`(可编辑表格)、`views/document/interfacedoc/`(容器页 :is 切换 List/Detail)。
|
||||
- `unplugin-auto-import` 已配置(vue/vue-router/@vueuse/core + Element Plus resolver):**禁止**手动 `import { ref, reactive } from 'vue'` 等自动导入项。
|
||||
|
||||
### 4.2 命名
|
||||
|
||||
- 组件名:PascalCase,且必须与路由 `name` 一致(`<script lang="ts">export default { name: 'xxx' }</script>`)。
|
||||
- 文件/目录:小写 + 短横线(kebab-case);views 目录用一级/二级菜单中文名或语义名。
|
||||
- API 函数:`getXxx` / `saveXxx` / `deleteXxx` / `exportXxx`(动词+名词)。
|
||||
- **禁止**语义为空的命名:`Manager` / `Helper` / `Util` / `Common` / `Misc` / `Tools`(工具类需用具体动词命名,如 `useTableQueryReload`)。
|
||||
|
||||
### 4.3 API 封装(`api/<服务别名>/*.ts`)
|
||||
|
||||
```ts
|
||||
import request from '@/framework/utils/request';
|
||||
|
||||
// GET:查询,参数走 params
|
||||
export function getOperationLogs(params: any = {}) {
|
||||
return request({ url: '/sms/operationLogs', method: 'get', params });
|
||||
}
|
||||
// POST:表单提交(urlencoded)或 JSON(默认)
|
||||
export function getAbacusAPIs(data: any = {}) {
|
||||
return request({
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
url: '/sms/document/abacusAPI/list', method: 'post', data
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- **必须**:`url` 首段为服务别名(§6.3 映射);默认参数 `params/data = {}`。
|
||||
- **必须**:`status === 200` 时拦截器已自动解包 `data`,**业务代码禁止再取 `.data`**(直接使用返回值)。
|
||||
- **禁止**:在组件内直接 `axios.get` / `fetch`;一律经 `request` 封装(自动携带 token / commonParam / 服务映射 / 错误提示)。
|
||||
|
||||
### 4.4 页面模式(对应用户给出的界面)
|
||||
|
||||
| 界面形态 | 标准实现 |
|
||||
|---|---|
|
||||
| 查询 + 列表 | `AbQueryForm`(fieldConfigs 配置式)+ `vxe-grid`(`gridDefaultProps` + `proxyConfig.ajax.query`) |
|
||||
| 服务端分页 | `clientPage: false`(默认,配 `pagerConfig`) |
|
||||
| 客户端分页 | `clientPage: true`(配 `tableDataCache` / `filterNameRef` / `useQueryAndClientPaging`) |
|
||||
| 新增/编辑表单 | `el-dialog` + `AbForm`(配置式) |
|
||||
| 详情 | `el-drawer` 或容器页 `:is` 切换 List/Detail 组件 |
|
||||
| 可编辑表格 | `editConfig: { trigger: 'dblclick', mode: 'row' }` + `editRender: { name: 'input' }` + `@edit-closed` 保存 |
|
||||
| 表内搜索 | `useInVxeGridSearch` |
|
||||
| 刷新/查询联动 | `useTableQueryReload` / `useQueryParamsHandle` |
|
||||
|
||||
列表页模板骨架:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Title ... /> <!-- 页头 -->
|
||||
<div class="form-container"><AbQueryForm :formModel :fieldConfigs :queryEvent/></div>
|
||||
<div class="table-container"><vxe-grid v-bind="tableOptions"/></div>
|
||||
<el-dialog>...</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default { name: 'OperationLog' } <!-- 与路由 name 一致 -->
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
const tableOptions = reactive<VxeGridProps>(gridDefaultProps({
|
||||
params: reactive<VxeTableParam>({ clientPage: false }),
|
||||
rowConfig: { keyField: 'id' },
|
||||
pagerConfig: { enabled: true, pageSize: 10 },
|
||||
proxyConfig: {
|
||||
seq: true, sort: true, filter: true,
|
||||
props: { result: 'data', total: 'total' }, <!-- 与后端分页契约一致,§6.4 -->
|
||||
ajax: { query: ({ page, sorts, filters }) => getOperationLogs(useQueryParamsHandle(params, page, sorts, filters)) }
|
||||
},
|
||||
columns: [{ type: 'seq', title: '序号' }, { field: 'menu', title: '系统模块' }, ...]
|
||||
}))
|
||||
</script>
|
||||
```
|
||||
|
||||
### 4.5 菜单与路由
|
||||
|
||||
- **唯一注册点**:`src/subsystem/router/dynamicRouter.ts`。一级菜单 `{path, name, redirect, meta:{title}}`,二级 `{path, name, component: () => import('@/subsystem/views/...'), meta:{title, keepAlive:true}}`。
|
||||
- 权限:无 `v-permission` 指令。框架 `permissionStore.filterAsyncRoutes` 将后端菜单(`listRoutes` / `publicData.extLocalRouter`)与 dynamicRouter 按 `url === path` 匹配、过滤无权限路由后 `addRoute`。**业务侧只需正确注册路由与后端菜单 URL 一致**。
|
||||
- 内置指令:`v-debounce` / `v-throttle`(`framework/directives/`)。
|
||||
|
||||
### 4.6 UI 组件优先级(必须按序选用)
|
||||
|
||||
1. `src/framework/components/standard/`(标准件:`AbForm`、`AbQueryForm`、`AbSection`、`ClickCopy`、`FilePreviewDialog`、`ImportExcel`、`LogShow`、`RealTimeSearchSelect`、`SensitiveText`、`Text`、`Layout/BottomFloat` 等,使用方式见框架示例模块);
|
||||
2. 表格一律使用 **vxe-table**(`vxe-grid` + `gridDefaultProps`);
|
||||
3. 除表格外使用 **Element Plus**(已自动按需引入);
|
||||
4. 以上均不满足时,用 css/html 自实现组件;**泛用性高的必须提交框架负责人**发布到公司 UI 组件库,不得仅留在业务内。
|
||||
|
||||
### 4.7 工具函数优先级
|
||||
|
||||
1. `src/framework/utils/standard/`(标准件:`vxe`、`dataHandleUtil`、`globalHandle`、`security`、`baiduMap`);
|
||||
2. 已集成的 `xe-utils`;
|
||||
3. 自研(同上,泛用性高需提交评审)。
|
||||
|
||||
### 4.8 状态管理(Pinia)
|
||||
|
||||
- 业务状态放 `subsystem/store/`,`defineStore` 命名 `use<Xxx>Store`。
|
||||
- 服务别名映射:`settingsStore.backendServices`(来自 `window.frameBaseConfig`),**业务代码不得篡改**。
|
||||
|
||||
### 4.9 发布与框架升级
|
||||
|
||||
- 打包:`npm run build:prod`(测试 `build:stage`、开发 `dev`);产物直出到后端 `resources/html/vue/<应用名>/`。
|
||||
- 发布前:在 `/data/doc/升级日志.md`(后端)记录本次升级内容。
|
||||
- 框架升级:只覆盖 `src/framework/**` 等非业务区;`public`、`vite.config.ts`、`src/subsystem` 不动,覆盖后 `npm install`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 后端开发规范(Spring Boot + abacus 微服务模板)
|
||||
|
||||
### 5.1 分层职责(包前缀 `abacus.springboot.<应用名>.`)
|
||||
|
||||
| 包 | 职责 | 扫描配置 | 示例 |
|
||||
|---|---|---|---|
|
||||
| `api` | 对外接口 Controller(微服务间/系统间) | springdoc.packages-to-scan | `JcBillController` |
|
||||
| `api.view` | 请求/响应 DTO(ApiReq* / ApiResp* / Api*) | — | `ApiReqJcBill` |
|
||||
| `controller` | 前端/内部接口 Controller | abacus.controllerPackages | `AutoCompleteController`、`BaseServlet` |
|
||||
| `dao` | JPA 实体 Entity(表映射) | abacus.entitypackages | `JcBill` |
|
||||
| `esb` | **Service 编排层**(@Service,只编排不做 SQL) | — | `JcBillBizService` |
|
||||
| `wsi` | 服务接口 *ServiceIF(esb/impl 依赖的接口) | — | `JcBillServiceIF` |
|
||||
| `pi` | 数据访问接口 *AccessIF | — | `JcBillAccessIF` |
|
||||
| `impl` | 数据访问实现(原生 SQL,**必须参数化**) | — | `JcBillAccess` |
|
||||
| `repository` | Spring Data JPA Repository | — | `JcBillRepository` |
|
||||
| `config` | @Configuration(Bean / 配置常量) | — | `JcConfiguration`、`JcConfigKey` |
|
||||
| `util` / `vo` | 工具 / 第三方 VO(**只可引用既有,新增禁止**,见 §8.4) | — | `DateUtil`、`BaiduToken` |
|
||||
|
||||
依赖方向(必须单向):`api/controller → esb → {wsi/pi/repository} → dao`;`impl` 实现 `pi`;`config` 被各层依赖;**禁止**跨层反向依赖(如 dao 依赖 api、esb 直接写 SQL)。
|
||||
|
||||
### 5.2 Controller 模板(必须遵循)
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping(value = "/3.0/jc")
|
||||
@Tag(name = "稽查接口服务", description = "稽查接口服务说明")
|
||||
public class JcBillController extends BaseServlet {
|
||||
@Autowired private JcBillBizService jcBillBizService;
|
||||
|
||||
@RequestMapping(value = "createJcBill", method = RequestMethod.POST)
|
||||
@Operation(summary = "稽查收货订单保存")
|
||||
public ApiReqJcBill createJcBill(@RequestParam(required = false) String billInfo) throws Exception {
|
||||
try {
|
||||
if (StringUtils.isBlank(billInfo)) throw new BusinessException("参数[billInfo]为空!");
|
||||
ApiReqJcBill reqJcBill = JSON.parseObject(billInfo, ApiReqJcBill.class);
|
||||
if (StringUtils.isBlank(reqJcBill.getId())) jcBillBizService.saveJcBill(reqJcBill);
|
||||
else jcBillBizService.updateJcbill(reqJcBill);
|
||||
return reqJcBill; // 直接返回业务对象,由 ResultResponseBodyWrapper 包装统一响应体
|
||||
} catch (Throwable e) {
|
||||
LOG.error("...", e);
|
||||
throw e; // 重新抛出,交框架统一处理
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **必须**:`@RestController` + `@RequestMapping` + `@Tag`(springdoc);方法级 `@RequestMapping(value, method)` + `@Operation`。
|
||||
- **必须**:方法签名 `throws Exception`;入参 `@RequestParam(required = false)`;参数空校验 `StringUtils.isBlank → throw new BusinessException("参数[x]为空!")`。
|
||||
- **必须**:**直接 return 业务对象**,禁止手动拼响应体;方法内 try/catch(Throwable) 记日志后 rethrow。
|
||||
- **禁止**:Controller 内写业务编排/SQL(下沉到 esb,见 §5.3);Controller 与 Repository 直接耦合。
|
||||
|
||||
### 5.3 Service 编排层(esb/)
|
||||
|
||||
- 一个业务用例对应一个 `XxxBizService`(@Service),负责事务边界、多仓储编排、DTO↔Entity 转换。
|
||||
- **只编排,不做 SQL**;数据访问通过 `wsi`(ServiceIF)或 `pi`(AccessIF)/ `repository` 完成。
|
||||
|
||||
### 5.4 数据访问
|
||||
|
||||
- **原生 SQL 复杂查询**:`pi/XxxAccessIF` 定义接口,`impl/XxxAccess` 实现;**SQL 一律 `?` 参数化,禁止字符串拼接**(示例 `JcBillAccess` 已全量参数化)。
|
||||
- **标准 CRUD / 派生查询**:`repository/XxxRepository extends JpaRepository<Entity, String>, JpaSpecificationExecutor<Entity>`,派生方法 `findByXxx(...)`。
|
||||
- 服务接口 `wsi/XxxServiceIF` 供上层依赖,隔离实现细节。
|
||||
|
||||
### 5.5 Entity(dao/)
|
||||
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "jc_bill")
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Schema(description = "稽查主单")
|
||||
public class JcBill implements Serializable {
|
||||
@Id @Schema(description = "稽查单号", required = true, example = "JC202411180001")
|
||||
private String id;
|
||||
@Version private Integer version = 1; // 乐观锁(必须)
|
||||
private int status = 1;
|
||||
public final static int STATUS_MINUS_THREE = -3; // 状态常量(集中定义)
|
||||
public final static int STATUS_ZERO = 0;
|
||||
@Transient private String recsource; // 非持久字段
|
||||
// 手写 getter/setter(禁止 Lombok 或代码生成依赖,与既有风格一致)
|
||||
}
|
||||
```
|
||||
|
||||
- **必须**:`@Entity @Table @JsonIgnoreProperties(ignoreUnknown=true) @Schema`;`@Id String`(业务主键,ID 由 DaoIdGenerator 生成,§5.7);`@Version Integer` 乐观锁;状态用 `public final static int STATUS_*` 常量;`@Transient` 标注非持久字段。
|
||||
|
||||
### 5.6 Config(config/)
|
||||
|
||||
- `XxxConfiguration`(@Configuration):注册框架配置项默认值(`@Bean AbacusConfigNote`,keyGroup 按业务域)、`DaoIdGenerator` 等基础设施 Bean。
|
||||
- `XxxConfigKey`:配置 key 常量类。
|
||||
|
||||
### 5.7 ID 生成(DaoIdGenerator)
|
||||
|
||||
```java
|
||||
@Bean(name = "jcBillIdGenerator")
|
||||
public DaoIdGenerator jcBillIdGenerator() {
|
||||
DaoIdGenerator g = new DaoIdGenerator();
|
||||
g.setTargetTableName("jc_bill");
|
||||
g.setPrefix("JC"); // 业务前缀,全局唯一
|
||||
g.setSerialLength(4);
|
||||
g.setUseDateFormat(true);
|
||||
g.setDateFormat("yyyyMMdd");
|
||||
return g;
|
||||
}
|
||||
```
|
||||
|
||||
**禁止**手动拼 ID(如 `UUID` 混用、自增主键代替业务主键),业务主键一律走 `DaoIdGenerator.takeDaoId(null)`。
|
||||
|
||||
### 5.8 SQL 变更(必须三处联动)
|
||||
|
||||
| 场景 | 位置 | 规则 |
|
||||
|---|---|---|
|
||||
| 建表 | `sql/table/mysql.sql` **与** `sql/table/sqlserver.sql` | **双份必须同步**(多库支持) |
|
||||
| 历史变更 | `sql/upgrade.xml` | `<upgrade version="yyyy-MM-dd[.count]">` 按版本升序执行;`translator="true"` 时以 **SQL Server 语法**书写由框架翻译到各库;明细表自增列写在字段类型后 |
|
||||
| 初始化数据 | `sql/data/` | acas 子系统/菜单/模块注册等 |
|
||||
|
||||
- **禁止**只改一份库脚本;禁止绕过 upgrade.xml 直接改已发布环境的表。
|
||||
|
||||
### 5.9 多环境配置(必须三套同步)
|
||||
|
||||
- `src/main/resources/config/{dev,test,prod}/` 三套**同构**:`application.yml` / `bootstrap.yml` / `application-xxljob.yml` / `logback-*.xml`。
|
||||
- 新增/修改配置项必须同步三个环境(或按需说明);字段结构保持一致。
|
||||
- Maven profile:`dev`(默认)/ `test` / `prod`,`-P` 指定;resources 仅打包当前环境 config + `sql/**` + `META-INF`。
|
||||
|
||||
### 5.10 打包发布
|
||||
|
||||
- `pom.xml` `<artifactId>` 设为 `abacus.springboot.<应用名>`;版本号在 `<version>`。
|
||||
- 发布包:`mvn package` → `target/${artifactId}-${version}-bin.zip`(jar + doc + sql + html,由 `assembly.xml` 组装)。
|
||||
- 前端产物:`resources/html/vue/<应用名>/` 自动打入 zip;jar 内**不含** html(动静分离)。
|
||||
|
||||
### 5.11 升级日志
|
||||
|
||||
- 每个功能更新在 `src/main/resources/doc/升级日志.md` 追加:`## <版本号>(<日期>)` 下按「依赖子系统 / 前置条件 / 更新说明」三段记录,更新说明逐条列变更。
|
||||
|
||||
---
|
||||
|
||||
## 6. 前后端接口契约(request.ts 实测确认)
|
||||
|
||||
### 6.1 统一响应体
|
||||
|
||||
```
|
||||
{ status, message, data, errorCode }
|
||||
```
|
||||
|
||||
- `status` 为**业务状态码,与 HTTP 状态码语义独立**。
|
||||
- **前端拦截器在 `status === 200` 时自动解包 `data`**——业务代码拿到的就是 data,**禁止再取 `.data`**。
|
||||
- 后端:Controller 直接 return 业务对象,由 `ResultResponseBodyWrapper` 包装;需排除的 URI 配在 `abacus.responseExcludePath`。
|
||||
|
||||
### 6.2 异常码(强制)
|
||||
|
||||
- **业务异常码首位必须为 `9`**(拦截器 `String(status)[0] === '9'` → 提示 message 并 reject);
|
||||
- **系统异常码首位必须为 `5`**(提示 `[errorCode]系统异常`)。
|
||||
- 前端对 401 / 429 / 503 有专门提示分支。
|
||||
|
||||
### 6.3 URL 别名映射
|
||||
|
||||
- 前端 `url` 首段 = **服务别名**;`request.ts` 的 `urlServiceHandle` 用 `settingsStore.backendServices[别名]`(来自 `window.frameBaseConfig`)替换为真实服务名。
|
||||
- 例:`/sms/operationLogs` → `backendServices.sms`(如 `acas`)→ 实际请求 `/acas/operationLogs`。
|
||||
- 新服务接入:在 `window.frameBaseConfig.backendServices` 增加别名映射;**禁止**前端硬编码真实服务名。
|
||||
|
||||
### 6.4 分页契约(vxe-grid ↔ 后端)
|
||||
|
||||
- 前端 `proxyConfig.props: { result: 'data', total: 'total' }`:**后端分页响应必须为 `{ data: [...], total: N }`**(data 为当前页记录,total 为总数)。
|
||||
- 查询参数:前端 `useQueryParamsHandle(params, page, sorts, filters)` 组装 page/sort/filter 参数;后端 Controller 接收并映射到分页查询。
|
||||
|
||||
### 6.5 Excel 导出(ArrayBuffer 不解包)
|
||||
|
||||
- 二进制响应(`response.data.data instanceof ArrayBuffer`)时拦截器**返回整个 response**;前端取 `response.data`(含 status/message)自行处理 Blob 下载,**不得**当 JSON 解包。
|
||||
|
||||
### 6.6 鉴权与 Token(前端自动处理,业务无感)
|
||||
|
||||
- 请求自动携带 `Authorization: Bearer <access_token>`。
|
||||
- **剩余有效期 < 10 分钟自动刷新 token**,并发请求进入队列、刷新完成后重放。
|
||||
- 请求配置 `custom: { noauth: true }` 可跳过 token(白名单接口)。
|
||||
- HTTP 401 → 提示并跳登录页;500/404/其他 → 统一提示。
|
||||
|
||||
### 6.7 commonParam Header(自动附带)
|
||||
|
||||
- 每个请求自动携带 `commonParam` Header(JSON 字符串):`menuName`(一级菜单)、`moduleId` / `moduleName`(二级菜单,经 `meta.id/title`)、`businessDomain` / `businessDomainName`(业务域)。
|
||||
- 后端可据此做菜单/模块/业务域审计;前端业务代码**无需也不得**手工伪造。
|
||||
|
||||
---
|
||||
|
||||
## 7. AI 生成代码工作流(用户提供界面 → 前后端代码)
|
||||
|
||||
> 面向智能体:收到用户界面素材后,**严格按下述顺序执行**,每步产出物明确。
|
||||
|
||||
### 7.1 输入与前置
|
||||
|
||||
- 用户输入:界面原型(图片/链接/描述)+ 业务说明。
|
||||
- AI 必须先确认:应用名(APP_NAME)、服务别名、数据库(MySQL / SQL Server 二选一为主)、所属子系统编码(`public/data/data.js` 的 `currentSubsystem`)。
|
||||
|
||||
### 7.2 步骤序列(每步产出物)
|
||||
|
||||
| 步骤 | 动作 | 产出物 |
|
||||
|---|---|---|
|
||||
| S1 | 界面 → 页面清单 | `views/<一级>/<二级>/index.vue`(+ List/Detail 拆分),确定查询字段/表格列/表单字段/操作按钮 |
|
||||
| S2 | 生成 API 封装 | `subsystem/api/<别名>/<模块>.ts`(每页一个文件,§4.3) |
|
||||
| S3 | 注册路由 | `subsystem/router/dynamicRouter.ts` 增加菜单项(name 与组件名一致) |
|
||||
| S4 | 后端 Controller | `controller/` 或 `api/` 下 XxxController(§5.2 模板);确定 URL(与前端 ts 首段+路径一致) |
|
||||
| S5 | 后端 Service/数据访问 | `esb/XxxBizService` + `wsi/XxxServiceIF` + `pi/XxxAccessIF` + `impl/XxxAccess`(原生 SQL 参数化)或 `repository/XxxRepository` |
|
||||
| S6 | 后端 Entity/DTO | `dao/Xxx` + `api/view/` DTO(字段与前端表单/表格对齐,命名驼峰) |
|
||||
| S7 | SQL 脚本 | `sql/table/{mysql,sqlserver}.sql` 双份 + `sql/upgrade.xml` 追加版本段 |
|
||||
| S8 | 配置核对 | `application.yml` 扫描包、`vite.config.ts` proxy/outDir、`data.js` 子系统;如为新应用另核 bootstrap.yml/application.yml APP_NAME |
|
||||
| S9 | 联调自检 | 按 §7.3 清单逐项检查;后端启动 + 前端 `npm run dev` 冒烟 |
|
||||
| S10 | 收尾 | 升级日志(后端 `doc/升级日志.md`)、Conventional Commits 提交 |
|
||||
|
||||
### 7.3 必须/禁止清单(AI 生成代码的强制收尾检查)
|
||||
|
||||
**必须:**
|
||||
1. APP_NAME 四联检(§3 表 ①~④ 一致)。
|
||||
2. 前端 API 一律走 `request` 封装;`status===200` 后不取 `.data`。
|
||||
3. 后端 Controller return 业务对象、`throws Exception`、参数空校验抛 `BusinessException`。
|
||||
4. 原生 SQL 全量 `?` 参数化;建表脚本 mysql/sqlserver 双份同步。
|
||||
5. 组件/工具优先复用 standard 标准件(§4.6/§4.7)。
|
||||
6. 分页接口响应 `{ data, total }`;Excel 导出走 ArrayBuffer 分支。
|
||||
|
||||
**禁止:**
|
||||
1. 修改 `src/framework/**`、`pom.xml` 依赖版本、`assembly.xml` 结构。
|
||||
2. 前端硬编码服务名/手动拼 URL;后端手拼业务主键(须 `DaoIdGenerator`)。
|
||||
3. 新建 `Util/Helper/Manager/Common` 类;Controller 写 SQL;跨层反向依赖。
|
||||
4. 只改一份数据库脚本;绕过 upgrade.xml。
|
||||
5. 使用未在项目中的第三方库(先确认已引入或提交评审)。
|
||||
|
||||
### 7.4 联调自检清单(冒烟)
|
||||
|
||||
- [ ] 后端启动无 Bean/扫描异常(Nacos 已注册、接口文档 springdoc 能列出新接口)
|
||||
- [ ] 前端菜单可见(后端菜单数据已注册)、页面可打开、查询/新增/编辑/导出全链路通
|
||||
- [ ] 错误场景(参数为空、无权限)提示符合预期(9xx 业务提示 / 5xx 系统提示)
|
||||
- [ ] 多库(MySQL/SQL Server)下 DDL 均可执行(若涉及)
|
||||
|
||||
---
|
||||
|
||||
## 8. Vibe Coding 协作约定(团队通用)
|
||||
|
||||
### 8.1 提交规范
|
||||
|
||||
- **Conventional Commits**:`type(scope): subject`,如 `feat(jcbill): 新增稽查收货保存接口`、`fix(sms): 修复日志分页总数错误`。type 常用:`feat / fix / docs / refactor / chore / test`。
|
||||
|
||||
### 8.2 代码风格
|
||||
|
||||
- Java:4 空格缩进、行宽 ≤ 120;**每个方法必须写注释**(一行职责说明,复杂方法补充入参/出参/边界条件),便于人工复核;只在 WHY 不明显处补充 WHY 注释。
|
||||
- TS/Vue:遵循框架现有风格;组件选项式 `name` + `<script setup>` 组合;**每个方法/函数必须写一行职责注释**(`//` 或 JSDoc),便于人工复核。
|
||||
- 命名:动词+名词、语义明确;禁语义空名(见 §4.2)。
|
||||
|
||||
### 8.3 评审约定
|
||||
|
||||
- 提交前自检 §7.3 清单;PR 描述按「变更内容 / 验证方式 / 影响范围」三段。
|
||||
- 涉及框架标准件扩展:先与框架负责人确认。
|
||||
|
||||
### 8.4 util/vo 兼容说明(重要)
|
||||
|
||||
- 示例模块遗留的 `util/DateUtil`、`util/RequestUtil`、`vo/BaiduToken` 等为**既有兼容代码**:新代码**可以引用**,但**禁止新增**此类语义空名类;新工具按 §4.7 原则命名(如 `DateToUpperChinese` 保留既有)。
|
||||
- `util/`、`vo/` 包原则上不再新增文件;确需时先评审。
|
||||
|
||||
---
|
||||
|
||||
## 9. SaaS 多租户约束(强制)
|
||||
|
||||
> 所有业务系统默认多租户 SaaS。架构见 `architecture.md §7`。本节为编码时**必须 / 禁止**清单,AI 与人工共同遵守。
|
||||
|
||||
### 9.1 数据隔离(必须)
|
||||
|
||||
1. 行级默认:每张业务表必须含 `tenant_id` 列(DDL 双份同步,§5.8);主数据表(`tenant` / `tenant_config` / `tenant_quota`)本身由平台租户管理,可豁免过滤,但实体需标注 `@PlatformTenantOnly` 注释说明。
|
||||
2. 任何数据访问(JPA Repository / `pi` 原生 SQL / `esb` 编排调用)都必须带租户条件:`tenant_id = :tenantId`(JPA)或 `tenant_id = ?`(参数化,取 `TenantContextHolder`,沿用 §5.4)。
|
||||
3. 禁止省略租户条件;跨租户操作必须 `@CrossTenant` 标注并经评审,且走独立高权限接口。
|
||||
|
||||
### 9.2 租户上下文(必须)
|
||||
|
||||
4. 请求入口由 `TenantInterceptor` 从 JWT 取 `tenantId` 写入 `TenantContextHolder`;`afterCompletion` 必须 `remove()` 防泄漏。
|
||||
5. 数据访问禁止用 `null` 租户作为"查全部"的捷径;`TenantContextHolder.getTenantId()` 为空须显式按平台租户路径处理(带 `@PlatformTenantOnly`)。
|
||||
6. 缓存 key 必须前缀 `tenant:{tenantId}:`(`TenantCacheKeyGenerator`);禁止裸 key 跨租户共享。
|
||||
|
||||
### 9.3 异步与跨进程(必须)
|
||||
|
||||
7. `@Async` 必须走带 `TenantTaskDecorator` 的线程池;Kafka Producer 打 `tenantId` 头、Consumer 头部重建上下文;XXL-JOB 参数 / 分片带 `tenantId` 且 Job 多租户感知。
|
||||
|
||||
### 9.4 前端(必须 / 禁止)
|
||||
|
||||
8. 必须:登录后保存 `useTenantStore`、调用 `applyTenantTheme`、菜单经"权限 ∩ 模块订阅"过滤。
|
||||
9. 禁止:业务代码手写 `X-Tenant-Id` 头或硬编码租户标识;theme / logo 写死(须来自 `tenantConfig`)。
|
||||
|
||||
### 9.5 扩展(建议)
|
||||
|
||||
10. 租户表复合索引 `(tenant_id, ...)`;配额敏感操作前置 `QuotaService` 校验;开通走 `TenantProvisioningService`(幂等 + 可重试)。
|
||||
|
||||
### 9.6 检查清单补充(并入 §7.3)
|
||||
|
||||
- 新增数据访问:是否带租户过滤?缓存 key 是否前缀?
|
||||
- 新增异步 / Kafka / Job:租户上下文是否传播?
|
||||
- 新增前端页面:品牌 / 主题 / 菜单是否随租户动态?
|
||||
|
||||
---
|
||||
|
||||
## 附:文档导航
|
||||
|
||||
| 文档 | 内容 |
|
||||
|---|---|
|
||||
| `docs/architecture.md` | 动静分离架构总览与链路 |
|
||||
| **本文件(coding-standards.md)** | 前后端开发约束规范(权威) |
|
||||
| `docs/agent-guide.md` | AI 生成代码工作流与提示词协议 |
|
||||
| `docs/README.md` | 文档中心索引 |
|
||||
| 前端 `.project.agents/` | 前端仓库治理(CLAUDE/AGENTS/CONVENTIONS 等) |
|
||||
| 后端 `.project.agents/` | 后端仓库治理(CLAUDE/AGENTS/CONVENTIONS 等) |
|
||||
Reference in New Issue
Block a user