# 前后端开发约束规范（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 等） |
