wok-server 0.4.13 → 0.6.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/README.en.md +61 -0
- package/README.md +44 -29
- package/dist/cache/cache.js +98 -98
- package/dist/cache/config.js +19 -19
- package/dist/cache/index.js +27 -27
- package/dist/cache/purge-task.js +46 -46
- package/dist/cache/stat.js +47 -47
- package/dist/config/convert.js +36 -36
- package/dist/config/exception.js +14 -14
- package/dist/config/index.js +81 -81
- package/dist/http-client/index.js +136 -136
- package/dist/i18n/ar.js +17 -17
- package/dist/i18n/de.js +17 -17
- package/dist/i18n/en-us.js +17 -17
- package/dist/i18n/es.js +17 -17
- package/dist/i18n/fr.js +17 -17
- package/dist/i18n/i18n.js +231 -231
- package/dist/i18n/index.js +52 -52
- package/dist/i18n/ja.js +17 -17
- package/dist/i18n/ko.js +17 -17
- package/dist/i18n/msg.js +2 -2
- package/dist/i18n/pt.js +17 -17
- package/dist/i18n/ru.js +17 -17
- package/dist/i18n/tag.js +18 -18
- package/dist/i18n/zh-HK.js +17 -17
- package/dist/i18n/zh-TW.js +17 -17
- package/dist/i18n/zh-cn.js +17 -17
- package/dist/index.js +14 -14
- package/dist/lock/index.js +114 -114
- package/dist/log/config.js +35 -29
- package/dist/log/date.js +21 -21
- package/dist/log/file.js +198 -72
- package/dist/log/index.js +135 -105
- package/dist/log/level.js +33 -33
- package/dist/log/log.js +56 -0
- package/dist/log/store.js +19 -16
- package/dist/mongodb/collection.js +2 -2
- package/dist/mongodb/config.js +34 -34
- package/dist/mongodb/doc.js +2 -2
- package/dist/mongodb/exception.js +14 -14
- package/dist/mongodb/index.js +58 -58
- package/dist/mongodb/manager/base.js +563 -563
- package/dist/mongodb/manager/index.js +63 -63
- package/dist/mongodb/manager/tx-strict.js +84 -84
- package/dist/mongodb/manager/tx.js +30 -30
- package/dist/mongodb/migration.js +52 -52
- package/dist/mvc/access-log.js +33 -33
- package/dist/mvc/config.js +27 -27
- package/dist/mvc/exchange.js +113 -113
- package/dist/mvc/handler/index.js +7 -6
- package/dist/mvc/handler/json.js +65 -65
- package/dist/mvc/handler/restful.js +35 -35
- package/dist/mvc/handler/sse.js +65 -0
- package/dist/mvc/handler/upload.js +31 -31
- package/dist/mvc/index.js +50 -50
- package/dist/mvc/interceptor.js +2 -2
- package/dist/mvc/query.js +43 -43
- package/dist/mvc/render/file.js +132 -132
- package/dist/mvc/render/html/html.js +90 -90
- package/dist/mvc/render/html/index.js +18 -18
- package/dist/mvc/render/html/style.js +2 -2
- package/dist/mvc/render/index.js +7 -7
- package/dist/mvc/render/json.js +26 -26
- package/dist/mvc/render/text.js +16 -16
- package/dist/mvc/router.js +2 -2
- package/dist/mvc/server.js +272 -272
- package/dist/mvc/static/header.js +67 -67
- package/dist/mvc/static/index.js +6 -6
- package/dist/mvc/static/mime-type.js +84 -84
- package/dist/mvc/static/server-cache-config.js +66 -66
- package/dist/mvc/static/server-cache.js +133 -133
- package/dist/mvc/static/static-handler.js +372 -372
- package/dist/mysql/config.js +51 -51
- package/dist/mysql/exception.js +14 -14
- package/dist/mysql/index.js +87 -87
- package/dist/mysql/manager/base.js +239 -231
- package/dist/mysql/manager/index.js +107 -107
- package/dist/mysql/manager/ops/count.js +20 -20
- package/dist/mysql/manager/ops/criteria.js +356 -356
- package/dist/mysql/manager/ops/delete.js +65 -65
- package/dist/mysql/manager/ops/exist.js +26 -26
- package/dist/mysql/manager/ops/find.js +169 -130
- package/dist/mysql/manager/ops/index.js +14 -14
- package/dist/mysql/manager/ops/insert.js +106 -106
- package/dist/mysql/manager/ops/modify.js +10 -10
- package/dist/mysql/manager/ops/paginate.js +23 -23
- package/dist/mysql/manager/ops/query.js +9 -9
- package/dist/mysql/manager/ops/update.js +216 -216
- package/dist/mysql/manager/ops/utils.js +24 -24
- package/dist/mysql/manager/tx-strict.js +103 -100
- package/dist/mysql/manager/tx.js +30 -30
- package/dist/mysql/manager/utils.js +56 -56
- package/dist/mysql/migration.js +136 -136
- package/dist/mysql/table-info.js +8 -8
- package/dist/task/daily.js +59 -59
- package/dist/task/fixed-delay.js +38 -38
- package/dist/task/fixed-rate.js +42 -42
- package/dist/task/index.js +9 -9
- package/dist/task/task.js +56 -56
- package/dist/validation/exception.js +36 -36
- package/dist/validation/index.js +40 -40
- package/dist/validation/validator/array.js +34 -34
- package/dist/validation/validator/enum.js +28 -28
- package/dist/validation/validator/index.js +14 -14
- package/dist/validation/validator/length.js +40 -40
- package/dist/validation/validator/max-length.js +35 -35
- package/dist/validation/validator/max.js +29 -29
- package/dist/validation/validator/min-length.js +33 -33
- package/dist/validation/validator/min.js +29 -29
- package/dist/validation/validator/not-blank.js +33 -33
- package/dist/validation/validator/not-null.js +21 -21
- package/dist/validation/validator/plain-obj.js +32 -32
- package/dist/validation/validator/regexp.js +34 -34
- package/documentation/en/cache.md +56 -0
- package/documentation/en/config.md +96 -0
- package/documentation/en/engineering.md +256 -0
- package/documentation/en/http-client.md +32 -0
- package/documentation/en/i18n.md +143 -0
- package/documentation/en/index.md +24 -0
- package/documentation/en/lock.md +51 -0
- package/documentation/en/log.md +109 -0
- package/documentation/en/mongodb.md +256 -0
- package/documentation/en/mvc.md +688 -0
- package/documentation/en/mysql.md +552 -0
- package/documentation/en/task.md +45 -0
- package/documentation/en/test.md +56 -0
- package/documentation/en/validate.md +130 -0
- package/documentation/zh-cn/engineering.md +1 -1
- package/documentation/zh-cn/log.md +81 -8
- package/documentation/zh-cn/mvc.md +66 -24
- package/documentation/zh-cn/mysql.md +24 -23
- package/documentation/zh-cn/validate.md +2 -2
- package/package.json +3 -1
- package/skills/wok-server-api-rules/SKILL.md +350 -0
- package/skills/wok-server-cache/SKILL.md +216 -0
- package/skills/wok-server-config/SKILL.md +200 -0
- package/skills/wok-server-getting-started/SKILL.md +123 -0
- package/skills/wok-server-getting-started/references/engineering.md +169 -0
- package/skills/wok-server-http-client/SKILL.md +164 -0
- package/skills/wok-server-i18n/SKILL.md +214 -0
- package/skills/wok-server-lock/SKILL.md +144 -0
- package/skills/wok-server-log/SKILL.md +218 -0
- package/skills/wok-server-mongodb/SKILL.md +235 -0
- package/skills/wok-server-mvc/SKILL.md +251 -0
- package/skills/wok-server-mvc/references/respond-html.md +157 -0
- package/skills/wok-server-mvc/references/sse.md +121 -0
- package/skills/wok-server-mvc/references/static-files.md +47 -0
- package/skills/wok-server-mvc/references/upload.md +62 -0
- package/skills/wok-server-mvc/references/websocket.md +30 -0
- package/skills/wok-server-mysql/SKILL.md +315 -0
- package/skills/wok-server-mysql/references/multi-datasource.md +76 -0
- package/skills/wok-server-mysql/references/version-control.md +22 -0
- package/skills/wok-server-task/SKILL.md +158 -0
- package/skills/wok-server-validate/SKILL.md +167 -0
- package/src/cache/cache.ts +118 -0
- package/src/cache/config.ts +53 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/purge-task.ts +53 -0
- package/src/cache/stat.ts +47 -0
- package/src/config/convert.ts +27 -0
- package/src/config/exception.ts +8 -0
- package/src/config/index.ts +92 -0
- package/src/http-client/index.ts +202 -0
- package/src/i18n/ar.ts +16 -0
- package/src/i18n/de.ts +16 -0
- package/src/i18n/en-us.ts +16 -0
- package/src/i18n/es.ts +16 -0
- package/src/i18n/fr.ts +16 -0
- package/src/i18n/i18n.ts +230 -0
- package/src/i18n/index.ts +50 -0
- package/src/i18n/ja.ts +16 -0
- package/src/i18n/ko.ts +16 -0
- package/src/i18n/msg.ts +50 -0
- package/src/i18n/pt.ts +16 -0
- package/src/i18n/ru.ts +16 -0
- package/src/i18n/tag.ts +18 -0
- package/src/i18n/zh-HK.ts +16 -0
- package/src/i18n/zh-TW.ts +16 -0
- package/src/i18n/zh-cn.ts +16 -0
- package/src/index.ts +11 -0
- package/src/lock/index.ts +164 -0
- package/src/log/config.ts +71 -0
- package/src/log/date.ts +19 -0
- package/src/log/file.ts +215 -0
- package/src/log/index.ts +136 -0
- package/src/log/level.ts +29 -0
- package/src/log/log.ts +77 -0
- package/src/log/store.ts +31 -0
- package/src/mongodb/collection.ts +25 -0
- package/src/mongodb/config.ts +69 -0
- package/src/mongodb/doc.ts +12 -0
- package/src/mongodb/exception.ts +8 -0
- package/src/mongodb/index.ts +71 -0
- package/src/mongodb/manager/base.ts +674 -0
- package/src/mongodb/manager/index.ts +80 -0
- package/src/mongodb/manager/tx-strict.ts +153 -0
- package/src/mongodb/manager/tx.ts +34 -0
- package/src/mongodb/migration.ts +66 -0
- package/src/mvc/access-log.ts +33 -0
- package/src/mvc/config.ts +70 -0
- package/src/mvc/exchange.ts +126 -0
- package/src/mvc/handler/index.ts +4 -0
- package/src/mvc/handler/json.ts +96 -0
- package/src/mvc/handler/restful.ts +39 -0
- package/src/mvc/handler/sse.ts +90 -0
- package/src/mvc/handler/upload.ts +54 -0
- package/src/mvc/index.ts +48 -0
- package/src/mvc/interceptor.ts +12 -0
- package/src/mvc/query.ts +36 -0
- package/src/mvc/render/file.ts +148 -0
- package/src/mvc/render/html/html.ts +187 -0
- package/src/mvc/render/html/index.ts +16 -0
- package/src/mvc/render/html/style.ts +1201 -0
- package/src/mvc/render/index.ts +4 -0
- package/src/mvc/render/json.ts +24 -0
- package/src/mvc/render/text.ts +14 -0
- package/src/mvc/router.ts +13 -0
- package/src/mvc/server.ts +315 -0
- package/src/mvc/static/header.ts +86 -0
- package/src/mvc/static/index.ts +3 -0
- package/src/mvc/static/mime-type.ts +81 -0
- package/src/mvc/static/server-cache-config.ts +92 -0
- package/src/mvc/static/server-cache.ts +171 -0
- package/src/mvc/static/static-handler.ts +445 -0
- package/src/mysql/config.ts +130 -0
- package/src/mysql/exception.ts +8 -0
- package/src/mysql/index.ts +88 -0
- package/src/mysql/manager/base.ts +285 -0
- package/src/mysql/manager/index.ts +112 -0
- package/src/mysql/manager/ops/count.ts +30 -0
- package/src/mysql/manager/ops/criteria.ts +412 -0
- package/src/mysql/manager/ops/delete.ts +96 -0
- package/src/mysql/manager/ops/exist.ts +41 -0
- package/src/mysql/manager/ops/find.ts +226 -0
- package/src/mysql/manager/ops/index.ts +11 -0
- package/src/mysql/manager/ops/insert.ts +120 -0
- package/src/mysql/manager/ops/modify.ts +14 -0
- package/src/mysql/manager/ops/paginate.ts +60 -0
- package/src/mysql/manager/ops/query.ts +13 -0
- package/src/mysql/manager/ops/update.ts +294 -0
- package/src/mysql/manager/ops/utils.ts +20 -0
- package/src/mysql/manager/tx-strict.ts +138 -0
- package/src/mysql/manager/tx.ts +31 -0
- package/src/mysql/manager/utils.ts +75 -0
- package/src/mysql/migration.ts +149 -0
- package/src/mysql/table-info.ts +41 -0
- package/src/task/daily.ts +70 -0
- package/src/task/fixed-delay.ts +45 -0
- package/src/task/fixed-rate.ts +49 -0
- package/src/task/index.ts +4 -0
- package/src/task/task.ts +70 -0
- package/src/validation/exception.ts +27 -0
- package/src/validation/index.ts +61 -0
- package/src/validation/validator/array.ts +32 -0
- package/src/validation/validator/enum.ts +25 -0
- package/src/validation/validator/index.ts +11 -0
- package/src/validation/validator/length.ts +42 -0
- package/src/validation/validator/max-length.ts +33 -0
- package/src/validation/validator/max.ts +26 -0
- package/src/validation/validator/min-length.ts +31 -0
- package/src/validation/validator/min.ts +26 -0
- package/src/validation/validator/not-blank.ts +31 -0
- package/src/validation/validator/not-null.ts +19 -0
- package/src/validation/validator/plain-obj.ts +30 -0
- package/src/validation/validator/regexp.ts +32 -0
- package/types/cache/cache.d.ts +52 -52
- package/types/cache/config.d.ts +32 -32
- package/types/cache/index.d.ts +2 -2
- package/types/cache/purge-task.d.ts +11 -11
- package/types/cache/stat.d.ts +26 -26
- package/types/config/convert.d.ts +6 -6
- package/types/config/exception.d.ts +7 -7
- package/types/config/index.d.ts +25 -25
- package/types/http-client/index.d.ts +71 -71
- package/types/i18n/ar.d.ts +2 -2
- package/types/i18n/de.d.ts +2 -2
- package/types/i18n/en-us.d.ts +2 -2
- package/types/i18n/es.d.ts +2 -2
- package/types/i18n/fr.d.ts +2 -2
- package/types/i18n/i18n.d.ts +102 -102
- package/types/i18n/index.d.ts +9 -9
- package/types/i18n/ja.d.ts +2 -2
- package/types/i18n/ko.d.ts +2 -2
- package/types/i18n/msg.d.ts +50 -50
- package/types/i18n/pt.d.ts +2 -2
- package/types/i18n/ru.d.ts +2 -2
- package/types/i18n/tag.d.ts +11 -11
- package/types/i18n/zh-HK.d.ts +2 -2
- package/types/i18n/zh-TW.d.ts +2 -2
- package/types/i18n/zh-cn.d.ts +2 -2
- package/types/index.d.ts +11 -11
- package/types/lock/index.d.ts +64 -64
- package/types/log/config.d.ts +35 -27
- package/types/log/date.d.ts +2 -2
- package/types/log/file.d.ts +13 -5
- package/types/log/index.d.ts +53 -34
- package/types/log/level.d.ts +14 -14
- package/types/log/log.d.ts +40 -0
- package/types/log/store.d.ts +19 -12
- package/types/mongodb/collection.d.ts +25 -25
- package/types/mongodb/config.d.ts +45 -45
- package/types/mongodb/doc.d.ts +11 -11
- package/types/mongodb/exception.d.ts +7 -7
- package/types/mongodb/index.d.ts +29 -29
- package/types/mongodb/manager/base.d.ts +188 -188
- package/types/mongodb/manager/index.d.ts +38 -38
- package/types/mongodb/manager/tx-strict.d.ts +41 -41
- package/types/mongodb/manager/tx.d.ts +21 -21
- package/types/mongodb/migration.d.ts +12 -12
- package/types/mvc/access-log.d.ts +7 -7
- package/types/mvc/config.d.ts +42 -42
- package/types/mvc/exchange.d.ts +72 -72
- package/types/mvc/handler/index.d.ts +4 -3
- package/types/mvc/handler/json.d.ts +44 -44
- package/types/mvc/handler/restful.d.ts +11 -11
- package/types/mvc/handler/sse.d.ts +34 -0
- package/types/mvc/handler/upload.d.ts +36 -36
- package/types/mvc/index.d.ts +22 -22
- package/types/mvc/interceptor.d.ts +11 -11
- package/types/mvc/query.d.ts +13 -13
- package/types/mvc/render/file.d.ts +10 -10
- package/types/mvc/render/html/html.d.ts +98 -98
- package/types/mvc/render/html/index.d.ts +11 -11
- package/types/mvc/render/html/style.d.ts +1201 -1201
- package/types/mvc/render/index.d.ts +4 -4
- package/types/mvc/render/json.d.ts +17 -17
- package/types/mvc/render/text.d.ts +10 -10
- package/types/mvc/router.d.ts +11 -11
- package/types/mvc/server.d.ts +90 -90
- package/types/mvc/static/header.d.ts +27 -27
- package/types/mvc/static/index.d.ts +3 -3
- package/types/mvc/static/mime-type.d.ts +2 -2
- package/types/mvc/static/server-cache-config.d.ts +30 -30
- package/types/mvc/static/server-cache.d.ts +76 -76
- package/types/mvc/static/static-handler.d.ts +77 -77
- package/types/mysql/config.d.ts +90 -90
- package/types/mysql/exception.d.ts +7 -7
- package/types/mysql/index.d.ts +16 -16
- package/types/mysql/manager/base.d.ts +165 -159
- package/types/mysql/manager/index.d.ts +36 -36
- package/types/mysql/manager/ops/count.d.ts +13 -13
- package/types/mysql/manager/ops/criteria.d.ts +134 -134
- package/types/mysql/manager/ops/delete.d.ts +46 -46
- package/types/mysql/manager/ops/exist.d.ts +6 -6
- package/types/mysql/manager/ops/find.d.ts +86 -70
- package/types/mysql/manager/ops/index.d.ts +10 -10
- package/types/mysql/manager/ops/insert.d.ts +18 -18
- package/types/mysql/manager/ops/modify.d.ts +3 -3
- package/types/mysql/manager/ops/paginate.d.ts +36 -36
- package/types/mysql/manager/ops/query.d.ts +3 -3
- package/types/mysql/manager/ops/update.d.ts +76 -76
- package/types/mysql/manager/ops/utils.d.ts +5 -5
- package/types/mysql/manager/tx-strict.d.ts +36 -35
- package/types/mysql/manager/tx.d.ts +15 -15
- package/types/mysql/manager/utils.d.ts +17 -17
- package/types/mysql/migration.d.ts +8 -8
- package/types/mysql/table-info.d.ts +36 -36
- package/types/task/daily.d.ts +16 -16
- package/types/task/fixed-delay.d.ts +9 -9
- package/types/task/fixed-rate.d.ts +9 -9
- package/types/task/index.d.ts +4 -4
- package/types/task/task.d.ts +34 -34
- package/types/validation/exception.d.ts +38 -38
- package/types/validation/index.d.ts +32 -32
- package/types/validation/validator/array.d.ts +5 -5
- package/types/validation/validator/enum.d.ts +8 -8
- package/types/validation/validator/index.d.ts +11 -11
- package/types/validation/validator/length.d.ts +10 -10
- package/types/validation/validator/max-length.d.ts +8 -8
- package/types/validation/validator/max.d.ts +7 -7
- package/types/validation/validator/min-length.d.ts +6 -6
- package/types/validation/validator/min.d.ts +7 -7
- package/types/validation/validator/not-blank.d.ts +7 -7
- package/types/validation/validator/not-null.d.ts +6 -6
- package/types/validation/validator/plain-obj.d.ts +7 -7
- package/types/validation/validator/regexp.d.ts +8 -8
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Server-Sent Events (SSE)
|
|
2
|
+
|
|
3
|
+
从 0.4.0 版本开始,MVC 组件内置了 `createSseHandler`,封装了 SSE 协议细节,大幅简化服务端推送实现。
|
|
4
|
+
|
|
5
|
+
## 内置 SSE Handler
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createSseHandler } from 'wok-server'
|
|
9
|
+
|
|
10
|
+
await startWebServer({
|
|
11
|
+
routers: {
|
|
12
|
+
'/sse': createSseHandler({
|
|
13
|
+
async handle(ctx) {
|
|
14
|
+
let counter = 0
|
|
15
|
+
for (let i = 0; i < 10; i++) {
|
|
16
|
+
await new Promise<void>(resolve => setTimeout(resolve, 1000))
|
|
17
|
+
counter++
|
|
18
|
+
// ctx.send 自动处理 SSE 协议格式
|
|
19
|
+
ctx.send({ message: '实时更新', count: counter })
|
|
20
|
+
if (counter >= 10) {
|
|
21
|
+
break
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// 显式关闭连接(不调用也会在 handle 结束时自动关闭)
|
|
25
|
+
ctx.close()
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### SseContext
|
|
33
|
+
|
|
34
|
+
`handle` 函数接收的 `ctx` 对象提供以下属性与方法:
|
|
35
|
+
|
|
36
|
+
| 属性/方法 | 说明 |
|
|
37
|
+
| :--------------------------------------- | :----------------------------------------------------------- |
|
|
38
|
+
| `ctx.send(data, event?, id?)` | 发送 SSE 事件,`data` 会被 JSON 序列化。`event` 指定事件名(前端用 `addEventListener` 监听),`id` 设置事件 ID(用于断线重连的 `Last-Event-ID`) |
|
|
39
|
+
| `ctx.close()` | 显式结束 SSE 连接。`handle` 结束时连接也会自动关闭 |
|
|
40
|
+
| `ctx.request` | 原始 `IncomingMessage`,可读取请求头等信息 |
|
|
41
|
+
| `ctx.response` | 原始 `ServerResponse`,高级场景下使用 |
|
|
42
|
+
|
|
43
|
+
### 命名事件
|
|
44
|
+
|
|
45
|
+
通过 `event` 参数发送命名事件,前端可按事件类型分别处理:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
createSseHandler({
|
|
49
|
+
async handle(ctx) {
|
|
50
|
+
ctx.send({ title: '新消息' }, 'notification')
|
|
51
|
+
ctx.send({ progress: 50 }, 'progress')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
前端:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const es = new EventSource('/sse')
|
|
60
|
+
es.addEventListener('notification', e => {
|
|
61
|
+
const data = JSON.parse(e.data)
|
|
62
|
+
console.log('收到通知:', data.title)
|
|
63
|
+
})
|
|
64
|
+
es.addEventListener('progress', e => {
|
|
65
|
+
const data = JSON.parse(e.data)
|
|
66
|
+
console.log('进度:', data.progress)
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 断线重连
|
|
71
|
+
|
|
72
|
+
通过 `id` 参数设置事件 ID,前端断线重连时浏览器会自动发送 `Last-Event-ID` 请求头:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
createSseHandler({
|
|
76
|
+
async handle(ctx) {
|
|
77
|
+
const lastId = ctx.request.headers['last-event-id']
|
|
78
|
+
// 根据 lastId 确定从何处恢复推送
|
|
79
|
+
for (const event of events) {
|
|
80
|
+
ctx.send(event, undefined, event.id)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 连接生命周期
|
|
87
|
+
|
|
88
|
+
- `handle` 开始执行时,SSE 消息头已发送,连接已建立
|
|
89
|
+
- 客户端断开连接时,`ctx.send()` 调用变为空操作(自动忽略)
|
|
90
|
+
- `handle` 返回或抛异常时,连接自动关闭
|
|
91
|
+
- 手动调用 `ctx.close()` 可提前结束连接
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 手动实现(高级用法)
|
|
96
|
+
|
|
97
|
+
如需完全控制底层行为,仍可在普通路由处理器中手动操作 `response` 对象:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await startWebServer({
|
|
101
|
+
routers: {
|
|
102
|
+
'/sse': async exchange => {
|
|
103
|
+
const { response } = exchange
|
|
104
|
+
response.setHeader('Content-Type', 'text/event-stream')
|
|
105
|
+
response.setHeader('Cache-Control', 'no-cache')
|
|
106
|
+
response.setHeader('Connection', 'keep-alive')
|
|
107
|
+
|
|
108
|
+
let counter = 0
|
|
109
|
+
for (let i = 0; i < 10; i++) {
|
|
110
|
+
await new Promise<void>(resolve => setTimeout(resolve, 1000))
|
|
111
|
+
counter++
|
|
112
|
+
response.write(`data: ${JSON.stringify({ message: '实时更新', count: counter })}\n\n`)
|
|
113
|
+
if (counter >= 10) {
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
response.end()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 静态文件
|
|
2
|
+
|
|
3
|
+
通过 `startWebServer` 的 `static` 参数设置静态文件目录映射,将文件系统目录映射到请求路径。
|
|
4
|
+
|
|
5
|
+
## 基本配置
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
await startWebServer({
|
|
9
|
+
static: {
|
|
10
|
+
'/a': { dir: '/path/to/files', cacheAge: 300 },
|
|
11
|
+
'/a/b': { dir: '/path/to/other', cacheAge: 150 },
|
|
12
|
+
'/b': { dir: 'static', cacheAge: 0 }
|
|
13
|
+
},
|
|
14
|
+
routers: {}
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| 参数 | 说明 |
|
|
19
|
+
| :-------- | :----------------------------------------------- |
|
|
20
|
+
| `dir` | 映射的文件目录,支持绝对路径和相对路径(相对进程当前目录) |
|
|
21
|
+
| `cacheAge` | 缓存时间(秒),大于 0 则生成 `Cache-Control` 消息头 |
|
|
22
|
+
|
|
23
|
+
## 路径匹配规则
|
|
24
|
+
|
|
25
|
+
- 仅支持**前缀匹配**,不支持通配符。例如 `/a/demo.html` 匹配 `/a` 路径配置。
|
|
26
|
+
- 路径配置有**优先级**:更详细的路径优先匹配。例如访问 `/a/b/music.mp3` 会匹配 `/a/b` 而非 `/a`。
|
|
27
|
+
- 匹配失败不会降级尝试:如果 `/a/b` 目录下找不到文件,不会再去 `/a` 目录查找。
|
|
28
|
+
- 支持**主页自动映射**:访问 `/a/b/c` 时,会先找文件 `c`,找不到则尝试目录 `c` 下的 `index.html`。
|
|
29
|
+
|
|
30
|
+
## 服务器端缓存
|
|
31
|
+
|
|
32
|
+
从 0.3.0 版本开始,支持静态文件服务器端缓存。通过以下环境变量配置:
|
|
33
|
+
|
|
34
|
+
| 环境变量 | 说明 | 默认值 |
|
|
35
|
+
| :--------------------------------- | :-------------------------------------------- | :----- |
|
|
36
|
+
| `SERVER_STATIC_CACHE_ENABLE` | 是否启用服务器缓存 | false |
|
|
37
|
+
| `SERVER_STATIC_CACHE_MAX_AGE` | 服务器缓存时间(秒) | 600 |
|
|
38
|
+
| `SERVER_STATIC_CACHE_MAX_FILE_SIZE` | 最大可缓存的文件大小,支持语义化格式(如 10m、100k) | 10m |
|
|
39
|
+
| `SERVER_STATIC_CACHE_MAX_SIZE` | 缓存最大空间,超出后清理,支持语义化格式 | 100m |
|
|
40
|
+
|
|
41
|
+
0.3.2 版本新增 `removeServerStaticCache` 函数,可主动清除指定路径的静态文件缓存:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { removeServerStaticCache } from 'wok-server'
|
|
45
|
+
|
|
46
|
+
removeServerStaticCache('/assets/index.js')
|
|
47
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 文件上传
|
|
2
|
+
|
|
3
|
+
## 二进制(Binary)上传
|
|
4
|
+
|
|
5
|
+
二进制上传即 `application/octet-stream` 格式的上传,请求正文仅为文件内容,不包含其他信息。由 `createUploadHandler` 函数创建路由处理器,响应 JSON 格式。
|
|
6
|
+
|
|
7
|
+
这种形式的优势是简单、性能高——服务器端拿到请求正文即是文件,无需解析。但额外参数只能通过 QueryString 或 Header 传递,且一次只能传一个文件。
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createUploadHandler } from 'wok-server'
|
|
11
|
+
|
|
12
|
+
interface Resp {
|
|
13
|
+
url: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const uploadAvatar = createUploadHandler<Resp>({
|
|
17
|
+
async handle(body, exchange) {
|
|
18
|
+
const userId = exchange.query.getStr('userId') as string
|
|
19
|
+
if (body.byteLength > 2 * 1024 * 1024) {
|
|
20
|
+
throw new BusinessException('文件大小不得超过 2MB')
|
|
21
|
+
}
|
|
22
|
+
const key = `users/${userId}/avatar`
|
|
23
|
+
await oss.putObject(key, body)
|
|
24
|
+
return { url: oss.getUrl(key) }
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
如需更高自由度(如不返回 JSON),使用普通路由处理器,通过 `exchange.bodyBuffer()` 读取请求正文:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export const uploadAvatar: RouterHandler = async exchange => {
|
|
33
|
+
const file = await exchange.bodyBuffer()
|
|
34
|
+
const query = exchange.parseQueryString()
|
|
35
|
+
const userId = query.getStr('userId')
|
|
36
|
+
// 校验参数和存储文件等流程省略...
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## multipart/form-data 格式上传
|
|
41
|
+
|
|
42
|
+
组件目前尚未内置对 multipart/form-data 类型请求的处理,可通过第三方库(如 formidable)解析。
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import formidable from 'formidable'
|
|
46
|
+
|
|
47
|
+
await startWebServer({
|
|
48
|
+
routers: {
|
|
49
|
+
'/cover': async exchange => {
|
|
50
|
+
const form = formidable({})
|
|
51
|
+
const [fields, files] = await form.parse(exchange.request)
|
|
52
|
+
// todo 继续业务处理
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 注意事项
|
|
59
|
+
|
|
60
|
+
- 使用了第三方库读取 `request` 内容后,不能再使用 `exchange` 中的 `bodyXxx` 系列方法,调用时会抛出异常。
|
|
61
|
+
- 同样,调用了 `bodyXxx` 系列方法后也不能再用第三方库读取内容,否则将读取不到完整内容或引发异常。
|
|
62
|
+
- `bodyXxx` 系列方法之间可以重复调用,不会产生错误。例如调用 `bodyText()` 后再调用 `bodyJson()` 是允许的。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# WebSocket
|
|
2
|
+
|
|
3
|
+
MVC 组件本身未内置 WebSocket 处理,但通过 `startWebServer` 的 `preHandler` 参数可整合支持原生 `http` 模块的 WebSocket 库(如 socket.io)。
|
|
4
|
+
|
|
5
|
+
`preHandler` 在服务启动前执行,接收原生 `http.Server` 实例,可在此时挂载 WebSocket 服务。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Server } from 'socket.io'
|
|
9
|
+
|
|
10
|
+
await startWebServer({
|
|
11
|
+
routers: {
|
|
12
|
+
// 路由配置省略...
|
|
13
|
+
},
|
|
14
|
+
preHandler: async server => {
|
|
15
|
+
const io = new Server(server)
|
|
16
|
+
io.of('/chat').on('connection', socket => {
|
|
17
|
+
socket.on('message', data => {
|
|
18
|
+
/* 处理自定义事件 */
|
|
19
|
+
})
|
|
20
|
+
socket.on('disconnect', () => {
|
|
21
|
+
/* 连接断开处理 */
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 注意事项
|
|
29
|
+
|
|
30
|
+
- WebSocket 路径(如 `/chat`)与路由路径不会冲突。客户端以 HTTP 协议请求时走路由处理,以 WebSocket 协议请求时走 socket.io 处理。但不推荐使用相同的路径。
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wok-server-mysql
|
|
3
|
+
description: wok-server MySQL 组件使用指南,提供单表 CRUD、多数据源、版本管理和事务的完整支持。
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Peak Tai
|
|
7
|
+
email: peaktai@qq.com
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# wok-server MySQL 组件
|
|
11
|
+
|
|
12
|
+
## 概述
|
|
13
|
+
|
|
14
|
+
MySQL 组件基于 [mysql2](https://www.npmjs.com/package/mysql2) 封装,提供便捷的单表操作。支持:实体映射 CRUD、多数据源、SQL 版本管理、事务、慢查询告警。
|
|
15
|
+
|
|
16
|
+
## 源码与类型定义
|
|
17
|
+
|
|
18
|
+
安装 `wok-server` 后,可通过以下路径查看源码与类型定义:
|
|
19
|
+
|
|
20
|
+
- 源码目录:`node_modules/wok-server/src/mysql/`
|
|
21
|
+
- 类型定义:`node_modules/wok-server/types/` (引用 mysql 组件的 `.d.ts` 文件)
|
|
22
|
+
|
|
23
|
+
核心源码文件:
|
|
24
|
+
|
|
25
|
+
| 文件 | 说明 |
|
|
26
|
+
| :----------------- | :--------------------------------- |
|
|
27
|
+
| `index.ts` | 模块入口,`enableMysql`/`getMysqlManager` |
|
|
28
|
+
| `config.ts` | 配置定义与默认值 |
|
|
29
|
+
| `table-info.ts` | `Table<T>` 表映射接口 |
|
|
30
|
+
| `manager/` | 管理器层:`BaseMysqlManager`、`MysqlManager`、`MysqlTxSession` |
|
|
31
|
+
| `manager/ops/` | 各操作的 SQL 生成与执行 |
|
|
32
|
+
| `manager/ops/criteria.ts` | `MysqlCriteria` 条件构建器 |
|
|
33
|
+
| `migration.ts` | SQL 文件版本管理 |
|
|
34
|
+
| `exception.ts` | `MysqlException` 异常类 |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 环境变量
|
|
39
|
+
|
|
40
|
+
| 环境变量 | 说明 | 默认值 |
|
|
41
|
+
| :----------------------------- | :------------------- | :----------- |
|
|
42
|
+
| `MYSQL_HOST` | 主机名 | localhost |
|
|
43
|
+
| `MYSQL_PORT` | 端口号 | 3306 |
|
|
44
|
+
| `MYSQL_USER` | 用户名 | root |
|
|
45
|
+
| `MYSQL_PASSWORD` | 密码 | 123456 |
|
|
46
|
+
| `MYSQL_DATABASE` | 数据库名 | example |
|
|
47
|
+
| `MYSQL_CHARSET` | 字符集 | utf8mb4 |
|
|
48
|
+
| `MYSQL_CONNECTION_LIMIT` | 最大连接数 | 10 |
|
|
49
|
+
| `MYSQL_MAX_IDLE` | 最大闲置连接数 | 10 |
|
|
50
|
+
| `MYSQL_IDLE_TIMEOUT` | 闲置超时(ms) | 60000 |
|
|
51
|
+
| `MYSQL_CONNECT_TIMEOUT` | 连接超时(ms) | 10000 |
|
|
52
|
+
| `MYSQL_DEBUG` | 调试模式,输出 SQL | false |
|
|
53
|
+
| `MYSQL_SLOW_SQL_WARN` | 慢查询警告 | true |
|
|
54
|
+
| `MYSQL_SLOW_SQL_MS` | 慢查询阈值(ms) | 200 |
|
|
55
|
+
| `MYSQL_TIMEZONE` | 时区 | +08:00 |
|
|
56
|
+
| `MYSQL_VERSION_CONTROL_ENABLED`| 版本管理开启 | false |
|
|
57
|
+
| `MYSQL_VERSION_CONTROL_DIR` | 迁移文件目录 | db_migration |
|
|
58
|
+
| `MYSQL_TRANSACTION_TIMEOUT` | 事务超时(ms) | 5000 |
|
|
59
|
+
| `MYSQL_TRANSACTION_STRICT` | 事务严格模式 | true |
|
|
60
|
+
| `MYSQL_MAX_OPS_IN_STRICT_TX` | 严格事务最大操作次数 | 10 |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 初始化
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { enableMysql, getMysqlManager } from 'wok-server'
|
|
68
|
+
|
|
69
|
+
await enableMysql()
|
|
70
|
+
const manager = getMysqlManager()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
多数据源(如读写分离等高级用法)见[参考文档](./references/multi-datasource.md)。
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 实体类配置
|
|
78
|
+
|
|
79
|
+
表映射配置由 `Table<T>` 接口定义,字段分三部分:主键、普通列、时间列,三者不允许重叠。
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { Table } from 'wok-server'
|
|
83
|
+
|
|
84
|
+
export interface User {
|
|
85
|
+
id: string
|
|
86
|
+
nickname: string
|
|
87
|
+
hobby?: string
|
|
88
|
+
create_at?: number
|
|
89
|
+
update_at?: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const tableUser: Table<User> = {
|
|
93
|
+
tableName: 'user',
|
|
94
|
+
id: 'id',
|
|
95
|
+
columns: ['nickname', 'hobby'],
|
|
96
|
+
createdDate: { type: 'number', column: 'create_at' },
|
|
97
|
+
updatedDate: { type: 'number', column: 'update_at' }
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`createdDate` 和 `updatedDate` 配置的字段由框架自动管理:`insert`/`insertMany` 时自动填入创建时间和更新时间,`update`/`partialUpdate`/`updateMany` 时自动更新 `updatedDate` 字段。业务代码中不需要手动设置这两个字段的值。
|
|
102
|
+
|
|
103
|
+
> **⚠️ 重要:框架不支持字段名称映射。** 实体类型的字段名必须和数据库列名一模一样。比如数据库列名是 `entry_date`,实体字段不能写为 `entryDate` 或 `entryDate` 再用装饰器映射,必须是 `entry_date: Date`。如果之前用惯了其他 ORM 框架,很容易在这里习惯性写成驼峰而掉坑,务必注意。
|
|
104
|
+
|
|
105
|
+
### 类型映射规则
|
|
106
|
+
|
|
107
|
+
| JS 类型 | MySQL 字段类型 |
|
|
108
|
+
| :------------- | :------------------------------------------------------------------- |
|
|
109
|
+
| `boolean` | TINYINT |
|
|
110
|
+
| `number` | TINYINT, SMALLINT, INT, MEDIUMINT, YEAR, FLOAT, DOUBLE, BIGINT |
|
|
111
|
+
| `Date` | TIMESTAMP, DATE, DATETIME |
|
|
112
|
+
| `Buffer` | TINYBLOB, MEDIUMBLOB, LONGBLOB, BLOB, BINARY, VARBINARY, BIT |
|
|
113
|
+
| `string` | CHAR, VARCHAR, TINYTEXT, MEDIUMTEXT, LONGTEXT, TEXT, ENUM, SET, DECIMAL, TIME |
|
|
114
|
+
| `object`/`array` | JSON |
|
|
115
|
+
|
|
116
|
+
实体字段类型必须与数据库列类型匹配对应的 JS 原生类型,否则查询结果会不正确。可空字段在实体中也定义为可选(`?`)。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CRUD 操作
|
|
121
|
+
|
|
122
|
+
所有操作以 `Table<T>` 为第一个参数。
|
|
123
|
+
|
|
124
|
+
### 查询
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const user = await manager.findById(tableUser, '001') // 按 ID 查
|
|
128
|
+
const users = await manager.findByIdIn(tableUser, ['001','002']) // 批量 ID 查
|
|
129
|
+
const list = await manager.findAll(tableUser) // ⚠️ 全表,危险
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 条件查询
|
|
133
|
+
|
|
134
|
+
使用 `MysqlCriteria` 构建条件,默认 AND 关系:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const user = await manager.findFirst(tableUser, c =>
|
|
138
|
+
c.like('nickname', 'ff0%').gt('balance', 75).lt('balance', 77)
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**链式条件方法**:`eq`、`neq`、`gt`、`gte`、`lt`、`lte`、`like`、`notLike`、`between`、`in`、`notIn`、`isNull`、`isNotNull`、`or`、`and`。
|
|
143
|
+
|
|
144
|
+
### 复杂查询
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const list = await manager.find({
|
|
148
|
+
table: tableUser,
|
|
149
|
+
criteria: c => c.between('balance', 700, 800).like('id', 'find%'),
|
|
150
|
+
offset: 1,
|
|
151
|
+
limit: 10,
|
|
152
|
+
orderBy: [['balance', 'asc']]
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 分页
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const page = await manager.paginate({
|
|
160
|
+
table: tableUser,
|
|
161
|
+
criteria: c => c.like('id', 'pg0%'),
|
|
162
|
+
pn: 2, pz: 5,
|
|
163
|
+
orderBy: [['balance', 'asc'], ['id', 'asc']]
|
|
164
|
+
})
|
|
165
|
+
// { total: number, list: T[] }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 插入
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const newUser = await manager.insert(tableUser, { id: 'in001', nickname: '小明' })
|
|
172
|
+
await manager.insertMany(tableUser, [
|
|
173
|
+
{ id: 'im001', nickname: '张飞' },
|
|
174
|
+
{ id: 'im002', nickname: '关羽' }
|
|
175
|
+
])
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 更新
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// 完整更新(需要完整文档)
|
|
182
|
+
await manager.update(tableUser, { id: 'xxx', nickname: '王五' })
|
|
183
|
+
|
|
184
|
+
// 局部更新(使用元组语法:['inc', 22] 表示 +22,[null] 表示置空)
|
|
185
|
+
await manager.partialUpdate(tableUser, { id: 'pu000', balance: ['inc', 22] })
|
|
186
|
+
|
|
187
|
+
// 批量更新
|
|
188
|
+
await manager.updateMany(tableUser, c => c.like('nickname', 'um%'), { balance: ['inc', 2] })
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 删除
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
await manager.deleteById(tableUser, 'd0001')
|
|
195
|
+
await manager.deleteMany({ table: tableUser, criteria: { status: 'DISABLED' }, limit: 100 })
|
|
196
|
+
await manager.deleteOne(tableUser, { id: 'only_one' })
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**⚠️ `deleteMany` 是危险操作**,建议严格限制条件并设置 `limit` 参数。
|
|
200
|
+
|
|
201
|
+
### 自定义 SQL
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// 查询
|
|
205
|
+
const list = await manager.query<{ author: string; book: string }>(
|
|
206
|
+
'select u.nickname as author, b.name as book from ?? u left join ?? b on u.id=b.author_id where b.id is not null',
|
|
207
|
+
['user', 'book']
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
// 修改
|
|
211
|
+
const affected = await manager.modify(`update user set nickname='无名' where nickname='佚名'`)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 全部方法
|
|
215
|
+
|
|
216
|
+
| 方法 | 说明 | 危险 |
|
|
217
|
+
| :------------- | :--------------------------- | :--- |
|
|
218
|
+
| `findById` | 按 id 查询 | |
|
|
219
|
+
| `findByIdIn` | 批量 id 查询 | |
|
|
220
|
+
| `findFirst` | 查第一条符合条件的 | |
|
|
221
|
+
| `find` | 条件查询,支持 offset/limit | ⚠️ |
|
|
222
|
+
| `findSelect` | 条件查询,指定返回列 | ⚠️ |
|
|
223
|
+
| `findAll` | 全表查询 | ⚠️ |
|
|
224
|
+
| `existsBy` | 条件判断存在 | |
|
|
225
|
+
| `existsById` | id 判断存在 | |
|
|
226
|
+
| `count` | 统计数量 | ⚠️ |
|
|
227
|
+
| `paginate` | 分页查询 | ⚠️ |
|
|
228
|
+
| `insert` | 插入单条 | |
|
|
229
|
+
| `insertMany` | 批量插入 | |
|
|
230
|
+
| `update` | 完整更新 | |
|
|
231
|
+
| `partialUpdate`| 局部更新 | |
|
|
232
|
+
| `updateOne` | 更新第一条相等条件 | |
|
|
233
|
+
| `updateMany` | 批量更新 | ⚠️ |
|
|
234
|
+
| `deleteById` | 按 id 删除 | |
|
|
235
|
+
| `deleteOne` | 删除第一条相等条件 | |
|
|
236
|
+
| `deleteMany` | 批量删除 | ⚠️ |
|
|
237
|
+
| `query` | 自定义 SQL 查询 | |
|
|
238
|
+
| `modify` | 自定义 SQL 修改 | |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## JSON 类型支持
|
|
243
|
+
|
|
244
|
+
在实体中直接定义 JSON 字段对应的 TS 类型即可,框架自动解析:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
interface Question {
|
|
248
|
+
id: string
|
|
249
|
+
options: { title: string; correct?: boolean }[]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const tableQuestion: Table<Question> = {
|
|
253
|
+
tableName: 'question', id: 'id', columns: ['options']
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
条件查询支持 `json_extract` 和 `json_length`,使用元组代替列名:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
await manager.findFirst(tableQuestion, c =>
|
|
261
|
+
c.eq(['json_extract', 'question_setter', '$.id'], 'x333')
|
|
262
|
+
)
|
|
263
|
+
await manager.findFirst(tableQuestion, c =>
|
|
264
|
+
c.gt(['json_length', 'options'], 3)
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 事务
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
await manager.tx(async session => {
|
|
274
|
+
await session.partialUpdate(tableAccount, { id: accId, balance: ['inc', -amount] })
|
|
275
|
+
await session.insert(tableOrder, { id: orderId, amount })
|
|
276
|
+
}, { timeout: 3000 })
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
事务操作**必须使用 `session` 对象**,直接调用 `manager` 的方法不会在事务中生效。
|
|
280
|
+
|
|
281
|
+
选项:
|
|
282
|
+
- `timeout`:超时时间(ms),超时后自动回滚并抛出 `MysqlException`
|
|
283
|
+
- `isolationLevel`:隔离级别(`REPEATABLE READ` 等)
|
|
284
|
+
- `accessMode`:读写模式(`READ WRITE` / `READ ONLY`)
|
|
285
|
+
|
|
286
|
+
### 严格模式
|
|
287
|
+
|
|
288
|
+
`MYSQL_TRANSACTION_STRICT=true` 时启用严格模式,限制事务中的操作次数(`MYSQL_MAX_OPS_IN_STRICT_TX`,默认 10 次)。超限抛出 `MysqlException`。
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 内部实现要点
|
|
293
|
+
|
|
294
|
+
### 连接管理
|
|
295
|
+
|
|
296
|
+
`enableMysql` 注册配置 → 可选迁移 → 创建 `Pool` → 构建 `MysqlManager` → 存入 `managerMap`。进程退出前自动关闭连接池。
|
|
297
|
+
|
|
298
|
+
### 查询流程
|
|
299
|
+
|
|
300
|
+
`MysqlManager` 从连接池获取连接 → `Promise.race` 超时竞速 → 执行 SQL → 释放连接。
|
|
301
|
+
|
|
302
|
+
### 慢查询
|
|
303
|
+
|
|
304
|
+
所有 CRUD 操作(除自定义 SQL)自动计时,超过 `MYSQL_SLOW_SQL_MS` 时输出 WARN 日志。
|
|
305
|
+
|
|
306
|
+
### Migration 实现
|
|
307
|
+
|
|
308
|
+
版本管理的完整使用说明见[参考文档](./references/version-control.md)。
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 参考
|
|
313
|
+
|
|
314
|
+
- [多数据源(读写分离等高级用法)](./references/multi-datasource.md)
|
|
315
|
+
- [版本管理](./references/version-control.md)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# MySQL 多数据源
|
|
2
|
+
|
|
3
|
+
## 启用多数据源
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
await enableMysql() // 默认,以 MYSQL_ 为前缀
|
|
7
|
+
await enableMysql('d2') // 自定义,以 D2_ 为前缀
|
|
8
|
+
|
|
9
|
+
const mgr1 = getMysqlManager() // 默认
|
|
10
|
+
const mgr2 = getMysqlManager('d2') // d2
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
名称必须以英文字母开头,由字母数字下划线组成,不超过 32 位。环境变量前缀自动转换为大写。
|
|
14
|
+
|
|
15
|
+
环境变量配置示例:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 默认 enableMysql(),前缀 MYSQL_
|
|
19
|
+
MYSQL_HOST=localhost
|
|
20
|
+
MYSQL_PORT=3306
|
|
21
|
+
MYSQL_USER=test
|
|
22
|
+
MYSQL_PASSWORD=abc123
|
|
23
|
+
MYSQL_DATABASE=test1
|
|
24
|
+
|
|
25
|
+
# enableMysql('d2'),前缀 D2_
|
|
26
|
+
D2_HOST=localhost
|
|
27
|
+
D2_PORT=3306
|
|
28
|
+
D2_USER=test2
|
|
29
|
+
D2_PASSWORD=abcdefg
|
|
30
|
+
D2_DATABASE=test2
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 技巧:读写分离
|
|
36
|
+
|
|
37
|
+
实体类的表配置(`Table<T>`)与数据库实例没有强制绑定关系,同一套表配置可以给多个 `MysqlManager` 使用。利用这个特性和多数据源,可以实现读写分离。
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { enableMysql, getMysqlManager } from 'wok-server'
|
|
41
|
+
import { tableUser } from './user'
|
|
42
|
+
|
|
43
|
+
// 主库(读写)
|
|
44
|
+
await enableMysql('master')
|
|
45
|
+
// 只读库
|
|
46
|
+
await enableMysql('slave')
|
|
47
|
+
|
|
48
|
+
const masterMgr = getMysqlManager('master')
|
|
49
|
+
const slaveMgr = getMysqlManager('slave')
|
|
50
|
+
|
|
51
|
+
// 写操作使用主库
|
|
52
|
+
const newUser = await masterMgr.insert(tableUser, { id: '001', nickname: 'jack' })
|
|
53
|
+
|
|
54
|
+
// 读操作使用只读库
|
|
55
|
+
const user = await slaveMgr.findById(tableUser, '001')
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
环境变量分别配置两个源:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 主库
|
|
62
|
+
MASTER_HOST=master-db.example.com
|
|
63
|
+
MASTER_PORT=3306
|
|
64
|
+
MASTER_USER=writer
|
|
65
|
+
MASTER_PASSWORD=xxx
|
|
66
|
+
MASTER_DATABASE=mydb
|
|
67
|
+
|
|
68
|
+
# 只读库
|
|
69
|
+
SLAVE_HOST=slave-db.example.com
|
|
70
|
+
SLAVE_PORT=3306
|
|
71
|
+
SLAVE_USER=reader
|
|
72
|
+
SLAVE_PASSWORD=xxx
|
|
73
|
+
SLAVE_DATABASE=mydb
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
在 `main.ts` 入口中分别初始化后,将两个 manager 导出,业务模块按读写需求选择使用即可。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# MySQL 版本管理
|
|
2
|
+
|
|
3
|
+
开启 `MYSQL_VERSION_CONTROL_ENABLED=true`,在 `db_migration/` 目录下创建 SQL 文件,文件名为纯数字:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
db_migration/
|
|
7
|
+
1.sql # 版本号从 1 开始,必须连续递增
|
|
8
|
+
2.sql
|
|
9
|
+
3.sql
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
文件名必须是纯数字 + `.sql` 后缀(如 `1.sql`、`2.sql`),非数字前缀会导致 `parseInt` 失败而抛异常。编号必须从 1 开始连续递增,不连续也会抛异常。
|
|
13
|
+
|
|
14
|
+
启动时自动在事务中检测当前版本,顺序执行未执行的 SQL 并更新版本号。
|
|
15
|
+
|
|
16
|
+
版本信息存储在 `db_version` 表(单行 `key='db_version'`,`version` 列)。
|
|
17
|
+
|
|
18
|
+
> **⚠️ 注意事项:开销非常高的 SQL 不能放在版本管理中执行。** 比如在大表上创建/修改索引这类操作,执行耗时长且会锁表,可能阻塞线上服务。此类操作应单独在线上手动执行,不要放入迁移文件。
|
|
19
|
+
|
|
20
|
+
## 实现细节
|
|
21
|
+
|
|
22
|
+
使用独立的 `multipleStatements` 连接在事务中执行:读取当前版本 → 筛选待执行版本 → 逐个执行 SQL 并更新 `db_version` → 提交。出错需手动在数据库中修复后重新启动。
|