jsharness 1.0.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/README.md +199 -0
- package/.harness/agents/code-reviewer/contract.yaml +64 -0
- package/.harness/agents/developer/contract.yaml +72 -0
- package/.harness/agents/gate-controller/contract.yaml +64 -0
- package/.harness/agents/project-manager/contract.yaml +77 -0
- package/.harness/agents/prompt-templates.md +352 -0
- package/.harness/agents/requirements-analyst/contract.yaml +64 -0
- package/.harness/agents/solution-designer/contract.yaml +75 -0
- package/.harness/agents/tester/contract.yaml +92 -0
- package/.harness/config/models.yaml +67 -0
- package/.harness/dev-map/backend/api-definition.md +131 -0
- package/.harness/dev-map/backend/auth-security.md +131 -0
- package/.harness/dev-map/backend/conventions-java.md +471 -0
- package/.harness/dev-map/backend/conventions.md +192 -0
- package/.harness/dev-map/backend/database.md +106 -0
- package/.harness/dev-map/backend/structure.md +140 -0
- package/.harness/dev-map/decisions.md +275 -0
- package/.harness/dev-map/frontend/api-integration.md +139 -0
- package/.harness/dev-map/frontend/components.md +178 -0
- package/.harness/dev-map/frontend/conventions.md +416 -0
- package/.harness/dev-map/frontend/state-management.md +170 -0
- package/.harness/dev-map/frontend/structure.md +103 -0
- package/.harness/dev-map/overview.md +267 -0
- package/.harness/docs/integration-test-plan.md +248 -0
- package/.harness/docs/team-guidelines/README.md +161 -0
- package/.harness/docs/team-guidelines/arch-team.md +811 -0
- package/.harness/docs/team-guidelines/collaboration.md +556 -0
- package/.harness/docs/team-guidelines/pm-team.md +337 -0
- package/.harness/docs/team-guidelines/qa-team.md +562 -0
- package/.harness/docs/team-guidelines/rd-team.md +714 -0
- package/.harness/docs/training-materials.md +280 -0
- package/.harness/gate/baseline.js +220 -0
- package/.harness/gate/checks/build-gates-frontend.js +152 -0
- package/.harness/gate/checks/build-gates-java.js +155 -0
- package/.harness/gate/checks/build-gates.js +119 -0
- package/.harness/gate/checks/engineering-consistency.js +138 -0
- package/.harness/gate/checks/security-quality.js +129 -0
- package/.harness/gate/checks/static-compliance.js +313 -0
- package/.harness/gate/checks/test-compliance.js +114 -0
- package/.harness/gate/index.js +315 -0
- package/.harness/mcp/config.yaml +435 -0
- package/.harness/rules/global/coding-standard.md +232 -0
- package/.harness/rules/global/commit-convention.md +165 -0
- package/.harness/rules/global/process-discipline.md +192 -0
- package/.harness/rules/global/security-baseline.md +306 -0
- package/.harness/rules/project/frontend-vue3.md +293 -0
- package/.harness/rules/project/java-backend.md +460 -0
- package/.harness/rules/project/web-specific.md +231 -0
- package/.harness/skills/build.md +192 -0
- package/.harness/skills/code-review.md +251 -0
- package/.harness/skills/docker-build.md +227 -0
- package/.harness/skills/docs-update.md +164 -0
- package/.harness/skills/java-build.md +261 -0
- package/.harness/skills/lint-check.md +482 -0
- package/.harness/skills/task-board-maintenance.md +105 -0
- package/.harness/skills/test-api.md +461 -0
- package/.harness/skills/test-e2e.md +431 -0
- package/.harness/skills/test-unit.md +649 -0
- package/.harness/skills/vue-frontend-build.md +344 -0
- package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
- package/.harness/task-board.md +121 -0
- package/.harness/workflow/definition.yaml +504 -0
- package/.harness/workflow/validate.js +320 -0
- package/.harness/workflow/variants.yaml +253 -0
- package/README.md +237 -0
- package/bin/jsharness.js +53 -0
- package/lib/index.mjs +778 -0
- package/package.json +1 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# 后端分区 — Java 编码惯例 (conventions-java)
|
|
2
|
+
|
|
3
|
+
> **来源**: `files/java-backend-coding-standards/SKILL.md` (JSCICD 项目后端 Java 规范)
|
|
4
|
+
> **归档日期**: 2026-05-21
|
|
5
|
+
> **技术栈**: Spring Boot 3.2.12 / JDK 21 (虚拟线程) / MySQL 8.0 / Redis 5.x / MyBatis-Plus
|
|
6
|
+
|
|
7
|
+
## 技术栈版本速查
|
|
8
|
+
|
|
9
|
+
| 组件 | 推荐版本 | 说明 |
|
|
10
|
+
|------|---------|------|
|
|
11
|
+
| Spring Boot | 3.2.x | 主框架 |
|
|
12
|
+
| JDK | 21 | 虚拟线程支持 |
|
|
13
|
+
| MySQL | 8.0+ | 数据库 |
|
|
14
|
+
| Redis | 5.x / 6.x | 缓存 |
|
|
15
|
+
| MyBatis-Plus | 3.5.x | ORM 框架 |
|
|
16
|
+
| Lombok | 1.18.x | 注解简化 |
|
|
17
|
+
| Redisson | 3.x | 分布式锁/Redis 客户端 |
|
|
18
|
+
| Hutool | 5.x | 工具库(按需使用)|
|
|
19
|
+
| Knife4j/Swagger | OpenAPI 3 | API 文档 |
|
|
20
|
+
| Nacos | 2.x | 配置中心 + 注册中心 |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Maven 四层模块详细结构
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
jscicd-project/
|
|
28
|
+
├── pom.xml # 父 POM(统一依赖版本管理)
|
|
29
|
+
├── app/ # 启动入口模块
|
|
30
|
+
│ ├── pom.xml
|
|
31
|
+
│ └── src/main/java/com/jieshun/
|
|
32
|
+
│ └── app/
|
|
33
|
+
│ ├── JscicdApplication.java # @SpringBootApplication
|
|
34
|
+
│ └── config/ # 启动配置类
|
|
35
|
+
│ └── WebMvcConfig.java
|
|
36
|
+
│
|
|
37
|
+
├── domain/ # 业务核心模块
|
|
38
|
+
│ ├── pom.xml
|
|
39
|
+
│ └── src/main/java/com/jieshun/
|
|
40
|
+
│ └── domain/
|
|
41
|
+
│ ├── controller/ # Controller 层
|
|
42
|
+
│ │ ├── admin/ # 后台管理 Controller
|
|
43
|
+
│ │ │ └── UserController.java
|
|
44
|
+
│ │ └── app/ # 用户侧 Controller
|
|
45
|
+
│ │ └── AppUserController.java
|
|
46
|
+
│ ├── service/ # Service 层
|
|
47
|
+
│ │ ├── UserService.java # 接口定义
|
|
48
|
+
│ │ └── impl/ # 实现类子包
|
|
49
|
+
│ │ └── UserServiceImpl.java
|
|
50
|
+
│ ├── mapper/ # Mapper 层
|
|
51
|
+
│ │ ├── UserMapper.java # Mapper 接口
|
|
52
|
+
│ │ └── xml/ # XML 映射文件目录
|
|
53
|
+
│ │ └── UserMapper.xml
|
|
54
|
+
│ ├── data/ # 数据对象层
|
|
55
|
+
│ │ ├── entity/ # 数据库实体
|
|
56
|
+
│ │ │ └── UserDO.java
|
|
57
|
+
│ │ ├── vo/ # 视图对象
|
|
58
|
+
│ │ │ ├── UserVO.java
|
|
59
|
+
│ │ │ ├── UserRespVO.java # 响应对象
|
|
60
|
+
│ │ │ └── UserCreateReqVO.java # 请求对象
|
|
61
|
+
│ │ └── dto/ # 数据传输对象
|
|
62
|
+
│ │ └── UserDTO.java
|
|
63
|
+
│ └── enums/ # 枚举定义
|
|
64
|
+
│ └── UserStatusEnum.java
|
|
65
|
+
│
|
|
66
|
+
├── integration/ # 第三方集成模块
|
|
67
|
+
│ ├── pom.xml
|
|
68
|
+
│ └── src/main/java/com/jieshun/integration/
|
|
69
|
+
│ ├── client/ # 外部 API 客户端
|
|
70
|
+
│ │ └── sms/SmsClient.java # 短信服务客户端
|
|
71
|
+
│ └── mq/ # 消息队列
|
|
72
|
+
│ └── producer/MqProducer.java
|
|
73
|
+
│
|
|
74
|
+
└── common/ # 公共工具模块
|
|
75
|
+
├── pom.xml
|
|
76
|
+
└── src/main/java/com/jieshun/common/
|
|
77
|
+
├── constant/ # 常量
|
|
78
|
+
│ ├── ErrorCodeConstants.java # 错误码枚举
|
|
79
|
+
│ └── RedisKeyConstants.java # Redis Key 常量
|
|
80
|
+
├── exception/ # 异常定义
|
|
81
|
+
│ ├── ServiceException.java
|
|
82
|
+
│ └── JscicdBizException.java # 业务异常基类
|
|
83
|
+
├── util/ # 工具类
|
|
84
|
+
│ └── ObjectUtils.java
|
|
85
|
+
└── enums/ # 通用枚举
|
|
86
|
+
└── CommonStatusEnum.java
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 标准代码模板
|
|
92
|
+
|
|
93
|
+
### Controller 标准模板
|
|
94
|
+
|
|
95
|
+
```java
|
|
96
|
+
/**
|
|
97
|
+
* 用户管理 Controller
|
|
98
|
+
*
|
|
99
|
+
* @description 提供用户 CRUD 和分页查询接口
|
|
100
|
+
* @author jscicd-backend-team
|
|
101
|
+
*/
|
|
102
|
+
@RestController
|
|
103
|
+
@RequestMapping("/api/v1/user")
|
|
104
|
+
@RequiredArgsConstructor
|
|
105
|
+
@Tag(name = "用户管理")
|
|
106
|
+
public class UserController {
|
|
107
|
+
|
|
108
|
+
private final UserService userService;
|
|
109
|
+
|
|
110
|
+
@PostMapping("/create")
|
|
111
|
+
@Operation(summary = "创建用户")
|
|
112
|
+
public CommonResult<Long> create(@Valid @RequestBody UserCreateReqVO reqVO) {
|
|
113
|
+
return success(userService.createUser(reqVO));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@PostMapping("/update")
|
|
117
|
+
@Operation(summary = "更新用户")
|
|
118
|
+
public CommonResult<Boolean> update(@Valid @RequestBody UserUpdateReqVO reqVO) {
|
|
119
|
+
userService.updateUser(reqVO);
|
|
120
|
+
return success(true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@PostMapping("/delete")
|
|
124
|
+
@Operation(summary = "删除用户")
|
|
125
|
+
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
|
126
|
+
userService.deleteUser(id);
|
|
127
|
+
return success(true);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@PostMapping("/page")
|
|
131
|
+
@Operation(summary = "分页查询用户")
|
|
132
|
+
public CommonResult<PageResult<UserRespVO>> page(@Valid UserPageReqVO reqVO) {
|
|
133
|
+
return success(userService.getUserPage(reqVO));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@GetMapping("/get")
|
|
137
|
+
@Operation(summary = "获取用户详情")
|
|
138
|
+
public CommonResult<UserRespVO> get(@RequestParam("id") Long id) {
|
|
139
|
+
return success(userService.getUser(id));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Controller 要点**:
|
|
145
|
+
- `@RestController` + `@RequestMapping` 定义路由前缀
|
|
146
|
+
- `@RequiredArgsConstructor` 构造器注入
|
|
147
|
+
- HTTP 方法统一用 `@PostMapping`(RESTful 风格但 POST 优先)
|
|
148
|
+
- 参数校验使用 JSR303:`@Valid` / `@Validated`
|
|
149
|
+
- 返回值统一包装为 `CommonResult<T>`
|
|
150
|
+
- Swagger/OpenAPI 注解:`@Tag`, `@Operation`
|
|
151
|
+
|
|
152
|
+
### Service 接口 + Impl 标准模板
|
|
153
|
+
|
|
154
|
+
```java
|
|
155
|
+
// ========== 接口 ==========
|
|
156
|
+
public interface UserService {
|
|
157
|
+
/**
|
|
158
|
+
* 创建用户
|
|
159
|
+
*
|
|
160
|
+
* @param reqVO 创建请求参数
|
|
161
|
+
* @return 用户 ID
|
|
162
|
+
*/
|
|
163
|
+
Long createUser(UserCreateReqVO reqVO);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 更新用户信息
|
|
167
|
+
*/
|
|
168
|
+
void updateUser(UserUpdateReqVO reqVO);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 删除用户
|
|
172
|
+
*/
|
|
173
|
+
void deleteUser(Long id);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 分页查询用户
|
|
177
|
+
*/
|
|
178
|
+
PageResult<UserRespVO> getUserPage(UserPageReqVO reqVO);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 获取用户详情
|
|
182
|
+
*/
|
|
183
|
+
UserRespVO getUser(Long id);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ========== 实现类 ==========
|
|
187
|
+
@Service
|
|
188
|
+
@RequiredArgsConstructor
|
|
189
|
+
@Slf4j
|
|
190
|
+
public class UserServiceImpl implements UserService {
|
|
191
|
+
|
|
192
|
+
private final UserMapper userMapper;
|
|
193
|
+
private final RedisKeyConstants redisKeys;
|
|
194
|
+
|
|
195
|
+
@Override
|
|
196
|
+
@Transactional(rollbackFor = Exception.class)
|
|
197
|
+
public Long createUser(UserCreateReqVO reqVO) {
|
|
198
|
+
// 1. 参数校验与转换
|
|
199
|
+
// TODO: 校验手机号唯一性
|
|
200
|
+
UserDO entity = UserConvert.INSTANCE.convert(reqVO);
|
|
201
|
+
|
|
202
|
+
// 2. 业务逻辑编排
|
|
203
|
+
// (如:发送欢迎短信 — 在事务外调用)
|
|
204
|
+
|
|
205
|
+
// 3. 数据持久化
|
|
206
|
+
userMapper.insert(entity);
|
|
207
|
+
|
|
208
|
+
// 4. 返回结果
|
|
209
|
+
return entity.getId();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@Override
|
|
213
|
+
public PageResult<UserRespVO> getUserPage(UserPageReqVO reqVO) {
|
|
214
|
+
// 分页参数设置
|
|
215
|
+
PageHelper.startPage(reqVO.getPageNo(), reqVO.getPageSize());
|
|
216
|
+
|
|
217
|
+
// 执行查询
|
|
218
|
+
List<UserDO> list = userMapper.selectList(reqVO);
|
|
219
|
+
|
|
220
|
+
// 转换返回
|
|
221
|
+
return new PageResult<>(UserConvert.INSTANCE.convertList(list),
|
|
222
|
+
new PageInfo<>(list).getTotal());
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Service 要点**:
|
|
228
|
+
- 接口和实现分离,实现放在 `service/impl/` 子包
|
|
229
|
+
- 使用 `@RequiredArgsConstructor` + Lombok 构造注入
|
|
230
|
+
- `@Slf4j` 日志注解
|
|
231
|
+
- 事务方法加 `@Transactional(rollbackFor = Exception.class)`
|
|
232
|
+
- 外部调用(HTTP/消息)放事务外
|
|
233
|
+
|
|
234
|
+
### Mapper + XML 标准模板
|
|
235
|
+
|
|
236
|
+
```java
|
|
237
|
+
// Mapper 接口
|
|
238
|
+
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);
|
|
243
|
+
void deleteById(@Param("id") Long id);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```xml
|
|
248
|
+
<!-- XML 映射文件: resources/mapper/user/UserMapper.xml -->
|
|
249
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
250
|
+
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
251
|
+
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
252
|
+
<mapper namespace="com.jieshun.domain.mapper.UserMapper">
|
|
253
|
+
|
|
254
|
+
<sql id="selectFields">
|
|
255
|
+
id, username, nickname, mobile, email, status,
|
|
256
|
+
create_time, update_time, deleted
|
|
257
|
+
</sql>
|
|
258
|
+
|
|
259
|
+
<select id="selectList" resultType="com.jieshun.domain.data.entity.UserDO">
|
|
260
|
+
SELECT <include refid="selectFields"/>
|
|
261
|
+
FROM system_users
|
|
262
|
+
<where>
|
|
263
|
+
<if test="reqVO.username != null and reqVO.username != ''">
|
|
264
|
+
AND username LIKE CONCAT('%', #{reqVO.username}, '%')
|
|
265
|
+
</if>
|
|
266
|
+
<if test="reqVO.status != null">
|
|
267
|
+
AND status = #{reqVO.status}
|
|
268
|
+
</if>
|
|
269
|
+
AND deleted = 0
|
|
270
|
+
</where>
|
|
271
|
+
ORDER BY create_time DESC
|
|
272
|
+
</select>
|
|
273
|
+
|
|
274
|
+
</mapper>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Mapper 要点**:
|
|
278
|
+
- 纯接口,不继承 BaseMapper
|
|
279
|
+
- SQL 全部写在 XML 中,使用 `<sql>` 复制公共字段
|
|
280
|
+
- 参数绑定必须用 `#{}`,禁止 `${}`
|
|
281
|
+
- 分页由拦截器自动处理,XML 中不写 LIMIT
|
|
282
|
+
|
|
283
|
+
### Entity/DTO/VO 定义模板
|
|
284
|
+
|
|
285
|
+
```java
|
|
286
|
+
// ========== Entity ==========
|
|
287
|
+
@Data
|
|
288
|
+
@TableName("system_users")
|
|
289
|
+
public class UserDO {
|
|
290
|
+
/** 用户 ID */
|
|
291
|
+
private Long id;
|
|
292
|
+
/** 用户名 */
|
|
293
|
+
private String username;
|
|
294
|
+
/** 昵称 */
|
|
295
|
+
private String nickname;
|
|
296
|
+
/** 手机号(SM4 加密存储)*/
|
|
297
|
+
private String mobile;
|
|
298
|
+
/** 邮箱 */
|
|
299
|
+
private String email;
|
|
300
|
+
/** 状态(0=正常 1=停用)*/
|
|
301
|
+
private Integer status;
|
|
302
|
+
/** 创建时间 */
|
|
303
|
+
private LocalDateTime createTime;
|
|
304
|
+
/** 更新时间 */
|
|
305
|
+
private LocalDateTime updateTime;
|
|
306
|
+
/** 是否删除 */
|
|
307
|
+
private Boolean deleted;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ========== ReqVO ==========
|
|
311
|
+
@Data
|
|
312
|
+
public class UserCreateReqVO {
|
|
313
|
+
@NotBlank(message = "用户名不能为空")
|
|
314
|
+
@Size(min = 2, max = 20, message = "用户名长度为 2-20 位")
|
|
315
|
+
private String username;
|
|
316
|
+
|
|
317
|
+
@NotBlank(message = "手机号不能为空")
|
|
318
|
+
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
|
319
|
+
private String mobile;
|
|
320
|
+
|
|
321
|
+
private String nickname;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ========== RespVO ==========
|
|
325
|
+
@Data
|
|
326
|
+
public class UserRespVO {
|
|
327
|
+
private Long id;
|
|
328
|
+
private String username;
|
|
329
|
+
private String nickname;
|
|
330
|
+
|
|
331
|
+
/** 手机号脱敏显示 */
|
|
332
|
+
private String mobile; // 格式: 138****1234
|
|
333
|
+
|
|
334
|
+
private Integer status;
|
|
335
|
+
private LocalDateTime createTime;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 异常处理标准模板
|
|
340
|
+
|
|
341
|
+
```java
|
|
342
|
+
// ========== 统一异常处理器 ==========
|
|
343
|
+
@RestControllerAdvice
|
|
344
|
+
@Slf4j
|
|
345
|
+
public class GlobalExceptionHandler {
|
|
346
|
+
|
|
347
|
+
/** 业务异常 — 直接透传错误信息 */
|
|
348
|
+
@ExceptionHandler(JscicdBizException.class)
|
|
349
|
+
public CommonResult<?> handleBizException(JscicdBizException e) {
|
|
350
|
+
log.warn("[handleBizException][code({})message({})]", e.getCode(), e.getMessage());
|
|
351
|
+
return CommonResult.error(e.getCode(), e.getMessage());
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 系统异常 — 记录日志 + 返回通用错误信息 */
|
|
355
|
+
@ExceptionHandler(Exception.class)
|
|
356
|
+
public CommonResult<?> handleException(Exception e) {
|
|
357
|
+
log.error("[handleException]", e);
|
|
358
|
+
return CommonResult.error(ErrorCode.SYSTEM_ERROR.getCode(),
|
|
359
|
+
ErrorCode SYSTEM_ERROR.getMessage());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ========== 业务异常抛出 ==========
|
|
364
|
+
try {
|
|
365
|
+
// 业务逻辑
|
|
366
|
+
} catch (SpecificBusinessException e) {
|
|
367
|
+
log.warn("[createUser]业务异常: context={}", context, e);
|
|
368
|
+
throw e; // 向上抛出,由全局处理器统一处理
|
|
369
|
+
} catch (Exception e) {
|
|
370
|
+
log.error("[createUser]系统异常: context={}", context, e);
|
|
371
|
+
throw new SystemException(ErrorCode.SYSTEM_ERROR);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 错误码分配表
|
|
378
|
+
|
|
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 |
|
|
388
|
+
|
|
389
|
+
```java
|
|
390
|
+
/** 错误码常量示例 */
|
|
391
|
+
public interface ErrorCodeConstants {
|
|
392
|
+
|
|
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, "系统繁忙,请稍后重试");
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Redis Key 命名约定表
|
|
403
|
+
|
|
404
|
+
| 用途 | Key 模式 | TTL | 示例 |
|
|
405
|
+
|------|---------|-----|------|
|
|
406
|
+
| 登录 Token | `user:login:token:{userId}` | 7天 | `user:login:token:1001` |
|
|
407
|
+
| 验证码 | `user:captcha:{mobile}:{scene}` | 5分钟 | `user:captcha:13800138000:LOGIN` |
|
|
408
|
+
| 用户缓存 | `cache:user:info:{userId}` | 30分钟 | `cache:user:info:1001` |
|
|
409
|
+
| 接口限流 | `limit:{api}:{ip}:{minute}` | 60秒 | `limit:/api/user/create:192.168.1.1:202401011200` |
|
|
410
|
+
| 分布式锁 | `lock:{business}:{key}` | 自动释放 | `lock:order:create:ORD20240101001` |
|
|
411
|
+
|
|
412
|
+
```java
|
|
413
|
+
/** Redis Key 常量类 */
|
|
414
|
+
public final class RedisKeyConstants {
|
|
415
|
+
private RedisKeyConstants() {}
|
|
416
|
+
|
|
417
|
+
public static final String USER_LOGIN_TOKEN = "user:login:token:%s";
|
|
418
|
+
public static final String USER_CAPTCHA = "user:captcha:%s:%s";
|
|
419
|
+
public static final String CACHE_USER_INFO = "cache:user:info:%s";
|
|
420
|
+
|
|
421
|
+
// TTL 定义(秒)
|
|
422
|
+
public static final long TOKEN_EXPIRE_SECONDS = 7 * 24 * 3600L; // 7 天
|
|
423
|
+
public static final long CAPTCHA_EXPIRE_SECONDS = 300L; // 5 分钟
|
|
424
|
+
public static final long CACHE_EXPIRE_SECONDS = 1800L; // 30 分钟
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## 数据库设计约定
|
|
431
|
+
|
|
432
|
+
| 规则 | 要求 | 说明 |
|
|
433
|
+
|------|------|------|
|
|
434
|
+
| 字符集 | utf8mb4 | 支持完整 Unicode(含 emoji)|
|
|
435
|
+
| 引擎 | InnoDB | 默认引擎 |
|
|
436
|
+
| 禁止外键 | 不在数据库层面创建外键 | 应用层维护引用完整性 |
|
|
437
|
+
| 时间字段 | `datetime` 类型(对应 LocalDateTime) | 禁止 timestamp(时区问题)|
|
|
438
|
+
| 主键 | bigint unsigned 自增或雪花算法 | 分布式环境推荐雪花 |
|
|
439
|
+
| 软删除 | 必须有 deleted 字段(tinyint/boolean) | 禁物理删除业务数据 |
|
|
440
|
+
| 审计字段 | 必须有 create_time / update_time | 由框架自动填充 |
|
|
441
|
+
| 表结构变更 | 通过 ALTER DDL 脚本执行 | 禁止删重建表 |
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Nacos 配置组织方式
|
|
446
|
+
|
|
447
|
+
```yaml
|
|
448
|
+
# 本地 bootstrap.yml — 仅保留最小必要配置
|
|
449
|
+
spring:
|
|
450
|
+
application:
|
|
451
|
+
name: jscicd-app-server
|
|
452
|
+
cloud:
|
|
453
|
+
nacos:
|
|
454
|
+
discovery:
|
|
455
|
+
server-addr: ${NACOS_ADDR:localhost:8848}
|
|
456
|
+
config:
|
|
457
|
+
server-addr: ${NACOS_ADDR:localhost:8848}
|
|
458
|
+
file-extension: yaml
|
|
459
|
+
|
|
460
|
+
# Nacos 远程配置 key 规范(camelCase):
|
|
461
|
+
# - spring.datasource.url
|
|
462
|
+
# - spring.redis.host
|
|
463
|
+
# - jscicd.security.sm4-secret-key
|
|
464
|
+
# - logging.level.com.jieshun.domain
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**配置规范**:
|
|
468
|
+
- 本地只保留 Nacos 连接地址
|
|
469
|
+
- 其他所有配置从 Nacos 拉取
|
|
470
|
+
- Key 使用 camelCase 格式
|
|
471
|
+
- 禁止使用 shared-configs 共享过多公共配置
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# 后端分区 — 编码惯例
|
|
2
|
+
|
|
3
|
+
## TypeScript 规范
|
|
4
|
+
|
|
5
|
+
### 严格模式
|
|
6
|
+
|
|
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
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 类型定义优先级
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Interface > Type Alias > Class(纯数据类型场景)
|
|
25
|
+
Class > Interface(需要实例化的场景)
|
|
26
|
+
```
|
|
27
|
+
|
|
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
|
+
```
|
|
49
|
+
|
|
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
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 错误处理规范
|
|
78
|
+
|
|
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 };
|
|
107
|
+
}
|
|
108
|
+
|
|
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
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
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;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 日志级别使用规范:
|
|
143
|
+
// logger.debug() — 详细调试信息(仅开发环境输出)
|
|
144
|
+
// logger.log() — 一般业务日志
|
|
145
|
+
// logger.warn() — 需要注意但不致命的情况
|
|
146
|
+
// logger.error() — 错误信息(必须包含 stack trace)
|
|
147
|
+
// logger.verbose() — 更多细节(通常不用)
|
|
148
|
+
|
|
149
|
+
// ⚠️ 禁止:日志中出现敏感信息
|
|
150
|
+
// ❌ logger.info(`Login success: ${password}`); // 绝不记录密码
|
|
151
|
+
// ✅ logger.info(`Login success: userId=${user.id}`); // 只记录必要标识
|
|
152
|
+
```
|
|
153
|
+
|
|
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
|
+
}
|
|
192
|
+
```
|