create-lumfall 1.0.0 → 1.1.1

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.
Files changed (82) hide show
  1. package/README.md +24 -17
  2. package/cli.js +179 -24
  3. package/package.json +5 -7
  4. package/templates/basic/README.md +0 -113
  5. package/templates/basic/app/controller/demo.js +0 -28
  6. package/templates/basic/app/extend/README.md +0 -3
  7. package/templates/basic/app/middleware/README.md +0 -2
  8. package/templates/basic/app/middleware.js +0 -13
  9. package/templates/basic/app/pages/home/entry.home.js +0 -4
  10. package/templates/basic/app/pages/home/home.vue +0 -164
  11. package/templates/basic/app/router/demo.js +0 -17
  12. package/templates/basic/app/router-schema/demo.js +0 -29
  13. package/templates/basic/app/service/demo.js +0 -51
  14. package/templates/basic/app/webpack.config.js +0 -4
  15. package/templates/basic/build.js +0 -7
  16. package/templates/basic/config/config.beta.js +0 -4
  17. package/templates/basic/config/config.default.js +0 -32
  18. package/templates/basic/config/config.local.js +0 -4
  19. package/templates/basic/config/config.prod.js +0 -6
  20. package/templates/basic/package.json +0 -31
  21. package/templates/basic/server.js +0 -26
  22. package/templates/document/README.md +0 -145
  23. package/templates/document/app/extend/README.md +0 -6
  24. package/templates/document/app/middleware/README.md +0 -2
  25. package/templates/document/app/middleware.js +0 -2
  26. package/templates/document/app/pages/docs/assets/docs-logo.svg +0 -5
  27. package/templates/document/app/pages/docs/components/doc-layout.vue +0 -144
  28. package/templates/document/app/pages/docs/components/doc-navbar.vue +0 -103
  29. package/templates/document/app/pages/docs/components/doc-search.vue +0 -131
  30. package/templates/document/app/pages/docs/components/doc-sidebar.vue +0 -29
  31. package/templates/document/app/pages/docs/components/doc-toc.vue +0 -21
  32. package/templates/document/app/pages/docs/content.js +0 -28
  33. package/templates/document/app/pages/docs/docs-config.js +0 -154
  34. package/templates/document/app/pages/docs/docs.vue +0 -24
  35. package/templates/document/app/pages/docs/entry.docs.js +0 -26
  36. package/templates/document/app/pages/docs/markdown/highlight.js +0 -30
  37. package/templates/document/app/pages/docs/markdown/index.js +0 -129
  38. package/templates/document/app/pages/docs/search.js +0 -142
  39. package/templates/document/app/pages/docs/styles/docs.less +0 -1091
  40. package/templates/document/app/pages/docs/styles/vars.less +0 -87
  41. package/templates/document/app/pages/docs/theme.js +0 -47
  42. package/templates/document/app/pages/docs/utils.js +0 -58
  43. package/templates/document/app/pages/docs/views/doc-home.vue +0 -56
  44. package/templates/document/app/pages/docs/views/doc-page.vue +0 -147
  45. package/templates/document/app/webpack.config.js +0 -15
  46. package/templates/document/build.js +0 -6
  47. package/templates/document/config/config.beta.js +0 -2
  48. package/templates/document/config/config.default.js +0 -9
  49. package/templates/document/config/config.local.js +0 -2
  50. package/templates/document/config/config.prod.js +0 -2
  51. package/templates/document/docs/advanced/dashboard.md +0 -66
  52. package/templates/document/docs/advanced/health.md +0 -72
  53. package/templates/document/docs/advanced/monitoring.md +0 -60
  54. package/templates/document/docs/advanced/security.md +0 -88
  55. package/templates/document/docs/core/app-instance.md +0 -114
  56. package/templates/document/docs/core/controller-service.md +0 -113
  57. package/templates/document/docs/core/lifecycle.md +0 -60
  58. package/templates/document/docs/core/middleware.md +0 -83
  59. package/templates/document/docs/core/plugins.md +0 -77
  60. package/templates/document/docs/core/router-schema.md +0 -102
  61. package/templates/document/docs/dsl/api-contract.md +0 -88
  62. package/templates/document/docs/dsl/extend.md +0 -311
  63. package/templates/document/docs/dsl/menu.md +0 -101
  64. package/templates/document/docs/dsl/model-project.md +0 -122
  65. package/templates/document/docs/dsl/overview.md +0 -116
  66. package/templates/document/docs/dsl/reference.md +0 -175
  67. package/templates/document/docs/dsl/schema-actions.md +0 -135
  68. package/templates/document/docs/dsl/schema.md +0 -121
  69. package/templates/document/docs/frontend/build.md +0 -118
  70. package/templates/document/docs/frontend/curl.md +0 -75
  71. package/templates/document/docs/frontend/page.md +0 -99
  72. package/templates/document/docs/frontend/widgets.md +0 -151
  73. package/templates/document/docs/guide/config.md +0 -108
  74. package/templates/document/docs/guide/deployment.md +0 -115
  75. package/templates/document/docs/guide/getting-started.md +0 -199
  76. package/templates/document/docs/guide/introduction.md +0 -55
  77. package/templates/document/docs/guide/structure.md +0 -98
  78. package/templates/document/docs/reference/commands.md +0 -70
  79. package/templates/document/docs/reference/faq.md +0 -94
  80. package/templates/document/package.json +0 -36
  81. package/templates/document/scripts/build-static.js +0 -129
  82. package/templates/document/server.js +0 -13
@@ -1,88 +0,0 @@
1
- # 安全策略
2
-
3
- 框架内置两道 API 防线,由 `config.security` 控制(不配置时等价于
4
- 「签名关闭 + project_key 开启」),只作用于 `/api` 开头的请求。
5
-
6
- ## 接口签名校验(apiSignature)
7
-
8
- 开启后,所有 `/api` 请求必须携带签名请求头,防重放、防伪造:
9
-
10
- ```js
11
- // config/config.default.js
12
- module.exports = {
13
- security: {
14
- apiSignature: {
15
- enabled: true,
16
- secret: process.env.API_SIGN_SECRET, // 不配时退化为默认串 "lumfall"(只适合本地)
17
- maxAgeMs: 600000, // 时间戳有效期,默认 10 分钟
18
- },
19
- },
20
- };
21
- ```
22
-
23
- 算法:`s_sign = md5(secret + "_" + st)`。
24
-
25
- | 请求头 | 含义 |
26
- | --- | --- |
27
- | `s_sign`(或 `s_sign` 别名 `ssign`) | `md5(secret + "_" + st)` |
28
- | `s_t`(或 `s_t` 别名 `st`) | 毫秒时间戳 |
29
-
30
- 失败条件(任一满足即拒绝,返回 `code 445`):
31
-
32
- - 缺少签名或时间戳
33
- - 时间戳不是合法数字
34
- - 签名不匹配
35
- - 时间差超过 `maxAgeMs`,或时间戳在未来
36
-
37
- 框架的 `$lumfallCurl` 默认带 `s_sign` / `s_t`(用默认串 `lumfall` 签名);
38
- 服务端换了 `secret` 时客户端要同步实现同样的算法。
39
-
40
- ## project_key 校验(projectKey)
41
-
42
- 多项目(多租户)场景下,归属某个项目的接口要求请求头声明项目:
43
-
44
- ```js
45
- module.exports = {
46
- security: {
47
- projectKey: {
48
- enabled: true,
49
- headerName: "project_key", // 可改
50
- freePaths: ["/api/project/custom"], // 追加豁免路径
51
- },
52
- },
53
- };
54
- ```
55
-
56
- - 只作用于 `/api/project/` 开头的路径
57
- - 内置豁免:`/api/project/model_list`、`/api/project/list`
58
- (项目列表页初始化时还没有项目上下文),可用 `freePaths` 追加
59
- - 缺少请求头返回 `code 446`;通过后 `ctx.projectKey` 可在 controller / service 里使用
60
- - `$lumfallCurl` 在 URL 带 `?projectKey=xxx` 时自动追加这个请求头
61
-
62
- ## 错误码总表
63
-
64
- | 中间件 | 触发条件 | 响应 |
65
- | --- | --- | --- |
66
- | `apiParamsVerify` | router-schema 校验不通过 | `{ success: false, code: 442 }` |
67
- | `apiSignVerify` | 缺签名 / 签名不匹配 / 时间戳非法或过期 | `{ success: false, code: 445 }` |
68
- | `projectHandler` | 缺少 `project_key` | `{ success: false, code: 446 }` |
69
-
70
- 三者都返回 HTTP 200,前端按 `success` / `code` 判断。
71
-
72
- ## 中间件链位置
73
-
74
- 安全策略位于框架中间件链的最后一环(最内层业务之前):
75
-
76
- ```text
77
- static → nunjucks → bodyParser → errorHandler → monitoring → apiParamsVerify → securityPolicy
78
- ```
79
-
80
- 所以参数校验(442)先于安全校验(445 / 446)执行。
81
-
82
- ## 安全建议
83
-
84
- - 生产环境**必须**开启 `apiSignature` 并配置强随机 `secret`
85
- - `secret` 走环境变量或配置中心;泄露后签名机制形同虚设
86
- - md5 + 时间戳是轻量防重放方案;对安全等级要求更高的系统,把签名逻辑
87
- 换成自定义中间件(HMAC、nonce 等)替换或叠加在 `securityPolicy` 之前
88
- - `/view/...` 页面路由不走这两道校验,页面级权限需要业务自己控制
@@ -1,114 +0,0 @@
1
- # app 对象与启动流程
2
-
3
- `serviceStart(options)` 返回的 `app` 是一个 Koa 实例,框架把所有能力都装配在它上面。
4
- 理解启动流程,就理解了框架的几乎所有约束。
5
-
6
- ## serviceStart 选项
7
-
8
- | 选项 | 作用 |
9
- | --- | --- |
10
- | `name` | 应用名,渲染页面模板 `<title>` 时使用 |
11
- | `homePath` | 完全未命中路由时的 302 兜底目标 |
12
- | `configSchema` | 校验合并后配置的 JSON Schema(见[配置](../guide/config.md)) |
13
- | `lifecycle` | 启动 / 停止 hook(见[生命周期](./lifecycle.md)) |
14
- | `plugins` | 插件描述符数组(见[插件](./plugins.md)) |
15
- | `monitoring` | 请求级观测 hook(见[请求观测](../advanced/monitoring.md)) |
16
-
17
- ::: warning homePath 默认值陷阱
18
- 完全不传参时 `homePath` 默认是 `/view/health`;但**一旦传了 options 对象**,
19
- 框架不再套用默认值——没写 `homePath` 时兜底重定向会变成 `/`。
20
- 所以只要传 options,就显式声明 `homePath`。
21
- :::
22
-
23
- 其他启动相关默认值:监听 `0.0.0.0:3000`,可用 `IP` / `PORT` 环境变量覆盖。
24
-
25
- ## 启动流程
26
-
27
- `serviceStart()` 内部固定按这个顺序同步装配:
28
-
29
- ```js
30
- app.options = options;
31
- app.baseDir = process.cwd();
32
- app.businessPath = path.resolve(app.baseDir, "app");
33
- app.env = env();
34
-
35
- middlewareLoader(app); // app.middlewares
36
- routerSchemaLoader(app); // app.routerSchema
37
- controllerLoader(app); // app.controllers(类在这里被 new)
38
- serviceLoader(app); // app.services(类在这里被 new)
39
- configLoader(app); // app.config
40
- extendLoader(app); // 返回值直接挂到 app(app.logger / app.health / ...)
41
-
42
- registerPlugins(app, options.plugins); // app.plugins
43
-
44
- require("<lumfall>/app/middleware.js")(app); // 框架全局中间件
45
- require("<app-root>/app/middleware.js")(app); // 业务全局中间件
46
-
47
- // lifecycle.beforeRouteLoad(app)
48
- routerLoader(app); // app.router + 兜底 302 路由
49
- // lifecycle.afterRouteLoad(app)
50
- app.diagnostics = createDiagnostics(app); // 诊断清单
51
-
52
- app.server = app.listen(PORT || 3000, IP || "0.0.0.0");
53
- // lifecycle.afterStart(app)
54
- ```
55
-
56
- 兜底路由注册在所有业务与框架路由之后:任何未命中路由的 GET 请求会
57
- 302 到 `homePath`。业务路由先于框架路由加载,路径冲突时业务优先。
58
-
59
- 任何一步抛错(文件导出不合法、schema 与路由不匹配、插件循环依赖、
60
- 配置校验失败……)都会中断启动并抛出异常,`lifecycle.onError` 会先被调用。
61
-
62
- ## 框架全局中间件链
63
-
64
- 框架 `app/middleware.js` 按固定顺序 `app.use()`:
65
-
66
- ```text
67
- koa-static(app/public) 静态资源
68
- koa-nunjucks-2(app/public) 模板引擎(ext: tpl)
69
- koa-bodyparser 请求体解析(json / form / text)
70
- errorHandler 统一异常兜底
71
- monitoring 请求观测(未配置时是 passthrough)
72
- apiParamsVerify /api 参数校验(router-schema)
73
- securityPolicy 安全策略(apiSignVerify + projectHandler)
74
- ```
75
-
76
- 业务的 `app/middleware.js` 在框架之后执行,因此业务中间件位于这一串的**内层**:
77
- 请求先经过框架的静态资源、模板、bodyParser、错误处理、参数校验和安全策略,
78
- 再到业务中间件。
79
-
80
- ## 诊断清单
81
-
82
- 排查「文件明明写了却没生效」时非常有用:
83
-
84
- ```js
85
- const manifest = app.diagnostics.getManifest();
86
- // {
87
- // version: "1.1.0",
88
- // environment: "local",
89
- // loaders: ["middleware", "router-schema", "controller", "service", "config", "extend", "router"],
90
- // routes: [{ path, methods }],
91
- // pages: [{ name, entry, route, source }],
92
- // healthChecks: [{ name, timeoutMs }],
93
- // }
94
- ```
95
-
96
- - `routes`:已注册路由及方法
97
- - `pages`:发现的页面入口,`source` 标记 `framework` / `business`
98
- - `healthChecks`:已注册探针
99
- - 内容可 JSON 序列化,不含凭证与探针函数,可以直接打成日志或挂在内部排查接口上
100
-
101
- ## 优雅退出
102
-
103
- `app.stop()` 返回 Promise,可重复调用(内部会复用同一次流程):
104
-
105
- ```text
106
- lifecycle.beforeStop → 关闭 HTTP server → lifecycle.afterStop
107
- ```
108
-
109
- 适合在容器 / pm2 的 `SIGTERM` 处理里调用,等待在途请求处理完再退出。
110
-
111
- ## 下一步
112
-
113
- - [Controller 与 Service](./controller-service.md)
114
- - [生命周期](./lifecycle.md)
@@ -1,113 +0,0 @@
1
- # Controller 与 Service
2
-
3
- controller 负责接收请求、组装参数、返回统一响应;service 负责业务逻辑。
4
- 两者都以「工厂函数返回 class」的形式编写,由 loader 自动实例化。
5
-
6
- ## Controller
7
-
8
- `app/controller/<name>.js`,工厂返回 class,继承 `Controller.Base(app)`:
9
-
10
- ```js
11
- module.exports = (app) => {
12
- const BaseController = require("lumfall").Controller.Base(app);
13
-
14
- return class ArticleController extends BaseController {
15
- async getList(ctx) {
16
- const { article: articleService } = this.services;
17
- const { data, total } = await articleService.list({
18
- page: Number(ctx.request.query.page) || 1,
19
- size: Number(ctx.request.query.pageSize) || 20,
20
- });
21
- await this.success(ctx, data, { total });
22
- }
23
- };
24
- };
25
- ```
26
-
27
- 挂载结果:`app.controllers.article`(无子目录时)或
28
- `app.controllers.admin.articleList`(`app/controller/admin/article-list.js`)。
29
-
30
- 基类提供:
31
-
32
- | 成员 | 说明 |
33
- | --- | --- |
34
- | `this.app` | app 实例 |
35
- | `this.services` | `app.services`(getter,请求阶段取,安全) |
36
- | `this.config` | `app.config`(getter) |
37
- | `this.success(ctx, data, metadata)` | 成功响应 |
38
- | `this.fail(ctx, message, code)` | 失败响应 |
39
-
40
- ## 统一响应结构
41
-
42
- 成功与失败都返回 HTTP 200,前端按 body 里的 `success` / `code` 判断:
43
-
44
- ```js
45
- // this.success(ctx, data, metadata)
46
- { "success": true, "data": ..., "metadata": { "total": 0 } }
47
-
48
- // this.fail(ctx, message, code)
49
- { "success": false, "message": "获取失败", "code": 50000 }
50
- ```
51
-
52
- ::: warning this 绑定
53
- 路由绑定 controller 方法时必须 `.bind(controller)`,
54
- 否则方法内的 `this`(`this.services` 等)会丢失:
55
-
56
- ```js
57
- router.get("/api/article/list", articleController.getList.bind(articleController));
58
- ```
59
- :::
60
-
61
- ## Service
62
-
63
- `app/service/<name>.js`,工厂返回 class,继承 `Service.Base(app)`:
64
-
65
- ```js
66
- module.exports = (app) => {
67
- const BaseService = require("lumfall").Service.Base(app);
68
-
69
- return class ArticleService extends BaseService {
70
- async list({ page = 1, size = 20 }) {
71
- // 读配置用 this.config(getter,请求阶段安全)
72
- // 调其他服务用 this.app.services.xxx
73
- return { data: [], total: 0, page, size };
74
- }
75
- };
76
- };
77
- ```
78
-
79
- service 基类提供 `this.app` 与 `this.config`。跨 service 调用:
80
- `this.app.services.orderService`。
81
-
82
- ## 加载时机约束
83
-
84
- loader 顺序是 controller → service → config(详见
85
- [app 对象与启动流程](./app-instance.md)),由此得到两条铁律:
86
-
87
- 1. **controller 工厂 / 构造期不要取 `app.services`**——那时 service 还没加载。
88
- 基类的 `this.services` 是 getter,请求阶段解析,所以总是安全的
89
- 2. **不要在工厂执行期读 `app.config`**——配置还没合并。同样推迟到请求阶段
90
-
91
- ```js
92
- // ❌ 错误:工厂执行期读配置
93
- module.exports = (app) => {
94
- const maxSize = app.config.maxSize; // undefined,config 还没加载
95
- return class extends BaseController { /* ... */ };
96
- };
97
-
98
- // ✅ 正确:请求阶段读
99
- module.exports = (app) => {
100
- const BaseService = require("lumfall").Service.Base(app);
101
- return class DemoService extends BaseService {
102
- getInfo() {
103
- return { maxSize: this.config.maxSize };
104
- }
105
- };
106
- };
107
- ```
108
-
109
- ## 状态与单例
110
-
111
- controller / service 实例在启动时创建、全进程共享——**单例**。
112
- 不要把请求级状态挂在 `this` 上(并发请求会互相污染);请求级数据放 `ctx`,
113
- 跨请求状态放外部存储或通过插件初始化的客户端。
@@ -1,60 +0,0 @@
1
- # 生命周期
2
-
3
- `serviceStart({ lifecycle })` 提供启动与退出的 hook:
4
-
5
- ```js
6
- serviceStart({
7
- lifecycle: {
8
- beforeStart(app) {}, // 全部 loader 之前
9
- beforeRouteLoad(app) {}, // 全局中间件之后、路由加载之前
10
- afterRouteLoad(app) {}, // 路由之后、listen 之前
11
- afterStart(app) {}, // listen 之后
12
- onError(error, app) {}, // 启动期异常;处理后错误仍会抛出
13
- async beforeStop(app) {}, // app.stop() 的第一步
14
- async afterStop(app) {}, // server 关闭之后
15
- },
16
- });
17
- ```
18
-
19
- ## 约束
20
-
21
- - **启动期 hook(beforeStart / beforeRouteLoad / afterRouteLoad / afterStart)必须同步**,
22
- 返回 Promise 会直接启动失败。需要异步初始化(连数据库、拉远程配置)时,
23
- 在调用 `serviceStart()` **之前**完成,再把结果传进来
24
- - 未知的 hook 名或非函数值会启动失败
25
- - `onError` 只提供观测点,不能吞掉错误——处理完异常仍会向上抛
26
-
27
- ```js
28
- // ❌ 启动期异步初始化
29
- lifecycle: {
30
- async beforeStart(app) {
31
- app.db = await connect(); // 启动失败:hook must be synchronous
32
- },
33
- }
34
-
35
- // ✅ 先初始化,再启动
36
- const db = await connect();
37
- const app = serviceStart({
38
- plugins: [{ name: "database", register: () => ({ client: db }) }],
39
- });
40
- ```
41
-
42
- ## 优雅退出
43
-
44
- `app.stop()` 返回 Promise,按顺序执行:
45
-
46
- ```text
47
- lifecycle.beforeStop → server.close() → lifecycle.afterStop
48
- ```
49
-
50
- - 可以重复调用,内部复用同一次关闭流程
51
- - 适合接 `SIGTERM`(容器 / pm2 场景):
52
-
53
- ```js
54
- process.on("SIGTERM", async () => {
55
- await app.stop();
56
- process.exit(0);
57
- });
58
- ```
59
-
60
- - `beforeStop` / `afterStop` 是唯二支持异步的 hook,用来刷缓冲、关连接
@@ -1,83 +0,0 @@
1
- # 中间件
2
-
3
- Lumfall 有两种中间件:**可复用中间件**(目录扫描、按名挂载)和
4
- **全局中间件**(`app.use` 注册到请求链上)。
5
-
6
- ## 可复用中间件
7
-
8
- `app/middleware/**/*.js`,导出工厂 `(app) => (ctx, next) => {}`:
9
-
10
- ```js
11
- // app/middleware/api/logger.js
12
- module.exports = (app) => async (ctx, next) => {
13
- const start = Date.now();
14
- await next();
15
- app.logger.info(`${ctx.method} ${ctx.path} ${Date.now() - start}ms`);
16
- };
17
- ```
18
-
19
- 挂载结果:`app.middlewares.api.logger`。框架内置的可复用中间件
20
- (`app.middlewares.errorHandler`、`app.middlewares.apiParamsVerify`、
21
- `app.middlewares.securityPolicy`、`app.middlewares.monitoring`、
22
- `app.middlewares.apiSignVerify`、`app.middlewares.projectHandler`)
23
- 也是同一套约定,业务可以直接复用或覆盖。
24
-
25
- ::: warning 命名
26
- 文件名用 kebab-case / snake_case,挂载名是 camelCase:
27
- `params-verify.js` → `app.middlewares.xxx.paramsVerify`。
28
- 用文件原名访问会拿到 undefined。
29
- :::
30
-
31
- 可复用中间件**不会自动生效**,需要在路由或全局中间件里显式使用:
32
-
33
- ```js
34
- router.post("/api/article", app.middlewares.api.logger, controller.create.bind(controller));
35
- ```
36
-
37
- ## 全局中间件
38
-
39
- `app/middleware.js`(注意是文件不是目录),导出 `(app) => {}`,
40
- 在里面 `app.use()`:
41
-
42
- ```js
43
- module.exports = (app) => {
44
- app.use(async (ctx, next) => {
45
- const start = Date.now();
46
- await next();
47
- console.log(`${ctx.method} ${ctx.path} ${ctx.status} ${Date.now() - start}ms`);
48
- });
49
- };
50
- ```
51
-
52
- 执行时机:框架全局中间件之后、路由之前——所以业务全局中间件位于
53
- 框架中间件链的**内层**。
54
-
55
- 框架全局中间件的注册顺序:
56
-
57
- ```text
58
- static → nunjucks → bodyParser → errorHandler → monitoring → apiParamsVerify → securityPolicy
59
- ```
60
-
61
- 含义:
62
-
63
- - 静态资源与页面模板在**最外层**,不受安全策略影响
64
- - 错误处理包住了后面所有中间件,任何内层异常都会被统一兜底
65
- - `/api/` 请求在进 controller 前会依次经过参数校验(442)与安全策略(445 / 446)
66
- - bodyParser 限制了表单大小 1000mb,支持 json / form / text
67
-
68
- ## 错误处理行为
69
-
70
- `errorHandler` 的兜底策略:
71
-
72
- | 异常 | 响应 |
73
- | --- | --- |
74
- | 消息含 `template not found`(页面模板缺失) | 302 重定向到 `homePath` |
75
- | 其他异常 | HTTP 200 + `{ success: false, code: 5000, message: "Internal Server Error" }`,异常详情进日志 |
76
-
77
- 业务代码里主动抛错会被这里兜住;想给前端返回业务错误,用
78
- `this.fail(ctx, message, code)` 而不是 throw。
79
-
80
- ## 下一步
81
-
82
- - [安全策略](../advanced/security.md):securityPolicy 中间件的细节
83
- - [请求观测](../advanced/monitoring.md):monitoring 的使用方式
@@ -1,77 +0,0 @@
1
- # 插件
2
-
3
- 插件用于把「要先于业务中间件 / 路由初始化」的能力组装起来:
4
- 数据库连接、缓存客户端、feature 模块等。
5
-
6
- ## 基本用法
7
-
8
- ```js
9
- serviceStart({
10
- plugins: [
11
- {
12
- name: "database",
13
- register(app) {
14
- return { client: connect() }; // 返回值挂到 app.plugins.database
15
- },
16
- },
17
- {
18
- name: "article-module",
19
- dependencies: ["database"], // 依赖先注册
20
- register(app) {
21
- return { db: app.plugins.database.client };
22
- },
23
- },
24
- ],
25
- });
26
- ```
27
-
28
- 注册时机:在全部 loader、config、extend 之后,全局中间件与路由**之前**——
29
- 所以插件的产物在中间件 / 路由里已经可用。
30
-
31
- ## 规则
32
-
33
- | 规则 | 违反后果 |
34
- | --- | --- |
35
- | `name` 唯一且非空 | 启动失败 |
36
- | `register(app)` 必须同步 | 异步 register 启动失败(异步初始化放在 `serviceStart()` 之前做) |
37
- | 返回值必须是普通对象或 `undefined` | 返回其他类型启动失败 |
38
- | `dependencies` 里声明的插件必须存在 | 缺依赖启动失败 |
39
- | 依赖图不能有环 | 循环依赖启动失败 |
40
- | 依赖先注册 | 框架按依赖拓扑排序执行 register |
41
-
42
- 与生命周期 hook 一样,register 里做不了异步。标准做法是先初始化再传入:
43
-
44
- ```js
45
- const dbClient = await connect(); // serviceStart() 之前
46
-
47
- serviceStart({
48
- plugins: [
49
- { name: "database", register: () => ({ client: dbClient }) },
50
- ],
51
- });
52
- ```
53
-
54
- ## 使用插件产物
55
-
56
- - 服务端:`app.plugins.<name>`
57
- - 业务代码里同样遵守「请求阶段再取」的原则——插件注册在 controller / service
58
- 实例化**之后**,工厂执行期读 `app.plugins` 拿到的还是 undefined
59
-
60
- ```js
61
- // app/service/user.js
62
- module.exports = (app) => {
63
- const BaseService = require("lumfall").Service.Base(app);
64
- return class UserService extends BaseService {
65
- async find(id) {
66
- const { client: db } = app.plugins.database; // 请求阶段取,已就绪
67
- return db.query("select * from user where id = ?", [id]);
68
- }
69
- };
70
- };
71
- ```
72
-
73
- ::: tip 什么时候用插件,什么时候用 extend
74
- - 插件:有初始化动作、可能被其他模块依赖、需要按序装配的能力(数据库、缓存)
75
- - extend:挂一个现成的工具对象(`app.utils`、`app.http`),无依赖顺序诉求
76
- (见[目录结构与挂载点](../guide/structure.md))
77
- :::
@@ -1,102 +0,0 @@
1
- # 路由与参数校验
2
-
3
- 路由文件负责把 URL 绑到 controller 方法;router-schema 用 JSON Schema(Ajv)
4
- 声明参数约束,框架中间件在请求进入 controller 前自动校验。
5
-
6
- ## 注册路由
7
-
8
- `app/router/<name>.js`,导出 `(app, router) => {}`:
9
-
10
- ```js
11
- module.exports = (app, router) => {
12
- const { article: articleController } = app.controllers;
13
-
14
- router.get("/api/article/list", articleController.getList.bind(articleController));
15
-
16
- router.post(
17
- "/api/article",
18
- app.middlewares.apiParamsVerify, // 可选:挂额外中间件
19
- articleController.create.bind(articleController)
20
- );
21
- };
22
- ```
23
-
24
- - 框架自带路由:`/view/:page` 与 `/view/:page/*`(页面渲染)、
25
- `/health/live`、`/health/ready`、`/api/project/*`(Dashboard 数据)
26
- - 业务路由先于框架路由加载,同路径时业务优先
27
- - 完全未命中的 GET 请求 302 到 `homePath`
28
-
29
- ## 声明参数校验
30
-
31
- `app/router-schema/<name>.js`,导出「path → method → schema」的映射,
32
- 或 `(app) => map` 工厂:
33
-
34
- ```js
35
- module.exports = {
36
- "/api/article/list": {
37
- get: {
38
- query: {
39
- type: "object",
40
- properties: {
41
- page: { type: "integer", minimum: 1 },
42
- pageSize: { type: "integer", minimum: 1, maximum: 200 },
43
- },
44
- },
45
- },
46
- },
47
- "/api/article": {
48
- post: {
49
- body: {
50
- type: "object",
51
- properties: {
52
- title: { type: "string", minLength: 1 },
53
- },
54
- required: ["title"],
55
- },
56
- },
57
- },
58
- };
59
- ```
60
-
61
- 可校验的位置:`headers` / `body` / `query` / `params`(JSON Schema draft-07 风格)。
62
-
63
- ## 校验行为
64
-
65
- - 只作用于 **`/api/` 开头**的请求,其余路径不校验
66
- - schema 里没声明的 path / method 直接放行
67
- - 校验失败返回 **HTTP 200** + 业务错误码:
68
-
69
- ```json
70
- {
71
- "success": false,
72
- "message": "request validate fail: data should have required property 'title'",
73
- "code": 442
74
- }
75
- ```
76
-
77
- ## 启动期一致性校验
78
-
79
- router-schema 与路由的一致性在**启动时**校验,不匹配直接启动失败:
80
-
81
- - key(path)必须是已注册路由的 path
82
- - method 必须全小写,且该路由确实以这个方法注册过
83
-
84
- ```text
85
- Error: [router] router-schema path "/api/artile/list" does not match a registered route
86
- ```
87
-
88
- ::: tip 常见坑
89
- - path 手打错一个字母 → 启动失败(这是故意的,把问题提前到启动期)
90
- - method 写成 `"GET"` → 启动失败,必须小写
91
- - 校验只对 `/api/` 开头的路径生效,`/view/...` 不参与
92
- :::
93
-
94
- ## 校验器缓存
95
-
96
- Ajv 编译后的校验器按 `method + path + 位置` 缓存(schema 是启动期静态数据),
97
- 运行时没有重复编译开销。
98
-
99
- ## 下一步
100
-
101
- - [中间件](./middleware.md):参数校验在整条中间件链的位置
102
- - [安全策略](../advanced/security.md):445 / 446 错误码来自哪里