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

28 KiB
Raw Blame History

前后端开发约束规范(abacus 动静分离微服务)

版本:1.02026-08-19) · 权威来源:本文件(docs/coding-standards.md 适用范围:基于 abacus-static-frameworkVue 前端底座)与 abacus.springboot.exampleSpring 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.examplepom.xml / assembly.xml / config/{dev,test,prod}/ 基础结构 src/main/java/abacus/springboot.<应用名>/** + src/main/resources/sql/** + doc/升级日志.md
  • 禁止src/framework/** 内新增或修改代码;业务组件/工具无法复用标准件时,按 §4.6/§4.7 提交框架负责人评审后纳入框架,而非绕过。
  • 前端框架升级不覆盖清单:publicvite.config.tssrc/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 注册 @Beanabacus.responseExcludePath 排除不包装的 URI)。

3. APP_NAME 四处一致性(最高优先级,漏配静默失败)

新系统/新应用开发时,应用名(APP_NAME)必须在以下四个位置一致,任一处漏配即静默失败(启动正常但页面 404 / 接口不可达 / 路由不装配):

# 位置 作用 示例
后端 bootstrap.ymlspring.application.name Nacos 注册名(须唯一) example
后端 application.ymlabacus.controllerPackages / abacus.entitypackages / springdoc.packages-to-scan包前缀 框架装配 Controller / Entity / 接口文档扫描 abacus.springboot.example
前端 src/subsystem/api/<APP_NAME>/*.tsURL 首段 服务别名映射(§6.3 /example/xxx
前端 vite.config.tsbuild.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

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 页面模式(对应用户给出的界面)

界面形态 标准实现
查询 + 列表 AbQueryFormfieldConfigs 配置式)+ vxe-gridgridDefaultProps + 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

列表页模板骨架:

<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-throttleframework/directives/)。

4.6 UI 组件优先级(必须按序选用)

  1. src/framework/components/standard/(标准件:AbFormAbQueryFormAbSectionClickCopyFilePreviewDialogImportExcelLogShowRealTimeSearchSelectSensitiveTextTextLayout/BottomFloat 等,使用方式见框架示例模块);
  2. 表格一律使用 vxe-tablevxe-grid + gridDefaultProps);
  3. 除表格外使用 Element Plus(已自动按需引入);
  4. 以上均不满足时,用 css/html 自实现组件;泛用性高的必须提交框架负责人发布到公司 UI 组件库,不得仅留在业务内。

4.7 工具函数优先级

  1. src/framework/utils/standard/(标准件:vxedataHandleUtilglobalHandlesecuritybaiduMap);
  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/** 等非业务区;publicvite.config.tssrc/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 AutoCompleteControllerBaseServlet
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 @ConfigurationBean / 配置常量) JcConfigurationJcConfigKey
util / vo 工具 / 第三方 VO只可引用既有,新增禁止,见 §8.4 DateUtilBaiduToken

依赖方向(必须单向):api/controller → esb → {wsi/pi/repository} → daoimpl 实现 piconfig 被各层依赖;禁止跨层反向依赖(如 dao 依赖 api、esb 直接写 SQL)。

5.2 Controller 模板(必须遵循)

@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 + @Tagspringdoc);方法级 @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;数据访问通过 wsiServiceIF)或 piAccessIF/ 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/

@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 AbacusConfigNotekeyGroup 按业务域)、DaoIdGenerator 等基础设施 Bean。
  • XxxConfigKey:配置 key 常量类。

5.7 ID 生成(DaoIdGenerator

@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 profiledev(默认)/ test / prod-P 指定;resources 仅打包当前环境 config + sql/** + META-INF

5.10 打包发布

  • pom.xml <artifactId> 设为 abacus.springboot.<应用名>;版本号在 <version>
  • 发布包:mvn packagetarget/${artifactId}-${version}-bin.zipjar + 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.tsurlServiceHandlesettingsStore.backendServices[别名](来自 window.frameBaseConfig)替换为真实服务名。
  • 例:/sms/operationLogsbackendServices.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.jscurrentSubsystem)。

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 Commitstype(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/DateUtilutil/RequestUtilvo/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 = :tenantIdJPA)或 tenant_id = ?(参数化,取 TenantContextHolder,沿用 §5.4)。
  3. 禁止省略租户条件;跨租户操作必须 @CrossTenant 标注并经评审,且走独立高权限接口。

9.2 租户上下文(必须)

  1. 请求入口由 TenantInterceptor 从 JWT 取 tenantId 写入 TenantContextHolderafterCompletion 必须 remove() 防泄漏。
  2. 数据访问禁止用 null 租户作为"查全部"的捷径;TenantContextHolder.getTenantId() 为空须显式按平台租户路径处理(带 @PlatformTenantOnly)。
  3. 缓存 key 必须前缀 tenant:{tenantId}:TenantCacheKeyGenerator);禁止裸 key 跨租户共享。

9.3 异步与跨进程(必须)

  1. @Async 必须走带 TenantTaskDecorator 的线程池;Kafka Producer 打 tenantId 头、Consumer 头部重建上下文;XXL-JOB 参数 / 分片带 tenantId 且 Job 多租户感知。

9.4 前端(必须 / 禁止)

  1. 必须:登录后保存 useTenantStore、调用 applyTenantTheme、菜单经"权限 ∩ 模块订阅"过滤。
  2. 禁止:业务代码手写 X-Tenant-Id 头或硬编码租户标识;theme / logo 写死(须来自 tenantConfig)。

9.5 扩展(建议)

  1. 租户表复合索引 (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 等)