create-dp-koa 1.1.12 → 1.1.13

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.12",
3
+ "version": "1.1.13",
4
4
  "description": "Scaffold a DP-Koa framework project from the official template",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@ alwaysApply: true
15
15
 
16
16
  ### 1) 识别任务类型(多选)
17
17
  - Controller/API 开发
18
+ - 服务端 SSR 模板(art-template、`views/`)
18
19
  - DTO/参数校验
19
20
  - Service/Repository/事务
20
21
  - 错误处理/日志
@@ -29,6 +30,7 @@ alwaysApply: true
29
30
  - **Controller/API**:
30
31
  - `10-backend-api.skill.md`
31
32
  -(需要范式/注解速查时)`11-backend-controller-recipes.skill.md`
33
+ - **服务端 SSR 模板(art-template)**:`34-backend-art-template.skill.md`
32
34
  - **Service**:
33
35
  - `21-backend-service.skill.md`
34
36
  - **DTO/校验**:`30-backend-validation.skill.md`
@@ -0,0 +1,172 @@
1
+ ---
2
+ alwaysApply: false
3
+ ---
4
+ # Skill:服务端模板(art-template)
5
+
6
+ ## 适用与触发
7
+ - 新增或修改 **SSR 页面**、`views/**/*.html`、或入口里 **`initArtTemplate`** / Controller 里 **`return template(...)`** 时启用本 Skill。
8
+ - 触发口令建议:「请启用 `34-backend-art-template.skill.md`,按项目约定写模板」。
9
+
10
+ ---
11
+
12
+ ## 一、入口初始化(必须)
13
+
14
+ - 在应用入口链路中(例如 `setBeforeBootstrap`)调用 **`initArtTemplate`**,早于会 `return template(...)` 的路由生效即可。
15
+ - 典型配置:`root` 指向项目根下 **`views/`**,**`extname: '.html'`**,按需 **`enabled`**。
16
+ - **`enabled: false`** 时不得再 `return template(...)`(运行时会抛错)。
17
+
18
+ ---
19
+
20
+ ## 二、Controller 写法(与 redirect 一致)
21
+
22
+ - 使用 **`return template('相对 views 的路径(不含扩展名)', data)`**;由 **`dp-koa-framework-core`** 路由层渲染为 HTML。
23
+ - **不要**在 Controller 里为 SSR 直接改 **`ctx.body`** 或拼接 HTML 字符串(与 `redirect()` 的声明式风格保持一致)。
24
+
25
+ ---
26
+
27
+ ## 三、视图文件拆分约定
28
+
29
+ | 类型 | 内容 | 说明 |
30
+ |------|------|------|
31
+ | **页面模板** | 完整 **`<!DOCTYPE html>` → `<html>` → `<head>` → `<body>` → … → `</html>`** | 页面级文件保留文档骨架;正文宜放在 **`<main>`**(或语义等价区域) |
32
+ | **片段模板** | 仅可嵌入片段,如单个 **`<header>...</header>`**、**`<footer>...</footer>`** | **禁止**在片段内再写一套 `DOCTYPE` / `html` / `head` / `body` 外壳 |
33
+ | **嵌入** | **`{{include './partials/文件名'}}`** | 路径相对**当前模板文件**所在目录;移动文件时需同步修正 include |
34
+
35
+ ---
36
+
37
+ ## 四、art-template 语法要点
38
+
39
+ - **条件**:`{{if cond}} ... {{/if}}`,可选 `{{else}}`。
40
+ - **列表(v4)**:`{{each list item}}` … `{{/each}}`,循环体内用 **`{{ item.field }}`**(避免旧写法 `each list as item` 的升级告警)。
41
+ - **字面量**:模板正文中**不要**出现会被误解析的 **`{{...}}`** 占位展示(例如展示双大括号语法);改用说明文字或 HTML 实体等。
42
+
43
+ ---
44
+
45
+ ## 五、缓存与性能
46
+
47
+ - **`@ControllerCache` + `template()`** 时,框架缓存的是**渲染后的 HTML**;**`cacheyFn`** 的返回值必须随影响页面内容的入参(`query` / `params` / 用户态等)变化,避免错页。
48
+
49
+ ---
50
+
51
+ ## 六、依赖
52
+
53
+ - 应用 **`package.json`** 需包含 **`art-template`**(与 `dp-koa-framework-core` 的实现对齐)。
54
+
55
+ ---
56
+
57
+ ## 七、示例代码(可复制)
58
+
59
+ ### 7.1 入口:`initArtTemplate`(如 `setBeforeBootstrap` 内)
60
+
61
+ ```ts
62
+ import path from "path";
63
+ import { initArtTemplate } from "dp-koa-framework-core";
64
+
65
+ initArtTemplate({
66
+ enabled: true,
67
+ root: path.join(process.cwd(), "views"),
68
+ extname: ".html",
69
+ });
70
+ ```
71
+
72
+ ### 7.2 Controller:`return template(...)`(与 `redirect` 同风格)
73
+
74
+ ```ts
75
+ import { Get, template } from "dp-koa-framework-core";
76
+ import { BaseController } from "@src/controllers/base.controller";
77
+
78
+ export class PageController extends BaseController {
79
+ @Get("/demo/page/hello")
80
+ hello() {
81
+ return template("demo/hello", {
82
+ title: "标题",
83
+ siteName: "站点名",
84
+ footerText: "页脚文案",
85
+ year: new Date().getFullYear(),
86
+ message: "正文说明",
87
+ showNotice: true,
88
+ showVip: false,
89
+ steps: [
90
+ { name: "步骤一", done: true },
91
+ { name: "步骤二", done: false },
92
+ ],
93
+ });
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### 7.3 页面模板:`views/demo/hello.html`(整页骨架 + include 片段)
99
+
100
+ ```html
101
+ <!DOCTYPE html>
102
+ <html lang="zh-CN">
103
+ <head>
104
+ <meta charset="utf-8" />
105
+ <title>{{ title }}</title>
106
+ </head>
107
+ <body>
108
+ {{include './partials/site-header'}}
109
+
110
+ <main>
111
+ <h1>{{ title }}</h1>
112
+ <p>{{ message }}</p>
113
+ {{if showNotice}}
114
+ <aside>通知区域</aside>
115
+ {{/if}}
116
+ <ul>
117
+ {{each steps step}}
118
+ <li>{{ step.name }} — {{if step.done}}已完成{{else}}待完成{{/if}}</li>
119
+ {{/each}}
120
+ </ul>
121
+ </main>
122
+
123
+ {{include './partials/site-footer'}}
124
+ </body>
125
+ </html>
126
+ ```
127
+
128
+ ### 7.4 片段模板(仅片段,无 `DOCTYPE` / `html` / `body` 外壳)
129
+
130
+ `views/demo/partials/site-header.html`:
131
+
132
+ ```html
133
+ <header>
134
+ <strong>{{ siteName }}</strong>
135
+ </header>
136
+ ```
137
+
138
+ `views/demo/partials/site-footer.html`:
139
+
140
+ ```html
141
+ <footer>
142
+ <p>{{ footerText }} · {{ year }}</p>
143
+ </footer>
144
+ ```
145
+
146
+ ### 7.5 可选:`@ControllerCache`(`cacheyFn` 形参与方法入参顺序一致)
147
+
148
+ `cacheyFn` 会收到**与方法参数相同顺序**的注入值;下面示例第一个入参为 `@Query()`,缓存 key 必须带上影响 HTML 的 `lang`。
149
+
150
+ ```ts
151
+ import { Get, Query, template, ControllerCache } from "dp-koa-framework-core";
152
+ import { BaseController } from "@src/controllers/base.controller";
153
+
154
+ export class CachedPageController extends BaseController {
155
+ @Get("/demo/page/cached")
156
+ @ControllerCache((query: any) => `demo-hello:${String(query?.lang ?? "zh")}`, { ttl: 60 })
157
+ cachedHtml(@Query() query: any) {
158
+ return template("demo/hello", {
159
+ title: "缓存页",
160
+ siteName: "Demo",
161
+ footerText: "footer",
162
+ year: new Date().getFullYear(),
163
+ message: `lang=${query?.lang ?? "zh"}`,
164
+ showNotice: false,
165
+ showVip: false,
166
+ steps: [],
167
+ });
168
+ }
169
+ }
170
+ ```
171
+
172
+ > 说明:多入参时 **`cacheyFn` 的参数列表顺序** 须与 **`@Body` / `@Query` / `@Params` …** 在方法上的声明顺序一致,否则 key 会错位。
@@ -62,9 +62,9 @@ alwaysApply: false
62
62
  ## 六、注册位置与顺序(必须)
63
63
 
64
64
  ### 6.1 全局中间件(bootstrap 阶段)
65
- 在 **`dp-koa-framework-core`** 的 `bootstrap` 流程中,框架已注册:
65
+ 在 **`dp-koa-framework-core`** 的 `bootstrap` 流程中,框架默认仅挂载 **`loggingMiddleware`**(只记录 **HTTP 4xx/5xx** 与 **未捕获异常**;`ctx.logger` / `ctx.requestId` 仍注入供业务自行打日志):
66
66
  - CORS
67
- - loggingMiddleware / businessLoggingMiddleware / databaseLoggingMiddleware
67
+ - loggingMiddleware(请求错误日志)
68
68
  - frameworkErrorMiddleware(框架级错误处理中间件)
69
69
  - koaBody
70
70
  - router.routes/allowedMethods
@@ -94,7 +94,7 @@ alwaysApply: false
94
94
  ## 八、测试建议(可选但推荐)
95
95
  - 单元测试优先覆盖:
96
96
  - 鉴权中间件:缺 token / 无效 token / 有效 token 能写入 `ctx.state.user`
97
- - 日志中间件:是否写入 `ctx.requestId`、是否脱敏 headers
97
+ - 日志中间件:是否注入 `ctx.requestId` / `ctx.logger`(访问轨迹、慢请求等由业务自行记录)
98
98
  - 静态中间件:prefix 正规化(`/static` vs `/static/`)
99
99
 
100
100
 
@@ -20,6 +20,8 @@
20
20
  - Controller 编排规则:DTO + Service 调用 + 统一响应映射 + try/catch
21
21
  - **`11-backend-controller-recipes.skill.md`**
22
22
  - Controller 推荐模板(可复制)+ 常用注解速查(@Query/@Body/@State/...)
23
+ - **`34-backend-art-template.skill.md`**
24
+ - 服务端 SSR(**art-template**):入口 **`initArtTemplate`**、`return template(...)`(对齐 **`redirect`**)、`views/` 与 **`.html`**、整页骨架与 **`partials` + `{{include}}`**、**`{{each}}` v4** 写法、**`@ControllerCache`** 与 **`cacheKeyFn`**
23
25
 
24
26
  ### 25 - 注释与文档(按需启用)
25
27
  - **`25-backend-comments-and-doc.skill.md`**
@@ -64,7 +64,7 @@ alwaysApply: false
64
64
  ### 6.1 全局中间件(bootstrap 阶段)
65
65
  在 `src/framework/utils/bootstrap.ts` 中,框架已注册:
66
66
  - CORS
67
- - loggingMiddleware / businessLoggingMiddleware / databaseLoggingMiddleware
67
+ - loggingMiddleware(仅 HTTP 4xx/5xx 与未捕获异常;其余访问日志由业务自行挂载)
68
68
  - frameworkErrorMiddleware(框架级错误处理中间件)
69
69
  - koaBody
70
70
  - router.routes/allowedMethods
@@ -94,5 +94,5 @@ alwaysApply: false
94
94
  ## 八、测试建议(可选但推荐)
95
95
  - 单元测试优先覆盖:
96
96
  - 鉴权中间件:缺 token / 无效 token / 有效 token 能写入 `ctx.state.user`
97
- - 日志中间件:是否写入 `ctx.requestId`、是否脱敏 headers
97
+ - 日志中间件:是否注入 `ctx.requestId` / `ctx.logger`(访问轨迹、慢请求等由业务自行记录)
98
98
  - 静态中间件:prefix 正规化(`/static` vs `/static/`)
@@ -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.10",
68
+ "dp-koa-framework-core": "^0.2.0",
69
69
  "dp-koa-framework-libs": "^0.1.4",
70
70
  "jsonwebtoken": "^9.0.2",
71
71
  "koa": "^3.0.0",
@@ -70,7 +70,7 @@ setBeforeBootstrap(async function (app: Koa) {
70
70
 
71
71
  // 记录系统状态
72
72
  const systemStatus = MigrationHelper.getSystemStatus();
73
- logger.info(`注解系统状态: ${systemStatus.newSystemEnabled ? '新系统' : '旧系统'}, 环境: ${systemStatus.environment}`);
73
+ logger.debug(`注解系统状态: ${systemStatus.newSystemEnabled ? '新系统' : '旧系统'}, 环境: ${systemStatus.environment}`);
74
74
 
75
75
  Router();
76
76
  registerPluginRoutes(router, enabledPlugins);
@@ -106,11 +106,11 @@ setBeforeBootstrap(async function (app: Koa) {
106
106
  throw err;
107
107
  }
108
108
  }
109
- logger.info("启动前初始化完毕");
109
+ logger.debug("启动前初始化完毕");
110
110
  });
111
111
 
112
112
  setAfterBootstrap(async function (app: Koa) {
113
- logger.info("启动后执行");
113
+ logger.debug("启动后执行");
114
114
  const enabledPlugins = getEnabledPlugins();
115
115
  await runAfterBootstrapHooks(app, enabledPlugins);
116
116
  });
@@ -10,7 +10,7 @@ export function initializeCustomProcessors(): void {
10
10
  ProcessorManager.registerProcessor(new LoggingProcessor());
11
11
  ProcessorManager.registerProcessor(new PermissionProcessor());
12
12
  ProcessorManager.registerProcessor(new RateLimitProcessor());
13
- logger.info('自定义注解处理器已注册');
13
+ logger.debug('自定义注解处理器已注册');
14
14
  }
15
15
 
16
16
  /**
@@ -18,7 +18,7 @@ function isEnabled(plugin: PluginDescriptor): boolean {
18
18
  export function getEnabledPlugins(): PluginDescriptor[] {
19
19
  const enabled = plugins.filter(isEnabled);
20
20
  const disabledIds = plugins.filter((p) => !isEnabled(p)).map((p) => p.id);
21
- logger.info(
21
+ logger.debug(
22
22
  "Plugin registry initialized. enabled=%s disabled=%s",
23
23
  enabled.map((p) => p.id).join(",") || "-",
24
24
  disabledIds.join(",") || "-"
@@ -38,7 +38,7 @@ export async function runBeforeBootstrapHooks(app: Koa, enabled: PluginDescripto
38
38
  for (const plugin of enabled) {
39
39
  if (plugin.onBeforeBootstrap) {
40
40
  await plugin.onBeforeBootstrap(app);
41
- logger.info('Plugin "%s" onBeforeBootstrap executed', plugin.id);
41
+ logger.debug('Plugin "%s" onBeforeBootstrap executed', plugin.id);
42
42
  }
43
43
  }
44
44
  }
@@ -47,7 +47,7 @@ export async function runAfterBootstrapHooks(app: Koa, enabled: PluginDescriptor
47
47
  for (const plugin of enabled) {
48
48
  if (plugin.onAfterBootstrap) {
49
49
  await plugin.onAfterBootstrap(app);
50
- logger.info('Plugin "%s" onAfterBootstrap executed', plugin.id);
50
+ logger.debug('Plugin "%s" onAfterBootstrap executed', plugin.id);
51
51
  }
52
52
  }
53
53
  }
@@ -56,7 +56,7 @@ export function registerPluginRoutes(router: Router, enabled: PluginDescriptor[]
56
56
  for (const plugin of enabled) {
57
57
  if (plugin.registerRoutes) {
58
58
  plugin.registerRoutes(router);
59
- logger.info('Plugin "%s" routes registered', plugin.id);
59
+ logger.debug('Plugin "%s" routes registered', plugin.id);
60
60
  }
61
61
  }
62
62
  }
@@ -174,6 +174,6 @@ export function registerWebOfficeRoutes(router: Router): void {
174
174
  const prefixFromEnv = (process.env.WEBOFFICE_CALLBACK_PREFIX || "/weboffice").replace(/\/$/, "");
175
175
  registerUnderPrefix(router, prefixFromEnv);
176
176
  if (prefixFromEnv !== "") registerUnderPrefix(router, "");
177
- logger.info('WebOffice routes registered (prefix: %s and /v3/3rd)', prefixFromEnv);
177
+ logger.debug('WebOffice routes registered (prefix: %s and /v3/3rd)', prefixFromEnv);
178
178
  }
179
179
 
@@ -11,7 +11,7 @@ export const plugin: PluginDescriptor = {
11
11
  enabled: (env: NodeJS.ProcessEnv) => env.WEBOFFICE_ENABLED !== "0",
12
12
 
13
13
  onBeforeBootstrap(_app: Koa) {
14
- logger.info('Plugin "weboffice" before bootstrap');
14
+ logger.debug('Plugin "weboffice" before bootstrap');
15
15
  },
16
16
 
17
17
  registerRoutes(router: Router) {
@@ -9,5 +9,5 @@ export default () => {
9
9
  bindRouter("/demo/", AnnotationDemoController);
10
10
  bindRouter("/enterprise/", EnterpriseExampleController);
11
11
 
12
- logger.info("路由绑定完成");
12
+ logger.debug("路由绑定完成");
13
13
  }