jsharness 1.4.1 → 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.
- package/.harness/agents/developer/contract.yaml +1 -1
- package/.harness/agents/prompt-templates.md +28 -4
- package/.harness/agents/solution-designer/contract.yaml +84 -28
- package/.harness/dev-map/backend/api-definition.md +10 -12
- package/.harness/dev-map/backend/auth-security.md +53 -28
- package/.harness/dev-map/backend/conventions-java.md +72 -60
- package/.harness/dev-map/backend/conventions.md +127 -163
- package/.harness/dev-map/backend/structure.md +142 -92
- package/.harness/dev-map/decisions.md +1 -1
- package/.harness/dev-map/frontend/structure.md +1 -1
- package/.harness/dev-map/overview.md +10 -9
- package/.harness/gate/checks/static-compliance.js +2 -2
- package/.harness/gate/checks/test-compliance.js +4 -4
- package/.harness/rules/global/coding-standard.md +15 -8
- package/.harness/rules/global/commit-convention.md +7 -0
- package/.harness/rules/global/design-document-boundary.md +115 -0
- package/.harness/rules/global/process-discipline.md +7 -0
- package/.harness/rules/global/security-baseline.md +27 -23
- package/.harness/rules/project/frontend-vue3.md +9 -0
- package/.harness/rules/project/java-backend.md +129 -22
- package/.harness/rules/project/web-specific.md +27 -14
- package/.harness/skills/architecture-designer/SKILL.md +768 -0
- package/.harness/skills/{build.md → build/SKILL.md} +7 -0
- package/.harness/skills/{code-review.md → code-review/SKILL.md} +7 -0
- package/.harness/skills/{docker-build.md → docker-build/SKILL.md} +7 -0
- package/.harness/skills/{docs-update.md → docs-update/SKILL.md} +7 -0
- package/.harness/skills/{java-build.md → java-build/SKILL.md} +12 -2
- package/.harness/skills/{lint-check.md → lint-check/SKILL.md} +7 -0
- package/.harness/skills/{task-board-maintenance.md → task-board-maintenance/SKILL.md} +7 -0
- package/.harness/skills/{test-api.md → test-api/SKILL.md} +21 -14
- package/.harness/skills/{test-e2e.md → test-e2e/SKILL.md} +7 -0
- package/.harness/skills/{test-unit.md → test-unit/SKILL.md} +50 -44
- package/.harness/skills/{vue-frontend-build.md → vue-frontend-build/SKILL.md} +11 -0
- package/.harness/workflow/definition.yaml +37 -3
- package/bin/jsharness.js +1 -0
- package/lib/index.mjs +428 -31
- package/package.json +2 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# 后端分区 — Java 编码惯例 (conventions-java)
|
|
2
2
|
|
|
3
|
-
> **来源**: `files/java-backend-coding-standards/SKILL.md` (
|
|
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 |
|
|
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
|
-
|
|
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
|
-
│ ├──
|
|
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
|
-
│ │ │ └──
|
|
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
|
-
│ └──
|
|
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
|
|
102
|
+
* @author jieshun-backend-team
|
|
101
103
|
*/
|
|
102
104
|
@RestController
|
|
103
|
-
@RequestMapping("/api/v1/
|
|
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
|
|
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
|
-
@
|
|
118
|
+
@PutMapping("/{id}")
|
|
117
119
|
@Operation(summary = "更新用户")
|
|
118
|
-
public CommonResult<Boolean> update(@
|
|
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
|
-
@
|
|
126
|
+
@DeleteMapping("/{id}")
|
|
124
127
|
@Operation(summary = "删除用户")
|
|
125
|
-
public CommonResult<Boolean> delete(@
|
|
128
|
+
public CommonResult<Boolean> delete(@PathVariable("id") Long id) {
|
|
126
129
|
userService.deleteUser(id);
|
|
127
130
|
return success(true);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
@
|
|
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("/
|
|
139
|
+
@GetMapping("/{id}")
|
|
137
140
|
@Operation(summary = "获取用户详情")
|
|
138
|
-
public CommonResult<UserRespVO> get(@
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
217
|
+
// 分页参数设置(MyBatis-Plus 分页拦截器自动追加 LIMIT)
|
|
218
|
+
Page<UserEntity> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
|
|
216
219
|
|
|
217
220
|
// 执行查询
|
|
218
|
-
|
|
221
|
+
Page<UserEntity> result = userMapper.selectPage(page, reqVO);
|
|
219
222
|
|
|
220
223
|
// 转换返回
|
|
221
|
-
return new PageResult<>(UserConvert.INSTANCE.convertList(
|
|
222
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
void
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
349
|
-
public CommonResult<?> handleBizException(
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
384
|
+
| code | 含义 | 说明 |
|
|
385
|
+
|------|------|------|
|
|
386
|
+
| 200 | 成功 | 请求成功 |
|
|
387
|
+
| 400 | 参数错误 | 请求参数校验失败 |
|
|
388
|
+
| 401 | 未授权 | Token 无效或已过期 |
|
|
389
|
+
| 403 | 禁止访问 | 无权限访问该资源 |
|
|
390
|
+
| 404 | 资源不存在 | 请求的资源未找到 |
|
|
391
|
+
| 409 | 资源冲突 | 如用户名已存在 |
|
|
392
|
+
| 500 | 服务器内部错误 | 不可预期的系统异常 |
|
|
392
393
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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 字段(
|
|
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:
|
|
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
|
-
# -
|
|
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
|
-
|
|
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
|
-
```
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
```
|
|
80
|
-
// ✅
|
|
81
|
-
throw new
|
|
82
|
-
throw new
|
|
83
|
-
throw new
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
//
|
|
110
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// logger.verbose() — 更多细节(通常不用)
|
|
118
|
+
// log.debug() — 详细调试信息(仅开发环境输出)
|
|
119
|
+
// log.info() — 一般业务日志
|
|
120
|
+
// log.warn() — 需要注意但不致命的情况
|
|
121
|
+
// log.error() — 错误信息(必须保留异常对象 e)
|
|
148
122
|
|
|
149
123
|
// ⚠️ 禁止:日志中出现敏感信息
|
|
150
|
-
// ❌
|
|
151
|
-
// ✅
|
|
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
|
-
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 共享过多公共配置
|