adspecs 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/.adspecs/paths.json +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codebuddy-plugin/plugin.json +1 -1
  5. package/.qoder-plugin/plugin.json +1 -1
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/CLAUDE.md +2 -2
  8. package/README.md +1 -1
  9. package/package.json +1 -1
  10. package/references/ant6-front-standard/02-/347/273/204/344/273/266/350/247/204/350/214/203.md +7 -0
  11. package/references/ant6-front-standard/03-/345/210/227/350/241/250/350/247/204/350/214/203.md +222 -147
  12. package/references/ant6-front-standard/04-/350/241/250/345/215/225/350/247/204/350/214/203.md +8 -0
  13. package/references/ant6-front-standard/09-/345/270/270/350/247/201/351/227/256/351/242/230/350/247/204/350/214/203.md +1 -1
  14. package/references/ant6-front-standard/index.md +100 -99
  15. package/references/yudaocloud-end-standard/01-Java/345/220/216/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +1166 -0
  16. package/references/yudaocloud-end-standard/02-/346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/344/270/216/344/275/277/347/224/250/350/247/204/350/214/203.md +1023 -0
  17. package/references/yudaocloud-end-standard/03-/346/225/260/346/215/256/345/255/227/345/205/270/344/270/216/350/217/234/345/215/225/350/247/204/350/214/203.md +336 -0
  18. package/references/yudaocloud-end-standard/index.md +14 -3
  19. package/references/yudaocloud-end-standard/system_dict_type.sql +186 -0
  20. package/skills/adspecs-adversarial-review/SKILL.md +1 -1
  21. package/skills/adspecs-analyze/SKILL.md +564 -574
  22. package/skills/adspecs-plan/SKILL.md +1 -1
  23. package/skills/adspecs-utest/SKILL.md +62 -40
  24. package/skills/project-init/SKILL.md +4 -4
  25. package/src/lib/paths-defaults.js +1 -1
  26. package/src/lib/readme-gen.js +1 -1
  27. package/references/ant6-front-standard/05-/345/211/215/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +0 -1443
@@ -0,0 +1,1166 @@
1
+ # ultracloud 项目 Java 后端编码规范
2
+
3
+ > **版本**: v2.2
4
+ > **修订日期**: 2026-08-30
5
+ > **适用范围**: ultracloud 项目所有 Java 后端模块
6
+
7
+ 本规范基于项目实际代码(ultracloud-module-system、ultracloud-module-infra、ultracloud-module-bpm、ultracloud-module-ai)和 ultracloud-framework 框架源码编写,可直接落地、团队通用。
8
+
9
+ ---
10
+
11
+ ## 一、项目约定
12
+
13
+ ### 1.1 技术栈
14
+
15
+ | 组件 | 版本 |
16
+ | -------------------- | --------- |
17
+ | Java | JDK 25 |
18
+ | Spring Boot | 4.1.0 |
19
+ | Spring Cloud | 2025.1.2 |
20
+ | Spring Cloud Alibaba | 2025.1.0.0|
21
+ | MyBatis Plus | 3.5.16 |
22
+ | MyBatis Plus Join | 1.5.7 |
23
+ | Redis + Redisson | 4.6.1 |
24
+ | Flowable | 8.0.0 |
25
+ | MapStruct | 1.6.3 |
26
+ | Knife4j | 4.6.0.3 |
27
+ | Springdoc | 3.0.3 |
28
+ | Lombok | 1.18.46 |
29
+ | Hutool | 5.8.46 |
30
+ | Druid | 1.2.28 |
31
+ | Easy-Trans | 3.1.5 |
32
+ | Lock4j | 2.2.7 |
33
+
34
+ ### 1.2 基础约定
35
+
36
+ - **ID 类型**: 统一使用 `Long`(自增主键),不使用 UUID
37
+ - **时间类型**: 统一使用 `LocalDateTime`,不使用 `Date`/`Instant`
38
+ - **依赖注入**: 统一使用 `@Resource`(Jakarta),不使用 `@Autowired`
39
+ - **对象命名**: DO(数据对象)替代 PO,继承 BaseDO/TenantBaseDO
40
+ - **审计字段**: createTime/updateTime/creator/updater/deleted 由框架自动填充,无需手动维护
41
+ - **对象转换**: 简单转换使用 `BeanUtils.toBean()`,复杂映射使用 MapStruct
42
+ - **分页返回**: 使用 `PageResult<T>`,分页参数继承 `PageParam`
43
+ - **异常抛出**: 使用 `ServiceExceptionUtil.exception(ErrorCodeConstants.XXX)`
44
+ - **数据翻译**: 使用 `@TransMethodResult` + Easy-Trans 自动翻译关联字段
45
+ - **租户隔离**: 使用 `TenantBaseDO` + 框架自动过滤,跨租户查询加 `@TenantIgnore`
46
+ - **错误码段**: `1_XXX_YYY_ZZZ`(XXX 为模块编号,如 Infra=001, System=002, BPM=003, AI=040)
47
+
48
+ ### 1.3 模块结构
49
+
50
+ 每个业务模块采用 `-api` + `-server` 双模块结构:
51
+
52
+ ```
53
+ ultracloud-module-xxx/
54
+ ├── ultracloud-module-xxx-api/ # 跨模块 RPC 接口 + DTO + 枚举 + 错误码
55
+ │ └── cn.ultracloud.com.module.xxx/
56
+ │ ├── api/ # @FeignClient 接口定义
57
+ │ ├── dto/ # RPC 传输 DTO
58
+ │ └── enums/ # ErrorCodeConstants、业务枚举
59
+ └── ultracloud-module-xxx-server/ # 服务实现
60
+ └── cn.ultracloud.com.module.xxx/
61
+ ├── controller/admin/ # 管理端 REST(自动添加 /admin-api 前缀)
62
+ │ └── {domain}/vo/ # VO 定义(按业务域子目录)
63
+ ├── controller/app/ # C 端 API(自动添加 /app-api 前缀)
64
+ ├── service/{domain}/ # Service 接口 + Impl
65
+ ├── dal/dataobject/{domain}/ # DO 实体
66
+ ├── dal/mysql/{domain}/ # MyBatis Plus Mapper
67
+ ├── convert/ # MapStruct 转换器
68
+ ├── api/ # RPC API 实现(@RestController)
69
+ └── framework/config/ # 模块配置(SecurityConfiguration 等)
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 二、命名规范
75
+
76
+ ### 2.1 包名
77
+
78
+ 全部使用小写字母,单词之间用小数点分隔,禁止使用下划线、大写字母。
79
+
80
+ 格式:`cn.ultracloud.com.module.{module}.{layer}.{domain}`
81
+
82
+ 示例:
83
+ - `cn.ultracloud.com.module.wms.controller.admin.warehouse`
84
+ - `cn.ultracloud.com.module.wms.dal.dataobject`
85
+ - `cn.ultracloud.com.module.wms.service.warehouse`
86
+ - `cn.ultracloud.com.module.wms.dal.mysql`
87
+ - `cn.ultracloud.com.module.wms.enums`
88
+
89
+ ### 2.2 类名
90
+
91
+ 采用大驼峰命名法,望文知意,后缀明确:
92
+
93
+ | 类型 | 后缀 | 示例 |
94
+ | ---------------- | -------------------- | -------------------------------------------- |
95
+ | Controller | `Controller` | `WmsWarehouseController` |
96
+ | Service 接口 | `Service` | `WmsWarehouseService`(不加 `I` 前缀) |
97
+ | Service 实现 | `ServiceImpl` | `WmsWarehouseServiceImpl` |
98
+ | Mapper | `Mapper` | `WmsWarehouseMapper` |
99
+ | DO 实体 | `DO` | `WmsWarehouseDO`(继承 BaseDO/TenantBaseDO) |
100
+ | 创建/更新请求 | `SaveReqVO` | `WarehouseSaveReqVO` |
101
+ | 分页查询请求 | `PageReqVO` | `WarehousePageReqVO` |
102
+ | 列表查询请求 | `ListReqVO` | `DeptListReqVO` |
103
+ | 响应对象 | `RespVO` | `WarehouseRespVO` |
104
+ | 精简响应 | `SimpleRespVO` | `WarehouseSimpleRespVO` |
105
+ | RPC DTO | `ReqDTO` / `RespDTO` | `AdminUserRespDTO`(位于 -api 模块) |
106
+ | 枚举 | `Enum` | `WmsOrgStatusEnum` |
107
+ | MapStruct 转换器 | `Convert` | `ConfigConvert` |
108
+
109
+ ### 2.3 方法名
110
+
111
+ 采用小驼峰命名法,以动词开头:
112
+
113
+ | 操作 | 命名 | 示例 |
114
+ | -------- | ------------------------------------------------ | ------------------------------------------------ |
115
+ | 创建 | `create{Entity}` | `createWarehouse` |
116
+ | 更新 | `update{Entity}` | `updateWarehouse` |
117
+ | 删除 | `delete{Entity}` | `deleteWarehouse` |
118
+ | 批量删除 | `delete{Entity}List` / `delete{Entity}ListByIds` | `deleteWarehouseListByIds` |
119
+ | 获得单个 | `get{Entity}` | `getWarehouse` |
120
+ | 分页查询 | `get{Entity}Page` | `getWarehousePage` |
121
+ | 列表查询 | `get{Entity}List` | `getSimpleList` |
122
+ | 状态修改 | `{action}{Entity}` | `enableWarehouse` / `disableWarehouse` |
123
+ | 校验方法 | `validate{Condition}` | `validateWarehouseExists` / `validateCodeUnique` |
124
+
125
+ ### 2.4 变量名
126
+
127
+ 采用小驼峰命名法,语义清晰,禁止拼音、单字母、无意义缩写。
128
+
129
+ - 正确:`warehouseCode`、`createTime`、`orgId`
130
+ - 错误:`a`、`abc`、`ckbm`、`czsj`
131
+
132
+ ### 2.5 常量名
133
+
134
+ 全部大写,单词之间用下划线分隔,集中定义在 `ErrorCodeConstants` 或常量类中,禁止魔法值。
135
+
136
+ 示例:`WAREHOUSE_NOT_EXISTS`、`MAX_PAGE_SIZE`
137
+
138
+ ---
139
+
140
+ ## 三、API 接口规范
141
+
142
+ ### 3.1 请求路径
143
+
144
+ 项目采用**路径动词风格**,不依赖 HTTP method 区分操作:
145
+
146
+ | 操作 | 路径 | HTTP Method |
147
+ | -------- | ----------------------------- | ----------- |
148
+ | 创建 | `/wms/warehouse/create` | POST |
149
+ | 更新 | `/wms/warehouse/update` | PUT |
150
+ | 删除 | `/wms/warehouse/delete` | DELETE |
151
+ | 批量删除 | `/wms/warehouse/delete-list` | DELETE |
152
+ | 获得单个 | `/wms/warehouse/get` | GET |
153
+ | 分页查询 | `/wms/warehouse/page` | GET |
154
+ | 列表查询 | `/wms/warehouse/list` | GET |
155
+ | 导出 | `/wms/warehouse/export-excel` | GET |
156
+ | 其他操作 | `/wms/warehouse/{action}` | PUT/GET |
157
+
158
+ 路径规则:
159
+ - 全部使用小写字母,单词之间用中横线分隔
160
+ - `@RequestMapping("/wms/warehouse")` 不包含 `/admin-api` 或 `/app-api` 前缀
161
+ - `controller/admin/` 下的类由框架自动添加 `/admin-api` 前缀
162
+ - `controller/app/` 下的类自动添加 `/app-api` 前缀
163
+ - Gateway 负责路由转发
164
+
165
+ ### 3.2 接口返回格式
166
+
167
+ 所有接口返回统一使用 `CommonResult<T>`:
168
+
169
+ ```java
170
+ // 成功
171
+ return success(data); // CommonResult<Long>
172
+ return success(true); // CommonResult<Boolean>
173
+ return success(BeanUtils.toBean(warehouse, WarehouseRespVO.class)); // CommonResult<WarehouseRespVO>
174
+ return success(BeanUtils.toBean(pageResult, WarehouseRespVO.class)); // CommonResult<PageResult<WarehouseRespVO>>
175
+
176
+ // 失败(通过框架全局异常处理自动返回)
177
+ throw exception(WAREHOUSE_NOT_EXISTS); // CommonResult.error(code, msg)
178
+ ```
179
+
180
+ `CommonResult` 结构:
181
+ - `code`(Integer)— 状态码,使用 `ErrorCode` 对象
182
+ - `msg`(String)— 提示信息
183
+ - `data`(T)— 业务数据
184
+
185
+ ### 3.3 错误码体系
186
+
187
+ 错误码定义在 `-api` 模块的 `ErrorCodeConstants` **接口**中:
188
+
189
+ ```java
190
+ package cn.ultracloud.com.module.wms.enums;
191
+
192
+ import cn.ultracloud.com.framework.common.exception.ErrorCode;
193
+
194
+ public interface ErrorCodeConstants {
195
+
196
+ // ========== 仓库 1-060-003-000 ==========
197
+ ErrorCode WAREHOUSE_NOT_EXISTS = new ErrorCode(1_060_003_000, "仓库不存在");
198
+ ErrorCode WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_060_003_001, "仓库编码已存在");
199
+ ErrorCode WAREHOUSE_HAS_ZONES = new ErrorCode(1_060_003_002, "仓库下存在库区,不允许删除");
200
+ }
201
+ ```
202
+
203
+ 格式:`1_XXX_YYY_ZZZ`(Java 下划线数字字面量)
204
+ - `1` = 业务模块前缀
205
+ - `XXX` = 模块编号(Infra=001, System=002, BPM=003, AI=040,新增模块按序分配)
206
+ - `YYY` = 子模块编号
207
+ - `ZZZ` = 子模块内序号
208
+
209
+ 错误消息支持 `{}` 占位符:
210
+ ```java
211
+ ErrorCode DEPT_ORG_CODE_DUPLICATE = new ErrorCode(1_002_004_008, "已经存在编码为【{}】的组织");
212
+ // 使用:throw exception(DEPT_ORG_CODE_DUPLICATE, orgCode);
213
+ ```
214
+
215
+ ### 3.4 权限控制
216
+
217
+ 使用 `@PreAuthorize("@ss.hasPermission('module:resource:action')")`:
218
+
219
+ ```java
220
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:create')") // 创建
221
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:update')") // 修改
222
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:delete')") // 删除
223
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:query')") // 查询
224
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:export')") // 导出
225
+ ```
226
+
227
+ ### 3.5 API 文档注解
228
+
229
+ 使用 SpringDoc(OpenAPI 3)注解:
230
+
231
+ ```java
232
+ // Controller 类级别
233
+ @Tag(name = "管理后台 - WMS 仓库")
234
+
235
+ // 方法级别
236
+ @Operation(summary = "创建仓库")
237
+
238
+ // 参数级别
239
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
240
+
241
+ // VO 类级别
242
+ @Schema(description = "管理后台 - WMS 仓库新增/修改 Request VO")
243
+
244
+ // VO 字段级别
245
+ @Schema(description = "仓库编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001")
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 四、Controller 层规范
251
+
252
+ ### 4.1 标准写法
253
+
254
+ ```java
255
+ package cn.ultracloud.com.module.wms.controller.admin.warehouse;
256
+
257
+ import cn.ultracloud.com.framework.common.pojo.CommonResult;
258
+ import cn.ultracloud.com.framework.common.pojo.PageResult;
259
+ import cn.ultracloud.com.framework.common.util.object.BeanUtils;
260
+ import cn.ultracloud.com.module.wms.controller.admin.vo.warehouse.WarehousePageReqVO;
261
+ import cn.ultracloud.com.module.wms.controller.admin.vo.warehouse.WarehouseRespVO;
262
+ import cn.ultracloud.com.module.wms.controller.admin.vo.warehouse.WarehouseSaveReqVO;
263
+ import cn.ultracloud.com.module.wms.dal.dataobject.WmsWarehouseDO;
264
+ import cn.ultracloud.com.module.wms.service.warehouse.WmsWarehouseService;
265
+ import io.swagger.v3.oas.annotations.Operation;
266
+ import io.swagger.v3.oas.annotations.Parameter;
267
+ import io.swagger.v3.oas.annotations.tags.Tag;
268
+ import jakarta.annotation.Resource;
269
+ import jakarta.validation.Valid;
270
+ import org.springframework.security.access.prepost.PreAuthorize;
271
+ import org.springframework.validation.annotation.Validated;
272
+ import org.springframework.web.bind.annotation.*;
273
+
274
+ import static cn.ultracloud.com.framework.common.pojo.CommonResult.success;
275
+
276
+ @Tag(name = "管理后台 - WMS 仓库")
277
+ @RestController
278
+ @RequestMapping("/wms/warehouse")
279
+ @Validated
280
+ public class WmsWarehouseController {
281
+
282
+ @Resource
283
+ private WmsWarehouseService warehouseService;
284
+
285
+ @PostMapping("/create")
286
+ @Operation(summary = "创建仓库")
287
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:create')")
288
+ public CommonResult<Long> createWarehouse(@Valid @RequestBody WarehouseSaveReqVO createReqVO) {
289
+ return success(warehouseService.createWarehouse(createReqVO));
290
+ }
291
+
292
+ @PutMapping("/update")
293
+ @Operation(summary = "更新仓库")
294
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:update')")
295
+ public CommonResult<Boolean> updateWarehouse(@Valid @RequestBody WarehouseSaveReqVO updateReqVO) {
296
+ warehouseService.updateWarehouse(updateReqVO);
297
+ return success(true);
298
+ }
299
+
300
+ @DeleteMapping("/delete")
301
+ @Operation(summary = "删除仓库")
302
+ @Parameter(name = "id", description = "编号", required = true)
303
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:delete')")
304
+ public CommonResult<Boolean> deleteWarehouse(@RequestParam("id") Long id) {
305
+ warehouseService.deleteWarehouse(id);
306
+ return success(true);
307
+ }
308
+
309
+ @GetMapping("/get")
310
+ @Operation(summary = "获得仓库")
311
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
312
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:query')")
313
+ public CommonResult<WarehouseRespVO> getWarehouse(@RequestParam("id") Long id) {
314
+ WmsWarehouseDO warehouse = warehouseService.getWarehouse(id);
315
+ return success(BeanUtils.toBean(warehouse, WarehouseRespVO.class));
316
+ }
317
+
318
+ @GetMapping("/page")
319
+ @Operation(summary = "获得仓库分页")
320
+ @PreAuthorize("@ss.hasPermission('wms:warehouse:query')")
321
+ public CommonResult<PageResult<WarehouseRespVO>> getWarehousePage(@Valid WarehousePageReqVO pageReqVO) {
322
+ PageResult<WmsWarehouseDO> pageResult = warehouseService.getWarehousePage(pageReqVO);
323
+ return success(BeanUtils.toBean(pageResult, WarehouseRespVO.class));
324
+ }
325
+ }
326
+ ```
327
+
328
+ ### 4.2 禁止事项
329
+
330
+ 1. 禁止在 Controller 层编写任何业务逻辑(参数处理、数据转换、数据库操作)
331
+ 2. 禁止直接返回 `Map`、`Object`、`List` 等未封装对象,必须使用 `CommonResult`
332
+ 3. 禁止不做参数校验(POST/PUT 必须加 `@Valid @RequestBody`,GET 加 `@Valid`)
333
+ 4. 禁止使用 `@Autowired` 注入,统一使用 `@Resource`
334
+ 5. 禁止接口路径包含 `/admin-api` 或 `/app-api` 前缀(由框架自动添加)
335
+
336
+ ---
337
+
338
+ ## 五、Service 层规范
339
+
340
+ ### 5.1 接口与实现分离
341
+
342
+ ```java
343
+ // Service 接口(不加注解)
344
+ public interface WmsWarehouseService {
345
+
346
+ Long createWarehouse(WarehouseSaveReqVO createReqVO);
347
+
348
+ void updateWarehouse(WarehouseSaveReqVO updateReqVO);
349
+
350
+ void deleteWarehouse(Long id);
351
+
352
+ WmsWarehouseDO getWarehouse(Long id);
353
+
354
+ PageResult<WmsWarehouseDO> getWarehousePage(WarehousePageReqVO pageReqVO);
355
+ }
356
+
357
+ // Service 实现类
358
+ @Service
359
+ @Validated
360
+ @Slf4j
361
+ public class WmsWarehouseServiceImpl implements WmsWarehouseService {
362
+
363
+ @Resource
364
+ private WmsWarehouseMapper warehouseMapper;
365
+
366
+ @Override
367
+ public Long createWarehouse(WarehouseSaveReqVO createReqVO) {
368
+ validateCodeUnique(null, createReqVO.getCode());
369
+ WmsWarehouseDO warehouse = BeanUtils.toBean(createReqVO, WmsWarehouseDO.class);
370
+ warehouseMapper.insert(warehouse);
371
+ return warehouse.getId();
372
+ }
373
+
374
+ @Override
375
+ public void updateWarehouse(WarehouseSaveReqVO updateReqVO) {
376
+ validateExists(updateReqVO.getId());
377
+ validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
378
+ WmsWarehouseDO updateObj = BeanUtils.toBean(updateReqVO, WmsWarehouseDO.class);
379
+ warehouseMapper.updateById(updateObj);
380
+ }
381
+
382
+ @Override
383
+ public void deleteWarehouse(Long id) {
384
+ WmsWarehouseDO warehouse = validateExists(id);
385
+ if (!WmsOrgStatusEnum.PENDING.getStatus().equals(warehouse.getStatus())) {
386
+ throw exception(WAREHOUSE_STATUS_INVALID);
387
+ }
388
+ warehouseMapper.deleteById(id);
389
+ }
390
+
391
+ @Override
392
+ public WmsWarehouseDO getWarehouse(Long id) {
393
+ return warehouseMapper.selectById(id);
394
+ }
395
+
396
+ @Override
397
+ public PageResult<WmsWarehouseDO> getWarehousePage(WarehousePageReqVO pageReqVO) {
398
+ return warehouseMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<WmsWarehouseDO>()
399
+ .likeIfPresent(WmsWarehouseDO::getCode, pageReqVO.getCode())
400
+ .likeIfPresent(WmsWarehouseDO::getName, pageReqVO.getName())
401
+ .eqIfPresent(WmsWarehouseDO::getOrgId, pageReqVO.getOrgId())
402
+ .orderByDesc(WmsWarehouseDO::getId));
403
+ }
404
+
405
+ // ====== 私有校验方法 ======
406
+
407
+ private WmsWarehouseDO validateExists(Long id) {
408
+ WmsWarehouseDO warehouse = warehouseMapper.selectById(id);
409
+ if (warehouse == null) {
410
+ throw exception(WAREHOUSE_NOT_EXISTS);
411
+ }
412
+ return warehouse;
413
+ }
414
+
415
+ private void validateCodeUnique(Long id, String code) {
416
+ WmsWarehouseDO warehouse = warehouseMapper.selectOne(WmsWarehouseDO::getCode, code);
417
+ if (warehouse == null) return;
418
+ if (id == null || !id.equals(warehouse.getId())) {
419
+ throw exception(WAREHOUSE_CODE_DUPLICATE);
420
+ }
421
+ }
422
+ }
423
+ ```
424
+
425
+ **关键约定**:
426
+ - Service 方法直接接收 `SaveReqVO`/`PageReqVO`,返回 `DO` 对象(不是 RespVO)
427
+ - Controller 层负责 DO → RespVO 转换
428
+ - 接口无需注解,实现类加 `@Service` + `@Validated` + `@Slf4j`
429
+ - 私有校验方法使用 `throw exception(ErrorCodeConstants.XXX)` 抛出异常
430
+
431
+ ### 5.2 事务管理
432
+
433
+ - 涉及多表操作的方法加 `@Transactional(rollbackFor = Exception.class)`
434
+ - 单表 CRUD 不需要事务注解
435
+ - 主子表场景下代码生成器自动添加事务注解
436
+
437
+ ### 5.3 禁止事项
438
+
439
+ 1. 禁止 Service 层直接接收 HTTP 请求、返回响应
440
+ 2. 禁止 Service 层跳过 Mapper 层直接编写 SQL 操作数据库
441
+ 3. 禁止未加事务注解的多表修改操作
442
+ 4. 禁止手动设置审计字段(createTime/updateTime 等由框架自动填充)
443
+
444
+ ---
445
+
446
+ ## 六、Mapper/数据库规范
447
+
448
+ ### 6.1 Mapper 接口
449
+
450
+ ```java
451
+ package cn.ultracloud.com.module.wms.dal.mysql;
452
+
453
+ import cn.ultracloud.com.framework.mybatis.core.mapper.BaseMapperX;
454
+ import cn.ultracloud.com.module.wms.dal.dataobject.WmsWarehouseDO;
455
+ import org.apache.ibatis.annotations.Mapper;
456
+
457
+ import java.util.List;
458
+
459
+ @Mapper
460
+ public interface WmsWarehouseMapper extends BaseMapperX<WmsWarehouseDO> {
461
+
462
+ default List<WmsWarehouseDO> selectListByOrgId(Long orgId) {
463
+ return selectList(WmsWarehouseDO::getOrgId, orgId);
464
+ }
465
+
466
+ default Long selectCountByOrgId(Long orgId) {
467
+ return selectCount(WmsWarehouseDO::getOrgId, orgId);
468
+ }
469
+ }
470
+ ```
471
+
472
+ **关键约定**:
473
+ - 继承 `BaseMapperX<DO>`(扩展自 `MPJBaseMapper<T>`,支持 MyBatis Plus Join)
474
+ - 接口加 `@Mapper` 注解
475
+ - 位于 `dal/mysql/{domain}/` 目录
476
+ - 自定义方法使用 `default` 方法 + `LambdaQueryWrapperX` 链式 API
477
+
478
+ ### 6.2 LambdaQueryWrapperX 链式 API
479
+
480
+ 优先使用 `LambdaQueryWrapperX`,不手写 SQL:
481
+
482
+ ```java
483
+ default PageResult<ConfigDO> selectPage(ConfigPageReqVO reqVO) {
484
+ return selectPage(reqVO, new LambdaQueryWrapperX<ConfigDO>()
485
+ .likeIfPresent(ConfigDO::getConfigName, reqVO.getConfigName())
486
+ .eqIfPresent(ConfigDO::getConfigKey, reqVO.getConfigKey())
487
+ .eqIfPresent(ConfigDO::getType, reqVO.getType())
488
+ .betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime())
489
+ .orderByDesc(ConfigDO::getId));
490
+ }
491
+ ```
492
+
493
+ 常用条件方法:
494
+
495
+ | 方法 | 说明 |
496
+ | ------------------------------------------------------- | ---------------------------- |
497
+ | `likeIfPresent(column, value)` | 模糊匹配(值非空时才加条件) |
498
+ | `eqIfPresent(column, value)` | 精确匹配 |
499
+ | `betweenIfPresent(column, val1, val2)` | 区间匹配 |
500
+ | `inIfPresent(column, collection)` | IN 匹配 |
501
+ | `neIfPresent(column, value)` | 不等匹配 |
502
+ | `gtIfPresent / geIfPresent / ltIfPresent / leIfPresent` | 大小写比较 |
503
+ | `orderByDesc(column)` | 降序排序 |
504
+ | `orderByAsc(column)` | 升序排序 |
505
+
506
+ ### 6.3 BaseMapperX 内置方法
507
+
508
+ | 方法 | 说明 |
509
+ | ------------------------------------------- | ---------------------------------------- |
510
+ | `selectOne(field, value)` | 单字段精确查询,返回一条 |
511
+ | `selectOne(field1, value1, field2, value2)` | 多字段精确查询 |
512
+ | `selectCount(field, value)` | 计数查询 |
513
+ | `selectList(field, value)` | 单字段列表查询 |
514
+ | `selectList(Wrapper)` | Wrapper 条件查询 |
515
+ | `selectPage(PageParam, Wrapper)` | 分页查询,支持 `PAGE_SIZE_NONE` 返回全部 |
516
+ | `insert(entity)` | 插入单条 |
517
+ | `insertBatch(Collection)` | 批量插入 |
518
+ | `updateById(entity)` | 根据 ID 更新 |
519
+ | `updateBatch(Collection)` | 批量更新 |
520
+ | `deleteById(id)` | 根据 ID 删除 |
521
+ | `deleteBatch(Collection)` | 批量删除 |
522
+
523
+ ### 6.4 DO 实体
524
+
525
+ ```java
526
+ package cn.ultracloud.com.module.wms.dal.dataobject;
527
+
528
+ import cn.ultracloud.com.framework.tenant.core.db.TenantBaseDO;
529
+ import com.baomidou.mybatisplus.annotation.KeySequence;
530
+ import com.baomidou.mybatisplus.annotation.TableId;
531
+ import com.baomidou.mybatisplus.annotation.TableName;
532
+ import lombok.*;
533
+
534
+ @TableName("wms_warehouse")
535
+ @KeySequence("wms_warehouse_seq")
536
+ @Data
537
+ @EqualsAndHashCode(callSuper = true)
538
+ @ToString(callSuper = true)
539
+ @Builder
540
+ @NoArgsConstructor
541
+ @AllArgsConstructor
542
+ public class WmsWarehouseDO extends TenantBaseDO {
543
+
544
+ @TableId
545
+ private Long id;
546
+ private String code;
547
+ private String name;
548
+ private Long orgId;
549
+ // 其他字段... 不需要 @TableField,MyBatis Plus 自动驼峰转下划线
550
+ }
551
+ ```
552
+
553
+ **关键约定**:
554
+ - 命名:`{Entity}DO`,位于 `dal/dataobject/{domain}/` 目录
555
+ - 基类:`BaseDO`(标准)或 `TenantBaseDO`(多租户)
556
+ - 字段默认使用 camelCase,MyBatis Plus 自动映射为 snake_case,**不需要 `@TableField`**(唯一例外:JSONB 字段需加 `@TableField(typeHandler = JsonbTypeHandler.class)`,见 §6.9)
557
+ - 审计字段(createTime/updateTime/creator/updater/deleted)由 `BaseDO` 基类提供
558
+ - 多租户字段(tenantId)由 `TenantBaseDO` 基类提供,需用引用`
559
+ import cn.ultracloud.com.framework.tenant.core.db.TenantBaseDO;`
560
+
561
+ ### 6.5 BaseDO 提供的字段
562
+
563
+ | 字段 | 类型 | 说明 |
564
+ | ------------ | --------------- | -------------------------------------------- |
565
+ | `createTime` | `LocalDateTime` | `@TableField(fill = INSERT)` 自动填充 |
566
+ | `updateTime` | `LocalDateTime` | `@TableField(fill = INSERT_UPDATE)` 自动填充 |
567
+ | `creator` | `String` | `@TableField(fill = INSERT)` 自动填充 |
568
+ | `updater` | `String` | `@TableField(fill = INSERT_UPDATE)` 自动填充 |
569
+ | `deleted` | `Boolean` | `@TableLogic` 逻辑删除 |
570
+
571
+ ### 6.6 TenantBaseDO 提供的字段
572
+
573
+ `TenantBaseDO` 继承自 `BaseDO`,额外提供以下字段:
574
+
575
+ | 字段 | 类型 | 说明 |
576
+ | ---------- | ------ | ---------------------------------------------------------- |
577
+ | `tenantId` | `Long` | 租户编号,框架自动填充并过滤,跨租户查询加 `@TenantIgnore` |
578
+
579
+ 需要多租户隔离的 DO 继承 `TenantBaseDO`,引入路径:
580
+
581
+ ```java
582
+ import cn.ultracloud.com.framework.tenant.core.db.TenantBaseDO;
583
+ ```
584
+
585
+ ### 6.7 数据库表规范
586
+
587
+ - 表名:小写字母 + 下划线,语义清晰,如 `wms_warehouse`、`system_dept`
588
+ - 字段名:小写字母 + 下划线,与 DO 实体 camelCase 对应
589
+ - 主键:`id`(`BIGINT` 自增)
590
+ - 不需要手动定义 `create_time`、`update_time`、`is_deleted` 字段(由 BaseDO 管理)
591
+
592
+ ### 6.8 SQL 规范
593
+
594
+ - 优先使用 `LambdaQueryWrapperX` 链式 API,不手写 SQL
595
+ - 复杂 SQL(JOIN、子查询、聚合)才使用 XML Mapper
596
+ - XML 文件位于 `src/main/resources/mapper/{domain}/` 目录
597
+ - 逻辑删除由 `@TableLogic` 自动处理,无需手动加 `WHERE deleted = 0`
598
+
599
+ ### 6.9 JSONB 字段处理规范(PostgreSQL)
600
+
601
+ PostgreSQL 的 `jsonb` 列**不允许** `varchar → jsonb` 隐式转换。MyBatis-Plus 自带的 `JacksonTypeHandler` 通过 `PreparedStatement#setString` 写参,直接写 JSONB 列会抛:
602
+
603
+ ```
604
+ column is of type jsonb but expression is of type character varying
605
+ ```
606
+
607
+ 因此项目使用**自研 `JsonbTypeHandler`**(而非 `JacksonTypeHandler`),通过 `PGobject` 显式声明 jdbc 类型为 `jsonb` 精确写入。
608
+
609
+ **规范**:
610
+
611
+ 1. **DO 字段**:JSONB 列在 Java 侧映射为 `String`(JSON 文本),并加 `@TableField(typeHandler = JsonbTypeHandler.class)`:
612
+
613
+ ```java
614
+ @TableName("code_rule")
615
+ public class CodeRuleDO extends TenantBaseDO {
616
+
617
+ /** 段位配置(JSON 文本,对应 jsonb 列 segment_config) */
618
+ @TableField(typeHandler = JsonbTypeHandler.class)
619
+ private String segmentConfig;
620
+ }
621
+ ```
622
+
623
+ 2. **类型处理器**:`JsonbTypeHandler extends BaseTypeHandler<String>`,位于模块内 `framework/mybatis/core/type/JsonbTypeHandler.java`,加 `@MappedJdbcTypes(JdbcType.OTHER)`:
624
+
625
+ ```java
626
+ @MappedJdbcTypes(JdbcType.OTHER)
627
+ public class JsonbTypeHandler extends BaseTypeHandler<String> {
628
+
629
+ private static final String JSONB_TYPE = "jsonb";
630
+ private static final ThreadLocal<Boolean> IS_POSTGRESQL = new ThreadLocal<>();
631
+
632
+ @Override
633
+ public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
634
+ throws SQLException {
635
+ if (isPostgresql(ps)) {
636
+ // PostgreSQL JSONB 列:必须通过 PGobject 显式声明 jsonb 类型,否则 setString 会失败
637
+ PGobject pgObject = new PGobject();
638
+ pgObject.setType(JSONB_TYPE);
639
+ pgObject.setValue(parameter);
640
+ ps.setObject(i, pgObject);
641
+ } else {
642
+ // H2 / MySQL 等:列为 TEXT / JSON,直接写字符串(单元测试环境)
643
+ ps.setString(i, parameter);
644
+ }
645
+ }
646
+
647
+ // getNullableResult x3 使用 rs.getString / cs.getString 读取即可
648
+ }
649
+ ```
650
+
651
+ - 是否 PostgreSQL 用 `ThreadLocal<Boolean>` 缓存连接元数据(同一进程数据源固定),避免每次写入都查询 `DatabaseMetaData`
652
+ - 已有实现见 `ultracloud-module-ai`、`ultracloud-module-code` 等模块的 `framework/mybatis/core/type/JsonbTypeHandler.java`,新模块直接复制
653
+
654
+ 3. **JSON 字段前后端契约**:VO/DO/RPC DTO 中的 JSON 字段统一为 `String`(JSON 文本),**不使用** `List<T>` 强类型 VO;前端 `Form.List` 编辑为数组,提交前 `JSON.stringify` 序列化,读取时 `JSON.parse` 还原:
655
+
656
+ ```java
657
+ // SaveReqVO:JSON 字段声明为 String,用 @NotBlank 校验
658
+ @NotBlank(message = "段位配置不能为空")
659
+ private String segmentConfig;
660
+ ```
661
+
662
+ > **禁止**:JSONB 字段使用 `JacksonTypeHandler` + `@TableName(autoResultMap = true)`(在 PostgreSQL 下会因 varchar→jsonb 隐式转换失败报错)。
663
+
664
+ ---
665
+
666
+ ## 七、VO 规范
667
+
668
+ ### 7.1 VO 分类
669
+
670
+ | 类型 | 命名 | 用途 | 继承 | 校验 |
671
+ | ------------ | ---------------------- | ------------------------------------------- | ----------- | -------------------------- |
672
+ | SaveReqVO | `{Entity}SaveReqVO` | 创建/更新(id=null 为创建,非 null 为更新) | 无 | `@NotBlank`、`@NotNull` 等 |
673
+ | PageReqVO | `{Entity}PageReqVO` | 分页查询 | `PageParam` | 无 |
674
+ | ListReqVO | `{Entity}ListReqVO` | 列表查询(不分页) | 无 | 无 |
675
+ | RespVO | `{Entity}RespVO` | 响应展示 | 无 | 无 |
676
+ | SimpleRespVO | `{Entity}SimpleRespVO` | 精简响应(下拉列表) | 无 | 无 |
677
+
678
+ ### 7.2 SaveReqVO 示例
679
+
680
+ ```java
681
+ package cn.ultracloud.com.module.wms.controller.admin.vo.warehouse;
682
+
683
+ import io.swagger.v3.oas.annotations.media.Schema;
684
+ import jakarta.validation.constraints.NotBlank;
685
+ import jakarta.validation.constraints.NotNull;
686
+ import lombok.Data;
687
+
688
+ @Schema(description = "管理后台 - WMS 仓库新增/修改 Request VO")
689
+ @Data
690
+ public class WarehouseSaveReqVO {
691
+
692
+ @Schema(description = "仓库编号", example = "1024")
693
+ private Long id;
694
+
695
+ @Schema(description = "仓库编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001")
696
+ @NotBlank(message = "仓库编码不能为空")
697
+ private String code;
698
+
699
+ @Schema(description = "仓库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "华东主仓")
700
+ @NotBlank(message = "仓库名称不能为空")
701
+ private String name;
702
+
703
+ @Schema(description = "所属库存组织编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
704
+ @NotNull(message = "所属库存组织不能为空")
705
+ private Long orgId;
706
+ }
707
+ ```
708
+
709
+ ### 7.3 PageReqVO 示例
710
+
711
+ ```java
712
+ package cn.ultracloud.com.module.wms.controller.admin.vo.warehouse;
713
+
714
+ import cn.ultracloud.com.framework.common.pojo.PageParam;
715
+ import io.swagger.v3.oas.annotations.media.Schema;
716
+ import lombok.Data;
717
+ import org.springframework.format.annotation.DateTimeFormat;
718
+ import java.time.LocalDateTime;
719
+
720
+ import static cn.ultracloud.com.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
721
+
722
+ @Schema(description = "管理后台 - WMS 仓库分页 Request VO")
723
+ @Data
724
+ public class WarehousePageReqVO extends PageParam {
725
+
726
+ @Schema(description = "仓库编码", example = "WH001")
727
+ private String code;
728
+
729
+ @Schema(description = "仓库名称", example = "华东主仓")
730
+ private String name;
731
+
732
+ @Schema(description = "所属库存组织编号", example = "1")
733
+ private Long orgId;
734
+
735
+ @Schema(description = "创建时间")
736
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
737
+ private LocalDateTime[] createTime;
738
+ }
739
+ ```
740
+
741
+ ### 7.4 RespVO 示例
742
+
743
+ ```java
744
+ package cn.ultracloud.com.module.wms.controller.admin.vo.warehouse;
745
+
746
+ import io.swagger.v3.oas.annotations.media.Schema;
747
+ import lombok.Data;
748
+ import java.time.LocalDateTime;
749
+
750
+ @Schema(description = "管理后台 - WMS 仓库 Response VO")
751
+ @Data
752
+ public class WarehouseRespVO {
753
+
754
+ @Schema(description = "仓库编号", example = "1024")
755
+ private Long id;
756
+
757
+ @Schema(description = "仓库编码", example = "WH001")
758
+ private String code;
759
+
760
+ @Schema(description = "仓库名称", example = "华东主仓")
761
+ private String name;
762
+
763
+ @Schema(description = "所属库存组织编号", example = "1")
764
+ private Long orgId;
765
+
766
+ @Schema(description = "状态", example = "1")
767
+ private Integer status;
768
+
769
+ @Schema(description = "创建时间", example = "2026-01-01 00:00:00")
770
+ private LocalDateTime createTime;
771
+ }
772
+ ```
773
+
774
+ ### 7.5 VO 位置
775
+
776
+ 位于 `controller/admin/{domain}/vo/{domain}/` 下,按业务域组织子目录。
777
+
778
+ ### 7.6 DTO 与 VO 的区分
779
+
780
+ - **VO**:前端接口使用,位于 `-server` 模块的 `controller/admin/{domain}/vo/`
781
+ - **DTO**:跨模块 RPC 调用使用,位于 `-api` 模块的 `dto/{domain}/`
782
+ - 前端接口不使用 DTO,跨模块调用不使用 VO
783
+
784
+ ---
785
+
786
+ ## 八、对象转换规范
787
+
788
+ ### 8.1 BeanUtils.toBean()
789
+
790
+ 项目统一使用 `BeanUtils.toBean()`(基于 Hutool `BeanUtil`):
791
+
792
+ ```java
793
+ import static cn.ultracloud.com.framework.common.util.object.BeanUtils.toBean;
794
+
795
+ // 单对象转换
796
+ WmsWarehouseDO warehouse = warehouseMapper.selectById(id);
797
+ return success(toBean(warehouse, WarehouseRespVO.class));
798
+
799
+ // 分页转换(自动保留 total)
800
+ PageResult<WmsWarehouseDO> pageResult = warehouseMapper.selectPage(pageReqVO);
801
+ return success(toBean(pageResult, WarehouseRespVO.class));
802
+
803
+ // List 转换
804
+ List<WmsWarehouseDO> list = warehouseMapper.selectList(reqVO);
805
+ return success(toBean(list, WarehouseRespVO.class));
806
+
807
+ // 带回调的转换
808
+ return success(toBean(warehouse, WarehouseRespVO.class, vo -> {
809
+ vo.setExtraInfo("computed value");
810
+ }));
811
+ ```
812
+
813
+ ### 8.2 MapStruct 转换器
814
+
815
+ 复杂映射使用 MapStruct,位于 `convert/` 目录:
816
+
817
+ ```java
818
+ @Mapper
819
+ public interface ConfigConvert {
820
+ ConfigConvert INSTANCE = Mappers.getMapper(ConfigConvert.class);
821
+
822
+ ConfigDO convert(ConfigSaveReqVO bean);
823
+ ConfigRespVO convert(ConfigDO bean);
824
+ }
825
+ ```
826
+
827
+ ---
828
+
829
+ ## 九、枚举规范
830
+
831
+ ### 9.1 简单枚举
832
+
833
+ 不需要参数校验的枚举,普通定义即可:
834
+
835
+ ```java
836
+ @Getter
837
+ @AllArgsConstructor
838
+ public enum RoleTypeEnum {
839
+ SYSTEM(1),
840
+ CUSTOM(2);
841
+ private final Integer type;
842
+ }
843
+ ```
844
+
845
+ ### 9.2 ArrayValuable 枚举
846
+
847
+ 需要用于 `@InEnum` 参数校验的枚举:
848
+
849
+ ```java
850
+ package cn.ultracloud.com.module.wms.enums;
851
+
852
+ import cn.ultracloud.com.framework.common.core.ArrayValuable;
853
+ import lombok.AllArgsConstructor;
854
+ import lombok.Getter;
855
+
856
+ import java.util.Arrays;
857
+
858
+ @Getter
859
+ @AllArgsConstructor
860
+ public enum WmsOrgStatusEnum implements ArrayValuable<Integer> {
861
+
862
+ PENDING(0, "待启用"),
863
+ ENABLED(1, "已启用"),
864
+ DISABLED(2, "已停用");
865
+
866
+ public static final Integer[] ARRAYS = Arrays.stream(values())
867
+ .map(WmsOrgStatusEnum::getStatus).toArray(Integer[]::new);
868
+
869
+ private final Integer status;
870
+ private final String name;
871
+
872
+ @Override
873
+ public Integer[] array() {
874
+ return ARRAYS;
875
+ }
876
+ }
877
+ ```
878
+
879
+ ### 9.3 @InEnum 校验
880
+
881
+ 在 VO 字段上使用 `@InEnum` 进行枚举值校验:
882
+
883
+ ```java
884
+ @InEnum(WmsOrgStatusEnum.class)
885
+ @Schema(description = "状态", example = "1")
886
+ private Integer status;
887
+ ```
888
+
889
+ 枚举位于 `-api` 模块的 `enums/` 目录下。
890
+
891
+ ---
892
+
893
+ ## 十、参数校验规范
894
+
895
+ ### 10.1 Controller 校验
896
+
897
+ - Controller 类加 `@Validated` 注解
898
+ - POST/PUT 方法参数加 `@Valid @RequestBody`
899
+ - GET 方法参数加 `@Valid`(不加 @RequestBody)
900
+
901
+ ### 10.2 常用校验注解
902
+
903
+ 来自 `jakarta.validation.constraints`:
904
+
905
+ | 注解 | 说明 |
906
+ | -------------------------- | ------------------------------------------------ |
907
+ | `@NotBlank` | 字符串非空(禁止空白字符) |
908
+ | `@NotNull` | 对象非空(用于 Integer、Long、LocalDateTime 等) |
909
+ | `@NotEmpty` | 字符串/集合非空 |
910
+ | `@Size(min = N, max = M)` | 长度限制 |
911
+ | `@Email` | 邮箱格式 |
912
+ | `@Pattern(regexp = "...")` | 正则校验 |
913
+ | `@InEnum(XxxEnum.class)` | 枚举值校验 |
914
+
915
+ ### 10.3 时间参数
916
+
917
+ 项目统一使用 `LocalDateTime`,JSON 序列化由框架全局配置处理,**无需在字段上加 `@JsonFormat`**。
918
+
919
+ PageReqVO 中的时间区间查询:
920
+ ```java
921
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
922
+ private LocalDateTime[] createTime;
923
+ ```
924
+
925
+ ---
926
+
927
+ ## 十一、异常规范
928
+
929
+ ### 11.1 全局异常处理
930
+
931
+ 全局异常处理由 `ultracloud-spring-boot-starter-web` 的 `GlobalExceptionHandler` 统一提供(`@RestControllerAdvice`),业务模块**无需手写**。
932
+
933
+ 框架已处理的异常:
934
+ - 参数校验异常(`MethodArgumentNotValidException`、`BindException` 等)→ 400
935
+ - 业务异常(`ServiceException`)→ 返回 `CommonResult.error(code, msg)`
936
+ - 权限异常(`AccessDeniedException`)→ 403
937
+ - 资源不存在(`NoHandlerFoundException`)→ 404
938
+ - 兜底异常(`Exception`)→ 500,自动记录错误日志
939
+
940
+ ### 11.2 业务异常抛出
941
+
942
+ 使用 `ServiceExceptionUtil.exception()` 通过静态导入抛出:
943
+
944
+ ```java
945
+ import static cn.ultracloud.com.framework.common.exception.util.ServiceExceptionUtil.exception;
946
+ import static cn.ultracloud.com.module.wms.enums.ErrorCodeConstants.*;
947
+
948
+ // 简单抛出
949
+ throw exception(WAREHOUSE_NOT_EXISTS);
950
+
951
+ // 带占位符参数
952
+ throw exception(WAREHOUSE_CODE_DUPLICATE, code);
953
+ ```
954
+
955
+ ### 11.3 异常分类
956
+
957
+ | 类型 | 处理方式 |
958
+ | ------------ | ----------------------------------------------- |
959
+ | 业务异常 | `throw exception(ErrorCodeConstants.XXX)` |
960
+ | 参数校验异常 | `@Valid` + `@Validated`,框架自动捕获并返回 400 |
961
+ | 系统异常 | 框架兜底返回 500,自动记录错误日志 |
962
+
963
+ ### 11.4 禁止事项
964
+
965
+ 1. 禁止手写 `GlobalExceptionHandler`(框架已提供)
966
+ 2. 禁止手写 `ServiceException extends RuntimeException`(框架已提供 `ServiceExceptionUtil`)
967
+ 3. 禁止使用 `try-catch` 捕获所有异常(仅捕获特定异常,兜底异常交给全局处理)
968
+ 4. 禁止抛出 `Exception`、`RuntimeException` 等通用异常
969
+
970
+ ---
971
+
972
+ ## 十二、多租户开发规范
973
+
974
+ ### 12.1 实体层
975
+
976
+ - 需要租户隔离的 DO 继承 `TenantBaseDO`(自动获得 `tenantId` 字段)
977
+ - 不需要租户隔离的 DO 继承 `BaseDO`,并在类上加 `@TenantIgnore` 注解:
978
+
979
+ ```java
980
+ @TenantIgnore
981
+ @TableName("infra_config")
982
+ @Data
983
+ @EqualsAndHashCode(callSuper = true)
984
+ public class ConfigDO extends BaseDO {
985
+ // ...
986
+ }
987
+ ```
988
+
989
+ ### 12.2 查询层
990
+
991
+ - 框架自动在 SQL 中追加 `tenant_id = ?` 条件,无需手动添加
992
+ - 跨租户查询:在 Service 方法上加 `@TenantIgnore` 注解
993
+
994
+ ---
995
+
996
+ ## 十三、跨模块 RPC 调用规范
997
+
998
+ ### 13.1 Feign Client 定义(-api 模块)
999
+
1000
+ ```java
1001
+ package cn.ultracloud.com.module.system.api.user;
1002
+
1003
+ import cn.ultracloud.com.framework.common.pojo.CommonResult;
1004
+ import org.springframework.cloud.openfeign.FeignClient;
1005
+ import org.springframework.web.bind.annotation.GetMapping;
1006
+ import org.springframework.web.bind.annotation.RequestParam;
1007
+
1008
+ @FeignClient(name = ApiConstants.NAME, path = ApiConstants.PREFIX + "/user")
1009
+ public interface AdminUserApi {
1010
+
1011
+ @GetMapping("/get")
1012
+ CommonResult<AdminUserRespDTO> getUser(@RequestParam("id") Long id);
1013
+ }
1014
+ ```
1015
+
1016
+ ### 13.2 RPC API 实现(-server 模块)
1017
+
1018
+ ```java
1019
+ package cn.ultracloud.com.module.system.api.user;
1020
+
1021
+ import cn.ultracloud.com.framework.common.pojo.CommonResult;
1022
+ import cn.ultracloud.com.module.system.api.user.dto.AdminUserRespDTO;
1023
+ import cn.ultracloud.com.module.system.service.user.AdminUserService;
1024
+ import jakarta.annotation.Resource;
1025
+ import org.springframework.web.bind.annotation.RestController;
1026
+
1027
+ import static cn.ultracloud.com.framework.common.util.object.BeanUtils.toBean;
1028
+ import static cn.ultracloud.com.framework.common.pojo.CommonResult.success;
1029
+
1030
+ @RestController
1031
+ public class AdminUserApiImpl implements AdminUserApi {
1032
+
1033
+ @Resource
1034
+ private AdminUserService adminUserService;
1035
+
1036
+ @Override
1037
+ public CommonResult<AdminUserRespDTO> getUser(Long id) {
1038
+ return success(toBean(adminUserService.getUser(id), AdminUserRespDTO.class));
1039
+ }
1040
+ }
1041
+ ```
1042
+
1043
+ ### 13.3 DTO 定义
1044
+
1045
+ - 位于 `-api` 模块的 `dto/{domain}/` 目录
1046
+ - 命名以 `RespDTO` / `ReqDTO` 结尾
1047
+ - 不包含 VO 特有的 `@Schema`、校验注解等
1048
+
1049
+ ---
1050
+
1051
+ ## 十四、测试规范
1052
+
1053
+ ### 14.1 测试基类
1054
+
1055
+ - DB 单元测试:继承 `BaseDbUnitTest`(使用 H2 内存数据库)
1056
+ - 需要 Redis 的测试:继承 `BaseDbAndRedisUnitTest`(使用 jedis-mock)
1057
+
1058
+ ### 14.2 测试类模板
1059
+
1060
+ ```java
1061
+ package cn.ultracloud.com.module.wms.service.warehouse;
1062
+
1063
+ import cn.ultracloud.com.framework.test.core.ut.BaseDbUnitTest;
1064
+ import jakarta.annotation.Resource;
1065
+ import org.junit.jupiter.api.Test;
1066
+ import org.springframework.boot.test.context.SpringBootTest;
1067
+ import org.springframework.test.context.ActiveProfiles;
1068
+ import org.springframework.test.context.jdbc.Sql;
1069
+
1070
+ import static org.junit.jupiter.api.Assertions.*;
1071
+
1072
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
1073
+ @ActiveProfiles("unit-test")
1074
+ @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
1075
+ class WmsWarehouseServiceImplTest extends BaseDbUnitTest {
1076
+
1077
+ @Resource
1078
+ private WmsWarehouseServiceImpl warehouseService;
1079
+
1080
+ @Test
1081
+ void testCreateWarehouse_success() {
1082
+ // given
1083
+ WarehouseSaveReqVO reqVO = new WarehouseSaveReqVO();
1084
+ reqVO.setCode("WH001");
1085
+ reqVO.setName("华东主仓");
1086
+ // when
1087
+ Long id = warehouseService.createWarehouse(reqVO);
1088
+ // then
1089
+ assertNotNull(id);
1090
+ }
1091
+ }
1092
+ ```
1093
+
1094
+ ### 14.3 测试 SQL
1095
+
1096
+ - 创建表脚本:`src/test/resources/sql/create_tables.sql`
1097
+ - 清理脚本:`src/test/resources/sql/clean.sql`(每个测试方法执行后运行)
1098
+
1099
+ ### 14.4 测试方法命名
1100
+
1101
+ `test{Method}_{Scenario}`,如 `testCreateWarehouse_success`、`testCreateWarehouse_codeDuplicate`
1102
+
1103
+ ---
1104
+
1105
+ ## 十五、注释规范
1106
+
1107
+ ### 15.1 类注释
1108
+
1109
+ - DO 类:代码生成器自动生成 `/** ... */` Javadoc 包含 `@author`
1110
+ - 手写类:简洁注释说明作用即可,不强制 `@author` / `@date`
1111
+
1112
+ ### 15.2 方法注释
1113
+
1114
+ - 公共方法:简洁的 `// 说明` 注释或单行 Javadoc
1115
+ - 复杂业务逻辑方法:详细 Javadoc(方法作用、参数说明、返回值)
1116
+
1117
+ ### 15.3 字段注释
1118
+
1119
+ - VO 字段:使用 `@Schema(description = "...")` 替代 Javadoc
1120
+ - DO 字段:使用 `/** ... */` Javadoc 说明含义
1121
+
1122
+ ### 15.4 禁止事项
1123
+
1124
+ 1. 禁止无意义注释(如 `// 这里是循环`)
1125
+ 2. 禁止注释与代码不一致
1126
+ 3. 禁止使用拼音注释
1127
+
1128
+ ---
1129
+
1130
+ ## 十六、代码风格规范
1131
+
1132
+ 1. **缩进**: 统一使用 4 个空格,禁止 Tab
1133
+ 2. **大括号**: 左大括号紧跟语句末尾,右大括号单独成行
1134
+ 3. **空行**: 方法之间、类成员之间空一行,逻辑块之间空一行
1135
+ 4. **魔法值**: 禁止直接写数字、字符串,需定义为常量或枚举
1136
+ 5. **集合初始化**: 指定大小(如 `new ArrayList<>(10)`)
1137
+ 6. **空判断**: 使用 `ObjectUtil.isNull()` / `CollUtil.isEmpty()`(Hutool 工具类)
1138
+ 7. **代码简洁**: 禁止冗余代码,复杂逻辑拆分方法
1139
+ 8. **命名统一**: 同一含义的字段、方法,命名必须统一
1140
+
1141
+ ---
1142
+
1143
+ ## 十七、代码生成器
1144
+
1145
+ `ultracloud-module-infra` 提供代码生成器(codegen),可生成:
1146
+ - Controller、Service、Mapper、DO、VO(SaveReqVO/PageReqVO/ListReqVO/RespVO)
1147
+ - 单元测试类、SQL 脚本
1148
+ - 前端代码(Vue2 Element UI、Vue3 Element Plus、Vue3 Vben5 等)
1149
+
1150
+ 使用方式:管理后台 `/admin-api/infra/codegen/` 或 API 调用。
1151
+
1152
+ 生成的代码已遵循本规范中的所有约定。生成的错误码常量需手动补充到 `ErrorCodeConstants` 中。
1153
+
1154
+ ---
1155
+
1156
+ ## 十八、开发标准流程
1157
+
1158
+ 1. **需求分析**: 明确功能、入参、出参、业务逻辑
1159
+ 2. **定义 VO**: 编写 SaveReqVO(含校验)、PageReqVO、RespVO
1160
+ 3. **定义 DO**: 在 `dal/dataobject/` 下创建实体,继承 BaseDO/TenantBaseDO
1161
+ 4. **定义 Mapper**: 在 `dal/mysql/` 下创建接口,继承 BaseMapperX
1162
+ 5. **定义 Service**: 接口 + Impl,实现业务逻辑
1163
+ 6. **定义 Controller**: 定义接口路径、请求方法、权限注解,调用 Service
1164
+ 7. **定义错误码**: 在 `ErrorCodeConstants` 中补充业务错误码
1165
+ 8. **单元测试**: 编写测试用例,覆盖正常流程和异常场景
1166
+ 9. **联调测试**: 测试接口是否正常、参数校验是否生效、异常是否正确返回