jsharness 1.5.0 → 1.6.0

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 (37) hide show
  1. package/.harness/agents/developer/contract.yaml +1 -1
  2. package/.harness/agents/prompt-templates.md +28 -4
  3. package/.harness/agents/solution-designer/contract.yaml +84 -28
  4. package/.harness/dev-map/backend/api-definition.md +10 -12
  5. package/.harness/dev-map/backend/auth-security.md +53 -28
  6. package/.harness/dev-map/backend/conventions-java.md +72 -60
  7. package/.harness/dev-map/backend/conventions.md +127 -163
  8. package/.harness/dev-map/backend/structure.md +142 -92
  9. package/.harness/dev-map/decisions.md +1 -1
  10. package/.harness/dev-map/frontend/structure.md +1 -1
  11. package/.harness/dev-map/overview.md +10 -9
  12. package/.harness/gate/checks/static-compliance.js +2 -2
  13. package/.harness/gate/checks/test-compliance.js +4 -4
  14. package/.harness/rules/global/coding-standard.md +15 -8
  15. package/.harness/rules/global/commit-convention.md +7 -0
  16. package/.harness/rules/global/design-document-boundary.md +115 -0
  17. package/.harness/rules/global/process-discipline.md +7 -0
  18. package/.harness/rules/global/security-baseline.md +27 -23
  19. package/.harness/rules/project/frontend-vue3.md +9 -0
  20. package/.harness/rules/project/java-backend.md +129 -22
  21. package/.harness/rules/project/web-specific.md +27 -14
  22. package/.harness/skills/architecture-designer/SKILL.md +768 -0
  23. package/.harness/skills/{build.md → build/SKILL.md} +7 -0
  24. package/.harness/skills/{code-review.md → code-review/SKILL.md} +7 -0
  25. package/.harness/skills/{docker-build.md → docker-build/SKILL.md} +7 -0
  26. package/.harness/skills/{docs-update.md → docs-update/SKILL.md} +7 -0
  27. package/.harness/skills/{java-build.md → java-build/SKILL.md} +12 -2
  28. package/.harness/skills/{lint-check.md → lint-check/SKILL.md} +7 -0
  29. package/.harness/skills/{task-board-maintenance.md → task-board-maintenance/SKILL.md} +7 -0
  30. package/.harness/skills/{test-api.md → test-api/SKILL.md} +21 -14
  31. package/.harness/skills/{test-e2e.md → test-e2e/SKILL.md} +7 -0
  32. package/.harness/skills/{test-unit.md → test-unit/SKILL.md} +50 -44
  33. package/.harness/skills/{vue-frontend-build.md → vue-frontend-build/SKILL.md} +11 -0
  34. package/.harness/workflow/definition.yaml +37 -3
  35. package/bin/jsharness.js +1 -0
  36. package/lib/index.mjs +306 -27
  37. package/package.json +2 -1
@@ -1,8 +1,8 @@
1
1
  # 后端分区 — Java 编码惯例 (conventions-java)
2
2
 
3
- > **来源**: `files/java-backend-coding-standards/SKILL.md` (JSCICD 项目后端 Java 规范)
3
+ > **来源**: `files/java-backend-coding-standards/SKILL.md` (已重构为 jieshun 项目后端 Java 规范)
4
4
  > **归档日期**: 2026-05-21
5
- > **技术栈**: Spring Boot 3.2.12 / JDK 21 (虚拟线程) / MySQL 8.0 / Redis 5.x / MyBatis-Plus
5
+ > **技术栈**: Spring Boot 3.2.12 / JDK 21 (虚拟线程) / MySQL 8.0 / 达梦 DM8 / Redis 5.x / MyBatis-Plus
6
6
 
7
7
  ## 技术栈版本速查
8
8
 
@@ -17,20 +17,22 @@
17
17
  | Redisson | 3.x | 分布式锁/Redis 客户端 |
18
18
  | Hutool | 5.x | 工具库(按需使用)|
19
19
  | Knife4j/Swagger | OpenAPI 3 | API 文档 |
20
- | Nacos | 2.x | 配置中心 + 注册中心 |
20
+ | Nacos | 3.0 | 配置中心 + 注册中心 |
21
+ | 达梦 DM8 | 8.x | 国产化/信创数据库(替代 MySQL)|
22
+ | MapStruct | 1.5.x | 对象转换(Entity ↔ VO/DTO)|
21
23
 
22
24
  ---
23
25
 
24
26
  ## Maven 四层模块详细结构
25
27
 
26
28
  ```
27
- jscicd-project/
29
+ jieshun-project/
28
30
  ├── pom.xml # 父 POM(统一依赖版本管理)
29
31
  ├── app/ # 启动入口模块
30
32
  │ ├── pom.xml
31
33
  │ └── src/main/java/com/jieshun/
32
34
  │ └── app/
33
- │ ├── JscicdApplication.java # @SpringBootApplication
35
+ │ ├── JieshunApplication.java # @SpringBootApplication
34
36
  │ └── config/ # 启动配置类
35
37
  │ └── WebMvcConfig.java
36
38
 
@@ -48,12 +50,12 @@ jscicd-project/
48
50
  │ │ └── impl/ # 实现类子包
49
51
  │ │ └── UserServiceImpl.java
50
52
  │ ├── mapper/ # Mapper 层
51
- │ │ ├── UserMapper.java # Mapper 接口
53
+ │ │ ├── UserMapper.java # Mapper 接口(纯接口,不继承 BaseMapper)
52
54
  │ │ └── xml/ # XML 映射文件目录
53
55
  │ │ └── UserMapper.xml
54
56
  │ ├── data/ # 数据对象层
55
- │ │ ├── entity/ # 数据库实体
56
- │ │ │ └── UserDO.java
57
+ │ │ ├── entity/ # 数据库实体(XML resultType 映射,无需 @TableName)
58
+ │ │ │ └── UserEntity.java
57
59
  │ │ ├── vo/ # 视图对象
58
60
  │ │ │ ├── UserVO.java
59
61
  │ │ │ ├── UserRespVO.java # 响应对象
@@ -75,11 +77,11 @@ jscicd-project/
75
77
  ├── pom.xml
76
78
  └── src/main/java/com/jieshun/common/
77
79
  ├── constant/ # 常量
78
- │ ├── ErrorCodeConstants.java # 错误码枚举
80
+ │ ├── ErrorCodeConstants.java # 错误码常量(引用 ErrorCode 枚举)
79
81
  │ └── RedisKeyConstants.java # Redis Key 常量
80
82
  ├── exception/ # 异常定义
81
- │ ├── ServiceException.java
82
- │ └── JscicdBizException.java # 业务异常基类
83
+ │ ├── ServiceException.java # 系统服务异常(不可恢复,隐藏内部细节)
84
+ │ └── BusinessException.java # 业务异常基类(可恢复,透传错误信息给前端)
83
85
  ├── util/ # 工具类
84
86
  │ └── ObjectUtils.java
85
87
  └── enums/ # 通用枚举
@@ -97,45 +99,46 @@ jscicd-project/
97
99
  * 用户管理 Controller
98
100
  *
99
101
  * @description 提供用户 CRUD 和分页查询接口
100
- * @author jscicd-backend-team
102
+ * @author jieshun-backend-team
101
103
  */
102
104
  @RestController
103
- @RequestMapping("/api/v1/user")
105
+ @RequestMapping("/api/v1/users")
104
106
  @RequiredArgsConstructor
105
107
  @Tag(name = "用户管理")
106
108
  public class UserController {
107
109
 
108
110
  private final UserService userService;
109
111
 
110
- @PostMapping("/create")
112
+ @PostMapping
111
113
  @Operation(summary = "创建用户")
112
114
  public CommonResult<Long> create(@Valid @RequestBody UserCreateReqVO reqVO) {
113
115
  return success(userService.createUser(reqVO));
114
116
  }
115
117
 
116
- @PostMapping("/update")
118
+ @PutMapping("/{id}")
117
119
  @Operation(summary = "更新用户")
118
- public CommonResult<Boolean> update(@Valid @RequestBody UserUpdateReqVO reqVO) {
120
+ public CommonResult<Boolean> update(@PathVariable("id") Long id,
121
+ @Valid @RequestBody UserUpdateReqVO reqVO) {
119
122
  userService.updateUser(reqVO);
120
123
  return success(true);
121
124
  }
122
125
 
123
- @PostMapping("/delete")
126
+ @DeleteMapping("/{id}")
124
127
  @Operation(summary = "删除用户")
125
- public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
128
+ public CommonResult<Boolean> delete(@PathVariable("id") Long id) {
126
129
  userService.deleteUser(id);
127
130
  return success(true);
128
131
  }
129
132
 
130
- @PostMapping("/page")
133
+ @GetMapping
131
134
  @Operation(summary = "分页查询用户")
132
135
  public CommonResult<PageResult<UserRespVO>> page(@Valid UserPageReqVO reqVO) {
133
136
  return success(userService.getUserPage(reqVO));
134
137
  }
135
138
 
136
- @GetMapping("/get")
139
+ @GetMapping("/{id}")
137
140
  @Operation(summary = "获取用户详情")
138
- public CommonResult<UserRespVO> get(@RequestParam("id") Long id) {
141
+ public CommonResult<UserRespVO> get(@PathVariable("id") Long id) {
139
142
  return success(userService.getUser(id));
140
143
  }
141
144
  }
@@ -144,7 +147,7 @@ public class UserController {
144
147
  **Controller 要点**:
145
148
  - `@RestController` + `@RequestMapping` 定义路由前缀
146
149
  - `@RequiredArgsConstructor` 构造器注入
147
- - HTTP 方法统一用 `@PostMapping`(RESTful 风格但 POST 优先)
150
+ - HTTP 方法按 RESTful 语义使用:查询用 GET,创建用 POST,更新用 PUT/PATCH,删除用 DELETE
148
151
  - 参数校验使用 JSR303:`@Valid` / `@Validated`
149
152
  - 返回值统一包装为 `CommonResult<T>`
150
153
  - Swagger/OpenAPI 注解:`@Tag`, `@Operation`
@@ -197,7 +200,7 @@ public class UserServiceImpl implements UserService {
197
200
  public Long createUser(UserCreateReqVO reqVO) {
198
201
  // 1. 参数校验与转换
199
202
  // TODO: 校验手机号唯一性
200
- UserDO entity = UserConvert.INSTANCE.convert(reqVO);
203
+ UserEntity entity = UserConvert.INSTANCE.convert(reqVO);
201
204
 
202
205
  // 2. 业务逻辑编排
203
206
  // (如:发送欢迎短信 — 在事务外调用)
@@ -211,15 +214,15 @@ public class UserServiceImpl implements UserService {
211
214
 
212
215
  @Override
213
216
  public PageResult<UserRespVO> getUserPage(UserPageReqVO reqVO) {
214
- // 分页参数设置
215
- PageHelper.startPage(reqVO.getPageNo(), reqVO.getPageSize());
217
+ // 分页参数设置(MyBatis-Plus 分页拦截器自动追加 LIMIT)
218
+ Page<UserEntity> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
216
219
 
217
220
  // 执行查询
218
- List<UserDO> list = userMapper.selectList(reqVO);
221
+ Page<UserEntity> result = userMapper.selectPage(page, reqVO);
219
222
 
220
223
  // 转换返回
221
- return new PageResult<>(UserConvert.INSTANCE.convertList(list),
222
- new PageInfo<>(list).getTotal());
224
+ return new PageResult<>(UserConvert.INSTANCE.convertList(result.getRecords()),
225
+ result.getTotal());
223
226
  }
224
227
  }
225
228
  ```
@@ -234,12 +237,13 @@ public class UserServiceImpl implements UserService {
234
237
  ### Mapper + XML 标准模板
235
238
 
236
239
  ```java
237
- // Mapper 接口
240
+ // Mapper 接口(纯接口,不继承 BaseMapper)
238
241
  public interface UserMapper {
239
- List<UserDO> selectList(@Param("reqVO") UserPageReqVO reqVO);
240
- UserDO selectById(@Param("id") Long id);
241
- void insert(@Param("entity") UserDO entity);
242
- void updateById(@Param("entity") UserDO entity);
242
+ Page<UserEntity> selectPage(Page<UserEntity> page, @Param("reqVO") UserPageReqVO reqVO);
243
+ List<UserEntity> selectList(@Param("reqVO") UserPageReqVO reqVO);
244
+ UserEntity selectById(@Param("id") Long id);
245
+ void insert(@Param("entity") UserEntity entity);
246
+ void updateById(@Param("entity") UserEntity entity);
243
247
  void deleteById(@Param("id") Long id);
244
248
  }
245
249
  ```
@@ -256,7 +260,7 @@ public interface UserMapper {
256
260
  create_time, update_time, deleted
257
261
  </sql>
258
262
 
259
- <select id="selectList" resultType="com.jieshun.domain.data.entity.UserDO">
263
+ <select id="selectList" resultType="com.jieshun.domain.data.entity.UserEntity">
260
264
  SELECT <include refid="selectFields"/>
261
265
  FROM system_users
262
266
  <where>
@@ -285,8 +289,7 @@ public interface UserMapper {
285
289
  ```java
286
290
  // ========== Entity ==========
287
291
  @Data
288
- @TableName("system_users")
289
- public class UserDO {
292
+ public class UserEntity {
290
293
  /** 用户 ID */
291
294
  private Long id;
292
295
  /** 用户名 */
@@ -303,8 +306,8 @@ public class UserDO {
303
306
  private LocalDateTime createTime;
304
307
  /** 更新时间 */
305
308
  private LocalDateTime updateTime;
306
- /** 是否删除 */
307
- private Boolean deleted;
309
+ /** 是否删除(0=未删除 1=已删除)*/
310
+ private Integer deleted;
308
311
  }
309
312
 
310
313
  // ========== ReqVO ==========
@@ -345,8 +348,8 @@ public class UserRespVO {
345
348
  public class GlobalExceptionHandler {
346
349
 
347
350
  /** 业务异常 — 直接透传错误信息 */
348
- @ExceptionHandler(JscicdBizException.class)
349
- public CommonResult<?> handleBizException(JscicdBizException e) {
351
+ @ExceptionHandler(BusinessException.class)
352
+ public CommonResult<?> handleBizException(BusinessException e) {
350
353
  log.warn("[handleBizException][code({})message({})]", e.getCode(), e.getMessage());
351
354
  return CommonResult.error(e.getCode(), e.getMessage());
352
355
  }
@@ -374,26 +377,35 @@ try {
374
377
 
375
378
  ---
376
379
 
377
- ## 错误码分配表
380
+ ## 错误码规范
378
381
 
379
- | 模块 | 前缀 | 错误码范围 | 示例 |
380
- |------|------|-----------|------|
381
- | 用户模块 | USER_ | 1000-1999 | `USER_NOT_FOUND`=1001, `MOBILE_EXISTS`=1002 |
382
- | 权限模块 | AUTH_ | 2000-2999 | `AUTH_UNAUTHORIZED`=2001, `FORBIDDEN`=2003 |
383
- | 内容模块 | CONTENT_ | 3000-3999 | |
384
- | 订单模块 | ORDER_ | 4000-4999 | |
385
- | 支付模块 | PAYMENT_ | 5000-5999 | |
386
- | 文件模块 | FILE_ | 6000-6999 | |
387
- | 系统模块 | SYS_ | 9000-9999 | `SYSTEM_ERROR`=9999, `PARAM_INVALID`=9001 |
382
+ 统一响应中 `code` 字段采用 HTTP 状态码风格:
388
383
 
389
- ```java
390
- /** 错误码常量示例 */
391
- public interface ErrorCodeConstants {
384
+ | code | 含义 | 说明 |
385
+ |------|------|------|
386
+ | 200 | 成功 | 请求成功 |
387
+ | 400 | 参数错误 | 请求参数校验失败 |
388
+ | 401 | 未授权 | Token 无效或已过期 |
389
+ | 403 | 禁止访问 | 无权限访问该资源 |
390
+ | 404 | 资源不存在 | 请求的资源未找到 |
391
+ | 409 | 资源冲突 | 如用户名已存在 |
392
+ | 500 | 服务器内部错误 | 不可预期的系统异常 |
392
393
 
393
- ErrorCode USER_NOT_FOUND = new ErrorCode(1001, "用户不存在");
394
- ErrorCode MOBILE_EXISTS = new ErrorCode(1002, "手机号已被注册");
395
- ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(2001, "登录已过期");
396
- ErrorCode SYSTEM_ERROR = new ErrorCode(9999, "系统繁忙,请稍后重试");
394
+ **业务细粒度错误码**:在 HTTP 状态码基础上,通过 `message` 字段和业务异常枚举(ErrorCode)提供更细粒度的错误描述。
395
+
396
+ ```java
397
+ /** 错误码枚举示例 */
398
+ public enum ErrorCode {
399
+ USER_NOT_FOUND(404, "用户不存在"),
400
+ USER_PASSWORD_ERROR(400, "密码错误"),
401
+ USER_ALREADY_EXISTS(409, "用户名已存在"),
402
+ TOKEN_EXPIRED(401, "Token 已过期"),
403
+ SYSTEM_ERROR(500, "系统繁忙,请稍后重试"),
404
+ // ... 按业务需求扩展
405
+ ;
406
+
407
+ private final int code;
408
+ private final String message;
397
409
  }
398
410
  ```
399
411
 
@@ -436,7 +448,7 @@ public final class RedisKeyConstants {
436
448
  | 禁止外键 | 不在数据库层面创建外键 | 应用层维护引用完整性 |
437
449
  | 时间字段 | `datetime` 类型(对应 LocalDateTime) | 禁止 timestamp(时区问题)|
438
450
  | 主键 | bigint unsigned 自增或雪花算法 | 分布式环境推荐雪花 |
439
- | 软删除 | 必须有 deleted 字段(tinyint/boolean) | 禁物理删除业务数据 |
451
+ | 软删除 | 必须有 deleted 字段(Integer: 0/1) | 禁物理删除业务数据 |
440
452
  | 审计字段 | 必须有 create_time / update_time | 由框架自动填充 |
441
453
  | 表结构变更 | 通过 ALTER DDL 脚本执行 | 禁止删重建表 |
442
454
 
@@ -448,7 +460,7 @@ public final class RedisKeyConstants {
448
460
  # 本地 bootstrap.yml — 仅保留最小必要配置
449
461
  spring:
450
462
  application:
451
- name: jscicd-app-server
463
+ name: jieshun-app-server
452
464
  cloud:
453
465
  nacos:
454
466
  discovery:
@@ -460,7 +472,7 @@ spring:
460
472
  # Nacos 远程配置 key 规范(camelCase):
461
473
  # - spring.datasource.url
462
474
  # - spring.redis.host
463
- # - jscicd.security.sm4-secret-key
475
+ # - jieshun.security.sm4-secret-key
464
476
  # - logging.level.com.jieshun.domain
465
477
  ```
466
478
 
@@ -1,192 +1,156 @@
1
- # 后端分区 — 编码惯例
1
+ # 后端分区 — Java 编码惯例
2
2
 
3
- ## TypeScript 规范
3
+ > **来源**: `files/java-backend-coding-standards/SKILL.md` (已重构为 jieshun 项目后端 Java 规范)
4
+ > **归档日期**: 2026-05-21
5
+ > **技术栈**: Spring Boot 3.2 / JDK 21 (虚拟线程) / MySQL 8.0 / 达梦 DM8 / Redis 5.x / MyBatis-Plus
6
+
7
+ ## Java 编码规范
4
8
 
5
9
  ### 严格模式
6
10
 
7
- ```jsonc
8
- // tsconfig.json
9
- {
10
- "compilerOptions": {
11
- "strict": true,
12
- "noImplicitAny": true,
13
- "strictNullChecks": true,
14
- "strictFunctionTypes": true,
15
- "noUncheckedIndexedAccess": true,
16
- "exactOptionalPropertyTypes": true
17
- }
18
- }
11
+ ```java
12
+ // ✅ 所有公开类/接口必须有 JavaDoc
13
+ // ✅ 必须使用 Lombok @Data/@RequiredArgsConstructor 等简化代码
14
+ // ✅ 禁止裸 any/Object 等模糊类型
19
15
  ```
20
16
 
21
- ### 类型定义优先级
17
+ ### 命名约定
22
18
 
23
19
  ```
24
- Interface > Type Alias > Class(纯数据类型场景)
25
- Class > Interface(需要实例化的场景)
20
+ 类命名: PascalCase + 标准后缀
21
+ Entity: UserEntity # 数据库表映射
22
+ DTO: UserDTO # 跨层数据传输
23
+ VO: UserVO # 视图对象(通用)
24
+ ReqVO: UserCreateReqVO # 请求对象(JSR303 校验)
25
+ RespVO: UserRespVO # 响应对象(脱敏后返回前端)
26
+ Enum: UserStatusEnum # 状态码/类型码
27
+ Service: UserService # 业务接口定义
28
+ ServiceImpl: UserServiceImpl # 业务实现类
29
+ Controller: UserController # REST API 入口
30
+ Mapper: UserMapper # 数据库操作接口
31
+
32
+ 方法命名:
33
+ selectPage / selectById / insert / updateById / deleteById # Mapper 方法
34
+ createUser / getUserPage / getUser # Service 方法
35
+ create / page / get / update / delete # Controller 方法
36
+
37
+ 变量命名:
38
+ camelCase: userId, isLoading, mobile
39
+ 常量: UPPER_SNAKE_CASE: MAX_RETRY_COUNT, API_BASE_URL
40
+ 布尔变量: is/has/can 前缀: isActive, hasPermission
26
41
  ```
27
42
 
28
- ```typescript
29
- // ✅ 推荐:DTO 使用 Interface
30
- interface CreateUserDTO {
31
- email: string;
32
- password: string;
33
- name: string;
34
- role?: Role; // 可选属性
35
- }
36
-
37
- // 推荐:API 响应使用 Interface
38
- interface ApiResponse<T> {
39
- code: number;
40
- data: T;
41
- message: string;
42
- timestamp: number;
43
- }
44
-
45
- // ⚠️ 可接受:联合类型使用 Type
46
- type UserRole = 'admin' | 'editor' | 'viewer' | 'user';
47
- type ID = string; // UUID alias
48
- ```
43
+ ### 代码风格 — Controller/Service/Mapper 分层
44
+
45
+ ```java
46
+ // ✅ 推荐:清晰的 Controller 注解堆叠
47
+ @RestController
48
+ @RequestMapping("/api/v1/users")
49
+ @RequiredArgsConstructor
50
+ @Tag(name = "用户管理")
51
+ public class UserController {
52
+ private final UserService userService;
53
+
54
+ @PostMapping
55
+ @Operation(summary = "创建用户")
56
+ public CommonResult<Long> create(@Valid @RequestBody UserCreateReqVO reqVO) {
57
+ return success(userService.createUser(reqVO));
58
+ }
49
59
 
50
- ## 代码风格
51
-
52
- ### 类/装饰器风格(NestJS)
53
-
54
- ```typescript
55
- // ✅ 推荐:清晰的装饰器堆叠
56
- @Controller('api/v1/users')
57
- @UseGuards(JwtAuthGuard)
58
- @ApiTags('users')
59
- export class UserController {
60
- constructor(private readonly userService: UserService) {}
61
-
62
- @Get()
63
- @ApiOperation({ summary: '获取用户列表' })
64
- @ApiResponse({ status: 200, type: [User] })
65
- async findAll(@Query() query: QueryUserDTO): Promise<PaginatedResult<User>> {
66
- return this.userService.findAll(query);
67
- }
68
-
69
- @Get(':id')
70
- @ApiParam({ name: 'id' })
71
- async findOne(@Param('id') id: string): Promise<User> {
72
- return this.userService.findById(id);
73
- }
60
+ @GetMapping
61
+ @Operation(summary = "分页查询用户")
62
+ public CommonResult<PageResult<UserRespVO>> page(@Valid UserPageReqVO reqVO) {
63
+ return success(userService.getUserPage(reqVO));
64
+ }
74
65
  }
75
66
  ```
76
67
 
77
68
  ### 错误处理规范
78
69
 
79
- ```typescript
80
- // ✅ 统一抛出 NestJS 内置异常
81
- throw new BadRequestException('邮箱格式无效');
82
- throw new NotFoundException(`用户 ${id} 不存在`);
83
- throw new ForbiddenException('没有权限执行此操作');
84
- throw new ConflictException('该邮箱已被注册');
85
- throw new InternalServerErrorException('操作失败,请重试');
86
-
87
- // ✅ 自定义业务异常
88
- export class BusinessException extends HttpException {
89
- constructor(code: string, message: string, statusCode = 400) {
90
- super({ code, message }, statusCode);
91
- }
92
- }
93
- // throw new BusinessException('USER.NOT_FOUND', '用户不存在');
94
- ```
95
-
96
- ### 异步编程规范
97
-
98
- ```typescript
99
- // ✅ 全部使用 async/await
100
- async function getUserWithOrders(userId: string): Promise<UserWithOrders> {
101
- const user = await this.userRepo.findById(userId);
102
- if (!user) throw new NotFoundException('User not found');
103
-
104
- const orders = await this.orderRepo.findByUserId(userId);
105
-
106
- return { ...user, orders };
70
+ ```java
71
+ // ✅ 统一使用项目自定义业务异常
72
+ throw new BusinessException(ErrorCode.USER_NOT_FOUND);
73
+ throw new BusinessException(ErrorCode.USER_PASSWORD_ERROR);
74
+ throw new BusinessException(ErrorCode.USER_ALREADY_EXISTS);
75
+
76
+ // 系统异常使用 ServiceException(隐藏内部细节)
77
+ try {
78
+ // 外部调用
79
+ } catch (ExternalServiceException e) {
80
+ log.error("[callExternal][params({})] error", params, e);
81
+ throw new ServiceException(ErrorCode.SYSTEM_ERROR);
107
82
  }
108
83
 
109
- // 并行请求使用 Promise.all
110
- async function getDashboardData(userId: string) {
111
- const [profile, notifications, stats] = await Promise.all([
112
- this.userService.getProfile(userId),
113
- this.notificationService.getUnread(userId),
114
- this.statsService.getUserStats(userId),
115
- ]);
116
-
117
- return { profile, notifications, stats };
118
- }
84
+ // 禁止直接 throw RuntimeException
85
+ // 禁止返回错误码数字 return Result.fail(1001);
119
86
  ```
120
87
 
121
- ## 日志规范
122
-
123
- ```typescript
124
- // 使用 Logger (NestJS 内置)
125
- @Injectable()
126
- export class UserService {
127
- private readonly logger = new Logger(UserService.name);
128
-
129
- async createUser(dto: CreateUserDTO): Promise<User> {
130
- this.logger.log(`Creating user with email: ${dto.email}`);
131
- try {
132
- const user = await this.userRepo.create(dto);
133
- this.logger.debug(`User created: ${user.id}`);
134
- return user;
135
- } catch (error) {
136
- this.logger.error(`Failed to create user: ${error.message}`, error.stack);
137
- throw error;
88
+ ### 异常分层体系
89
+
90
+ | 异常类 | 用途 | 全局处理器行为 |
91
+ |--------|------|---------------|
92
+ | `BusinessException` | 业务规则违反(可恢复) | 直接透传错误码和消息给前端 |
93
+ | `ServiceException` | 系统服务异常(不可恢复) | 记录日志 + 返回通用错误信息,隐藏内部细节 |
94
+ | 未预期 `Exception` | 不可预期的系统异常 | 记录完整堆栈 + 返回安全错误信息 |
95
+
96
+ ### 日志规范(SLF4J + Logback)
97
+
98
+ ```java
99
+ @Service
100
+ @Slf4j
101
+ public class UserServiceImpl implements UserService {
102
+
103
+ @Override
104
+ public Long createUser(UserCreateReqVO reqVO) {
105
+ log.info("[createUser][reqVO({})]", reqVO);
106
+ try {
107
+ Long userId = userMapper.insert(entity);
108
+ log.debug("[createUser][userId({})]", userId);
109
+ return userId;
110
+ } catch (DuplicateKeyException e) {
111
+ log.warn("[createUser][mobile({})][reason({})]", reqVO.getMobile(), "重复注册");
112
+ throw new BusinessException(ErrorCode.USER_ALREADY_EXISTS);
113
+ }
138
114
  }
139
- }
140
115
  }
141
116
 
142
117
  // 日志级别使用规范:
143
- // logger.debug() — 详细调试信息(仅开发环境输出)
144
- // logger.log() — 一般业务日志
145
- // logger.warn() — 需要注意但不致命的情况
146
- // logger.error() 错误信息(必须包含 stack trace
147
- // logger.verbose() — 更多细节(通常不用)
118
+ // log.debug() — 详细调试信息(仅开发环境输出)
119
+ // log.info() — 一般业务日志
120
+ // log.warn() — 需要注意但不致命的情况
121
+ // log.error() 错误信息(必须保留异常对象 e
148
122
 
149
123
  // ⚠️ 禁止:日志中出现敏感信息
150
- // ❌ logger.info(`Login success: ${password}`); // 绝不记录密码
151
- // ✅ logger.info(`Login success: userId=${user.id}`); // 只记录必要标识
124
+ // ❌ log.info("Login success: password={}", password); // 绝不记录密码
125
+ // ✅ log.info("Login success: userId={}", userId); // 只记录必要标识
126
+ // ❌ log.info("创建用户: " + reqVO.toString()); // 禁止字符串拼接
127
+ // ✅ log.info("[createUser][reqVO({})]", reqVO); // 使用 {} 参数化
152
128
  ```
153
129
 
154
- ## 环境变量使用
155
-
156
- ```typescript
157
- // config/app.config.ts
158
- @register()
159
- export class AppConfig {
160
- @IsString()
161
- @IsUrl()
162
- APP_URL: string;
163
-
164
- @IsInt()
165
- @Min(1)
166
- PORT: number;
167
-
168
- @IsEnum(['development', 'staging', 'production'])
169
- NODE_ENV: string;
170
-
171
- @IsString()
172
- JWT_SECRET: string; // 必填
173
- @IsString()
174
- JWT_EXPIRES_IN: string = '15m';
175
-
176
- DATABASE_URL: string; // 必填
177
- REDIS_URL: string; // 必填
178
- }
179
-
180
- // 使用
181
- @Injectable()
182
- export class MyService {
183
- constructor(@Inject(AppConfig) private config: AppConfig) {}
184
-
185
- getJwtConfig() {
186
- return {
187
- secret: this.config.JWT_SECRET,
188
- expiresIn: this.config.JWT_EXPIRES_IN,
189
- };
190
- }
191
- }
130
+ ### 配置规范(Nacos 3.0)
131
+
132
+ ```yaml
133
+ # 本地 bootstrap.yml — 仅保留最小必要配置
134
+ spring:
135
+ application:
136
+ name: jieshun-app-server
137
+ cloud:
138
+ nacos:
139
+ discovery:
140
+ server-addr: ${NACOS_ADDR:localhost:8848}
141
+ config:
142
+ server-addr: ${NACOS_ADDR:localhost:8848}
143
+ file-extension: yaml
144
+
145
+ # Nacos 远程配置 key 规范(camelCase):
146
+ # - spring.datasource.url
147
+ # - spring.redis.host
148
+ # - jieshun.security.sm4-secret-key
149
+ # - logging.level.com.jieshun.domain
192
150
  ```
151
+
152
+ **配置规范**:
153
+ - 本地只保留 Nacos 连接地址
154
+ - 其他所有配置从 Nacos 拉取
155
+ - Key 使用 camelCase 格式
156
+ - 禁止使用 shared-configs 共享过多公共配置