create-dp-koa 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.cursor/commands/cheatsheet-backend-controller.md +2 -2
- package/template/.cursor/commands/implement-backend-api-controller.md +6 -4
- package/template/.cursor/rules/11-backend-controller-recipes.skill.md +89 -10
- package/template/.trae/skills/11-backend-controller-recipes.skill.md +91 -10
- package/template/docs/FRAMEWORK_V2_UPGRADE_GUIDE.md +134 -0
- package/template/package.json +2 -0
- package/template/scripts/sync-template.mjs +39 -1
- package/template/src/app.ts +21 -16
- package/template/src/controllers/demo/AnnotationDemoController.ts +1 -3
- package/template/src/controllers/example/EnterpriseExampleController.ts +2 -9
- package/template/src/controllers/example/ExampleController.ts +13 -4
- package/template/src/entity/index.ts +1 -15
- package/template/src/framework/decorator/processor/AnnotationProcessor.ts +5 -1
- package/template/src/routers/index.ts +1 -37
- package/template/src/utils/testDataInitializer.ts +2 -269
- package/template/test/controllers/example/ExampleController.test.ts +29 -31
- package/template/test/framework/annotation/AnnotationDecorators.test.ts +15 -15
- package/template/test/framework/annotation/AnnotationExecutor.test.ts +27 -32
- package/template/test/framework/annotation/AnnotationProcessor.test.ts +25 -24
- package/template/test/framework/annotation/CustomProcessors.test.ts +15 -25
- package/template/test/framework/annotation/NewRouter.test.ts +9 -7
- package/template/test/framework/annotation/ProcessorManager.test.ts +14 -27
- package/template/test/framework/databaseConfig.test.ts +2 -2
- package/template/test/integration/integration.test.ts +15 -72
- package/template/webpack.config.js +2 -0
- package/template/package-lock.json +0 -13240
- package/template/src/controllers/cacheManagement.controller.ts +0 -131
- package/template/src/controllers/captcha.controller.ts +0 -57
- package/template/src/controllers/example/NewAnnotationExampleController.ts +0 -159
- package/template/src/controllers/example/SwaggerExampleController.ts +0 -205
- package/template/src/controllers/example/TransactionExample.controller.ts +0 -336
- package/template/src/controllers/health.controller.ts +0 -235
- package/template/src/controllers/home/register.controller.ts +0 -58
- package/template/src/controllers/home/ytGoods.controller.ts +0 -92
- package/template/src/controllers/home/ytShop.controller.ts +0 -135
- package/template/src/controllers/home/ytUser.controller.ts +0 -89
- package/template/src/controllers/logManagement.controller.ts +0 -396
- package/template/src/controllers/public/emailSend.controller.ts +0 -65
- package/template/src/controllers/public/ytUserAuth.controller.ts +0 -174
- package/template/src/controllers/testData.controller.ts +0 -253
- package/template/src/dto/controller/example/NewAnnotationExampleController.dto.ts +0 -73
- package/template/src/dto/controller/home/emailSend.controller.dto.ts +0 -40
- package/template/src/dto/controller/home/register.controller.dto.ts +0 -45
- package/template/src/dto/controller/home/ytGoods.controller.dto.ts +0 -55
- package/template/src/dto/controller/home/ytShop.controller.dto.ts +0 -69
- package/template/src/dto/controller/home/ytUser.controller.dto.ts +0 -44
- package/template/src/dto/controller/public/ytUserAuth.controller.dto.ts +0 -63
- package/template/src/dto/goods.dto.ts +0 -212
- package/template/src/dto/service/ytService.dto.ts +0 -13
- package/template/src/dto/user.dto.ts +0 -177
- package/template/src/entity/columnTypes.ts +0 -13
- package/template/src/entity/goodsImagesUnlockKey.entity.ts +0 -33
- package/template/src/entity/goodsUnlocker.entity.ts +0 -34
- package/template/src/entity/shop.entity.ts +0 -52
- package/template/src/entity/shopUser.entity.ts +0 -41
- package/template/src/entity/ytGoods.entity.ts +0 -94
- package/template/src/entity/ytUser.entity.ts +0 -96
- package/template/src/examples/SwaggerProcessorExample.ts +0 -169
- package/template/src/examples/TransactionManagerDemo.ts +0 -377
- package/template/src/framework/utils/dynamicSwagger.ts +0 -410
- package/template/src/repository/UserRepository.ts +0 -122
- package/template/src/service/paramValidateTest.service.ts +0 -139
- package/template/src/service/ytGoods.service.ts +0 -42
- package/template/src/service/ytShop.service.ts +0 -90
- package/template/src/service/ytUser.service.ts +0 -451
- package/template/src/test/swaggerParameterTest.ts +0 -90
- package/template/test/controllers/controllers.test.ts +0 -173
- package/template/test/controllers/example/NewAnnotationExampleController.test.ts +0 -200
- package/template/test/framework/TransactionManagerDemo.test.ts +0 -363
- package/template/test/service/business.test.ts +0 -87
- package/template/test/service/paramValidateTest.service.test.ts +0 -184
- package/template/test/service/ytUser.service.test.ts +0 -566
- package/template/yarn.lock +0 -7354
package/package.json
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
- 入参 DTO 应放在哪个目录、命名建议
|
|
39
39
|
- Service 方法签名建议(含返回类型)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
-
|
|
41
|
+
【对齐的参考来源(必须列出)】
|
|
42
|
+
- 至少列出 **`11-backend-controller-recipes.skill.md` 内嵌示例**中的 2 个小节编号(例如 1.1.2、1.1.4);若同时参考了仓库内 `src/controllers/base.controller.ts` 或 `src/controllers/example/*`,可一并写出路径
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
|
|
@@ -50,9 +50,11 @@
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
52
|
## 写代码前的对齐动作(必须)
|
|
53
|
-
- 在生成/修改 Controller
|
|
54
|
-
- `
|
|
55
|
-
-
|
|
56
|
-
- `
|
|
53
|
+
- 在生成/修改 Controller 代码前,先对齐 **`11-backend-controller-recipes.skill.md` 内嵌示例**(章节 **1.1**),重点核对:
|
|
54
|
+
- **1.1.1** `BaseController` / `ControllerResponse` 与 `success` / `fail` 用法
|
|
55
|
+
- **1.1.2** `@State()` 读取整段 `ctx.state`
|
|
56
|
+
- **1.1.3** `@State('user')` 只读 `ctx.state.user`
|
|
57
|
+
- **1.1.4** `@ResponseValidateIf` 条件响应校验
|
|
58
|
+
- 若仓库中仍存在 `src/controllers/base.controller.ts` 或 `src/controllers/example/*`,可作为**补充**对照(以实现代码为准)。
|
|
57
59
|
|
|
58
60
|
|
|
@@ -12,11 +12,91 @@ alwaysApply: false
|
|
|
12
12
|
|
|
13
13
|
## 一、写 Controller 的推荐范式(必须对齐)
|
|
14
14
|
|
|
15
|
-
### 1.1
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 '@src/framework/decorator/controller';
|
|
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 '@src/framework/decorator/controller';
|
|
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 '@src/framework/decorator/controller';
|
|
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
|
|
@@ -76,10 +156,10 @@ export class SomeController extends BaseController {
|
|
|
76
156
|
- 读取 body 参数
|
|
77
157
|
- 推荐写法:`@Body(SomeBodyDto) body: XxxBodyDto`
|
|
78
158
|
- **`@State()`**:
|
|
79
|
-
- 读取整个 `ctx.state
|
|
159
|
+
- 读取整个 `ctx.state`(样例:见 **1.1.2**)
|
|
80
160
|
- 推荐写法:`@State() state: { user: { userId: number; type?: number } }`
|
|
81
161
|
- **`@State('user')`**:
|
|
82
|
-
- 读取 `ctx.state.user
|
|
162
|
+
- 读取 `ctx.state.user`(样例:见 **1.1.3**)
|
|
83
163
|
- 推荐写法:`@State('user') user: { userId: number; type?: number }`
|
|
84
164
|
|
|
85
165
|
禁止:
|
|
@@ -91,7 +171,7 @@ export class SomeController extends BaseController {
|
|
|
91
171
|
|
|
92
172
|
### 2.4 响应校验注解(进阶,按需)
|
|
93
173
|
- **`@ResponseValidator(DtoClass, objectKey?)`**:对响应数据做校验
|
|
94
|
-
- **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn
|
|
174
|
+
- **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn 返回真时才校验(样例:见 **1.1.4**)
|
|
95
175
|
|
|
96
176
|
---
|
|
97
177
|
|
|
@@ -101,9 +181,8 @@ export class SomeController extends BaseController {
|
|
|
101
181
|
- `@Get('/xxx') + @State('user')`(或 `@State()` 取整段 state)
|
|
102
182
|
|
|
103
183
|
### 3.2 带 body 的 POST 接口
|
|
104
|
-
- `@Post('/xxx') + @Body(SomeBodyDto) body:
|
|
184
|
+
- `@Post('/xxx') + @Body(SomeBodyDto) body: SomeBodyDto + @ResponseCode(201)`
|
|
105
185
|
|
|
106
186
|
### 3.3 需要自定义 header
|
|
107
187
|
- `@ResponseHeader('X-XXX', 'value')`
|
|
108
188
|
|
|
109
|
-
|
|
@@ -12,11 +12,91 @@ alwaysApply: false
|
|
|
12
12
|
|
|
13
13
|
## 一、写 Controller 的推荐范式(必须对齐)
|
|
14
14
|
|
|
15
|
-
### 1.1
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 '@src/framework/decorator/controller';
|
|
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 '@src/framework/decorator/controller';
|
|
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 '@src/framework/decorator/controller';
|
|
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
|
|
@@ -76,10 +156,10 @@ export class SomeController extends BaseController {
|
|
|
76
156
|
- 读取 body 参数
|
|
77
157
|
- 推荐写法:`@Body(SomeBodyDto) body: XxxBodyDto`
|
|
78
158
|
- **`@State()`**:
|
|
79
|
-
- 读取整个 `ctx.state
|
|
159
|
+
- 读取整个 `ctx.state`(样例:见 **1.1.2**)
|
|
80
160
|
- 推荐写法:`@State() state: { user: { userId: number; type?: number } }`
|
|
81
161
|
- **`@State('user')`**:
|
|
82
|
-
- 读取 `ctx.state.user
|
|
162
|
+
- 读取 `ctx.state.user`(样例:见 **1.1.3**)
|
|
83
163
|
- 推荐写法:`@State('user') user: { userId: number; type?: number }`
|
|
84
164
|
|
|
85
165
|
禁止:
|
|
@@ -91,7 +171,7 @@ export class SomeController extends BaseController {
|
|
|
91
171
|
|
|
92
172
|
### 2.4 响应校验注解(进阶,按需)
|
|
93
173
|
- **`@ResponseValidator(DtoClass, objectKey?)`**:对响应数据做校验
|
|
94
|
-
- **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn
|
|
174
|
+
- **`@ResponseValidateIf(DtoClass, fn)`**:仅当 fn 返回真时才校验(样例:见 **1.1.4**)
|
|
95
175
|
|
|
96
176
|
---
|
|
97
177
|
|
|
@@ -101,7 +181,8 @@ export class SomeController extends BaseController {
|
|
|
101
181
|
- `@Get('/xxx') + @State('user')`(或 `@State()` 取整段 state)
|
|
102
182
|
|
|
103
183
|
### 3.2 带 body 的 POST 接口
|
|
104
|
-
- `@Post('/xxx') + @Body(SomeBodyDto) body:
|
|
184
|
+
- `@Post('/xxx') + @Body(SomeBodyDto) body: SomeBodyDto + @ResponseCode(201)`
|
|
105
185
|
|
|
106
186
|
### 3.3 需要自定义 header
|
|
107
|
-
- `@ResponseHeader('X-XXX', 'value')`
|
|
187
|
+
- `@ResponseHeader('X-XXX', 'value')`
|
|
188
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Framework v2 升级落地指南(抽包 + 自动升级)
|
|
2
|
+
|
|
3
|
+
## 1. 为什么需要抽成独立 npm 包
|
|
4
|
+
当前脚手架 `create-dp-koa` 的“生成逻辑”是把模板目录整份递归拷贝到新项目里,因此它本质上是“快照生成”,而不是“运行时依赖可升级框架”。
|
|
5
|
+
|
|
6
|
+
- 生成阶段:`packages/create-dp-koa/index.mjs` 中使用递归拷贝模板(`cp(TEMPLATE_DIR, targetDir, { recursive: true })`)。
|
|
7
|
+
- 维护阶段:发布脚手架前会通过 `sync-template.mjs` 把仓库根目录同步到 `packages/create-dp-koa/template/`,再发布脚手架包。
|
|
8
|
+
|
|
9
|
+
因此:**当源框架(本仓库)更新公共能力/公共库后,已经创建的业务项目不会自动获得这些改动**;升级通常会变成“手工合并/拷贝/重构”。
|
|
10
|
+
|
|
11
|
+
要获得“尽量自动升级”的体验,最稳的工程化方式是:把公共/通用能力抽成可发布的独立 npm 包,业务项目只依赖这些包,然后通过 `npm update`/版本升级即可生效。
|
|
12
|
+
|
|
13
|
+
## 2. v2 目标与边界
|
|
14
|
+
### 2.1 目标(尽量自动)
|
|
15
|
+
1. 新建业务项目:生成后直接依赖 npm 包,公共能力升级主要通过升级依赖版本完成。
|
|
16
|
+
2. 存量业务项目:提供一次性迁移(codemod/脚本/文档),迁移完成后同样走 npm 升级链路。
|
|
17
|
+
|
|
18
|
+
### 2.2 抽包边界(与当前代码结构对齐)
|
|
19
|
+
结合当前模板中的职责分层:
|
|
20
|
+
|
|
21
|
+
- `src/framework/**`:框架核心能力(注解、拦截器、启动/路由装配、事务/迁移/缓存/错误处理等)
|
|
22
|
+
- `src/libs/**`:业务侧“共享适配层/小 SDK”(例如验证码、短信、COS、邮件发送适配等)
|
|
23
|
+
|
|
24
|
+
建议 v2 至少拆两类包:
|
|
25
|
+
1. `dp-koa-framework-core`:包含框架核心运行时与对外稳定 API。
|
|
26
|
+
2. `dp-koa-framework-libs`:包含原来 `src/libs/**` 下的共享适配层/小 SDK,并作为核心包的配套能力。
|
|
27
|
+
|
|
28
|
+
可选第三类包(当插件体系需要独立发布、或希望更细粒度升级时):
|
|
29
|
+
3. `dp-koa-framework-plugins`:内置示例插件(例如 `weboffice` 示例)或插件集合。
|
|
30
|
+
|
|
31
|
+
## 3. 关键技术难点(必须在文档/方案里提前解决)
|
|
32
|
+
### 3.1 `@src` 路径别名与运行时模块解析
|
|
33
|
+
当前模板中大量代码依赖 `@src/**` 路径别名:
|
|
34
|
+
|
|
35
|
+
- `packages/create-dp-koa/template/tsconfig.json` 定义了 `paths`(`@src/*` -> `./src/*`)。
|
|
36
|
+
- `packages/create-dp-koa/template/webpack.config.js` 配置了 `resolve.alias`(`@src` -> 模板的 `src/`)。
|
|
37
|
+
- `packages/create-dp-koa/template/src/framework/utils/bootstrap.ts` 运行时使用 `module-alias` 映射 `@src` 指向当前项目 `./src`。
|
|
38
|
+
|
|
39
|
+
抽成 npm 包后,包内部如果仍然假设“外部业务项目存在 `./src` 结构并且 `@src` 指向它”,就无法真正实现自动升级。
|
|
40
|
+
|
|
41
|
+
因此 v2 必须明确:
|
|
42
|
+
1. 包内部应尽量使用包内相对路径与显式导出,不再依赖业务项目的 `@src` 结构。
|
|
43
|
+
2. 业务项目只需保证“对包的导入路径存在”,而不是把业务 `src` 映射给包内部。
|
|
44
|
+
|
|
45
|
+
### 3.2 插件注册表当前是“硬编码示例插件”
|
|
46
|
+
当前插件注册在 `packages/create-dp-koa/template/src/framework/plugins/registry.ts` 中直接:
|
|
47
|
+
- 引入 `@src/plugins/weboffice`
|
|
48
|
+
- 将 `webofficePlugin` 放入固定 `plugins` 数组
|
|
49
|
+
|
|
50
|
+
这会导致抽包时,框架核心包无法脱离业务项目/模板结构(或必须把示例插件也一起抽成插件包)。
|
|
51
|
+
|
|
52
|
+
v2 需要在方案里确定其中一种:
|
|
53
|
+
1. 核心包暴露“注册接口”,由业务/插件包提供插件 descriptor 并注入注册(推荐)。
|
|
54
|
+
2. 把内置示例插件也封装进插件包,核心包只负责加载默认插件集合(次选)。
|
|
55
|
+
|
|
56
|
+
## 4. 抽包实施路线(不写代码,只给步骤与交付物)
|
|
57
|
+
### 4.1 先梳理核心公共 API
|
|
58
|
+
从模板入口开始,列出业务项目会用到的“稳定入口函数/类/类型”,例如:
|
|
59
|
+
|
|
60
|
+
- `setBeforeBootstrap` / `setAfterBootstrap`
|
|
61
|
+
- 应用启动的 `bootstrap`(或由业务应用注入核心启动流程)
|
|
62
|
+
- 注解系统初始化(`AnnotationSystemInitializer.initialize()` 等)
|
|
63
|
+
- router / middleware 装配(框架级中间件、错误处理、token/静态等)
|
|
64
|
+
- 缓存/事务/迁移/错误处理的对外函数(例如缓存的 `createCache`、迁移管理器、错误中间件)
|
|
65
|
+
|
|
66
|
+
原则:**把这些定义成 core 包的显式导出**,业务代码只依赖这些导出。
|
|
67
|
+
|
|
68
|
+
### 4.2 再抽 `src/libs/**` 为 libs 包
|
|
69
|
+
把模板中的 `src/libs/**` 下导出的能力整体搬迁到 `dp-koa-framework-libs`,并确保它们只依赖:
|
|
70
|
+
1. Node/外部依赖(如 `cos-nodejs-sdk-v5`、`svg-captcha` 等)
|
|
71
|
+
2. core 包导出的工具能力(如缓存工具等)
|
|
72
|
+
|
|
73
|
+
例如 `src/libs/mCache.ts` 当前会依赖框架缓存工具:
|
|
74
|
+
- `createCache` / `CacheType` 来自 `src/framework/utils/cache`
|
|
75
|
+
|
|
76
|
+
抽包后,这类依赖关系应改为从 `dp-koa-framework-core` import。
|
|
77
|
+
|
|
78
|
+
### 4.3 调整脚手架模板:新项目直接依赖 npm 包
|
|
79
|
+
更新 `packages/create-dp-koa/template/package.json`(以及最终生成项目的 `package.json`):
|
|
80
|
+
|
|
81
|
+
- 将当前模板中“框架源码快照依赖”替换为“npm 包依赖”
|
|
82
|
+
- 更新模板入口(如 `packages/create-dp-koa/template/src/app.ts`)中对框架/库的导入
|
|
83
|
+
|
|
84
|
+
重点:抽包后,**模板生成的新项目不应再包含完整框架源码**,否则依然无法做到升级自动化。
|
|
85
|
+
|
|
86
|
+
### 4.4 存量项目一次性迁移(codemod)
|
|
87
|
+
v2 推荐提供一份“迁移脚本/步骤文档”,迁移内容主要包括:
|
|
88
|
+
|
|
89
|
+
1. 依赖升级:
|
|
90
|
+
- 修改 `package.json`:加入 `dp-koa-framework-core` 与 `dp-koa-framework-libs` 的版本
|
|
91
|
+
2. import 路径替换:
|
|
92
|
+
- 将 `@src/framework/**` 替换为 `dp-koa-framework-core` 的对应导出路径
|
|
93
|
+
- 将 `@src/libs/**` 替换为 `dp-koa-framework-libs` 的对应导出路径
|
|
94
|
+
3. 如有插件体系相关:
|
|
95
|
+
- 更新插件注册方式(从硬编码改为注入注册或默认插件加载策略)
|
|
96
|
+
|
|
97
|
+
由于你选择了“存量项目可接受一次性迁移”,迁移脚本可以允许做较多 import/依赖层的机械改动。
|
|
98
|
+
|
|
99
|
+
## 5. 验证策略(强制把测试纳入升级标准)
|
|
100
|
+
抽包升级的风险主要来自:接口不一致、行为差异、路径/初始化流程变更、缓存/迁移/错误处理的绑定丢失。
|
|
101
|
+
|
|
102
|
+
因此建议把现有测试用例作为“最低回归集”:
|
|
103
|
+
|
|
104
|
+
1. 缓存单测:
|
|
105
|
+
- `test/framework/cache.test.ts`
|
|
106
|
+
- 关注点:`createCache`、缓存命中/未命中统计(`getCacheStats`)、清空缓存(`clearAllCaches`)、缓存过期行为
|
|
107
|
+
2. 集成测试:
|
|
108
|
+
- `test/integration/integration.test.ts`
|
|
109
|
+
- 关注点:数据库连接与查询、缓存与数据库配合、以及错误处理能否正确抛出/捕获
|
|
110
|
+
|
|
111
|
+
升级完成后,建议至少做到:
|
|
112
|
+
1. `npm test` 全量通过(或 CI 的等价配置)
|
|
113
|
+
2. 针对核心升级的“业务关键路径”用例再次验证(如果你们有业务自测项目/集成环境)
|
|
114
|
+
|
|
115
|
+
## 6. 风险与规避
|
|
116
|
+
1. **路径别名与运行时解析风险**:抽包后若仍依赖 `@src` 映射,运行时很容易出现找不到模块/实体收集失败。
|
|
117
|
+
- 规避:core/libs 包尽量不用 `@src` 假设;业务项目只作为使用者。
|
|
118
|
+
2. **插件系统耦合风险**:硬编码插件清单会导致核心包无法独立发布。
|
|
119
|
+
- 规避:把插件清单来源解耦(核心注入接口 or 插件包)。
|
|
120
|
+
3. **行为一致性风险**:缓存、迁移、错误处理属于“隐式依赖初始化顺序”的高风险模块。
|
|
121
|
+
- 规避:把对应测试纳入回归集,并确保 core 包暴露的初始化顺序与模板一致。
|
|
122
|
+
|
|
123
|
+
## 7. 版本发布与升级建议
|
|
124
|
+
1. 抽包属于大版本改造:建议核心包以主版本号(major)管理,遵循 semver。
|
|
125
|
+
2. 对外提供“稳定导出文档”(例如 core 包的 README),并列出弃用/迁移路径。
|
|
126
|
+
3. 对存量项目迁移:先在分支中完成一次迁移验证,再扩展到更多业务项目。
|
|
127
|
+
|
|
128
|
+
## 8. 附录:建议的目录/文件清单(用于团队对齐)
|
|
129
|
+
1. `packages/dp-koa-framework-core/**`:core 包代码与导出
|
|
130
|
+
2. `packages/dp-koa-framework-libs/**`:libs 包代码与导出
|
|
131
|
+
3. `packages/create-dp-koa/template/**`:模板改为依赖包
|
|
132
|
+
4. `scripts/**` 或 `docs/**`:存量迁移脚本/文档
|
|
133
|
+
5. `test/**`:回归测试集(至少覆盖缓存与集成)
|
|
134
|
+
|
package/template/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 注意:template 位于仓库内部,不能使用「整棵拷贝根目录」到 template(会构成拷贝到自身子路径)。
|
|
7
7
|
* 因此按顶层条目逐个拷贝。
|
|
8
8
|
*/
|
|
9
|
-
import { cp, mkdir, readdir, rm, stat } from 'fs/promises';
|
|
9
|
+
import { cp, mkdir, readdir, rm, stat, readFile, writeFile } from 'fs/promises';
|
|
10
10
|
import { dirname, join, relative } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
|
|
@@ -28,6 +28,18 @@ const EXCLUDED_SEGMENTS = new Set([
|
|
|
28
28
|
/** 仓库根下整目录排除 */
|
|
29
29
|
const EXCLUDED_TOP_LEVEL = new Set(['packages']);
|
|
30
30
|
|
|
31
|
+
// template 内部“必须保留”的文件(防止后续 sync-template 直接覆盖回旧实现)
|
|
32
|
+
// 相对路径以 DEST 为基准。
|
|
33
|
+
const PRESERVE_RELATIVE_PATHS = [
|
|
34
|
+
'package.json',
|
|
35
|
+
'webpack.config.js',
|
|
36
|
+
'src/app.ts',
|
|
37
|
+
'src/routers/index.ts',
|
|
38
|
+
'src/controllers/demo/AnnotationDemoController.ts',
|
|
39
|
+
'src/controllers/example/ExampleController.ts',
|
|
40
|
+
'src/controllers/example/EnterpriseExampleController.ts',
|
|
41
|
+
];
|
|
42
|
+
|
|
31
43
|
function shouldCopySource(absSrc, baseRoot) {
|
|
32
44
|
const rel = relative(baseRoot, absSrc);
|
|
33
45
|
if (!rel || rel === '.') return true;
|
|
@@ -48,12 +60,27 @@ async function pathExists(p) {
|
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
63
|
+
async function readIfExists(filePath) {
|
|
64
|
+
if (!(await pathExists(filePath))) return null;
|
|
65
|
+
return await readFile(filePath, 'utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
async function main() {
|
|
52
69
|
if (!(await pathExists(join(REPO_ROOT, 'package.json')))) {
|
|
53
70
|
console.error('sync-template: 请在仓库根目录运行(未找到根 package.json)');
|
|
54
71
|
process.exit(1);
|
|
55
72
|
}
|
|
56
73
|
|
|
74
|
+
// 同步前先把需要保留的文件内容读出来
|
|
75
|
+
const preserved = new Map();
|
|
76
|
+
for (const rel of PRESERVE_RELATIVE_PATHS) {
|
|
77
|
+
const abs = join(DEST, rel);
|
|
78
|
+
const content = await readIfExists(abs);
|
|
79
|
+
if (content !== null) {
|
|
80
|
+
preserved.set(rel, content);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
await rm(DEST, { recursive: true, force: true });
|
|
58
85
|
await mkdir(DEST, { recursive: true });
|
|
59
86
|
|
|
@@ -74,6 +101,17 @@ async function main() {
|
|
|
74
101
|
});
|
|
75
102
|
}
|
|
76
103
|
|
|
104
|
+
// 回填被保留的 template 文件内容
|
|
105
|
+
for (const [rel, content] of preserved.entries()) {
|
|
106
|
+
const targetAbs = join(DEST, rel);
|
|
107
|
+
await mkdir(dirname(targetAbs), { recursive: true });
|
|
108
|
+
await writeFile(targetAbs, content, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 删除 lock 文件,避免复制后出现 file: 路径依赖问题
|
|
112
|
+
await rm(join(DEST, 'package-lock.json'), { force: true });
|
|
113
|
+
await rm(join(DEST, 'yarn.lock'), { force: true });
|
|
114
|
+
|
|
77
115
|
console.log(`sync-template: 已写入 ${DEST}`);
|
|
78
116
|
}
|
|
79
117
|
|
package/template/src/app.ts
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { DataSourceOptions } from "typeorm";
|
|
2
|
-
import { bootstrap, setAfterBootstrap, setBeforeBootstrap } from "./framework/utils/bootstrap";
|
|
3
|
-
import { initDb, closeDb, getDataSource } from "@src/framework/utils/db";
|
|
4
|
-
import { clearAllCaches } from "@src/framework/utils/cache";
|
|
5
|
-
import { databaseConfigManager } from "@src/framework/utils/databaseConfig";
|
|
6
|
-
import { migrationManager } from "@src/framework/utils/MigrationManager";
|
|
7
|
-
import { transactionManager } from "@src/framework/utils/TransactionManager";
|
|
8
|
-
import { cacheManager } from "@src/framework/utils/CacheManager";
|
|
9
2
|
import { testDataInitializer } from "@src/utils/testDataInitializer";
|
|
10
|
-
import { isDebug, sleep } from "@src/framework/utils/function";
|
|
11
|
-
import { logger } from "@src/framework/utils/logger";
|
|
12
|
-
import { MigrationHelper } from "@src/framework/utils/MigrationHelper";
|
|
13
|
-
import { AnnotationSystemInitializer } from "@src/framework/decorator/processor/AnnotationSystemInitializer";
|
|
14
|
-
import { annotationRegistry } from "@src/framework/decorator/processor/AnnotationRegistry";
|
|
15
|
-
import { createGracefulShutdownController } from "@src/framework/utils/gracefulShutdown";
|
|
16
3
|
import Router from "@src/routers";
|
|
17
4
|
import dbEntities from "@src/entity"
|
|
18
|
-
import { router } from "@src/framework/utils/router";
|
|
19
5
|
import {
|
|
6
|
+
bootstrap,
|
|
7
|
+
setAfterBootstrap,
|
|
8
|
+
setBeforeBootstrap,
|
|
9
|
+
initDb,
|
|
10
|
+
closeDb,
|
|
11
|
+
getDataSource,
|
|
12
|
+
clearAllCaches,
|
|
13
|
+
databaseConfigManager,
|
|
14
|
+
migrationManager,
|
|
15
|
+
transactionManager,
|
|
16
|
+
cacheManager,
|
|
17
|
+
isDebug,
|
|
18
|
+
sleep,
|
|
19
|
+
logger,
|
|
20
|
+
MigrationHelper,
|
|
21
|
+
AnnotationSystemInitializer,
|
|
22
|
+
annotationRegistry,
|
|
23
|
+
createGracefulShutdownController,
|
|
24
|
+
router,
|
|
20
25
|
collectPluginEntities,
|
|
21
26
|
getEnabledPlugins,
|
|
22
27
|
registerPluginRoutes,
|
|
23
28
|
runAfterBootstrapHooks,
|
|
24
29
|
runBeforeBootstrapHooks,
|
|
25
|
-
|
|
30
|
+
staticMiddleware,
|
|
31
|
+
} from "dp-koa-framework-core";
|
|
26
32
|
import Koa from "koa";
|
|
27
33
|
import path from "path";
|
|
28
|
-
import staticMiddleware from "@src/middlewares/static.middleware";
|
|
29
34
|
|
|
30
35
|
const shutdown = createGracefulShutdownController({ logger });
|
|
31
36
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { Get, Query, State } from '
|
|
2
|
-
import { Logging, Permission, RateLimit } from '@src/framework/decorator/processor/AnnotationDecorators';
|
|
3
|
-
import { PerformanceMonitor } from '@src/annotations/decorators/PerformanceMonitor';
|
|
1
|
+
import { Get, Query, State, Logging, Permission, RateLimit, PerformanceMonitor } from 'dp-koa-framework-core';
|
|
4
2
|
import { BaseController } from '@src/controllers/base.controller';
|
|
5
3
|
|
|
6
4
|
/**
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
* 展示所有企业级注解的使用方法
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Get, Post, Body, Query, State } from '
|
|
7
|
-
import { Logging, Permission, RateLimit } from '@src/framework/decorator/processor/AnnotationDecorators';
|
|
8
|
-
import {
|
|
9
|
-
EnterprisePerformance,
|
|
10
|
-
SecurityAudit,
|
|
11
|
-
DistributedTracing,
|
|
12
|
-
ConfigManagement
|
|
13
|
-
} from '@src/annotations';
|
|
6
|
+
import { Get, Post, Body, Query, State, Logging, Permission, RateLimit, EnterprisePerformance, SecurityAudit, DistributedTracing, ConfigManagement } from 'dp-koa-framework-core';
|
|
14
7
|
import { BaseController } from '@src/controllers/base.controller';
|
|
15
8
|
|
|
16
9
|
/**
|
|
@@ -148,7 +141,7 @@ export class EnterpriseExampleController extends BaseController {
|
|
|
148
141
|
{ type: 'object' },
|
|
149
142
|
{
|
|
150
143
|
type: 'object',
|
|
151
|
-
custom: (value) => typeof value.newFeature === 'boolean'
|
|
144
|
+
custom: (value: any) => typeof (value as any).newFeature === 'boolean'
|
|
152
145
|
}
|
|
153
146
|
]
|
|
154
147
|
})
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { Get, Query, State } from '
|
|
2
|
-
import { Logging, Permission, RateLimit } from '@src/framework/decorator/processor/AnnotationDecorators';
|
|
3
|
-
import { PerformanceMonitor } from '@src/annotations/decorators/PerformanceMonitor';
|
|
1
|
+
import { Get, Query, State, Logging, Permission, RateLimit, PerformanceMonitor } from 'dp-koa-framework-core';
|
|
4
2
|
import { BaseController } from '@src/controllers/base.controller';
|
|
5
|
-
import { Api, ApiTags, ApiResponse, ApiOperation } from '
|
|
3
|
+
import { Api, ApiTags, ApiResponse, ApiOperation, ProcessorManager, LoggingProcessor, PermissionProcessor, RateLimitProcessor, logger } from 'dp-koa-framework-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 测试/示例用:注册自定义注解处理器。
|
|
7
|
+
* 注意:此函数不会影响框架的默认初始化流程;只用于按需注册。
|
|
8
|
+
*/
|
|
9
|
+
export function initializeCustomProcessors(): void {
|
|
10
|
+
ProcessorManager.registerProcessor(new LoggingProcessor());
|
|
11
|
+
ProcessorManager.registerProcessor(new PermissionProcessor());
|
|
12
|
+
ProcessorManager.registerProcessor(new RateLimitProcessor());
|
|
13
|
+
logger.info('自定义注解处理器已注册');
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* 示例控制器 - 展示新的注解系统用法
|
|
@@ -1,15 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { YtGoodsUnlockerEntity } from "@src/entity/goodsUnlocker.entity";
|
|
3
|
-
import { ShopEntity } from "@src/entity/shop.entity";
|
|
4
|
-
import { ShopAndUserEntity } from "@src/entity/shopUser.entity";
|
|
5
|
-
import { YtGoodsEntity } from "@src/entity/ytGoods.entity";
|
|
6
|
-
import { YtUserEntity } from "@src/entity/ytUser.entity";
|
|
7
|
-
|
|
8
|
-
export default [
|
|
9
|
-
YtUserEntity,
|
|
10
|
-
YtGoodsEntity,
|
|
11
|
-
YtGoodsUnlockerEntity,
|
|
12
|
-
GoodsImagesUnlockKeyEntity,
|
|
13
|
-
ShopEntity,
|
|
14
|
-
ShopAndUserEntity,
|
|
15
|
-
]
|
|
1
|
+
export default []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context } from 'koa';
|
|
2
2
|
import { annotationRegistry } from '@src/framework/decorator/processor/AnnotationRegistry';
|
|
3
|
+
import { logger } from '@src/framework/utils/logger';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 注解处理器接口
|
|
@@ -111,7 +112,10 @@ export class AnnotationExecutor {
|
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
113
114
|
} catch (error) {
|
|
114
|
-
|
|
115
|
+
// 处理器异常不应中断后续处理器;测试用例里该异常属于“预期流程”时不打印堆栈噪音
|
|
116
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
117
|
+
logger.error(`注解处理器 ${processor.name} 执行失败`, error as Error);
|
|
118
|
+
}
|
|
115
119
|
// 可以选择是否继续执行其他处理器
|
|
116
120
|
// 这里选择继续执行
|
|
117
121
|
}
|