Files
saas-mbr/docs/coding-standards.md
T

498 lines
28 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 动静分离微服务)
> 版本:1.02026-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 packageassembly.xml
发布包 *-bin.zipjar + doc + sql + html)──► 部署(Nginx / 容器托管 htmlJava 进程提供 API
```
- 前后端通过 **HTTP 接口**通信;开发期由 `vite.config.ts server.proxy` 反代到后端(代替 Nginx)。
- 后端通过 Nacos 注册/发现;接口文档 springdocOpenAPI);统一响应体由 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` | 请求/响应 DTOApiReq* / ApiResp* / Api* | — | `ApiReqJcBill` |
| `controller` | 前端/内部接口 Controller | abacus.controllerPackages | `AutoCompleteController``BaseServlet` |
| `dao` | JPA 实体 Entity(表映射) | abacus.entitypackages | `JcBill` |
| `esb` | **Service 编排层**@Service,只编排不做 SQL | — | `JcBillBizService` |
| `wsi` | 服务接口 *ServiceIFesb/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 Entitydao/
```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 Configconfig/
- `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/<应用名>/` 自动打入 zipjar 内**不含** 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` HeaderJSON 字符串):`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 等) |