wok-server 0.5.0 → 0.7.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 -35
- package/dist/log/date.js +21 -21
- package/dist/log/file.js +198 -198
- package/dist/log/index.js +135 -135
- package/dist/log/level.js +33 -33
- package/dist/log/log.js +56 -56
- package/dist/log/store.js +19 -19
- 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 +278 -239
- package/dist/mysql/manager/index.js +107 -107
- package/dist/mysql/manager/ops/count.js +20 -20
- package/dist/mysql/manager/ops/criteria.js +381 -356
- package/dist/mysql/manager/ops/delete.js +59 -65
- package/dist/mysql/manager/ops/exist.js +26 -26
- package/dist/mysql/manager/ops/find.js +149 -169
- package/dist/mysql/manager/ops/index.js +16 -14
- package/dist/mysql/manager/ops/insert.js +132 -106
- package/dist/mysql/manager/ops/modify.js +10 -10
- package/dist/mysql/manager/ops/order-by.js +28 -0
- package/dist/mysql/manager/ops/paginate.js +48 -23
- package/dist/mysql/manager/ops/query.js +9 -9
- package/dist/mysql/manager/ops/update.js +222 -216
- package/dist/mysql/manager/ops/upsert.js +178 -0
- package/dist/mysql/manager/ops/utils.js +28 -24
- package/dist/mysql/manager/tx-strict.js +103 -103
- 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 +682 -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/mvc.md +66 -24
- package/documentation/zh-cn/mysql.md +146 -17
- package/package.json +4 -1
- package/skills/wok-server-api-rules/SKILL.md +350 -0
- package/skills/wok-server-cache/SKILL.md +216 -0
- package/skills/wok-server-code-navigation/SKILL.md +153 -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 +388 -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 +332 -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 +446 -0
- package/src/mysql/manager/ops/delete.ts +91 -0
- package/src/mysql/manager/ops/exist.ts +41 -0
- package/src/mysql/manager/ops/find.ts +209 -0
- package/src/mysql/manager/ops/index.ts +13 -0
- package/src/mysql/manager/ops/insert.ts +158 -0
- package/src/mysql/manager/ops/modify.ts +14 -0
- package/src/mysql/manager/ops/order-by.ts +58 -0
- package/src/mysql/manager/ops/paginate.ts +100 -0
- package/src/mysql/manager/ops/query.ts +13 -0
- package/src/mysql/manager/ops/update.ts +318 -0
- package/src/mysql/manager/ops/upsert.ts +224 -0
- package/src/mysql/manager/ops/utils.ts +24 -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 -35
- package/types/log/date.d.ts +2 -2
- package/types/log/file.d.ts +13 -13
- package/types/log/index.d.ts +53 -53
- package/types/log/level.d.ts +14 -14
- package/types/log/log.d.ts +40 -40
- package/types/log/store.d.ts +19 -19
- 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 +196 -165
- 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 +144 -134
- package/types/mysql/manager/ops/delete.d.ts +47 -46
- package/types/mysql/manager/ops/exist.d.ts +6 -6
- package/types/mysql/manager/ops/find.d.ts +87 -86
- package/types/mysql/manager/ops/index.d.ts +12 -10
- package/types/mysql/manager/ops/insert.d.ts +32 -18
- package/types/mysql/manager/ops/modify.d.ts +3 -3
- package/types/mysql/manager/ops/order-by.d.ts +38 -0
- package/types/mysql/manager/ops/paginate.d.ts +53 -36
- package/types/mysql/manager/ops/query.d.ts +3 -3
- package/types/mysql/manager/ops/update.d.ts +99 -76
- package/types/mysql/manager/ops/upsert.d.ts +36 -0
- package/types/mysql/manager/ops/utils.d.ts +5 -5
- package/types/mysql/manager/tx-strict.d.ts +36 -36
- 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,31 @@
|
|
|
1
|
+
import { PoolConnection } from 'mysql2'
|
|
2
|
+
import { BaseMysqlManager } from './base'
|
|
3
|
+
import { MysqlConfig } from '../config'
|
|
4
|
+
import { MysqlException } from '../exception'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* mysql 事务会话.
|
|
8
|
+
*/
|
|
9
|
+
export class MysqlTxSession extends BaseMysqlManager {
|
|
10
|
+
/**
|
|
11
|
+
* 中止标识
|
|
12
|
+
*/
|
|
13
|
+
#aborted = false
|
|
14
|
+
|
|
15
|
+
constructor(config: MysqlConfig, conn: PoolConnection) {
|
|
16
|
+
super({ config, connection: conn })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected queryWithConnection<T>(queryFn: (conn: PoolConnection) => Promise<T>): Promise<T> {
|
|
20
|
+
if (this.#aborted) {
|
|
21
|
+
throw new MysqlException('Session has been aborted!')
|
|
22
|
+
}
|
|
23
|
+
return super.queryWithConnection(queryFn)
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 中止,被中止后的会话不能再进行任何操作
|
|
27
|
+
*/
|
|
28
|
+
abort() {
|
|
29
|
+
this.#aborted = true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// 工具集,将一些驱动的方法进行 promise 封装,方便操作
|
|
2
|
+
// 程序中没有使用 mysql2/promise ,主要是在测试中发现 mysql2/promise 不是很可靠
|
|
3
|
+
// mysql2/promise 在查询方便处理的不是很好
|
|
4
|
+
// query 查询在没有记录的情况下仍然会返回列表,里面会包含列定义信息,不符合预期
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Connection,
|
|
8
|
+
OkPacket,
|
|
9
|
+
Pool,
|
|
10
|
+
PoolConnection,
|
|
11
|
+
ProcedureCallPacket,
|
|
12
|
+
ResultSetHeader,
|
|
13
|
+
RowDataPacket,
|
|
14
|
+
format
|
|
15
|
+
} from 'mysql2'
|
|
16
|
+
import { MysqlConfig } from '../config'
|
|
17
|
+
import { getLogger } from '../../log'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 查询,适用于各种 sql 的执行
|
|
21
|
+
* @param config
|
|
22
|
+
* @param conn
|
|
23
|
+
* @param sql
|
|
24
|
+
* @param values
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export function promiseQuery(
|
|
28
|
+
config: MysqlConfig,
|
|
29
|
+
conn: Connection,
|
|
30
|
+
sql: string,
|
|
31
|
+
values: any[] = []
|
|
32
|
+
) {
|
|
33
|
+
return new Promise<
|
|
34
|
+
| OkPacket
|
|
35
|
+
| ResultSetHeader
|
|
36
|
+
| ResultSetHeader[]
|
|
37
|
+
| RowDataPacket[]
|
|
38
|
+
| RowDataPacket[][]
|
|
39
|
+
| OkPacket[]
|
|
40
|
+
| ProcedureCallPacket
|
|
41
|
+
>((res, rej) => {
|
|
42
|
+
const start = new Date().getTime()
|
|
43
|
+
conn.query(sql, values, (err, result) => {
|
|
44
|
+
if (config.slowSqlWarn) {
|
|
45
|
+
const cost = new Date().getTime() - start
|
|
46
|
+
// 慢 sql 警告
|
|
47
|
+
if (cost > config.slowSqlMs) {
|
|
48
|
+
getLogger().warn(`[mysql slow sql] ${cost}ms ${format(sql, values)}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (err) {
|
|
52
|
+
rej(err)
|
|
53
|
+
} else {
|
|
54
|
+
res(result)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取连接
|
|
62
|
+
* @param pool
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
export function promiseGetConnection(pool: Pool) {
|
|
66
|
+
return new Promise<PoolConnection>((res, rej) => {
|
|
67
|
+
pool.getConnection((err, conn) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
rej(err)
|
|
70
|
+
} else {
|
|
71
|
+
res(conn)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
|
2
|
+
import { Connection, RowDataPacket } from 'mysql2'
|
|
3
|
+
import { isAbsolute, resolve } from 'path'
|
|
4
|
+
import { getLogger } from '../log'
|
|
5
|
+
import { MysqlConfig } from './config'
|
|
6
|
+
import { MysqlException } from './exception'
|
|
7
|
+
import { promiseQuery } from './manager/utils'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 版本信息.
|
|
11
|
+
*/
|
|
12
|
+
interface MysqlVersion {
|
|
13
|
+
version: number
|
|
14
|
+
filePath: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 迁移.
|
|
19
|
+
* @param config
|
|
20
|
+
* @param conn
|
|
21
|
+
*/
|
|
22
|
+
export async function migrate(config: MysqlConfig, conn: Connection) {
|
|
23
|
+
const versionDir = config.versionControlDir
|
|
24
|
+
// 查找文件
|
|
25
|
+
const dir = isAbsolute(versionDir) ? versionDir : resolve(process.cwd(), versionDir)
|
|
26
|
+
if (!existsSync(dir)) {
|
|
27
|
+
throw new Error(`Directory ${versionDir} does not exist`)
|
|
28
|
+
}
|
|
29
|
+
const versions: MysqlVersion[] = []
|
|
30
|
+
// 忽略隐藏文件
|
|
31
|
+
const files = readdirSync(dir).filter(file => !file.startsWith('.'))
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
const filePath = resolve(dir, file)
|
|
34
|
+
const stat = statSync(filePath)
|
|
35
|
+
if (!stat.isFile()) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if (!file.endsWith('.sql')) {
|
|
39
|
+
throw new Error(`版本文件名没有以 .sql 为后缀:${file}`)
|
|
40
|
+
}
|
|
41
|
+
const version = parseInt(file.substring(0, file.length - 4))
|
|
42
|
+
if (isNaN(version)) {
|
|
43
|
+
throw new Error(`Version file is not named with a number:${file}`)
|
|
44
|
+
}
|
|
45
|
+
versions.push({ version, filePath })
|
|
46
|
+
}
|
|
47
|
+
// 排序,判定顺序
|
|
48
|
+
versions.sort((o1, o2) => o1.version - o2.version)
|
|
49
|
+
for (let i = 0; i < versions.length; i++) {
|
|
50
|
+
const version = versions[i]
|
|
51
|
+
if (version.version !== i + 1) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`The SQL version number must start from 1 and increment one by one,error version:${version.version}`
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await createVersionTableIfNotExist(config, conn)
|
|
58
|
+
|
|
59
|
+
// 在事务中执行版本管理,主要目的是为了通过锁来协调多个进程同时启动的情况
|
|
60
|
+
// 事务不能保存一个版本处理成功就完整回退,如有错误,仍然需要手动调整后再操作
|
|
61
|
+
await promiseQuery(config, conn, `SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED`)
|
|
62
|
+
await new Promise<void>((resolve, reject) => {
|
|
63
|
+
conn.beginTransaction(err => {
|
|
64
|
+
if (err) {
|
|
65
|
+
reject(err)
|
|
66
|
+
} else {
|
|
67
|
+
resolve()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
try {
|
|
72
|
+
// 执行 sql, 判定当前版本
|
|
73
|
+
let currentVersion = await getCurrentVersion(config, conn)
|
|
74
|
+
if (typeof currentVersion !== 'number') {
|
|
75
|
+
// 插入初始版本号
|
|
76
|
+
await promiseQuery(config, conn, 'insert `db_version`(`version`) values (0)')
|
|
77
|
+
}
|
|
78
|
+
const filnalCurrentVersion = currentVersion || 0
|
|
79
|
+
const pendingVersions = versions.filter(ver => ver.version > filnalCurrentVersion)
|
|
80
|
+
if (!pendingVersions.length) {
|
|
81
|
+
getLogger().info('The SQL version is already the latest.')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const nextVersion = filnalCurrentVersion + 1
|
|
85
|
+
if (pendingVersions[0].version !== nextVersion) {
|
|
86
|
+
throw new MysqlException(
|
|
87
|
+
`MySQL migration error, the next version should be ${nextVersion},but current is ${pendingVersions[0].version}`
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const verion of pendingVersions) {
|
|
92
|
+
getLogger().info(`Mysql migrating, version: ${verion.version}`)
|
|
93
|
+
let sql = readFileSync(verion.filePath, { encoding: 'utf-8' })
|
|
94
|
+
await promiseQuery(config, conn, sql)
|
|
95
|
+
// 版本号
|
|
96
|
+
await promiseQuery(config, conn, `UPDATE db_version SET version=${verion.version};`)
|
|
97
|
+
}
|
|
98
|
+
// 提交
|
|
99
|
+
await new Promise<void>((resolve, reject) => {
|
|
100
|
+
conn.commit(err => {
|
|
101
|
+
if (err) {
|
|
102
|
+
reject(err)
|
|
103
|
+
} else {
|
|
104
|
+
resolve()
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
getLogger().info('Mysql migration finished.')
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// 异常回退,仅能回退 dml 操作,对 ddl 无效
|
|
111
|
+
await new Promise<void>((resolve, reject) => {
|
|
112
|
+
conn.rollback(err => {
|
|
113
|
+
if (err) {
|
|
114
|
+
reject(err)
|
|
115
|
+
} else {
|
|
116
|
+
resolve()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
throw e
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function createVersionTableIfNotExist(config: MysqlConfig, conn: Connection): Promise<void> {
|
|
125
|
+
await promiseQuery(
|
|
126
|
+
config,
|
|
127
|
+
conn,
|
|
128
|
+
'CREATE TABLE IF NOT EXISTS `db_version` (' +
|
|
129
|
+
' `version` int NOT NULL,' +
|
|
130
|
+
' PRIMARY KEY (`version`)' +
|
|
131
|
+
') ENGINE = innodb DEFAULT CHARACTER SET = "utf8mb4" ' +
|
|
132
|
+
'COLLATE = "utf8mb4_unicode_ci";'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 获取当前版本,使用 for update 加锁,用于协调多进程并发场景
|
|
137
|
+
* @param config
|
|
138
|
+
* @param conn
|
|
139
|
+
* @returns
|
|
140
|
+
*/
|
|
141
|
+
async function getCurrentVersion(config: MysqlConfig, conn: Connection): Promise<number | null> {
|
|
142
|
+
const res = await promiseQuery(config, conn, 'select version from `db_version` for update')
|
|
143
|
+
const rows = res as RowDataPacket[]
|
|
144
|
+
if (rows.length >= 1) {
|
|
145
|
+
return rows[0].version
|
|
146
|
+
} else {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { notBlank, ValidationOpts } from '../validation'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 表信息. 表的字段分为三个部分:主键、普通列、时间列,分别对应的属性是:id、columns
|
|
5
|
+
* 和 createDate 还有 updatedDate,这三部分配置的字段名称不允许有重叠,否则会产生错误.
|
|
6
|
+
*/
|
|
7
|
+
export interface Table<T> {
|
|
8
|
+
/**
|
|
9
|
+
* 表名.
|
|
10
|
+
*/
|
|
11
|
+
tableName: string
|
|
12
|
+
/**
|
|
13
|
+
* id 字段名称.
|
|
14
|
+
*/
|
|
15
|
+
id: keyof T
|
|
16
|
+
/**
|
|
17
|
+
* 列字段名称.只有配置了名称的字段才会参与数据库的查询与更新,没有配置的其它字段可作其它用途。
|
|
18
|
+
* 注意:columns 配置的字段不能和 id 或更新时间和创建时间重叠,否则在更新时产生错误,将数据改错。
|
|
19
|
+
* 程序本身是不会做检查的,编写的时候必须要注意。
|
|
20
|
+
*/
|
|
21
|
+
columns: Array<keyof T>
|
|
22
|
+
/**
|
|
23
|
+
* 创建时间字段信息.
|
|
24
|
+
*/
|
|
25
|
+
createdDate?: {
|
|
26
|
+
column: keyof T
|
|
27
|
+
type: 'number' | 'date'
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 更新时间字段信息.
|
|
31
|
+
*/
|
|
32
|
+
updatedDate?: {
|
|
33
|
+
column: keyof T
|
|
34
|
+
type: 'number' | 'date'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tableValidation: ValidationOpts<Table<any>> = {
|
|
39
|
+
tableName: [notBlank('表名不能为空')],
|
|
40
|
+
id: [notBlank('id 不能为空')]
|
|
41
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { max, min, notNull, validate } from '../validation'
|
|
2
|
+
import { Task, TaskController, execTask } from './task'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 每日任务.
|
|
6
|
+
* @param hours 时
|
|
7
|
+
* @param minutes 分
|
|
8
|
+
* @param task 要执行的任务
|
|
9
|
+
* @param timeout 任务超时时间,单位毫秒
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
export function scheduleDailyTask(
|
|
13
|
+
hours: number,
|
|
14
|
+
minutes: number,
|
|
15
|
+
task: Task,
|
|
16
|
+
timeout?: number
|
|
17
|
+
): TaskController {
|
|
18
|
+
// 校验
|
|
19
|
+
validate(
|
|
20
|
+
{ hours, minutes },
|
|
21
|
+
{
|
|
22
|
+
hours: [notNull(), min(0), max(23)],
|
|
23
|
+
minutes: [notNull(), min(0), max(59)]
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
const taskController = new TaskController()
|
|
27
|
+
const delay = dailyTaskDelay(hours, minutes)
|
|
28
|
+
setTimeout(() => exec(hours, minutes, task, taskController, timeout), delay)
|
|
29
|
+
return taskController
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 计算到下次指定时间点的延迟
|
|
33
|
+
* @param hours
|
|
34
|
+
* @param minutes
|
|
35
|
+
*/
|
|
36
|
+
export function dailyTaskDelay(hours: number, minutes: number): number {
|
|
37
|
+
const now = new Date()
|
|
38
|
+
let todayTime = new Date()
|
|
39
|
+
todayTime.setHours(hours)
|
|
40
|
+
todayTime.setMinutes(minutes)
|
|
41
|
+
todayTime.setSeconds(0)
|
|
42
|
+
todayTime.setMilliseconds(0)
|
|
43
|
+
// 如果今天还没有到指定的点,今天就执行,否则明天执行
|
|
44
|
+
if (todayTime > now) {
|
|
45
|
+
return todayTime.getTime() - now.getTime()
|
|
46
|
+
}
|
|
47
|
+
// 明天时间
|
|
48
|
+
const oneDayMilliseconds = 1000 * 3600 * 24
|
|
49
|
+
const tomorrowTime = todayTime.getTime() + oneDayMilliseconds
|
|
50
|
+
return tomorrowTime - now.getTime()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function exec(
|
|
54
|
+
hours: number,
|
|
55
|
+
minutes: number,
|
|
56
|
+
task: Task,
|
|
57
|
+
controller: TaskController,
|
|
58
|
+
timeout?: number
|
|
59
|
+
) {
|
|
60
|
+
Promise.resolve()
|
|
61
|
+
.then(async () => {
|
|
62
|
+
if (controller.isStopped()) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
await execTask(task, timeout)
|
|
66
|
+
const delay = dailyTaskDelay(hours, minutes)
|
|
67
|
+
setTimeout(() => exec(hours, minutes, task, controller), delay)
|
|
68
|
+
})
|
|
69
|
+
.catch(console.error)
|
|
70
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getLockManager } from '../lock'
|
|
2
|
+
import { getLogger } from '../log'
|
|
3
|
+
import { max, min, notNull, validate } from '../validation'
|
|
4
|
+
import { Task, TaskController, execTask } from './task'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 固定延迟执行任务
|
|
8
|
+
* @param initialDelay 第一次执行延迟的时间,单位秒
|
|
9
|
+
* @param delay 每次的延迟时间,单位秒
|
|
10
|
+
* @param task 任务
|
|
11
|
+
* @param timeout 任务超时时间,单位毫秒
|
|
12
|
+
*/
|
|
13
|
+
export function scheduleWithFixedDelay(
|
|
14
|
+
initialDelay: number,
|
|
15
|
+
delay: number,
|
|
16
|
+
task: Task,
|
|
17
|
+
timeout?: number
|
|
18
|
+
): TaskController {
|
|
19
|
+
validate(
|
|
20
|
+
{ initialDelay, delay },
|
|
21
|
+
{
|
|
22
|
+
initialDelay: [notNull(), min(0), max(3600 * 24)],
|
|
23
|
+
delay: [notNull(), min(1), max(3600 * 24)]
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
const controller = new TaskController()
|
|
27
|
+
setTimeout(() => exec(delay, task, controller, timeout), initialDelay * 1000)
|
|
28
|
+
return controller
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function exec(delay: number, task: Task, controller: TaskController, timeout?: number) {
|
|
32
|
+
Promise.resolve()
|
|
33
|
+
.then(async () => {
|
|
34
|
+
if (controller.isStopped()) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
await execTask(task, timeout)
|
|
38
|
+
// 下次执行
|
|
39
|
+
setTimeout(() => exec(delay, task, controller), delay * 1000)
|
|
40
|
+
})
|
|
41
|
+
.catch(e => {
|
|
42
|
+
getLogger().error(`EXEC TASK ERROR: ${task.name}`, e)
|
|
43
|
+
})
|
|
44
|
+
.catch(console.error)
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getLogger } from '../log'
|
|
2
|
+
import { max, min, notNull, validate } from '../validation'
|
|
3
|
+
import { Task, TaskController, execTask } from './task'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 固定延迟执行任务
|
|
7
|
+
* @param initialDelay 第一次执行延迟的时间,单位秒
|
|
8
|
+
* @param period 每次的延迟时间,单位秒
|
|
9
|
+
* @param task 任务
|
|
10
|
+
* @param timeout 任务超时时间,单位毫秒
|
|
11
|
+
*/
|
|
12
|
+
export function scheduleWithFixedRate(
|
|
13
|
+
initialDelay: number,
|
|
14
|
+
period: number,
|
|
15
|
+
task: Task,
|
|
16
|
+
timeout?: number
|
|
17
|
+
): TaskController {
|
|
18
|
+
validate(
|
|
19
|
+
{ initialDelay, period },
|
|
20
|
+
{
|
|
21
|
+
initialDelay: [notNull(), min(0), max(3600 * 24)],
|
|
22
|
+
period: [notNull(), min(1), max(3600 * 24)]
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const taskController = new TaskController()
|
|
27
|
+
setTimeout(() => exec(period, task, taskController, timeout), initialDelay * 1000)
|
|
28
|
+
return taskController
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function exec(fixedDelay: number, task: Task, controller: TaskController, timeout?: number) {
|
|
32
|
+
Promise.resolve()
|
|
33
|
+
.then(async () => {
|
|
34
|
+
if (controller.isStopped()) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const res = await execTask(task, timeout)
|
|
38
|
+
// 下次执行
|
|
39
|
+
let delay = res.start + fixedDelay * 1000 - new Date().getTime()
|
|
40
|
+
if (delay < 0) {
|
|
41
|
+
delay = 0
|
|
42
|
+
}
|
|
43
|
+
setTimeout(() => exec(fixedDelay, task, controller), delay)
|
|
44
|
+
})
|
|
45
|
+
.catch(e => {
|
|
46
|
+
getLogger().error(`EXEC TASK ERROR: ${task.name}`, e)
|
|
47
|
+
})
|
|
48
|
+
.catch(console.error)
|
|
49
|
+
}
|
package/src/task/task.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getLogger } from '../log'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 任务.
|
|
5
|
+
*/
|
|
6
|
+
export interface Task {
|
|
7
|
+
/**
|
|
8
|
+
* 任务的名称,用于跟踪任务的执行情况,定位错误.
|
|
9
|
+
* 当任务执行时间过长或任务失败时,相关的错误提示信息会显示名称,以便于排查.
|
|
10
|
+
*/
|
|
11
|
+
name: string
|
|
12
|
+
/**
|
|
13
|
+
* 任务运行.
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
run: () => Promise<void>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 任务控制器
|
|
21
|
+
*/
|
|
22
|
+
export class TaskController {
|
|
23
|
+
#stopped = false
|
|
24
|
+
isStopped() {
|
|
25
|
+
return this.#stopped
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
stop() {
|
|
29
|
+
this.#stopped = true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 任务执行,封装任务执行过程中的一些通用信息输出和异常控制.
|
|
35
|
+
* @param task
|
|
36
|
+
* @param timeout 任务超时时间,单位毫秒
|
|
37
|
+
* @returns
|
|
38
|
+
*/
|
|
39
|
+
export async function execTask(
|
|
40
|
+
task: Task,
|
|
41
|
+
timeout?: number
|
|
42
|
+
): Promise<{ start: number; cost: number; end: number }> {
|
|
43
|
+
const start = new Date().getTime()
|
|
44
|
+
try {
|
|
45
|
+
getLogger().debug(`START TASK:${task.name}`)
|
|
46
|
+
// 支持任务超时设置
|
|
47
|
+
if (timeout && timeout > 0) {
|
|
48
|
+
await Promise.race([
|
|
49
|
+
task.run(),
|
|
50
|
+
new Promise<void>((_, reject) => {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
reject('The task has timed out.')
|
|
53
|
+
}, timeout)
|
|
54
|
+
})
|
|
55
|
+
])
|
|
56
|
+
} else {
|
|
57
|
+
await task.run()
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
getLogger().error(`TASK ERROR: ${task.name}`, e)
|
|
61
|
+
}
|
|
62
|
+
const end = Date.now()
|
|
63
|
+
const cost = end - start
|
|
64
|
+
if (cost > 1000 * 60 * 5) {
|
|
65
|
+
getLogger().warn(`Task "${task.name}" takes too long ,cost ${cost}ms`)
|
|
66
|
+
} else {
|
|
67
|
+
getLogger().debug(`Task "${task.name}" has finished, taking a total of ${cost} milliseconds.`)
|
|
68
|
+
}
|
|
69
|
+
return { start, cost, end }
|
|
70
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 校验异常.
|
|
3
|
+
*/
|
|
4
|
+
export class ValidationException extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
/**
|
|
7
|
+
* 异常信息,如 “不能为空” 之类的.
|
|
8
|
+
*/
|
|
9
|
+
readonly errMsg: string,
|
|
10
|
+
/**
|
|
11
|
+
* 校验器名称,如 length 、notNull 之类的.
|
|
12
|
+
*/
|
|
13
|
+
readonly validator: string,
|
|
14
|
+
/**
|
|
15
|
+
* 校验出错的路径.
|
|
16
|
+
*/
|
|
17
|
+
readonly propertyPath: string,
|
|
18
|
+
/**
|
|
19
|
+
* 值
|
|
20
|
+
*/
|
|
21
|
+
readonly val: any
|
|
22
|
+
) {
|
|
23
|
+
super(
|
|
24
|
+
`Field "${propertyPath}" failed to validate by validator ${validator} ,value:${val},info:${errMsg}`
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ValidationException } from './exception'
|
|
2
|
+
/**
|
|
3
|
+
* 校验结果。message:错误信息,反馈给调用处;validator 校验器名称,用于记录错误信息来自哪个校验器,
|
|
4
|
+
* 以便于程序在出错时知道信息来自哪里。
|
|
5
|
+
*/
|
|
6
|
+
export type ValidationResult =
|
|
7
|
+
| { ok: true }
|
|
8
|
+
| {
|
|
9
|
+
ok: false
|
|
10
|
+
message: string
|
|
11
|
+
validator: string
|
|
12
|
+
/**
|
|
13
|
+
* 如果校验的是层级深的对象,内部的属性发生了错误,可用于标记属性的路径
|
|
14
|
+
*/
|
|
15
|
+
propPath?: string[]
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 校验器.
|
|
19
|
+
*/
|
|
20
|
+
export type PropValidator<T> = (val: T) => ValidationResult
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 对象的校验选项
|
|
24
|
+
*/
|
|
25
|
+
export type ValidationOpts<T> = Partial<{ [key in keyof T]: PropValidator<T[key]>[] }>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 校验对象.
|
|
29
|
+
* @param obj
|
|
30
|
+
*/
|
|
31
|
+
export function validate<T>(obj: T, opts: ValidationOpts<T>) {
|
|
32
|
+
for (const entry of Object.entries(opts)) {
|
|
33
|
+
const [prop, validators] = entry
|
|
34
|
+
const val = obj[prop as keyof T]
|
|
35
|
+
for (const validator of validators as PropValidator<any>[]) {
|
|
36
|
+
const result = validator(val)
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
const propPath = [prop]
|
|
39
|
+
if (result.propPath) {
|
|
40
|
+
propPath.push(...result.propPath)
|
|
41
|
+
}
|
|
42
|
+
const fullPath = propPath
|
|
43
|
+
.map((prop, idx) => {
|
|
44
|
+
if (idx === 0) {
|
|
45
|
+
return prop
|
|
46
|
+
}
|
|
47
|
+
if (prop.startsWith('[') && prop.endsWith(']')) {
|
|
48
|
+
return prop
|
|
49
|
+
}
|
|
50
|
+
return '.' + prop
|
|
51
|
+
})
|
|
52
|
+
.join('')
|
|
53
|
+
throw new ValidationException(result.message, result.validator, fullPath, val)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 导出
|
|
60
|
+
export * from './exception'
|
|
61
|
+
export * from './validator'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { PropValidator } from '..'
|
|
2
|
+
import { getI18n } from '../../i18n'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 校验数组的条目.
|
|
6
|
+
*/
|
|
7
|
+
export function array<T>(opts: PropValidator<T>[]): PropValidator<T[] | undefined> {
|
|
8
|
+
const validator = 'array'
|
|
9
|
+
return val => {
|
|
10
|
+
if (!val) {
|
|
11
|
+
return { ok: true }
|
|
12
|
+
}
|
|
13
|
+
if (!Array.isArray(val)) {
|
|
14
|
+
return { ok: false, validator, message: getI18n().buildMsg('validate-err-array') }
|
|
15
|
+
}
|
|
16
|
+
// 条目处理
|
|
17
|
+
for (let i = 0; i < val.length; i++) {
|
|
18
|
+
const item = val[i] as T
|
|
19
|
+
for (const validation of opts) {
|
|
20
|
+
const result = validation(item)
|
|
21
|
+
if (!result.ok) {
|
|
22
|
+
const propPath = [`[${i}]`]
|
|
23
|
+
if (result.propPath) {
|
|
24
|
+
propPath.push(...result.propPath)
|
|
25
|
+
}
|
|
26
|
+
return { ok: false, validator, message: result.message, propPath }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { ok: true }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PropValidator } from '..'
|
|
2
|
+
import { getI18n } from '../../i18n'
|
|
3
|
+
/**
|
|
4
|
+
* 枚举,校验值必须是指定列表中的一个.
|
|
5
|
+
* @param list 支持的值列表,仅支持数字和字符串类型
|
|
6
|
+
* @param msg
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function enumerate<T>(list: (number | string)[], msg?: string): PropValidator<T> {
|
|
10
|
+
const validator = 'enumerate'
|
|
11
|
+
return val => {
|
|
12
|
+
if (!val) {
|
|
13
|
+
return { ok: true }
|
|
14
|
+
}
|
|
15
|
+
if (!list.some(item => item === val)) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
validator,
|
|
19
|
+
// must be one of [1,2,3]
|
|
20
|
+
message: msg || getI18n().buildMsg('validate-err-enum', `[${list.join(',')}]`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { ok: true }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './not-null'
|
|
2
|
+
export * from './not-blank'
|
|
3
|
+
export * from './max'
|
|
4
|
+
export * from './min'
|
|
5
|
+
export * from './regexp'
|
|
6
|
+
export * from './length'
|
|
7
|
+
export * from './enum'
|
|
8
|
+
export * from './array'
|
|
9
|
+
export * from './plain-obj'
|
|
10
|
+
export * from './max-length'
|
|
11
|
+
export * from './min-length'
|