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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dp-koa",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Scaffold a DP-Koa framework project from the official template",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@ alwaysApply: true
12
12
 
13
13
  ## 运行环境判定(debug 口径,必须)
14
14
 
15
- **唯一标准**:以 `src/framework/utils/function.ts` 为准,**不要**用 `process.env.NODE_ENV` 作为“是否生产/是否调试”的单一判断。
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 '@src/framework/decorator/controller';
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: { user: { userId: number; type?: number } },
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 '@src/framework/decorator/controller';
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: { userId: number; type?: number },
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 '@src/framework/decorator/controller';
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: { user: { userId: number } },
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 '@src/framework/decorator/controller';
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 '@src/framework/utils/logger';
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 '@src/framework/types/ServiceResult';
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 '@src/framework/decorator/controller';
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
- - 使用 `CommonServiceResult.fail()`
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`)或框架启动流程(`src/framework/utils/bootstrap.ts`)时启用本 Skill。
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
- 以 `src/framework/utils/bootstrap.ts` 为准:
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.ts`)
39
+ ### 2.1.1 环境变量文件与 `.env` 选择(对齐 `dp-koa-framework-core` 的 bootstrap
40
40
 
41
- - **与 `NODE_ENV` 解耦**:`bootstrap.ts` 最早加载的 `.env` 文件由 **`isDebug()`** 决定:
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
- alwaysApply: false
3
- ---
2
+
3
+ ## alwaysApply: false
4
+
4
5
  # Skill:路由注册规范(routers/index.ts + bindRouter)
5
6
 
6
7
  ## 适用与触发(重要)
7
- - 仅当需要新增/修改路由绑定(`src/routers/index.ts`)或调整路由系统(`src/framework/utils/router.ts`)时启用本 Skill。
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
- 本项目路由注册入口:`src/routers/index.ts`
15
- 路由绑定工具:`src/framework/utils/router.ts` 的 `bindRouter(...)`
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/*`)或调整中间件注册顺序(`src/framework/utils/bootstrap.ts`、`src/app.ts`、`src/routers/index.ts`)时启用本 Skill。
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
- 在 `src/framework/utils/bootstrap.ts` 中,框架已注册:
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
- - `src/libs/**` 下的类库/适配层
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
- ## 二、`src/libs/**` 放什么(必须)
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 不适合放在 libs 的内容
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
- - 是 → 优先放 `libs/`(适配层/小 SDK)
128
+ - 是 → 优先放共享适配层(`dp-koa-framework-libs` 或独立 npm 包)
74
129
  2. **是否明显只服务某一个业务模块?**
75
- - 是 → 放到该模块自己的 `xxx.utils.ts`
130
+ - 是 → 放到该模块自己的 `xxx.utils.ts`
76
131
  3. **是否 100% 纯函数、无状态且可跨多个模块复用?**
77
- - 是 → 放到全局 `utils/`
132
+ - 是 → 放到全局 `utils/`
78
133
  4. **未来是否有机会抽为独立 npm 包?**
79
- - 是 → 更倾向放 `libs/`
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
- ## 七、框架级 `src/framework/utils/function.ts`(必须)
149
+ ## 七、运行环境判定(必须)
95
150
 
96
- 本文件提供**运行环境判定**,全项目应统一使用,避免散落 `NODE_ENV === 'production'` 判断。
151
+ 应统一使用 `dp-koa-framework-core` 导出的**运行环境判定** API,避免散落 `NODE_ENV === 'production'` 判断。
97
152
 
98
- | API | 含义 |
99
- |-----|------|
100
- | `isDebug()` | `process.argv` 含 `--env=debug` 为 `true` |
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`(见 `src/framework/plugins/types.ts`)。
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/framework/plugins/registry.ts`
42
+ - 插件注册表:`src/plugins/registry.ts`
43
43
  - 宿主启动接入点:`src/app.ts`
44
- - beforeBootstrap:计算启用插件、执行插件 before hook、注册插件路由、合并插件实体后初始化 DB
45
- - afterBootstrap:执行插件 after hook(定时任务/订阅等)
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
- - `src/libs` vs `src/utils` 的放置规则与决策清单
41
- - `src/framework/utils/function.ts`:`isDebug()` / `getRuntimeEnvironmentLabel()` 约定
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 '@src/framework/decorator/controller';
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: { user: { userId: number; type?: number } },
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 '@src/framework/decorator/controller';
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: { userId: number; type?: number },
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 '@src/framework/decorator/controller';
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: { user: { userId: number } },
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 '@src/framework/decorator/controller';
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 '@src/framework/utils/logger';
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 '@src/framework/types/ServiceResult';
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
- - 使用 `CommonServiceResult.fail()`
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
- - `src/libs/**` 下的类库/适配层
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
- ## 二、`src/libs/**` 放什么(必须)
22
+ ## 二、共享适配/可抽包代码放什么(必须)
23
23
 
24
- ### 2.1 适合放在 libs 的内容
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 不适合放在 libs 的内容
33
+ ### 2.2 不适合放到共享适配层的内容
34
34
  - 单一的小工具函数(字符串、日期、数字转换等)
35
35
  - 强业务耦合的逻辑(如订单价格计算、业务规则判断)
36
36
 
37
37
  ---
38
38
 
39
- ## 三、`src/utils/**` 放什么(必须)
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
- - 是 → 优先放 `libs/`(适配层/小 SDK)
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
- - 是 → 更倾向放 `libs/`
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
  ## 三、测试文件结构
@@ -0,0 +1,9 @@
1
+ export interface UserState {
2
+ userId: number;
3
+ type?: number;
4
+ }
5
+
6
+ export interface AppCtxState {
7
+ user: UserState;
8
+ }
9
+