create-dp-koa 1.1.10 → 1.1.12

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.10",
3
+ "version": "1.1.12",
4
4
  "description": "Scaffold a DP-Koa framework project from the official template",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,14 +5,14 @@ alwaysApply: true
5
5
 
6
6
  ## 适用范围
7
7
  - 本项目所有 TypeScript 后端代码
8
- - 技术栈:Koa + TypeORM + dp-ioc2 + 自定义装饰器体系
8
+ - 技术栈:Koa + TypeORM + dp-ioc2 + 自定义装饰器体系;框架以 npm 包 **`dp-koa-framework-core`**(运行时与对外稳定 API)与 **`dp-koa-framework-libs`**(共享适配/配套能力)引入,版本以项目 `package.json` 为准。
9
9
  - **包管理工具:必须使用 yarn(禁止使用 npm)**
10
10
 
11
11
  ---
12
12
 
13
13
  ## 运行环境判定(debug 口径,必须)
14
14
 
15
- **唯一标准**:以 `src/framework/utils/function.ts` 为准,**不要**用 `process.env.NODE_ENV` 作为“是否生产/是否调试”的单一判断。
15
+ **唯一标准**:以 **`dp-koa-framework-core`** 导出的运行环境判定 API(`isDebug()`、`getRuntimeEnvironmentLabel()` 等)为准,**不要**用 `process.env.NODE_ENV` 作为“是否生产/是否调试”的单一判断。
16
16
 
17
17
  - **`isDebug()`**:当进程启动参数中包含 `--env=debug` 时为 `true`。
18
18
  - **非 debug**:一律按**生产口径**处理(例如:迁移、静态资源长缓存、对外错误信息脱敏等)。
@@ -12,23 +12,111 @@ alwaysApply: false
12
12
 
13
13
  ## 一、写 Controller 的推荐范式(必须对齐)
14
14
 
15
- ### 1.1 参考样例(写代码前先对齐)
16
- - 推荐在实现前先对齐以下文件的写法(不要臆造新风格):
17
- - `src/controllers/example/NewAnnotationExampleController.ts`
18
- - `src/controllers/home/ytUser.controller.ts`
19
- - `src/controllers/base.controller.ts`
15
+ ### 1.1 内嵌参考样例(本 Skill 自包含)
16
+ 以下为从本项目常用写法整理的最小示例;**实现新接口时请先对齐本节的装饰器与类型风格**。若仓库中另有 `src/controllers/example/*` 等运行时代码,可作为补充参考,但**不必依赖**特定业务 Controller 文件是否存在。
17
+
18
+ #### 1.1.1 `BaseController` 与 `ControllerResponse`(统一响应)
19
+
20
+ ```ts
21
+ export class ControllerResponse<T> {
22
+ code = 0;
23
+ data: T | null = null;
24
+ message = '';
25
+
26
+ constructor(code: number, data: T | null, message?: string) {
27
+ this.code = code;
28
+ this.data = data;
29
+ if (message) this.message = message;
30
+ }
31
+ }
32
+
33
+ export class BaseController {
34
+ success<T>(data: T, message?: string) {
35
+ return new ControllerResponse<T>(0, data, message);
36
+ }
37
+ fail<T>(code: number, message?: string) {
38
+ return new ControllerResponse<T>(code, null, message);
39
+ }
40
+ }
41
+ ```
42
+
43
+ #### 1.1.2 `@State()`:读取整段 `ctx.state`
44
+
45
+ ```ts
46
+ import { Get, State } from 'dp-koa-framework-core';
47
+ import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
48
+
49
+ export class ExampleStateController extends BaseController {
50
+ @Get('/example/state-full')
51
+ async getWithFullState(
52
+ @State() state: { user: { userId: number; type?: number } },
53
+ ): Promise<ControllerResponse<{ userId: number }>> {
54
+ return this.success({ userId: state.user.userId });
55
+ }
56
+ }
57
+ ```
58
+
59
+ #### 1.1.3 `@State('user')`:只读 `ctx.state.user`
60
+
61
+ ```ts
62
+ import { Get, State } from 'dp-koa-framework-core';
63
+ import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
64
+
65
+ export class ExampleUserSliceController extends BaseController {
66
+ @Get('/example/state-user')
67
+ async getWithUser(
68
+ @State('user') user: { userId: number; type?: number },
69
+ ): Promise<ControllerResponse<{ ok: boolean }>> {
70
+ return this.success({ ok: user.userId > 0 });
71
+ }
72
+ }
73
+ ```
74
+
75
+ #### 1.1.4 `@ResponseValidateIf`:仅在条件成立时校验响应体
76
+
77
+ ```ts
78
+ import { Get, State, ResponseValidateIf } from 'dp-koa-framework-core';
79
+ import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
80
+
81
+ // 示例 DTO:按实际接口替换
82
+ class UserProfileResponseDto {
83
+ id!: number;
84
+ nickName!: string;
85
+ }
86
+
87
+ export class ExampleResponseValidateController extends BaseController {
88
+ @ResponseValidateIf(UserProfileResponseDto, (data) => Boolean(data && data.data))
89
+ @Get('/example/profile')
90
+ async getProfile(
91
+ @State() state: { user: { userId: number } },
92
+ ): Promise<ControllerResponse<UserProfileResponseDto | null>> {
93
+ return this.success({
94
+ id: state.user.userId,
95
+ nickName: 'demo',
96
+ });
97
+ }
98
+ }
99
+ ```
20
100
 
21
101
  ### 1.2 Controller 标准骨架(复制这个结构)
22
102
  目标:DTO 入参 → 调用 Service → 统一响应(`success/fail`)→ try/catch + logger
23
103
 
24
104
  ```ts
25
- import { Get, Post, Query, Body, State, ResponseCode, ResponseHeader } from '@src/framework/decorator/controller';
105
+ import {
106
+ Get,
107
+ Post,
108
+ Query,
109
+ Body,
110
+ State,
111
+ ResponseCode,
112
+ ResponseHeader,
113
+ logger,
114
+ CommonServiceResultCode,
115
+ } from 'dp-koa-framework-core';
26
116
  import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
27
117
  import { Inject } from 'dp-ioc2';
28
- import { logger } from '@src/framework/utils/logger';
29
118
  import { SomeService } from '@src/service/some.service';
30
119
  import { SomeQueryDto, SomeBodyDto } from '@src/dto/controller/xxx/some.controller.dto';
31
- import { CommonServiceResultCode } from '@src/framework/types/ServiceResult';
32
120
 
33
121
  export class SomeController extends BaseController {
34
122
  @Inject(SomeService)
@@ -37,9 +125,9 @@ export class SomeController extends BaseController {
37
125
  @Get('/some/path')
38
126
  @ResponseCode(200)
39
127
  async getSomething(
40
- @Query() query: SomeQueryDto,
128
+ @Query(SomeQueryDto) query: SomeQueryDto,
41
129
  @State('user') user: { userId: number; type?: number },
42
- ): Promise<ControllerResponse<SomeResponseDto>> {
130
+ ): Promise<ControllerResponse<any>> {
43
131
  try {
44
132
  const result = await this.someService.someMethod(query, user.userId);
45
133
  if (result.code !== CommonServiceResultCode.SUCCESS) {
@@ -59,7 +147,6 @@ export class SomeController extends BaseController {
59
147
  - Controller **不直接访问数据库/Repository**
60
148
  - Controller **不 return Service 原始结果**,必须映射 `success/fail`
61
149
  - Controller `async` **必须 try/catch**
62
- !- Controller 的返回类型 `ControllerResponse<T>` **必须显式指定 T,禁止使用 `any`**
63
150
 
64
151
  ---
65
152
 
@@ -72,19 +159,17 @@ export class SomeController extends BaseController {
72
159
  ### 2.2 参数注解(重点)
73
160
  - **`@Query()`**:
74
161
  - 读取 query 参数
75
- - 推荐写法:`@Query() query: XxxQueryDto`
162
+ - 推荐写法:`@Query(SomeQueryDto) query: XxxQueryDto`
76
163
  - **`@Body()`**:
77
164
  - 读取 body 参数
78
- - 推荐写法:`@Body() body: XxxBodyDto`
79
- - **`@Params()`**:
80
- - 读取路径参数(如 `/:id`、`/:id/files/:fileId`)
81
- - 推荐写法:`@Params(ParamsDto) params: XxxParamsDto`
165
+ - 推荐写法:`@Body(SomeBodyDto) body: XxxBodyDto`
82
166
  - **`@State()`**:
83
- - 读取整个 `ctx.state`(样例:`ytUser.controller.ts`)
167
+ - 读取整个 `ctx.state`(样例:见 **1.1.2**)
84
168
  - 推荐写法:`@State() state: { user: { userId: number; type?: number } }`
85
169
  - **`@State('user')`**:
86
- - 读取 `ctx.state.user`(样例:`NewAnnotationExampleController.ts`)
170
+ - 读取 `ctx.state.user`(样例:见 **1.1.3**)
87
171
  - 推荐写法:`@State('user') user: { userId: number; type?: number }`
172
+ - **`@Session` / `@Cookie`**:会话与普通 Cookie 的注入规则见 **2.5**(均从 **`dp-koa-framework-core`** 导入)
88
173
 
89
174
  禁止:
90
175
  - ❌ `@Query() query: any` / `@Body() body: any` / `@State() state: any`(尽量不要)
@@ -92,10 +177,97 @@ export class SomeController extends BaseController {
92
177
  ### 2.3 响应元信息注解
93
178
  - **`@ResponseCode(200|201|...)`**:设置 HTTP 状态码(样例中 GET 用 200,POST 用 201)
94
179
  - **`@ResponseHeader(key, value)`**:为响应添加 header
180
+ - **HTTP 3xx 重定向**:使用 **`return redirect(...)`**(**`dp-koa-framework-core`**),规则见 **§2.6**;与 `this.success` / JSON 体返回**互斥**。
95
181
 
96
182
  ### 2.4 响应校验注解(进阶,按需)
97
183
  - **`@ResponseValidator(DtoClass, objectKey?)`**:对响应数据做校验
98
- - **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn 返回真时才校验(样例:`ytUser.controller.ts`)
184
+ - **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn 返回真时才校验(样例:见 **1.1.4**)
185
+
186
+ ### 2.5 `@Session` 与 `@Cookie`(**`dp-koa-framework-core`**)
187
+
188
+ 均由 **`dp-koa-framework-core`** 导出(与 `@Get` / `@State` 同源:`import { Session, Cookie, ... } from 'dp-koa-framework-core'`)。
189
+
190
+ #### `@Session`(`ctx.session`)
191
+
192
+ - **何时用**:与 JWT + `ctx.state`(`@State`)**并存**;适合 OAuth2/OIDC/SSO 等需要在服务端存**短期握手数据**的场景,**不替代**鉴权中间件写入的 `ctx.state.user`。
193
+ - **谁负责启用**:**`dp-koa-framework-core`** 在 `bootstrap` 中已注册 `koa-session`,业务 **`src/app.ts` 一般不要再 `app.use(session(...))`**。
194
+ - **语义与绑定数据**:
195
+ - `@Session()`:注入**整段** `ctx.session`(类型建议用 interface/明确结构,避免裸 `any`)。
196
+ - `@Session('uid')`:注入 `ctx.session['uid']`(字符串参数表示 **key**)。
197
+ - `@Session(SomeDtoClass)`:传入 **class** 时,与 `@Body(SomeDto)` 类似,会对**当前取到的 session 对象**(无 key 时为整段 session)做 **class-transformer + class-validator**。
198
+ - **默认行为(框架内建)**:会话 Cookie 名默认 `dpkoa.sid`、`httpOnly`、`signed`、`rolling`、`sameSite: 'lax'` 等由 **`dp-koa-framework-core`** 的 bootstrap 与 `koa-session` 决定;可用环境变量覆盖。
199
+ - **环境变量(摘要)**:
200
+ - `SESSION_KEYS`:逗号分隔多 key(**优先**作签名密钥);
201
+ - `SESSION_SECRET`:单 key(无 `SESSION_KEYS` 时使用);
202
+ - `SESSION_KEY`:Cookie 名;
203
+ - `SESSION_MAX_AGE_MS`:过期毫秒数。
204
+ - **实践要求**:生产**必须**配置 `SESSION_KEYS` 或 `SESSION_SECRET`;Session 仅存短期字段,流程结束**清理**一次性键;跨站回调重点核对 `SameSite`、HTTPS、域名与代理头。
205
+ - **延伸阅读**:`docs/SESSION_DECORATOR_GUIDE.md`(Session 环境变量与更多示例)。
206
+
207
+ #### `@Cookie`(读 / 写 Cookie)
208
+
209
+ - **`@Cookie('name')`**:读取名为 `name` 的单个 Cookie;底层为带 **`signed: true`** 的 `ctx.cookies.get`。
210
+ - **`@Cookie()`**(无参):注入 **`CookieHandle`**:`{ get(name, opts?), set(name, value, opts?) }`,默认读写仍带 **`signed: true`**,可在 `opts` 中按需覆盖。
211
+ - **硬性限制**:**不支持** `@Cookie(SomeDtoClass)`;装饰器要求参数为 **string 或省略**,否则**抛错**。需要多字段时多次 `@Cookie('a')` / `@Cookie('b')`,或极少数场景自行解析(仍保持 Controller 薄)。
212
+ - **前置条件**:依赖 Koa 标准 **`ctx.cookies`**;`set` 时按需传入 `maxAge`、`path`、`httpOnly`、`sameSite` 等安全相关选项。
213
+
214
+ #### 示例(Session / Cookie / State 组合)
215
+
216
+ ```ts
217
+ import { Get, Session, Cookie, State } from 'dp-koa-framework-core';
218
+ import { BaseController, ControllerResponse } from '@src/controllers/base.controller';
219
+
220
+ export class OauthDemoController extends BaseController {
221
+ @Get('/oauth/callback')
222
+ async callback(
223
+ @Session('oauthState') oauthState: string | undefined,
224
+ @State('user') user: { userId: number } | undefined,
225
+ ): Promise<ControllerResponse<{ ok: boolean }>> {
226
+ return this.success({ ok: Boolean(oauthState && user) });
227
+ }
228
+
229
+ @Get('/prefs')
230
+ async prefs(@Cookie('theme') theme: string | undefined): Promise<ControllerResponse<{ theme: string | null }>> {
231
+ return this.success({ theme: theme ?? null });
232
+ }
233
+
234
+ @Get('/notice-dismiss')
235
+ async dismiss(
236
+ @Cookie() jar: { get: (name: string, opts?: object) => string | undefined; set: (name: string, value: string | null, opts?: object) => void },
237
+ ): Promise<ControllerResponse<null>> {
238
+ jar.set('notice', '', { maxAge: 0 });
239
+ return this.success(null);
240
+ }
241
+ }
242
+ ```
243
+
244
+ ### 2.6 声明式 HTTP 重定向(`redirect`)
245
+
246
+ - **导入**:`import { redirect } from 'dp-koa-framework-core'`。另导出 `RedirectResponse`、`RedirectStatusCode`、`isRedirectResponse`、`applyRedirectToCtx`(后两者多用于中间件或脱离 `bindRouter` 的手动写入;常规控制器 **`return redirect(...)`** 即可)。
247
+ - **基本用法**:**`return redirect(location)`**;`location` 将作为 **`Location`** 响应头。可选 **`redirect(location, { status?, headers? })`**。
248
+ - **`status`**:仅 **`302` | `303` | `307` | `308`**,默认 **`302`**。
249
+ - **路由层**:识别重定向后写入 **`ctx.status` / `Location` / `Cache-Control: no-store`**,**`ctx.body = ''`** 并短路;**跳过**响应 DTO 校验与 JSON 赋值。
250
+ - **与 `@ControllerCache`**:命中缓存时直接返回缓存体,与本次 `redirect` 二选一;勿在同一方法混用两种出口语义。
251
+ - **不要**与 `this.success` / `ControllerResponse` 混用;跳转方法可省略 `@ResponseCode`(最终以 `redirect` 的 `status` 为准)。
252
+ - **开放重定向**:对用户可控 `next`、回调 URL 等做白名单或站内路径约束,并正确编码 query。
253
+ - **延伸阅读**:`packages/dp-koa-framework-core/README.md` 中「HTTP 重定向(SSO 常用)」。
254
+
255
+ #### 示例(SSO 入口)
256
+
257
+ ```ts
258
+ import { Get, Query, redirect } from 'dp-koa-framework-core';
259
+ import { BaseController } from '@src/controllers/base.controller';
260
+
261
+ export class SsoController extends BaseController {
262
+ @Get('/sso/login')
263
+ async login(@Query() query: { next?: string }): Promise<ReturnType<typeof redirect>> {
264
+ const next = query?.next;
265
+ const safeNext = next && next.startsWith('/') ? next : '/';
266
+ const url = `https://sso.example.com/authorize?next=${encodeURIComponent(safeNext)}`;
267
+ return redirect(url, { status: 302 });
268
+ }
269
+ }
270
+ ```
99
271
 
100
272
  ---
101
273
 
@@ -105,9 +277,15 @@ export class SomeController extends BaseController {
105
277
  - `@Get('/xxx') + @State('user')`(或 `@State()` 取整段 state)
106
278
 
107
279
  ### 3.2 带 body 的 POST 接口
108
- - `@Post('/xxx') + @Body() body: Dto + @ResponseCode(201)`
280
+ - `@Post('/xxx') + @Body(SomeBodyDto) body: SomeBodyDto + @ResponseCode(201)`
109
281
 
110
282
  ### 3.3 需要自定义 header
111
283
  - `@ResponseHeader('X-XXX', 'value')`
112
284
 
285
+ ### 3.4 Session 与登录态 / OAuth
286
+ - 回调或握手:`@Session('state')` / `@Session()` 承载临时字段,与 `@State('user')` 分职责(Session=临时、State=已鉴权用户)。
287
+ - 非 Session 的普通 Cookie:在方法内使用 `@Cookie()` 注入的 `CookieHandle.set(...)`,避免在 Controller 外散落 `ctx.cookies`。
288
+
289
+ ### 3.5 HTTP 重定向(SSO / 登录页)
290
+ - **`return redirect(url)`** 或带 **`{ status: 303 }`** 等选项,详见 **§2.6**;与 **`this.success` / `ControllerResponse`** 不要混在同一返回路径。
113
291
 
@@ -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)
@@ -199,9 +199,8 @@ async createUser(@Body(CreateUserControllerDto) body: CreateUserControllerDto) {
199
199
  - 常用装饰器:`@IsString()`, `@IsEmail()`, `@IsNotEmpty()`, `@MinLength()`, `@IsOptional()` 等
200
200
 
201
201
  ### 4.2 Controller DTO 特殊处理
202
- - **@Transform 必须使用 class-transformer**:Controller 参数绑定走 `plainToInstance`,只有 **class-transformer** 的 `@Transform` 会生效。须 `import { Transform } from 'class-transformer'`,**不要**使用框架 `@src/framework/decorator/controller` 的 Transform(后者不参与 plainToInstance,会不生效)。
203
- - 使用 `@Transform(({ value }) => yourTransform(value))` 进行数据转换(如逗号分隔字符串转数组、trim、自定义转 number)。
204
- - 可以包含 HTTP 层特定字段(如 `vcodeToken`、`requestId`)。
202
+ - 可以使用 `@Transform` 进行数据转换(如字符串转数字、trim)
203
+ - 可以包含 HTTP 层特定的字段(如 `vcodeToken`、`requestId`)
205
204
 
206
205
  ### 4.3 Service DTO 要求
207
206
  - **禁止**包含 HTTP 层特性(如 `@Transform`、HTTP 特定字段)
@@ -210,30 +209,17 @@ async createUser(@Body(CreateUserControllerDto) body: CreateUserControllerDto) {
210
209
 
211
210
  ### 4.4 示例对比
212
211
 
213
- #### Controller DTO(包含 HTTP 层转换,使用 class-transformer)
212
+ #### Controller DTO(包含 HTTP 层转换)
214
213
  ```typescript
215
- import { Transform } from 'class-transformer';
216
-
217
214
  export class CreateUserControllerDto {
218
- @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
215
+ @Transform((val) => Trim(String(val)))
219
216
  @IsString()
220
217
  @IsEmail()
221
218
  email: string;
222
219
 
223
- // number 可由 enableImplicitConversion 自动转,无需 @Transform;若需自定义再用:
224
- @Transform(({ value }) => (value != null && value !== '' ? Number(value) : undefined))
225
- @IsOptional()
226
- @IsInt()
227
- age?: number;
228
- }
229
-
230
- // Query 中单字符串/逗号分隔转数组示例
231
- export class QueryDto {
232
- @Transform(({ value }) => toTagsArray(value)) // 如 "a,b" -> ["a","b"],已是数组则原样
233
- @IsOptional()
234
- @IsArray()
235
- @IsString({ each: true })
236
- tags?: string[];
220
+ @Transform((val) => Number(val))
221
+ @IsNumber()
222
+ age: number;
237
223
  }
238
224
  ```
239
225
 
@@ -249,14 +235,6 @@ export class CreateUserServiceDto {
249
235
  }
250
236
  ```
251
237
 
252
- ### 4.5 class-transformer 与 Controller 参数转换(框架内置)
253
-
254
- - **框架行为**:对 `@Body(Dto)` / `@Query(Dto)` / `@Params(Dto)`,框架(dp-koa-framework)会使用 **class-transformer** 的 `plainToInstance(Dto, data, { enableImplicitConversion: true })` 从原始请求数据构建 DTO 实例,再经 **class-validator** 校验;校验通过后将该实例写回控制器参数,因此控制器方法收到的是**已转换且已校验的 DTO 实例**,而非原始 `ctx.body` / `ctx.query` / `ctx.params`。
255
- - **Query/Params 类型**:HTTP Query 与 Params 均为字符串。启用 `enableImplicitConversion` 后,DTO 中声明为 `number` 的字段(如 `page`、`pageSize`)会自动从字符串转为数字,**无需**在 Controller DTO 上为每个字段写 `@Transform`。
256
- - **适用范围**:仅对 **Body、Query、Params** 做转换与校验;**State、Headers** 不经过 plainToInstance,保持原样传入。
257
- - **自定义转换**:若需更细粒度控制(如逗号分隔字符串转数组、trim),在 Controller DTO 上使用 **class-transformer** 的 `@Transform`:`import { Transform } from 'class-transformer'`,写法为 `@Transform(({ value }) => yourFn(value))`,在 plainToInstance 时自动应用。
258
- - **DtoValidator**:在 Service 层手动调用 `DtoValidator.validate` 时,框架同样使用 `plainToInstance` + `enableImplicitConversion`,与上述参数绑定行为一致。
259
-
260
238
  ---
261
239
 
262
240
  ## 五、校验流程
@@ -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`** 暴露的启动 API(`bootstrap`、`setBeforeBootstrap` 等)的接入方式时启用本 Skill;若需修改框架内置启动顺序,应到 **`dp-koa-framework-core`** 包源码修改。
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。
@@ -4,7 +4,7 @@ alwaysApply: false
4
4
  # Skill:路由注册规范(routers/index.ts + bindRouter)
5
5
 
6
6
  ## 适用与触发(重要)
7
- - 仅当需要新增/修改路由绑定(`src/routers/index.ts`)或调整路由系统(`src/framework/utils/router.ts`)时启用本 Skill。
7
+ - 仅当需要新增/修改路由绑定(`src/routers/index.ts`)或调整对 **`dp-koa-framework-core`** 导出的路由工具(如 `bindRouter`)的使用方式时启用本 Skill。
8
8
  - 触发口令建议:
9
9
  - “请按 `60-backend-router-registration.skill.md` 规范注册路由”
10
10
 
@@ -12,7 +12,7 @@ alwaysApply: false
12
12
 
13
13
  ## 一、路由注册机制(必须理解)
14
14
  本项目路由注册入口:`src/routers/index.ts`
15
- 路由绑定工具:`src/framework/utils/router.ts` `bindRouter(...)`
15
+ 路由绑定工具:从 **`dp-koa-framework-core`** 导入 `bindRouter(...)`(业务侧通常在 `src/routers/index.ts` 使用)。
16
16
 
17
17
  ### 1.1 bindRouter(...) 的真实行为(以代码为准)
18
18
  - 调用形式:
@@ -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(框架级错误处理中间件)
@@ -13,6 +13,7 @@ alwaysApply: false
13
13
 
14
14
  ## 一、总体原则
15
15
 
16
+ - **框架内置能力**:与框架重叠的通用工具/适配层,优先使用 **`dp-koa-framework-libs`** 或 **`dp-koa-framework-core`** 的稳定导出;业务目录下的 `src/libs` 仅承载**本项目特有**的适配与 SDK 封装。
16
17
  - **libs**:更偏向“小框架 / 小 SDK / 第三方服务适配层”
17
18
  - **utils**:更偏向“纯函数工具、小颗粒度无状态函数”
18
19
  - 业务相关的工具优先放在**各自业务模块内**,而不是全局 `utils/`
@@ -91,9 +92,9 @@ alwaysApply: false
91
92
 
92
93
  ---
93
94
 
94
- ## 七、框架级 `src/framework/utils/function.ts`(必须)
95
+ ## 七、框架级运行环境判定(**`dp-koa-framework-core`**,必须)
95
96
 
96
- 本文件提供**运行环境判定**,全项目应统一使用,避免散落 `NODE_ENV === 'production'` 判断。
97
+ 以下 API 由 **`dp-koa-framework-core`** 提供,全项目应统一使用,避免散落 `NODE_ENV === 'production'` 判断。
97
98
 
98
99
  | API | 含义 |
99
100
  |-----|------|
@@ -1,307 +1,65 @@
1
- # 后端插件体系规范(dp-koa-framework
1
+ # 后端插件体系规范(**`dp-koa-framework-core`** / 模板)
2
2
 
3
- > 目标:让类似 `weboffice` 的“独立功能体”以插件形式存在,可插拔、易维护、与宿主框架低耦合。
3
+ > 目标:让“独立功能体”以插件形式存在,可插拔、易维护、与宿主框架低耦合。
4
4
 
5
5
  ---
6
6
 
7
7
  ## 1. 插件的基本形态
8
8
 
9
- - 插件是**独立功能域**:可以包含路由、Service、实体/迁移、脚本,但通过统一接口暴露给宿主。
10
9
  - 插件统一放在 `src/plugins/<plugin-id>/` 目录下。
11
- - 插件入口文件:`src/plugins/<plugin-id>/index.ts`,必须导出一个 `PluginDescriptor`:
10
+ - 插件根目录只保留 `index.ts`(插件入口)。
11
+ - 插件入口必须导出一个 `PluginDescriptor`(类型由 **`dp-koa-framework-core`** 导出)。
12
12
 
13
- ```ts
14
- import type { PluginDescriptor } from "@src/framework/plugins/types";
13
+ 推荐目录结构:
15
14
 
16
- export const plugin: PluginDescriptor = {
17
- id: "weboffice",
18
- displayName: "WPS WebOffice 集成",
19
- enabled: (env) => env.WEBOFFICE_ENABLED !== "0",
20
- registerRoutes(router) {
21
- // 注册本插件路由
22
- },
23
- entities: [
24
- // 插件自有实体
25
- ],
26
- };
27
- ```
15
+ - `src/plugins/<plugin-id>/index.ts`
16
+ - `src/plugins/<plugin-id>/core/`:类型、错误、上下文解析、纯函数工具
17
+ - `src/plugins/<plugin-id>/http/`:HTTP 路由入口(Koa Router)
18
+ - `src/plugins/<plugin-id>/entities/`:TypeORM 实体(插件自有表)
19
+ - `src/plugins/<plugin-id>/services/`:插件业务 Service
20
+ - `src/plugins/<plugin-id>/scripts/`:种子/运维脚本(可选)
28
21
 
29
22
  ---
30
23
 
31
- ## 2. PluginDescriptor 约定
24
+ ## 2. 宿主与插件的依赖方向
32
25
 
33
- 定义位置:`src/framework/plugins/types.ts`
26
+ ### 2.1 插件 → 宿主
34
27
 
35
- 关键字段:
28
+ - 插件内部业务代码应优先使用相对路径组织。
29
+ - 插件调用宿主业务能力(如用户、项目、权限)应通过 **plugin-api 门面层**(建议放在 `src/plugin-api/*`),避免直接 import 宿主 Service/Repository。
36
30
 
37
- - `id: string`:插件唯一 ID,建议与目录名一致(如 `weboffice`)。
38
- - `displayName?: string`:可读名称,用于日志与管理界面。
39
- - `enabled?: boolean | (env: NodeJS.ProcessEnv) => boolean`:
40
- - 未设置:默认启用;
41
- - boolean:固定启/停;
42
- - function:根据环境变量或配置动态启/停(如 `WEBOFFICE_ENABLED`)。
43
- - 生命周期钩子:
44
- - `onBeforeBootstrap?(app: Koa)`:Koa 启动前执行,适合注册中间件、事件等。
45
- - `onAfterBootstrap?(app: Koa)`:Koa 启动后执行,适合注册定时任务、订阅等。
46
- - 路由与实体:
47
- - `registerRoutes?(router: Router)`:注册插件自己的 HTTP 路由。
48
- - `entities?: Function[]`:插件自有 TypeORM 实体(仅包含插件表)。
31
+ ### 2.2 宿主 插件
49
32
 
50
- ---
51
-
52
- ## 3. 插件注册与加载流程
53
-
54
- ### 3.1 注册表
55
-
56
- - 所有内置插件集中注册在 `src/framework/plugins/registry.ts`:
57
-
58
- ```ts
59
- import { plugin as webofficePlugin } from "@src/plugins/weboffice";
60
-
61
- export const plugins: PluginDescriptor[] = [webofficePlugin];
62
- ```
63
-
64
- - 框架提供辅助方法:
65
- - `getEnabledPlugins()`:计算启用的插件(考虑 `enabled` 字段)。
66
- - `collectPluginEntities(enabledPlugins)`:合并所有插件实体。
67
- - `runBeforeBootstrapHooks(app, enabledPlugins)` / `runAfterBootstrapHooks(app, enabledPlugins)`:
68
- 串行执行插件生命周期钩子。
69
- - `registerPluginRoutes(router, enabledPlugins)`:批量注册插件路由。
70
-
71
- ### 3.2 与 bootstrap 集成(`src/app.ts`)
72
-
73
- - 在 `setBeforeBootstrap` 中:
74
- - 计算启用的插件:`const enabledPlugins = getEnabledPlugins();`
75
- - 将插件列表挂到 `app`(只读,用于调试或中间件):`(app as any).plugins = enabledPlugins;`
76
- - 调用 `runBeforeBootstrapHooks(app, enabledPlugins)`。
77
- - 调用 `Router()` 注册宿主自身路由。
78
- - 调用 `registerPluginRoutes(router, enabledPlugins)` 注册所有插件路由。
79
- - 在数据库初始化前,合并实体:
80
-
81
- ```ts
82
- const allEntities = [...dbEntities, ...collectPluginEntities(enabledPlugins)];
83
- const dbconfig = databaseConfigManager.getDatabaseConfig(allEntities);
84
- ```
85
-
86
- - 在 `setAfterBootstrap` 中:
87
- - 再次获取启用插件,调用 `runAfterBootstrapHooks(app, enabledPlugins)`。
88
-
89
- ---
90
-
91
- ## 4. 宿主与插件的依赖方向约束
92
-
93
- ### 4.1 插件 → 宿主:只能通过 `plugin-api`
94
-
95
- - 插件 **禁止直接 import 项目的业务 Service**(如 `TaskService`、`ProjectService`)。
96
- - 宿主通过 `src/plugin-api/*` 暴露给插件一组稳定的门面函数/接口:
97
-
98
- ```ts
99
- // 例:task 相关 API(只示意)
100
- export interface TaskPluginApi {
101
- getById(id: number): Promise<TaskDto | null>;
102
- addSystemComment(taskId: number, content: string): Promise<void>;
103
- }
104
-
105
- export const taskPluginApi: TaskPluginApi = {
106
- // 内部实际调用 TaskService
107
- };
108
- ```
109
-
110
- - 插件内部只能 import `plugin-api`,例如:
111
-
112
- ```ts
113
- import { taskPluginApi } from "@src/plugin-api/taskApi";
114
- await taskPluginApi.addSystemComment(taskId, "来自插件的系统备注");
115
- ```
116
-
117
- ### 4.2 宿主 → 插件:只通过“事件”和“SPI”
118
-
119
- - 宿主禁止直接依赖某个具体插件的 Service。
120
- - 向插件“要能力”的方式:
121
- - **事件扩展点**(推荐,用于大多数“附加效果”):
122
-
123
- ```ts
124
- export type CoreEvent =
125
- | { type: "task.created"; payload: { taskId: number } }
126
- | { type: "task.completed"; payload: { taskId: number } };
127
-
128
- export type CoreEventHandler = (event: CoreEvent) => Promise<void> | void;
129
-
130
- export function registerCoreEventHandler(h: CoreEventHandler): void;
131
- export async function emitCoreEvent(event: CoreEvent): Promise<void>;
132
- ```
133
-
134
- - 核心 Service:`await emitCoreEvent({ type: "task.completed", payload: { taskId } });`
135
- - 插件:在 `onAfterBootstrap` 或 `registerTasks` 中调用 `registerCoreEventHandler` 订阅事件。
136
-
137
- - **SPI(Service Provider Interface,可替换实现)**:
138
- - 适用于可替换能力(审计、搜索、文件存储等)。
139
-
140
- ```ts
141
- export interface AuditSpi {
142
- writeLog(entry: { type: string; data: any }): Promise<void>;
143
- }
144
-
145
- export function registerAuditSpi(impl: AuditSpi): void;
146
- export function getAuditSpi(): AuditSpi; // 总能返回一个实现(可为 Noop)
147
- ```
148
-
149
- - 宿主:`await getAuditSpi().writeLog(...);`
150
- - 插件:实现 `AuditSpi` 并在初始化时 `registerAuditSpi(new MyAuditImpl())`。
151
-
152
- ---
153
-
154
- ## 5. 插件的数据库规范
155
-
156
- ### 5.1 插件是否必须有数据库能力?
157
-
158
- 否。按能力划分三类插件:
159
-
160
- 1. **纯逻辑/集成插件**:不建表,仅消费宿主事件/HTTP/外部服务。
161
- 2. **只读宿主数据插件**:通过 `plugin-api` 访问宿主表,不自建表。
162
- 3. **带独立数据模型的重插件**:有自己的实体和迁移,但只操作自己的 schema。
163
-
164
- ### 5.2 表与实体命名规范
165
-
166
- 当插件需要自建表时:
167
-
168
- - 表名必须使用**插件 ID 作为前缀**:
169
- - 插件 `weboffice`:
170
- - 表:`weboffice_files`、`weboffice_file_versions`
171
- - 索引:`IDX_weboffice_files_fileId`
172
- - 外键:`FK_weboffice_file_versions_webofficeFileId`
173
- - TypeScript 实体命名也应包含插件前缀:
174
- - 如:`WebofficeFileEntity`、`WebofficeFileVersionEntity`
175
- - 迁移文件名包含插件标识:
176
- - 如:`1731600000000-WebofficeTables.ts`
177
-
178
- 约束:
179
-
180
- - 插件迁移**原则上只操作自己前缀的表**;
181
- - 若必须修改宿主核心表结构,迁移应由宿主维护,插件只访问已经存在的字段。
182
-
183
- ### 5.3 实体注册方式
184
-
185
- - 宿主核心实体集中在 `src/entity/index.ts`,**不包含插件实体**。
186
- - 插件实体通过 `PluginDescriptor.entities` 暴露,由 `collectPluginEntities(enabledPlugins)` 统一合并后交给 `DatabaseConfigManager`。
33
+ - 宿主禁止直接 import 插件 Service。
34
+ - 宿主感知插件能力应通过:
35
+ - 事件扩展点(emit + register handler)
36
+ - SPI(可替换实现:getXxxSpi + registerXxxSpi)
187
37
 
188
38
  ---
189
39
 
190
- ## 6. 插件生命周期与卸载策略
40
+ ## 3. 插件注册与加载
191
41
 
192
- ### 6.1 安装插件
193
-
194
- 1. `src/plugins/<plugin-id>/` 下创建插件目录和 `index.ts`,导出 `PluginDescriptor`。
195
- 2. 若有 DB:
196
- - 在 `entities/` 下定义实体,并加入 `plugin.entities`。
197
- - 在 `migrations/` 下定义迁移(放在全局 `src/migrations` 或插件子目录中,按项目规范处理)。
198
- 3. 在 `framework/plugins/registry.ts` 将插件加入 `plugins` 数组。
199
- 4. 重启服务,观察日志中的插件加载信息,确认路由与表生效。
200
-
201
- ### 6.2 禁用插件(推荐默认“卸载”方式)
202
-
203
- - 在 `plugins/registry.ts` 中从 `plugins` 数组移除,或设置 `enabled: false` / 使用环境变量关闭。
204
- - 重启后:
205
- - 插件不再注册路由;
206
- - 不再订阅事件或注册 SPI;
207
- - 插件相关数据表和数据仍保留,方便未来重新启用或迁移。
208
-
209
- ### 6.3 物理卸载与数据清理(高风险,可选)
210
-
211
- - 不建议在正常启动流程自动执行“删库”操作。
212
- - 若必须清理插件数据,应通过**独立 CLI/脚本**实现。
213
- - 插件可选择在 `PluginDescriptor` 中额外导出卸载辅助方法(例如 `uninstall()`),仅供 CLI 调用,用于:
214
- - 关闭外部订阅;
215
- - 提供需要删除/归档的表信息等。
216
- - 所有物理卸载操作应当:
217
- - 需要人工确认;
218
- - 具备日志或变更记录,便于审计与回溯。
42
+ - 插件注册表:**`src/plugins/registry.ts`**(`PluginDescriptor` 来自 **`dp-koa-framework-core`**)
43
+ - 宿主启动接入点:`src/app.ts`
44
+ - beforeBootstrap:计算启用插件、执行插件 before hook、注册插件路由、合并插件实体后初始化 DB
45
+ - afterBootstrap:执行插件 after hook(定时任务/订阅等)
219
46
 
220
47
  ---
221
48
 
222
- ## 7. 插件测试规范(单元 + 集成)
223
-
224
- 目标:插件测试要**可复用、稳定、可在 CI 中长期运行**。遵循“测试金字塔”:
225
-
226
- - **单元测试(默认)**:覆盖插件 `core/*` 的纯函数/解析逻辑,不启动 Koa、不连接真实数据库。
227
- - **集成测试(少量但关键)**:覆盖插件对外契约(HTTP 路由、DB 交互),使用 Koa 最小实例 + SQLite 内存库。
228
-
229
- ### 7.1 测试目录与命名
49
+ ## 4. 插件数据库规范
230
50
 
231
- - 单元测试:`test/plugins/<plugin-id>/*.test.ts`
232
- - 集成测试:`test/plugins/<plugin-id>/*.int.test.ts`
233
-
234
- 示例(weboffice):
235
-
236
- - `test/plugins/weboffice/core.utils.test.ts`
237
- - `test/plugins/weboffice/core.context.test.ts`
238
- - `test/plugins/weboffice/http.routes.int.test.ts`
239
-
240
- ### 7.2 单元测试边界
241
-
242
- - 仅测试插件内部逻辑(如字段规范化、上下文解析、参数转换)。
243
- - 禁止依赖:
244
- - Koa app 启动
245
- - TypeORM DataSource
246
- - dp-ioc2 Provider 注入
247
- - 外部服务(COS、第三方 HTTP)
248
-
249
- ### 7.3 集成测试边界(推荐最小闭环)
250
-
251
- - 只启动最小 Koa + Router:
252
-
253
- 1. `const app = new Koa(); const router = new Router();`
254
- 2. `plugin.registerRoutes?.(router)`
255
- 3. `app.use(router.routes()).use(router.allowedMethods())`
256
- 4. 使用 `supertest` 对 `app.callback()` 发请求,断言 `code/data` 与关键字段
257
-
258
- - 数据库使用 SQLite 内存库(`:memory:`):
259
- - 通过 `TestDatabaseHelper.initTestDatabase({ extraEntities: [...] })` 将**插件实体追加**到宿主实体列表。
260
- - 集成测试自行准备必要数据(seed 插件自己的表)。
261
-
262
- ### 7.4 外部依赖与 ESM 包处理
263
-
264
- 若插件依赖存在 Jest 解析问题的 ESM 包(典型:`uuid@13`),优先在测试中 mock:
265
-
266
- ```ts
267
- jest.mock("uuid", () => ({ v4: () => "test-uuid" }));
268
- ```
269
-
270
- 原则:
271
-
272
- - **测试只关心插件对外行为**,不应被第三方包的模块格式(CJS/ESM)牵连。
273
- - 若确需验证第三方包行为,再考虑 Jest 转译配置或替换依赖。
274
-
275
- ### 7.5 weboffice 集成测试最小断言建议
276
-
277
- - 至少覆盖:
278
- - `GET /v3/3rd/files/:file_id/permission` 返回 `{ code: 0, data: { user_id, read, update, ... } }`
279
- - `user_id` 应符合 WPS 规范(例如 `test-token` -> `test_token`)
51
+ - 插件自建表必须使用插件 ID 作为表名前缀(例如 `weboffice_files`、`weboffice_file_versions`)。
52
+ - 插件迁移原则上只操作自身前缀表,避免修改宿主核心表结构。
280
53
 
281
54
  ---
282
55
 
283
- ## 8. weboffice 插件作为示例
284
-
285
- 当前模板框架中已经将 WebOffice 集成改造成首个插件示例:
286
-
287
- - 插件入口:`src/plugins/weboffice/index.ts`
288
- - `id: "weboffice"`,`displayName: "WPS WebOffice 集成"`。
289
- - `enabled: (env) => env.WEBOFFICE_ENABLED !== "0"`,可通过环境变量关闭。
290
- - `registerRoutes` 复用插件内 `src/plugins/weboffice/http/routes.ts`,对接 WPS 回调网关。
291
- - `entities` 包含 `WebofficeFileEntity` 与 `WebofficeFileVersionEntity`。
292
- - 数据表:
293
- - `weboffice_files`、`weboffice_file_versions`,前缀即插件 ID。
294
- - 迁移示例:`src/migrations/1731600000000-WebofficeTables.ts`。
56
+ ## 5. 示例插件:weboffice
295
57
 
296
- ### weboffice 插件目录结构约定(示例)
58
+ 模板内置示例插件:`src/plugins/weboffice/`
297
59
 
298
- - 根目录仅保留 `index.ts`(插件入口)
299
- - 其余代码按职责归档:
300
- - `core/`:`types.ts`、`errors.ts`、`context.ts`、`utils.ts`
301
- - `entities/`:TypeORM 实体
302
- - `services/`:业务 Service
303
- - `scripts/`:种子/运维脚本
304
- - 其他入口文件(如路由)建议放在 `http/` 或保持在根目录 `routes.ts`(按团队偏好统一)
60
+ - 通过 `WEBOFFICE_ENABLED` 环境变量控制启用(默认启用;设为 `0` 禁用)。
61
+ - 路由入口:`src/plugins/weboffice/http/routes.ts`
62
+ - 插件实体:`src/plugins/weboffice/entities/*`
305
63
 
306
- 新增插件时,强烈建议参考 weboffice 插件的结构与写法,保持一致的工程风格与解耦思路。
64
+ > 注意:模板示例插件的 `getUsers` 为占位实现(不依赖宿主业务用户表)。真实业务接入时应改为通过 plugin-api 调宿主用户体系。
307
65
 
@@ -21,6 +21,11 @@
21
21
  - **`11-backend-controller-recipes.skill.md`**
22
22
  - Controller 推荐模板(可复制)+ 常用注解速查(@Query/@Body/@State/...)
23
23
 
24
+ ### 25 - 注释与文档(按需启用)
25
+ - **`25-backend-comments-and-doc.skill.md`**
26
+ - Controller / Service / Entity / DTO 的 **`/** ... */`** 注释粒度与必写项(类注释、对外路由方法注释等)
27
+ - 禁止逐行翻译式注释;可与测试、PRD 交叉引用
28
+
24
29
  ### 20~40 - 数据/校验/错误(按需启用)
25
30
  - **`20-backend-repository.skill.md`**:Repository 使用与事务(`transactionManager.executeInTransaction`)
26
31
  - **`30-backend-validation.skill.md`**:DTO 分层规范(Controller DTO / Service DTO)、命名规范、转换规范、参数校验(@ParamValidate)
@@ -38,7 +43,7 @@
38
43
  ### 80 - 工程结构(按需启用)
39
44
  - **`80-backend-utils-and-libs.skill.md`**
40
45
  - `src/libs` vs `src/utils` 的放置规则与决策清单
41
- - `src/framework/utils/function.ts`:`isDebug()` / `getRuntimeEnvironmentLabel()` 约定
46
+ - **`dp-koa-framework-core`**:`isDebug()` / `getRuntimeEnvironmentLabel()` 约定
42
47
 
43
48
  ### 90 - 测试(按需启用)
44
49
  - **`90-backend-testing.skill.md`**
@@ -5,7 +5,7 @@ alwaysApply: true
5
5
 
6
6
  ## 适用范围
7
7
  - 本项目所有 TypeScript 后端代码
8
- - 技术栈:Koa + TypeORM + dp-ioc2 + 自定义装饰器体系
8
+ - 技术栈:Koa + TypeORM + dp-ioc2 + 自定义装饰器体系;框架以 npm 包 **`dp-koa-framework-core`**(运行时与对外稳定 API)与 **`dp-koa-framework-libs`**(共享适配/配套能力)引入,版本以项目 `package.json` 为准。
9
9
  - **包管理工具:必须使用 yarn(禁止使用 npm)**
10
10
 
11
11
  ---
@@ -14,7 +14,6 @@
14
14
  "env": {
15
15
  "NODE_ENV": "development",
16
16
  "USE_NEW_ANNOTATION_SYSTEM": "1",
17
- "db_enable": "1",
18
17
  "PATH": "${env:HOME}/.nvm/versions/node/v24.12.0/bin:${env:PATH}"
19
18
  }
20
19
  },
@@ -30,8 +29,7 @@
30
29
  "protocol": "inspector",
31
30
  "env": {
32
31
  "NODE_ENV": "development",
33
- "USE_NEW_ANNOTATION_SYSTEM": "1",
34
- "db_enable": "1"
32
+ "USE_NEW_ANNOTATION_SYSTEM": "1"
35
33
  }
36
34
  }
37
35
  ]
@@ -65,7 +65,7 @@
65
65
  "dotenv": "^16.5.0",
66
66
  "dp-ioc2": "^1.0.1",
67
67
  "dp-mqueue": "^1.0.4",
68
- "dp-koa-framework-core": "^0.1.9",
68
+ "dp-koa-framework-core": "^0.1.10",
69
69
  "dp-koa-framework-libs": "^0.1.4",
70
70
  "jsonwebtoken": "^9.0.2",
71
71
  "koa": "^3.0.0",