create-dp-koa 1.1.3 → 1.1.4
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/package.json +1 -1
- package/template/.cursor/rules/00-backend-core.skill.md +1 -1
- package/template/.cursor/rules/01-backend-skill-router.skill.md +8 -2
- package/template/.cursor/rules/10-backend-api.skill.md +8 -0
- package/template/.cursor/rules/11-backend-controller-recipes.skill.md +12 -9
- package/template/.cursor/rules/21-backend-service.skill.md +14 -0
- package/template/.cursor/rules/30-backend-validation.skill.md +1 -1
- package/template/.cursor/rules/40-backend-error-logging.skill.md +9 -5
- package/template/.cursor/rules/50-backend-bootstrap-lifecycle.skill.md +4 -4
- package/template/.cursor/rules/60-backend-router-registration.skill.md +16 -6
- package/template/.cursor/rules/70-backend-middleware.skill.md +2 -2
- package/template/.cursor/rules/80-backend-utils-and-libs.skill.md +71 -14
- package/template/.cursor/rules/85-backend-plugins.rule.md +4 -4
- package/template/.cursor/rules/90-backend-testing.skill.md +26 -0
- package/template/.cursor/rules/README.md +2 -2
- package/template/.trae/skills/11-backend-controller-recipes.skill.md +12 -9
- package/template/.trae/skills/21-backend-service.skill.md +15 -1
- package/template/.trae/skills/40-backend-error-logging.skill.md +9 -5
- package/template/.trae/skills/80-backend-utils-and-libs.skill.md +77 -8
- package/template/.trae/skills/90-backend-testing.skill.md +26 -0
- package/template/src/types/ctxState.ts +9 -0
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ alwaysApply: true
|
|
|
12
12
|
|
|
13
13
|
## 运行环境判定(debug 口径,必须)
|
|
14
14
|
|
|
15
|
-
**唯一标准**:以 `
|
|
15
|
+
**唯一标准**:以 `dp-koa-framework-core` 导出的 `isDebug()` / `getRuntimeEnvironmentLabel()` 为准,**不要**用 `process.env.NODE_ENV` 作为“是否生产/是否调试”的单一判断。
|
|
16
16
|
|
|
17
17
|
- **`isDebug()`**:当进程启动参数中包含 `--env=debug` 时为 `true`。
|
|
18
18
|
- **非 debug**:一律按**生产口径**处理(例如:迁移、静态资源长缓存、对外错误信息脱敏等)。
|
|
@@ -6,6 +6,7 @@ alwaysApply: true
|
|
|
6
6
|
你现在的角色是【后端 Skill 路由器】:你的目标是帮助选择应启用的 rules/skills/commands,避免盲用规则。
|
|
7
7
|
|
|
8
8
|
## 总原则(必须遵守)
|
|
9
|
+
|
|
9
10
|
- **优先使用 Commands** 来触发正确的 Skills,而不是把所有规则都写成 alwaysApply。
|
|
10
11
|
- 如果用户需求不清晰,必须先提问澄清(不要臆造不存在的接口/文件/配置)。
|
|
11
12
|
|
|
@@ -14,18 +15,21 @@ alwaysApply: true
|
|
|
14
15
|
## 你必须做的事(每次用户提出开发/改动请求时)
|
|
15
16
|
|
|
16
17
|
### 1) 识别任务类型(多选)
|
|
18
|
+
|
|
17
19
|
- Controller/API 开发
|
|
18
20
|
- DTO/参数校验
|
|
19
21
|
- Service/Repository/事务
|
|
20
22
|
- 错误处理/日志
|
|
21
23
|
- 启动流程(`app.ts`/`bootstrap.ts`,before/after)
|
|
22
24
|
- 路由注册(`routers/index.ts`、`bindRouter`)
|
|
23
|
-
- 中间件(`src/middlewares
|
|
25
|
+
- 中间件(`src/middlewares/`*)
|
|
24
26
|
- 测试(Jest)
|
|
25
27
|
- 目录结构与放置(libs/utils)
|
|
26
28
|
|
|
27
29
|
### 2) 给出推荐启用的 Skills(只列文件名即可)
|
|
30
|
+
|
|
28
31
|
按任务类型映射:
|
|
32
|
+
|
|
29
33
|
- **Controller/API**:
|
|
30
34
|
- `10-backend-api.skill.md`
|
|
31
35
|
-(需要范式/注解速查时)`11-backend-controller-recipes.skill.md`
|
|
@@ -41,9 +45,11 @@ alwaysApply: true
|
|
|
41
45
|
- **libs/utils 放置**:`80-backend-utils-and-libs.skill.md`
|
|
42
46
|
|
|
43
47
|
注意:
|
|
48
|
+
|
|
44
49
|
- `00-backend-core.skill.md` 已是 alwaysApply,无需重复声明
|
|
45
50
|
|
|
46
51
|
### 3) 推荐使用的 Command(优先)
|
|
52
|
+
|
|
47
53
|
- 需要先规划:`.cursor/commands/plan-backend.md`
|
|
48
54
|
- 要实现/修改 Controller:`.cursor/commands/implement-backend-api-controller.md`
|
|
49
55
|
- 只要 Controller 速查:`.cursor/commands/cheatsheet-backend-controller.md`
|
|
@@ -51,7 +57,7 @@ alwaysApply: true
|
|
|
51
57
|
---
|
|
52
58
|
|
|
53
59
|
## 输出要求(简短)
|
|
60
|
+
|
|
54
61
|
- 当用户让你“直接写代码”:直接进入实现,但先在心里完成上述路由选择
|
|
55
62
|
- 当用户不确定或信息不足:先问 2~5 个关键澄清问题
|
|
56
63
|
|
|
57
|
-
|
|
@@ -4,6 +4,7 @@ alwaysApply: false
|
|
|
4
4
|
# Skill:后端 API 开发规范
|
|
5
5
|
|
|
6
6
|
## 适用与触发(重要)
|
|
7
|
+
|
|
7
8
|
- **仅在**你要生成/修改 **Controller 路由接口** 时应用本 Skill(否则忽略)
|
|
8
9
|
- 触发口令建议:
|
|
9
10
|
- “按 `10-backend-api.skill.md` 实现/重构这个 Controller”
|
|
@@ -12,6 +13,7 @@ alwaysApply: false
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
## 一、路由与参数装饰器(必须)
|
|
16
|
+
|
|
15
17
|
- 路由装饰器:`@Get` / `@Post`
|
|
16
18
|
- 参数装饰器:`@Query` / `@Body` / `@State`
|
|
17
19
|
- 禁止在 Controller 中手动解析 ctx(除非该 Controller 明确以 ctx 作为参数且项目已有同类写法)
|
|
@@ -21,33 +23,39 @@ alwaysApply: false
|
|
|
21
23
|
## 二、Controller 编排规范(必须)
|
|
22
24
|
|
|
23
25
|
### 2.1 只做三件事
|
|
26
|
+
|
|
24
27
|
- **参数接收与校验**(DTO)
|
|
25
28
|
- **调用 Service**
|
|
26
29
|
- **映射为统一响应**(`BaseController.success/fail`)
|
|
27
30
|
|
|
28
31
|
### 2.2 统一响应(必须)
|
|
32
|
+
|
|
29
33
|
- Controller 必须返回 `ControllerResponse<T>`(通过 `this.success()` / `this.fail()`)
|
|
30
34
|
- 禁止 `return result`(Service 原始结果):
|
|
31
35
|
- 成功:`return this.success(result.data, result.message?)`
|
|
32
36
|
- 失败:`return this.fail(result.code, result.message)`
|
|
33
37
|
|
|
34
38
|
### 2.3 错误处理(必须)
|
|
39
|
+
|
|
35
40
|
- Controller 的 `async` 方法必须 `try/catch`
|
|
36
41
|
- catch:记录日志(见 `40-backend-error-logging.skill.md`)+ 返回通用失败响应
|
|
37
42
|
|
|
38
43
|
### 2.4 DTO(必须)
|
|
44
|
+
|
|
39
45
|
- `@Query()` / `@Body()` 必须使用 **DTO 类型**(禁止 `any`)
|
|
40
46
|
- DTO 的定义与校验遵循 `30-backend-validation.skill.md`
|
|
41
47
|
|
|
42
48
|
---
|
|
43
49
|
|
|
44
50
|
## 三、接口文档与元信息(可选但优先)
|
|
51
|
+
|
|
45
52
|
- 对外接口优先补齐 Swagger:`@ApiTags` / `@Api` / `@ApiResponse`
|
|
46
53
|
- 需要显式状态码/响应头时使用:`@ResponseCode` / `@ResponseHeader`
|
|
47
54
|
|
|
48
55
|
---
|
|
49
56
|
|
|
50
57
|
## 四、禁止行为(必须)
|
|
58
|
+
|
|
51
59
|
- ❌ Controller 中编写业务逻辑(应下沉到 Service)
|
|
52
60
|
- ❌ Controller 中直接访问数据库/Repository
|
|
53
61
|
- ❌ Controller 中返回 Service 原始结果(必须映射为统一响应)
|
|
@@ -43,13 +43,14 @@ export class BaseController {
|
|
|
43
43
|
#### 1.1.2 `@State()`:读取整段 `ctx.state`
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import { Get, State } from '
|
|
46
|
+
import { Get, State } from 'dp-koa-framework-core';
|
|
47
47
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
48
|
+
import type { AppCtxState } from '@src/types/ctxState';
|
|
48
49
|
|
|
49
50
|
export class ExampleStateController extends BaseController {
|
|
50
51
|
@Get('/example/state-full')
|
|
51
52
|
async getWithFullState(
|
|
52
|
-
@State() state:
|
|
53
|
+
@State() state: AppCtxState,
|
|
53
54
|
): Promise<ControllerResponse<{ userId: number }>> {
|
|
54
55
|
return this.success({ userId: state.user.userId });
|
|
55
56
|
}
|
|
@@ -59,13 +60,14 @@ export class ExampleStateController extends BaseController {
|
|
|
59
60
|
#### 1.1.3 `@State('user')`:只读 `ctx.state.user`
|
|
60
61
|
|
|
61
62
|
```ts
|
|
62
|
-
import { Get, State } from '
|
|
63
|
+
import { Get, State } from 'dp-koa-framework-core';
|
|
63
64
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
65
|
+
import type { UserState } from '@src/types/ctxState';
|
|
64
66
|
|
|
65
67
|
export class ExampleUserSliceController extends BaseController {
|
|
66
68
|
@Get('/example/state-user')
|
|
67
69
|
async getWithUser(
|
|
68
|
-
@State('user') user:
|
|
70
|
+
@State('user') user: UserState,
|
|
69
71
|
): Promise<ControllerResponse<{ ok: boolean }>> {
|
|
70
72
|
return this.success({ ok: user.userId > 0 });
|
|
71
73
|
}
|
|
@@ -75,8 +77,9 @@ export class ExampleUserSliceController extends BaseController {
|
|
|
75
77
|
#### 1.1.4 `@ResponseValidateIf`:仅在条件成立时校验响应体
|
|
76
78
|
|
|
77
79
|
```ts
|
|
78
|
-
import { Get, State, ResponseValidateIf } from '
|
|
80
|
+
import { Get, State, ResponseValidateIf } from 'dp-koa-framework-core';
|
|
79
81
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
82
|
+
import type { AppCtxState } from '@src/types/ctxState';
|
|
80
83
|
|
|
81
84
|
// 示例 DTO:按实际接口替换
|
|
82
85
|
class UserProfileResponseDto {
|
|
@@ -88,7 +91,7 @@ export class ExampleResponseValidateController extends BaseController {
|
|
|
88
91
|
@ResponseValidateIf(UserProfileResponseDto, (data) => Boolean(data && data.data))
|
|
89
92
|
@Get('/example/profile')
|
|
90
93
|
async getProfile(
|
|
91
|
-
@State() state:
|
|
94
|
+
@State() state: AppCtxState,
|
|
92
95
|
): Promise<ControllerResponse<UserProfileResponseDto | null>> {
|
|
93
96
|
return this.success({
|
|
94
97
|
id: state.user.userId,
|
|
@@ -102,13 +105,13 @@ export class ExampleResponseValidateController extends BaseController {
|
|
|
102
105
|
目标:DTO 入参 → 调用 Service → 统一响应(`success/fail`)→ try/catch + logger
|
|
103
106
|
|
|
104
107
|
```ts
|
|
105
|
-
import { Get, Post, Query, Body, State, ResponseCode, ResponseHeader } from '
|
|
108
|
+
import { Get, Post, Query, Body, State, ResponseCode, ResponseHeader } from 'dp-koa-framework-core';
|
|
106
109
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
107
110
|
import { Inject } from 'dp-ioc2';
|
|
108
|
-
import { logger } from '
|
|
111
|
+
import { logger } from 'dp-koa-framework-core';
|
|
109
112
|
import { SomeService } from '@src/service/some.service';
|
|
110
113
|
import { SomeQueryDto, SomeBodyDto } from '@src/dto/controller/xxx/some.controller.dto';
|
|
111
|
-
import { CommonServiceResultCode } from '
|
|
114
|
+
import { CommonServiceResultCode } from 'dp-koa-framework-core';
|
|
112
115
|
|
|
113
116
|
export class SomeController extends BaseController {
|
|
114
117
|
@Inject(SomeService)
|
|
@@ -133,5 +133,19 @@ alwaysApply: false
|
|
|
133
133
|
- Controller 只能调用 Service 的公开方法,不应调用 Repository
|
|
134
134
|
- Service 不应调用 Controller,也不应依赖 Koa `Context`
|
|
135
135
|
- Service 之间如需调用,应通过依赖注入(`@Inject(OtherService)`),避免静态调用/循环依赖
|
|
136
|
+
- 禁止在 Service 内手动创建实例:
|
|
137
|
+
- ❌ 禁止 `new OtherService()` / `new XxxService()`(包括在方法内部临时 new)
|
|
138
|
+
- ❌ 禁止通过手写单例/缓存实例绕过 IoC 容器
|
|
139
|
+
- ✅ 必须依赖 dp-ioc2 在运行期注入(例如字段注入或构造注入)
|
|
140
|
+
- 推荐写法(字段注入示例):
|
|
141
|
+
```ts
|
|
142
|
+
import { Inject } from "dp-ioc2";
|
|
143
|
+
import { OtherService } from "./other.service";
|
|
144
|
+
|
|
145
|
+
export class MyService extends BaseService {
|
|
146
|
+
@Inject(OtherService)
|
|
147
|
+
private otherService!: OtherService;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
136
150
|
|
|
137
151
|
|
|
@@ -124,7 +124,7 @@ async createUser(@Body(CreateUserControllerDto) body: CreateUserControllerDto):
|
|
|
124
124
|
#### 响应 DTO 使用
|
|
125
125
|
```typescript
|
|
126
126
|
import { GetUserInfoResponseDto } from '@src/dto/controller/home/ytUser.controller.dto';
|
|
127
|
-
import { ResponseValidateIf } from '
|
|
127
|
+
import { ResponseValidateIf } from 'dp-koa-framework-core';
|
|
128
128
|
|
|
129
129
|
// 使用 @ResponseValidateIf 装饰器校验响应数据
|
|
130
130
|
@ResponseValidateIf(GetUserInfoResponseDto, (data) => data && data.data)
|
|
@@ -6,11 +6,15 @@ alwaysApply: false
|
|
|
6
6
|
## 一、错误处理
|
|
7
7
|
|
|
8
8
|
- 所有 async 方法必须 try/catch
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
9
|
+
- 业务错误(可预期、可分类):
|
|
10
|
+
- 主要在 **Service 层 catch 中**处理
|
|
11
|
+
- 返回 `CommonServiceResult.fail()/validationError/notFound/...`(由 Service 统一封装)
|
|
12
|
+
- 系统错误(不可预期、疑似异常):
|
|
13
|
+
- **必须记录日志(logger.error)**
|
|
14
|
+
- Service 层返回通用失败的 `CommonServiceResult.error(...)`
|
|
15
|
+
- 映射到 Controller:
|
|
16
|
+
- Controller 不应直接返回 `CommonServiceResult`
|
|
17
|
+
- Controller 必须把 Service 的 `CommonServiceResult` 通过 `this.success()/this.fail()` 映射为 `ControllerResponse`
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
@@ -4,14 +4,14 @@ alwaysApply: false
|
|
|
4
4
|
# Skill:启动生命周期规范(setBeforeBootstrap / setAfterBootstrap)
|
|
5
5
|
|
|
6
6
|
## 适用与触发(重要)
|
|
7
|
-
- 仅当需要修改/新增启动逻辑(`src/app.ts
|
|
7
|
+
- 仅当需要修改/新增启动逻辑(`src/app.ts`)或框架启动流程(由 `dp-koa-framework-core` 的 bootstrap 决定)时启用本 Skill。
|
|
8
8
|
- 触发口令建议:
|
|
9
9
|
- “请按 `50-backend-bootstrap-lifecycle.skill.md` 调整启动流程”
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## 一、真实执行顺序(必须理解并遵守)
|
|
14
|
-
以 `
|
|
14
|
+
以 `dp-koa-framework-core` 的 bootstrap 内部执行顺序为准:
|
|
15
15
|
|
|
16
16
|
1. `bootstrap()` 内部 **先执行** `beforeBootstraps(app)`(如果已注册)
|
|
17
17
|
2. 然后框架挂载:CORS → 日志中间件 → 框架错误处理中间件 → `koaBody()`
|
|
@@ -36,9 +36,9 @@ alwaysApply: false
|
|
|
36
36
|
- 生产环境迁移(如有)
|
|
37
37
|
- 内存库模式初始化测试数据(如有)
|
|
38
38
|
|
|
39
|
-
### 2.1.1 环境变量文件与 `.env` 选择(对齐 `bootstrap
|
|
39
|
+
### 2.1.1 环境变量文件与 `.env` 选择(对齐 `dp-koa-framework-core` 的 bootstrap)
|
|
40
40
|
|
|
41
|
-
- **与 `NODE_ENV` 解耦**:`
|
|
41
|
+
- **与 `NODE_ENV` 解耦**:`dp-koa-framework-core` 的 bootstrap 最早加载的 `.env` 文件由 **`isDebug()`** 决定:
|
|
42
42
|
- `--env=debug` → `.env.development`
|
|
43
43
|
- 否则 → `.env.production`
|
|
44
44
|
- **本地开发**:`package.json` 的 `dev` 建议在 `node dist/main.js` 后追加 `--env=debug`,避免误加载生产 env。
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
## alwaysApply: false
|
|
4
|
+
|
|
4
5
|
# Skill:路由注册规范(routers/index.ts + bindRouter)
|
|
5
6
|
|
|
6
7
|
## 适用与触发(重要)
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
- 仅当需要新增/修改路由绑定(`src/routers/index.ts`)或调整路由系统(由 `dp-koa-framework-core` 的 `bindRouter` 机制决定)时启用本 Skill。
|
|
8
10
|
- 触发口令建议:
|
|
9
11
|
- “请按 `60-backend-router-registration.skill.md` 规范注册路由”
|
|
10
12
|
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## 一、路由注册机制(必须理解)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
本项目路由注册入口:`src/routers/index.ts`
|
|
18
|
+
路由绑定工具:`dp-koa-framework-core` 导出的 `bindRouter(...)`
|
|
16
19
|
|
|
17
20
|
### 1.1 bindRouter(...) 的真实行为(以代码为准)
|
|
21
|
+
|
|
18
22
|
- 调用形式:
|
|
19
23
|
- `bindRouter(prefixPath, ControllerClass)`
|
|
20
24
|
- `bindRouter(prefixPath, middleware1, middleware2, ..., ControllerClass)`
|
|
@@ -34,6 +38,7 @@ alwaysApply: false
|
|
|
34
38
|
## 二、`src/routers/index.ts` 注册规范(必须遵守)
|
|
35
39
|
|
|
36
40
|
### 2.1 只做“路由绑定”
|
|
41
|
+
|
|
37
42
|
- `routers/index.ts` 只负责:
|
|
38
43
|
- 导入 Controller
|
|
39
44
|
- 选择是否需要 middlewares(鉴权/日志等)
|
|
@@ -41,7 +46,9 @@ alwaysApply: false
|
|
|
41
46
|
- 禁止在此文件写业务逻辑或数据库操作
|
|
42
47
|
|
|
43
48
|
### 2.2 路由前缀命名(推荐统一)
|
|
49
|
+
|
|
44
50
|
建议用“模块/领域 + 子模块 + 资源”的层级前缀,保持稳定:
|
|
51
|
+
|
|
45
52
|
- `"/public/..."`:公开接口(一般不加“认证/鉴权 middleware”)
|
|
46
53
|
- `"/home/..."`:业务接口(按需加“认证/鉴权 middleware”)
|
|
47
54
|
- `"/admin/..."`:管理接口(一般需要认证/鉴权;可在此基础上再加权限/审计中间件)
|
|
@@ -49,6 +56,7 @@ alwaysApply: false
|
|
|
49
56
|
- `"/test"`:测试/调试接口(仅测试环境或内存库模式有效时暴露)
|
|
50
57
|
|
|
51
58
|
### 2.3 middleware 使用规范(必须)
|
|
59
|
+
|
|
52
60
|
- 需要认证/鉴权的路由必须在 `bindRouter` 里显式挂“认证/鉴权 middleware”(名称不固定,以项目实际为准):
|
|
53
61
|
- 示例(本项目当前实现):`bindRouter("/home/user/yt_user", tokenMiddleware(), YtUserController);`
|
|
54
62
|
- middlewares 必须传“可执行的 koa middleware 函数”:
|
|
@@ -57,17 +65,19 @@ alwaysApply: false
|
|
|
57
65
|
- 例如:`authMiddleware()` → 其他业务中间件
|
|
58
66
|
|
|
59
67
|
### 2.4 禁止重复绑定(必须)
|
|
68
|
+
|
|
60
69
|
- ❌ 禁止对同一个 `prefixPath + Controller` 重复调用 `bindRouter`
|
|
61
70
|
- 重复绑定会导致路由重复注册、重复执行中间件、行为不可预测
|
|
62
71
|
|
|
63
72
|
### 2.5 尾斜杠(推荐)
|
|
73
|
+
|
|
64
74
|
- 推荐 `bindRouter` 的 `prefixPath` **不要以 `/` 结尾**,保持统一(除非明确需要)
|
|
65
75
|
- 若使用了尾斜杠,框架会额外注册无尾斜杠版本;应避免产生重复/歧义的路由
|
|
66
76
|
|
|
67
77
|
---
|
|
68
78
|
|
|
69
79
|
## 三、与启动流程的配合(必须)
|
|
80
|
+
|
|
70
81
|
- `Router()`(即 `src/routers/index.ts` 的默认导出函数)必须在 `app.use(router.routes())` 之前执行
|
|
71
82
|
- 因此通常应在 `setBeforeBootstrap(...)` 中调用(见 `50-backend-bootstrap-lifecycle.skill.md`)
|
|
72
83
|
|
|
73
|
-
|
|
@@ -4,7 +4,7 @@ alwaysApply: false
|
|
|
4
4
|
# Skill:中间件开发与注册规范(src/middlewares)
|
|
5
5
|
|
|
6
6
|
## 适用与触发(重要)
|
|
7
|
-
- 仅当需要新增/修改中间件(`src/middlewares
|
|
7
|
+
- 仅当需要新增/修改中间件(`src/middlewares/*`)或调整中间件注册顺序(由 `dp-koa-framework-core` 的 bootstrap + `src/app.ts` + `src/routers/index.ts` 共同决定)时启用本 Skill。
|
|
8
8
|
- 触发口令建议:
|
|
9
9
|
- “请按 `70-backend-middleware.skill.md` 新增/调整中间件”
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ alwaysApply: false
|
|
|
62
62
|
## 六、注册位置与顺序(必须)
|
|
63
63
|
|
|
64
64
|
### 6.1 全局中间件(bootstrap 阶段)
|
|
65
|
-
在 `
|
|
65
|
+
在 `dp-koa-framework-core` 的 bootstrap 挂载过程中,框架已注册:
|
|
66
66
|
- CORS
|
|
67
67
|
- loggingMiddleware / businessLoggingMiddleware / databaseLoggingMiddleware
|
|
68
68
|
- frameworkErrorMiddleware(框架级错误处理中间件)
|
|
@@ -4,8 +4,9 @@ alwaysApply: false
|
|
|
4
4
|
# Skill:libs 与 utils 规划规范
|
|
5
5
|
|
|
6
6
|
## 适用与触发(重要)
|
|
7
|
+
|
|
7
8
|
- 当需要新增/调整以下内容时启用本 Skill:
|
|
8
|
-
- `
|
|
9
|
+
- 共享适配/可抽包的代码(优先使用 `dp-koa-framework-libs`;仅临时代码可放业务模块内)
|
|
9
10
|
- `src/utils/**` 下的工具函数
|
|
10
11
|
- 业务模块内部的 `xxx.utils.ts`
|
|
11
12
|
|
|
@@ -19,9 +20,10 @@ alwaysApply: false
|
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## 二、共享适配/可抽包代码放什么(必须)
|
|
24
|
+
|
|
25
|
+
### 2.1 适合的内容
|
|
23
26
|
|
|
24
|
-
### 2.1 适合放在 libs 的内容
|
|
25
27
|
- **第三方服务适配/封装**:
|
|
26
28
|
- 如:短信服务封装、支付网关封装、HTTP SDK 封装等
|
|
27
29
|
- 对外暴露一个干净的接口:如 `sendSms(phone, templateId, params)`
|
|
@@ -30,15 +32,66 @@ alwaysApply: false
|
|
|
30
32
|
- **有内部状态或生命周期的组件**:
|
|
31
33
|
- 连接池包装器、复杂缓存管理器、网关客户端等
|
|
32
34
|
|
|
33
|
-
### 2.2
|
|
35
|
+
### 2.2 不适合放到共享适配层的内容
|
|
36
|
+
|
|
34
37
|
- 单一的小工具函数(字符串、日期、数字转换等)
|
|
35
38
|
- 强业务耦合的逻辑(如订单价格计算、业务规则判断)
|
|
36
39
|
|
|
40
|
+
### 2.3 框架库内置工具(缓存/短信/验证码等)(重点)
|
|
41
|
+
当你需要使用“框架已经内置好的工具”,优先直接从 `dp-koa-framework-libs` 导入稳定导出;其中**缓存**建议按业务通过 `createCache + CacheType` 创建你的“业务缓存”对象。
|
|
42
|
+
|
|
43
|
+
#### 2.3.1 缓存工具:用 `createCache` + `CacheType` 构建你的缓存
|
|
44
|
+
- 核心提供缓存工厂:`createCache(name, type, customConfig?)`(配合 `CacheType`)
|
|
45
|
+
- 你可以完全参考 `dp-koa-framework-libs/src/libs/mCache.ts`(1-8 行)的写法,在你自己的 `libs/` 里创建“业务自定义缓存对象”
|
|
46
|
+
- 注意:`name` 作为全局缓存标识,同名会复用已有缓存实例(避免重复创建)
|
|
47
|
+
- `CacheType` 用于选择默认 TTL/容量策略(来自 `dp-koa-framework-core` 缓存模块默认配置):
|
|
48
|
+
- `CacheType.USER`:用户缓存(默认 `stdTTL=1800` 秒,`maxKeys=500`)
|
|
49
|
+
- `CacheType.CONTROLLER`:控制器结果缓存(默认 `stdTTL=60` 秒,`maxKeys=2000`)
|
|
50
|
+
- `CacheType.SESSION`:会话缓存(默认 `stdTTL=3600` 秒,`maxKeys=1000`)
|
|
51
|
+
- `CacheType.CAPTCHA`:验证码缓存(默认 `stdTTL=300` 秒,`maxKeys=100`)
|
|
52
|
+
- `CacheType.TEMP`:临时缓存(默认 `stdTTL=60` 秒,`maxKeys=100`)
|
|
53
|
+
- `customConfig` 的行为:会在“默认配置(DEFAULT_CACHE_CONFIG)+ CacheType 配置”基础上进行合并,从而覆盖 `stdTTL/maxKeys/checkperiod/...` 等参数
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createCache, CacheType } from "dp-koa-framework-core";
|
|
57
|
+
|
|
58
|
+
// 例:给当前业务域创建一个“用户缓存”
|
|
59
|
+
export const myUserCache = createCache("my-user", CacheType.USER, {
|
|
60
|
+
stdTTL: 600, // 10分钟过期(秒)
|
|
61
|
+
maxKeys: 500, // 用户缓存最大条目数
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
使用方式示例(以 `node-cache` 风格的 `get/set` 为准):
|
|
66
|
+
```ts
|
|
67
|
+
import { myUserCache } from "@src/libs/myUserCache"; // 示例:把 myUserCache 放到你的 libs 文件并导出
|
|
68
|
+
|
|
69
|
+
const cached = myUserCache.get(String(userId));
|
|
70
|
+
if (!cached) {
|
|
71
|
+
const value = await buildValue();
|
|
72
|
+
myUserCache.set(String(userId), value); // TTL 由 createCache 配置决定
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 2.3.2 其它常用内置工具(快速导入)
|
|
77
|
+
- 验证码:`CaptchaGenerator`
|
|
78
|
+
- 短信:
|
|
79
|
+
- `TencentSms`(类)
|
|
80
|
+
- `tencentSms`(函数/实例)
|
|
81
|
+
- 邮件:`AokEmailSender`
|
|
82
|
+
- COS/文件:
|
|
83
|
+
- `isCosConfigured`
|
|
84
|
+
- `uploadToCos`
|
|
85
|
+
- `getFilePublicUrl`
|
|
86
|
+
- `webofficeCosKey`
|
|
87
|
+
- 通用业务校验:`ServiceValidate`
|
|
88
|
+
|
|
37
89
|
---
|
|
38
90
|
|
|
39
|
-
## 三、`src/utils
|
|
91
|
+
## 三、`src/utils/`** 放什么(必须)
|
|
40
92
|
|
|
41
93
|
### 3.1 适合放在 utils 的内容
|
|
94
|
+
|
|
42
95
|
- **与业务无关的纯函数工具**:
|
|
43
96
|
- 时间、字符串、数字处理
|
|
44
97
|
- 通用 Promise/重试/节流/防抖等
|
|
@@ -46,6 +99,7 @@ alwaysApply: false
|
|
|
46
99
|
- 例如:框架级测试数据初始化工具、通用校验帮助函数(若确实需要全局可见)
|
|
47
100
|
|
|
48
101
|
### 3.2 不适合放在 utils 的内容
|
|
102
|
+
|
|
49
103
|
- 直接访问数据库的函数(应放 Service/Repository 或框架层 utils 内聚)
|
|
50
104
|
- 调用第三方 HTTP API 的函数(应放 libs 作为适配层)
|
|
51
105
|
- 仅服务单一业务模块的工具(应放在该模块内部的 `xxx.utils.ts`)
|
|
@@ -60,6 +114,7 @@ alwaysApply: false
|
|
|
60
114
|
- `src/service/goods/goods.utils.ts`
|
|
61
115
|
|
|
62
116
|
规则:
|
|
117
|
+
|
|
63
118
|
- 仅供本模块使用的业务工具函数,应优先放在模块自身的 utils 文件中
|
|
64
119
|
- 全局 `utils/` 仅存放 **跨模块通用** 的工具
|
|
65
120
|
|
|
@@ -70,13 +125,13 @@ alwaysApply: false
|
|
|
70
125
|
新增工具/类库时,按以下顺序判断:
|
|
71
126
|
|
|
72
127
|
1. **是否依赖第三方服务/外部系统或有复杂状态?**
|
|
73
|
-
|
|
128
|
+
- 是 → 优先放共享适配层(`dp-koa-framework-libs` 或独立 npm 包)
|
|
74
129
|
2. **是否明显只服务某一个业务模块?**
|
|
75
|
-
|
|
130
|
+
- 是 → 放到该模块自己的 `xxx.utils.ts`
|
|
76
131
|
3. **是否 100% 纯函数、无状态且可跨多个模块复用?**
|
|
77
|
-
|
|
132
|
+
- 是 → 放到全局 `utils/`
|
|
78
133
|
4. **未来是否有机会抽为独立 npm 包?**
|
|
79
|
-
|
|
134
|
+
- 是 → 更倾向放共享适配层(`dp-koa-framework-libs` 或独立 npm 包)
|
|
80
135
|
|
|
81
136
|
---
|
|
82
137
|
|
|
@@ -91,15 +146,17 @@ alwaysApply: false
|
|
|
91
146
|
|
|
92
147
|
---
|
|
93
148
|
|
|
94
|
-
##
|
|
149
|
+
## 七、运行环境判定(必须)
|
|
95
150
|
|
|
96
|
-
|
|
151
|
+
应统一使用 `dp-koa-framework-core` 导出的**运行环境判定** API,避免散落 `NODE_ENV === 'production'` 判断。
|
|
97
152
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
153
|
+
|
|
154
|
+
| API | 含义 |
|
|
155
|
+
| ------------------------------ | -------------------------------------------------------- |
|
|
156
|
+
| `isDebug()` | `process.argv` 含 `--env=debug` 为 `true` |
|
|
101
157
|
| `getRuntimeEnvironmentLabel()` | `'development'`(debug)或 `'production'`(非 debug),用于日志/元数据 |
|
|
102
158
|
|
|
159
|
+
|
|
103
160
|
**约定**:
|
|
104
161
|
|
|
105
162
|
- **非 debug = 生产口径**(迁移、静态缓存策略、对外错误信息是否脱敏等)。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- 插件统一放在 `src/plugins/<plugin-id>/` 目录下。
|
|
10
10
|
- 插件根目录只保留 `index.ts`(插件入口)。
|
|
11
|
-
- 插件入口必须导出一个 `PluginDescriptor`(见 `
|
|
11
|
+
- 插件入口必须导出一个 `PluginDescriptor`(见 `dp-koa-framework-core` 的 `PluginDescriptor` 类型)。
|
|
12
12
|
|
|
13
13
|
推荐目录结构:
|
|
14
14
|
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
|
|
40
40
|
## 3. 插件注册与加载
|
|
41
41
|
|
|
42
|
-
- 插件注册表:`src/
|
|
42
|
+
- 插件注册表:`src/plugins/registry.ts`
|
|
43
43
|
- 宿主启动接入点:`src/app.ts`
|
|
44
|
-
-
|
|
45
|
-
-
|
|
44
|
+
- before:计算启用插件、执行插件 `onBeforeBootstrap` hook、注册插件路由、合并插件实体后初始化 DB
|
|
45
|
+
- after:执行插件 `onAfterBootstrap` hook(定时任务/订阅等)
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -20,6 +20,32 @@ alwaysApply: false
|
|
|
20
20
|
- 测试必须相互独立
|
|
21
21
|
- 每个测试前重置数据
|
|
22
22
|
|
|
23
|
+
## 二.2 涉及数据库读写的 Service 测试:必须使用内存数据库
|
|
24
|
+
|
|
25
|
+
- 如果测试中调用的 Service(或其依赖)会发生数据库读写(查询/插入/更新/删除、事务、迁移/初始化等):
|
|
26
|
+
- 必须启用内存数据库模式:通过 `TestDatabaseHelper.initTestDatabase(...)` 初始化测试用 `DataSource`
|
|
27
|
+
- 必须在测试结束后清理:调用 `TestDatabaseHelper.cleanupTestDatabase()`(确保释放资源,并清理缓存)
|
|
28
|
+
- 禁止为了“方便”而只模拟数据:
|
|
29
|
+
- 不允许用简单对象/假 Repository 来替代数据库读写(会掩盖 SQL/实体映射/事务边界问题)
|
|
30
|
+
- 允许 mock 的范围:仅限非数据库行为(如外部 HTTP 调用、消息发送、第三方 SDK 等)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 二.1 测试是否发“真实 HTTP”(需要理解)
|
|
35
|
+
|
|
36
|
+
- `test/controllers/**`:Controller 单元测试
|
|
37
|
+
- 通常是 `new Controller()` 后直接调用 controller 方法
|
|
38
|
+
- 不会触发真实的 `app.listen` 端口监听
|
|
39
|
+
- 这是在验证“编排/返回结构/装饰器效果(mock 情况)”的正确性
|
|
40
|
+
|
|
41
|
+
- `test/plugins/**` / `test/integration/**`:路由/插件集成测试
|
|
42
|
+
- 会创建 `Koa` + `router`
|
|
43
|
+
- 通过 `supertest` 调用 `request(app.callback()).get/post/...`
|
|
44
|
+
- 走完整 Koa 路由链路,但仍是“in-memory callback”,**不需要也不建议**真实 `app.listen`
|
|
45
|
+
|
|
46
|
+
原则:
|
|
47
|
+
- 测试环境不要 `app.listen` 开端口(避免端口占用、慢、难复现)
|
|
48
|
+
|
|
23
49
|
---
|
|
24
50
|
|
|
25
51
|
## 三、测试文件结构
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
|
|
38
38
|
### 80 - 工程结构(按需启用)
|
|
39
39
|
- **`80-backend-utils-and-libs.skill.md`**
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
40
|
+
- 共享适配/可抽包代码的放置策略(与 `dp-koa-framework-libs` 对齐)
|
|
41
|
+
- 运行环境判定:使用 `dp-koa-framework-core` 导出的 `isDebug()` / `getRuntimeEnvironmentLabel()`(不要引用本地 `src/framework` 文件)
|
|
42
42
|
|
|
43
43
|
### 90 - 测试(按需启用)
|
|
44
44
|
- **`90-backend-testing.skill.md`**
|
|
@@ -43,13 +43,14 @@ export class BaseController {
|
|
|
43
43
|
#### 1.1.2 `@State()`:读取整段 `ctx.state`
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import { Get, State } from '
|
|
46
|
+
import { Get, State } from 'dp-koa-framework-core';
|
|
47
47
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
48
|
+
import type { AppCtxState } from '@src/types/ctxState';
|
|
48
49
|
|
|
49
50
|
export class ExampleStateController extends BaseController {
|
|
50
51
|
@Get('/example/state-full')
|
|
51
52
|
async getWithFullState(
|
|
52
|
-
@State() state:
|
|
53
|
+
@State() state: AppCtxState,
|
|
53
54
|
): Promise<ControllerResponse<{ userId: number }>> {
|
|
54
55
|
return this.success({ userId: state.user.userId });
|
|
55
56
|
}
|
|
@@ -59,13 +60,14 @@ export class ExampleStateController extends BaseController {
|
|
|
59
60
|
#### 1.1.3 `@State('user')`:只读 `ctx.state.user`
|
|
60
61
|
|
|
61
62
|
```ts
|
|
62
|
-
import { Get, State } from '
|
|
63
|
+
import { Get, State } from 'dp-koa-framework-core';
|
|
63
64
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
65
|
+
import type { UserState } from '@src/types/ctxState';
|
|
64
66
|
|
|
65
67
|
export class ExampleUserSliceController extends BaseController {
|
|
66
68
|
@Get('/example/state-user')
|
|
67
69
|
async getWithUser(
|
|
68
|
-
@State('user') user:
|
|
70
|
+
@State('user') user: UserState,
|
|
69
71
|
): Promise<ControllerResponse<{ ok: boolean }>> {
|
|
70
72
|
return this.success({ ok: user.userId > 0 });
|
|
71
73
|
}
|
|
@@ -75,8 +77,9 @@ export class ExampleUserSliceController extends BaseController {
|
|
|
75
77
|
#### 1.1.4 `@ResponseValidateIf`:仅在条件成立时校验响应体
|
|
76
78
|
|
|
77
79
|
```ts
|
|
78
|
-
import { Get, State, ResponseValidateIf } from '
|
|
80
|
+
import { Get, State, ResponseValidateIf } from 'dp-koa-framework-core';
|
|
79
81
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
82
|
+
import type { AppCtxState } from '@src/types/ctxState';
|
|
80
83
|
|
|
81
84
|
// 示例 DTO:按实际接口替换
|
|
82
85
|
class UserProfileResponseDto {
|
|
@@ -88,7 +91,7 @@ export class ExampleResponseValidateController extends BaseController {
|
|
|
88
91
|
@ResponseValidateIf(UserProfileResponseDto, (data) => Boolean(data && data.data))
|
|
89
92
|
@Get('/example/profile')
|
|
90
93
|
async getProfile(
|
|
91
|
-
@State() state:
|
|
94
|
+
@State() state: AppCtxState,
|
|
92
95
|
): Promise<ControllerResponse<UserProfileResponseDto | null>> {
|
|
93
96
|
return this.success({
|
|
94
97
|
id: state.user.userId,
|
|
@@ -102,13 +105,13 @@ export class ExampleResponseValidateController extends BaseController {
|
|
|
102
105
|
目标:DTO 入参 → 调用 Service → 统一响应(`success/fail`)→ try/catch + logger
|
|
103
106
|
|
|
104
107
|
```ts
|
|
105
|
-
import { Get, Post, Query, Body, State, ResponseCode, ResponseHeader } from '
|
|
108
|
+
import { Get, Post, Query, Body, State, ResponseCode, ResponseHeader } from 'dp-koa-framework-core';
|
|
106
109
|
import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
|
|
107
110
|
import { Inject } from 'dp-ioc2';
|
|
108
|
-
import { logger } from '
|
|
111
|
+
import { logger } from 'dp-koa-framework-core';
|
|
109
112
|
import { SomeService } from '@src/service/some.service';
|
|
110
113
|
import { SomeQueryDto, SomeBodyDto } from '@src/dto/controller/xxx/some.controller.dto';
|
|
111
|
-
import { CommonServiceResultCode } from '
|
|
114
|
+
import { CommonServiceResultCode } from 'dp-koa-framework-core';
|
|
112
115
|
|
|
113
116
|
export class SomeController extends BaseController {
|
|
114
117
|
@Inject(SomeService)
|
|
@@ -132,4 +132,18 @@ alwaysApply: false
|
|
|
132
132
|
## 六、调用关系约束(必须)
|
|
133
133
|
- Controller 只能调用 Service 的公开方法,不应调用 Repository
|
|
134
134
|
- Service 不应调用 Controller,也不应依赖 Koa `Context`
|
|
135
|
-
- Service 之间如需调用,应通过依赖注入(`@Inject(OtherService)`),避免静态调用/循环依赖
|
|
135
|
+
- Service 之间如需调用,应通过依赖注入(`@Inject(OtherService)`),避免静态调用/循环依赖
|
|
136
|
+
- 禁止在 Service 内手动创建实例:
|
|
137
|
+
- ❌ 禁止 `new OtherService()` / `new XxxService()`(包括在方法内部临时 new)
|
|
138
|
+
- ❌ 禁止通过手写单例/缓存实例绕过 IoC 容器
|
|
139
|
+
- ✅ 必须依赖 dp-ioc2 在运行期注入(例如字段注入或构造注入)
|
|
140
|
+
- 推荐写法(字段注入示例):
|
|
141
|
+
```ts
|
|
142
|
+
import { Inject } from "dp-ioc2";
|
|
143
|
+
import { OtherService } from "./other.service";
|
|
144
|
+
|
|
145
|
+
export class MyService extends BaseService {
|
|
146
|
+
@Inject(OtherService)
|
|
147
|
+
private otherService!: OtherService;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
@@ -6,11 +6,15 @@ alwaysApply: false
|
|
|
6
6
|
## 一、错误处理
|
|
7
7
|
|
|
8
8
|
- 所有 async 方法必须 try/catch
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
9
|
+
- 业务错误(可预期、可分类):
|
|
10
|
+
- 主要在 **Service 层 catch 中**处理
|
|
11
|
+
- 返回 `CommonServiceResult.fail()/validationError/notFound/...`(由 Service 统一封装)
|
|
12
|
+
- 系统错误(不可预期、疑似异常):
|
|
13
|
+
- **必须记录日志(logger.error)**
|
|
14
|
+
- Service 层返回通用失败的 `CommonServiceResult.error(...)`
|
|
15
|
+
- 映射到 Controller:
|
|
16
|
+
- Controller 不应直接返回 `CommonServiceResult`
|
|
17
|
+
- Controller 必须把 Service 的 `CommonServiceResult` 通过 `this.success()/this.fail()` 映射为 `ControllerResponse`
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
@@ -5,7 +5,7 @@ alwaysApply: false
|
|
|
5
5
|
|
|
6
6
|
## 适用与触发(重要)
|
|
7
7
|
- 当需要新增/调整以下内容时启用本 Skill:
|
|
8
|
-
- `
|
|
8
|
+
- 共享适配/可抽包的代码(优先使用 `dp-koa-framework-libs`;仅临时代码可放业务模块内)
|
|
9
9
|
- `src/utils/**` 下的工具函数
|
|
10
10
|
- 业务模块内部的 `xxx.utils.ts`
|
|
11
11
|
|
|
@@ -19,9 +19,9 @@ alwaysApply: false
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 二、共享适配/可抽包代码放什么(必须)
|
|
23
23
|
|
|
24
|
-
### 2.1
|
|
24
|
+
### 2.1 适合的内容
|
|
25
25
|
- **第三方服务适配/封装**:
|
|
26
26
|
- 如:短信服务封装、支付网关封装、HTTP SDK 封装等
|
|
27
27
|
- 对外暴露一个干净的接口:如 `sendSms(phone, templateId, params)`
|
|
@@ -30,15 +30,67 @@ alwaysApply: false
|
|
|
30
30
|
- **有内部状态或生命周期的组件**:
|
|
31
31
|
- 连接池包装器、复杂缓存管理器、网关客户端等
|
|
32
32
|
|
|
33
|
-
### 2.2
|
|
33
|
+
### 2.2 不适合放到共享适配层的内容
|
|
34
34
|
- 单一的小工具函数(字符串、日期、数字转换等)
|
|
35
35
|
- 强业务耦合的逻辑(如订单价格计算、业务规则判断)
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
### 2.3 框架库内置工具(缓存/短信/验证码等)(重点)
|
|
40
|
+
当你需要使用“框架已经内置好的工具”,优先直接从 `dp-koa-framework-libs` 导入稳定导出;其中**缓存**建议按业务通过 `createCache + CacheType` 创建你的“业务缓存”对象。
|
|
41
|
+
|
|
42
|
+
#### 2.3.1 缓存工具:用 `createCache` + `CacheType` 构建你的缓存
|
|
43
|
+
- 核心提供缓存工厂:`createCache(name, type, customConfig?)`(配合 `CacheType`)
|
|
44
|
+
- 你可以完全参考 `dp-koa-framework-libs/src/libs/mCache.ts`(1-8 行)的写法,在你自己的 `libs/` 里创建“业务自定义缓存对象”
|
|
45
|
+
- 注意:`name` 作为全局缓存标识,同名会复用已有缓存实例(避免重复创建)
|
|
46
|
+
- `CacheType` 用于选择默认 TTL/容量策略(来自 `dp-koa-framework-core` 缓存模块默认配置):
|
|
47
|
+
- `CacheType.USER`:用户缓存(默认 `stdTTL=1800` 秒,`maxKeys=500`)
|
|
48
|
+
- `CacheType.CONTROLLER`:控制器结果缓存(默认 `stdTTL=60` 秒,`maxKeys=2000`)
|
|
49
|
+
- `CacheType.SESSION`:会话缓存(默认 `stdTTL=3600` 秒,`maxKeys=1000`)
|
|
50
|
+
- `CacheType.CAPTCHA`:验证码缓存(默认 `stdTTL=300` 秒,`maxKeys=100`)
|
|
51
|
+
- `CacheType.TEMP`:临时缓存(默认 `stdTTL=60` 秒,`maxKeys=100`)
|
|
52
|
+
- `customConfig` 的行为:会在“默认配置(DEFAULT_CACHE_CONFIG)+ CacheType 配置”基础上进行合并,从而覆盖 `stdTTL/maxKeys/checkperiod/...` 等参数
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { createCache, CacheType } from "dp-koa-framework-core";
|
|
56
|
+
|
|
57
|
+
// 例:给当前业务域创建一个“用户缓存”
|
|
58
|
+
export const myUserCache = createCache("my-user", CacheType.USER, {
|
|
59
|
+
stdTTL: 600, // 10分钟过期(秒)
|
|
60
|
+
maxKeys: 500, // 用户缓存最大条目数
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
使用方式示例(以 `node-cache` 风格的 `get/set` 为准):
|
|
65
|
+
```ts
|
|
66
|
+
import { myUserCache } from "@src/libs/myUserCache"; // 示例:把 myUserCache 放到你的 libs 文件并导出
|
|
67
|
+
|
|
68
|
+
const cached = myUserCache.get(String(userId));
|
|
69
|
+
if (!cached) {
|
|
70
|
+
const value = await buildValue();
|
|
71
|
+
myUserCache.set(String(userId), value); // TTL 由 createCache 配置决定
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### 2.3.2 其它常用内置工具(快速导入)
|
|
76
|
+
- 验证码:`CaptchaGenerator`
|
|
77
|
+
- 短信:
|
|
78
|
+
- `TencentSms`(类)
|
|
79
|
+
- `tencentSms`(函数/实例)
|
|
80
|
+
- 邮件:`AokEmailSender`
|
|
81
|
+
- COS/文件:
|
|
82
|
+
- `isCosConfigured`
|
|
83
|
+
- `uploadToCos`
|
|
84
|
+
- `getFilePublicUrl`
|
|
85
|
+
- `webofficeCosKey`
|
|
86
|
+
- 通用业务校验:`ServiceValidate`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 三、`src/utils/`** 放什么(必须)
|
|
40
91
|
|
|
41
92
|
### 3.1 适合放在 utils 的内容
|
|
93
|
+
|
|
42
94
|
- **与业务无关的纯函数工具**:
|
|
43
95
|
- 时间、字符串、数字处理
|
|
44
96
|
- 通用 Promise/重试/节流/防抖等
|
|
@@ -70,13 +122,13 @@ alwaysApply: false
|
|
|
70
122
|
新增工具/类库时,按以下顺序判断:
|
|
71
123
|
|
|
72
124
|
1. **是否依赖第三方服务/外部系统或有复杂状态?**
|
|
73
|
-
- 是 →
|
|
125
|
+
- 是 → 优先放共享适配层(`dp-koa-framework-libs` 或独立 npm 包)
|
|
74
126
|
2. **是否明显只服务某一个业务模块?**
|
|
75
127
|
- 是 → 放到该模块自己的 `xxx.utils.ts`
|
|
76
128
|
3. **是否 100% 纯函数、无状态且可跨多个模块复用?**
|
|
77
129
|
- 是 → 放到全局 `utils/`
|
|
78
130
|
4. **未来是否有机会抽为独立 npm 包?**
|
|
79
|
-
- 是 →
|
|
131
|
+
- 是 → 更倾向放共享适配层(`dp-koa-framework-libs` 或独立 npm 包)
|
|
80
132
|
|
|
81
133
|
---
|
|
82
134
|
|
|
@@ -87,4 +139,21 @@ alwaysApply: false
|
|
|
87
139
|
- 导出:优先导出类或工厂函数,如 `export class TencentSmsClient { ... }`
|
|
88
140
|
- `utils` 目录下:
|
|
89
141
|
- 文件名:`date.utils.ts` / `string.utils.ts` / `testDataInitializer.ts` 等
|
|
90
|
-
- 导出:纯函数 `export function xxx(...) { ... }`
|
|
142
|
+
- 导出:纯函数 `export function xxx(...) { ... }`
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 七、运行环境判定(必须)
|
|
147
|
+
|
|
148
|
+
应统一使用 `dp-koa-framework-core` 导出的**运行环境判定** API,避免散落 `NODE_ENV === 'production'` 判断。
|
|
149
|
+
|
|
150
|
+
| API | 含义 |
|
|
151
|
+
| ------------------------------ | -------------------------------------------------------- |
|
|
152
|
+
| `isDebug()` | `process.argv` 含 `--env=debug` 为 `true` |
|
|
153
|
+
| `getRuntimeEnvironmentLabel()` | `'development'`(debug)或 `'production'`(非 debug),用于日志/元数据 |
|
|
154
|
+
|
|
155
|
+
**约定**:
|
|
156
|
+
|
|
157
|
+
- **非 debug = 生产口径**(迁移、静态缓存策略、对外错误信息是否脱敏等)。
|
|
158
|
+
- **不要**单独用 `NODE_ENV` 替代上述判定;`NODE_ENV=test` 仅用于 Jest 等测试分支。
|
|
159
|
+
- 新增种子脚本若需读 `.env.development`,启动命令应带 `--env=debug`(与 `bootstrap` 一致)。
|
|
@@ -20,6 +20,32 @@ alwaysApply: false
|
|
|
20
20
|
- 测试必须相互独立
|
|
21
21
|
- 每个测试前重置数据
|
|
22
22
|
|
|
23
|
+
## 二.2 涉及数据库读写的 Service 测试:必须使用内存数据库
|
|
24
|
+
|
|
25
|
+
- 如果测试中调用的 Service(或其依赖)会发生数据库读写(查询/插入/更新/删除、事务、迁移/初始化等):
|
|
26
|
+
- 必须启用内存数据库模式:通过 `TestDatabaseHelper.initTestDatabase(...)` 初始化测试用 `DataSource`
|
|
27
|
+
- 必须在测试结束后清理:调用 `TestDatabaseHelper.cleanupTestDatabase()`(确保释放资源,并清理缓存)
|
|
28
|
+
- 禁止为了“方便”而只模拟数据:
|
|
29
|
+
- 不允许用简单对象/假 Repository 来替代数据库读写(会掩盖 SQL/实体映射/事务边界问题)
|
|
30
|
+
- 允许 mock 的范围:仅限非数据库行为(如外部 HTTP 调用、消息发送、第三方 SDK 等)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 二.1 测试是否发“真实 HTTP”(需要理解)
|
|
35
|
+
|
|
36
|
+
- `test/controllers/**`:Controller 单元测试
|
|
37
|
+
- 通常是 `new Controller()` 后直接调用 controller 方法
|
|
38
|
+
- 不会触发真实的 `app.listen` 端口监听
|
|
39
|
+
- 这是在验证“编排/返回结构/装饰器效果(mock 情况)”的正确性
|
|
40
|
+
|
|
41
|
+
- `test/plugins/**` / `test/integration/**`:路由/插件集成测试
|
|
42
|
+
- 会创建 `Koa` + `router`
|
|
43
|
+
- 通过 `supertest` 调用 `request(app.callback()).get/post/...`
|
|
44
|
+
- 走完整 Koa 路由链路,但仍是“in-memory callback”,**不需要也不建议**真实 `app.listen`
|
|
45
|
+
|
|
46
|
+
原则:
|
|
47
|
+
- 测试环境不要 `app.listen` 开端口(避免端口占用、慢、难复现)
|
|
48
|
+
|
|
23
49
|
---
|
|
24
50
|
|
|
25
51
|
## 三、测试文件结构
|