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.
Files changed (68) hide show
  1. package/.harness/README.md +199 -0
  2. package/.harness/agents/code-reviewer/contract.yaml +64 -0
  3. package/.harness/agents/developer/contract.yaml +72 -0
  4. package/.harness/agents/gate-controller/contract.yaml +64 -0
  5. package/.harness/agents/project-manager/contract.yaml +77 -0
  6. package/.harness/agents/prompt-templates.md +352 -0
  7. package/.harness/agents/requirements-analyst/contract.yaml +64 -0
  8. package/.harness/agents/solution-designer/contract.yaml +75 -0
  9. package/.harness/agents/tester/contract.yaml +92 -0
  10. package/.harness/config/models.yaml +67 -0
  11. package/.harness/dev-map/backend/api-definition.md +131 -0
  12. package/.harness/dev-map/backend/auth-security.md +131 -0
  13. package/.harness/dev-map/backend/conventions-java.md +471 -0
  14. package/.harness/dev-map/backend/conventions.md +192 -0
  15. package/.harness/dev-map/backend/database.md +106 -0
  16. package/.harness/dev-map/backend/structure.md +140 -0
  17. package/.harness/dev-map/decisions.md +275 -0
  18. package/.harness/dev-map/frontend/api-integration.md +139 -0
  19. package/.harness/dev-map/frontend/components.md +178 -0
  20. package/.harness/dev-map/frontend/conventions.md +416 -0
  21. package/.harness/dev-map/frontend/state-management.md +170 -0
  22. package/.harness/dev-map/frontend/structure.md +103 -0
  23. package/.harness/dev-map/overview.md +267 -0
  24. package/.harness/docs/integration-test-plan.md +248 -0
  25. package/.harness/docs/team-guidelines/README.md +161 -0
  26. package/.harness/docs/team-guidelines/arch-team.md +811 -0
  27. package/.harness/docs/team-guidelines/collaboration.md +556 -0
  28. package/.harness/docs/team-guidelines/pm-team.md +337 -0
  29. package/.harness/docs/team-guidelines/qa-team.md +562 -0
  30. package/.harness/docs/team-guidelines/rd-team.md +714 -0
  31. package/.harness/docs/training-materials.md +280 -0
  32. package/.harness/gate/baseline.js +220 -0
  33. package/.harness/gate/checks/build-gates-frontend.js +152 -0
  34. package/.harness/gate/checks/build-gates-java.js +155 -0
  35. package/.harness/gate/checks/build-gates.js +119 -0
  36. package/.harness/gate/checks/engineering-consistency.js +138 -0
  37. package/.harness/gate/checks/security-quality.js +129 -0
  38. package/.harness/gate/checks/static-compliance.js +313 -0
  39. package/.harness/gate/checks/test-compliance.js +114 -0
  40. package/.harness/gate/index.js +315 -0
  41. package/.harness/mcp/config.yaml +435 -0
  42. package/.harness/rules/global/coding-standard.md +232 -0
  43. package/.harness/rules/global/commit-convention.md +165 -0
  44. package/.harness/rules/global/process-discipline.md +192 -0
  45. package/.harness/rules/global/security-baseline.md +306 -0
  46. package/.harness/rules/project/frontend-vue3.md +293 -0
  47. package/.harness/rules/project/java-backend.md +460 -0
  48. package/.harness/rules/project/web-specific.md +231 -0
  49. package/.harness/skills/build.md +192 -0
  50. package/.harness/skills/code-review.md +251 -0
  51. package/.harness/skills/docker-build.md +227 -0
  52. package/.harness/skills/docs-update.md +164 -0
  53. package/.harness/skills/java-build.md +261 -0
  54. package/.harness/skills/lint-check.md +482 -0
  55. package/.harness/skills/task-board-maintenance.md +105 -0
  56. package/.harness/skills/test-api.md +461 -0
  57. package/.harness/skills/test-e2e.md +431 -0
  58. package/.harness/skills/test-unit.md +649 -0
  59. package/.harness/skills/vue-frontend-build.md +344 -0
  60. package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
  61. package/.harness/task-board.md +121 -0
  62. package/.harness/workflow/definition.yaml +504 -0
  63. package/.harness/workflow/validate.js +320 -0
  64. package/.harness/workflow/variants.yaml +253 -0
  65. package/README.md +237 -0
  66. package/bin/jsharness.js +53 -0
  67. package/lib/index.mjs +778 -0
  68. 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
+ ```