jfexmd 0.0.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.
- package/.prompt +13 -0
- package/CHANGELOG.md +7 -0
- package/README.MD +649 -0
- package/bin/build-aux.sh +10 -0
- package/bin/build.sh +11 -0
- package/bin/check-node.sh +17 -0
- package/bin/copy-to-publish.sh +8 -0
- package/bin/feature.sh +3 -0
- package/bin/generate-changelog.js +47 -0
- package/bin/publish-auxiliary.sh +2 -0
- package/bin/publish-exmd.sh +2 -0
- package/bin/push.js +30 -0
- package/bin/replace-aux.sh +8 -0
- package/bin/replace-to-test.sh +26 -0
- package/docs/readme.md +7 -0
- package/package.json +61 -0
- package/resource-pack/auxiliary/README.MD +22 -0
- package/resource-pack/auxiliary/package.json +14 -0
- package/resource-pack/readme.md +0 -0
- package/resources/index.d.ts +1 -0
- package/src/api-server/auth-api.js +204 -0
- package/src/api-server/graceful-shutdown.js +95 -0
- package/src/api-server/health.js +57 -0
- package/src/api-server/index.js +553 -0
- package/src/api-server/init-email.js +8 -0
- package/src/api-server/readme.md +4 -0
- package/src/api-server/swagger-data.js +33 -0
- package/src/authorization/auth-controller.js +378 -0
- package/src/authorization/auth-service/query-by-username.js +35 -0
- package/src/authorization/db-ope.js +29 -0
- package/src/authorization/default-user-model.js +63 -0
- package/src/authorization/readme.md +10 -0
- package/src/authorization/util-password.js +33 -0
- package/src/authorization/util-time.js +29 -0
- package/src/authorization/util-token.js +65 -0
- package/src/auxiliary/aux-db-server.js +58 -0
- package/src/auxiliary/index.js +56 -0
- package/src/auxiliary/readme.md +6 -0
- package/src/auxiliary/views-utils.js +46 -0
- package/src/db-server/base-model-fun/add.js +66 -0
- package/src/db-server/base-model-fun/common-fun.js +209 -0
- package/src/db-server/base-model-fun/delete.js +57 -0
- package/src/db-server/base-model-fun/get.js +58 -0
- package/src/db-server/base-model-fun/modify.js +53 -0
- package/src/db-server/base-model-fun/query.js +107 -0
- package/src/db-server/base-model-fun/readme.md +9 -0
- package/src/db-server/base-model.js +64 -0
- package/src/db-server/datasource-registry.js +36 -0
- package/src/db-server/db-Adapter.js +9 -0
- package/src/db-server/db-client-runtime.js +71 -0
- package/src/db-server/db-connect/postgresql-connect.js +13 -0
- package/src/db-server/index.js +73 -0
- package/src/db-server/readme.md +5 -0
- package/src/email/index.js +5 -0
- package/src/email/tencenmail.js +45 -0
- package/src/index.js +192 -0
- package/src/lib/multer/LICENSE +17 -0
- package/src/lib/multer/README.md +333 -0
- package/src/lib/multer/index.js +106 -0
- package/src/lib/multer/lib/counter.js +28 -0
- package/src/lib/multer/lib/file-appender.js +67 -0
- package/src/lib/multer/lib/make-middleware.js +173 -0
- package/src/lib/multer/lib/multer-error.js +31 -0
- package/src/lib/multer/lib/remove-uploaded-files.js +28 -0
- package/src/lib/multer/package.json +95 -0
- package/src/lib/multer/storage/disk.js +66 -0
- package/src/lib/multer/storage/memory.js +21 -0
- package/src/lib/readme.md +0 -0
- package/src/migrate/asyncdb.js +20 -0
- package/src/migrate/build-model.js +75 -0
- package/src/migrate/build-table.js +245 -0
- package/src/migrate/file-handle.js +66 -0
- package/src/migrate/index.js +20 -0
- package/src/migrate/postgresql-migrate.js +102 -0
- package/src/plugin/builtin/request-log-plugin.js +23 -0
- package/src/plugin/plugin-manager.js +111 -0
- package/src/readme.md +13 -0
- package/src/release/changelog.js +56 -0
- package/src/security/auth-middlewares.js +83 -0
- package/src/security/cors.js +53 -0
- package/src/security/helmet-config.js +39 -0
- package/src/upload/init.js +52 -0
- package/src/upload/upload-controller.js +47 -0
- package/src/utils/RequestTypeUtil.js +59 -0
- package/src/utils/cache-utils.js +51 -0
- package/src/utils/config-migration.js +52 -0
- package/src/utils/config-validator.js +48 -0
- package/src/utils/deprecation.js +24 -0
- package/src/utils/json-to-model.js +36 -0
- package/src/utils/logger.js +58 -0
- package/src/utils/md5.js +365 -0
- package/src/utils/metrics.js +42 -0
- package/src/utils/model-util.js +17 -0
- package/src/utils/moment.js +33 -0
- package/src/utils/promise.js +32 -0
- package/src/utils/rate-limit.js +72 -0
- package/src/utils/readme.md +10 -0
- package/src/utils/string-utils.js +17 -0
- package/src/utils/validate-request.js +22 -0
- package/src/vo/HttpStatus.js +80 -0
- package/src/vo/ResultVo.js +24 -0
- package/src/vo/objectToVo.js +53 -0
- package/test/line1.js +8 -0
- package/test/line2.js +8 -0
- package/test/test-lines.js +11 -0
- package/test/timer.js +17 -0
- package/test/v02-engineering.js +71 -0
- package/test/v03-reliability.js +107 -0
- package/test/v04-extensibility.js +165 -0
- package/test/v05-release-quality.js +82 -0
- package/test/v06-observability.js +69 -0
- package/test/v07-operations.js +73 -0
- package/test/v08-security.js +104 -0
- package/webpack.config.js +70 -0
package/.prompt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 项目名称: exmd
|
|
2
|
+
# 用途: EXMD 核心后端服务与业务接口
|
|
3
|
+
# 主要技术: Node.js, Express/Koa (以项目实际依赖为准)
|
|
4
|
+
# 目录约定:
|
|
5
|
+
# - controller/: 请求入口与参数校验
|
|
6
|
+
# - services/: 业务逻辑与数据处理
|
|
7
|
+
# - models/: 数据模型/ORM
|
|
8
|
+
# - router/: 路由注册与中间件
|
|
9
|
+
# 开发建议:
|
|
10
|
+
# - 变更前先定位对应路由与服务层
|
|
11
|
+
# - 严格保留公共错误码与响应结构
|
|
12
|
+
# - 新增接口需补充单元/集成测试
|
|
13
|
+
# 运行与调试: 参考 README 或 package.json scripts
|
package/CHANGELOG.md
ADDED
package/README.MD
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# exmd内核
|
|
2
|
+
|
|
3
|
+
```exmd```的核心代码,包含以下功能:
|
|
4
|
+
|
|
5
|
+
**核心能力**
|
|
6
|
+
1. ```startProject``` 启动项目(返回 Promise\<server\>,支持手动 shutdown)
|
|
7
|
+
2. ```BaseModel``` model基类(含事务、参数化 SQL)
|
|
8
|
+
3. ```migrate``` 数据库表转model
|
|
9
|
+
4. ```asyncdb``` model转数据库表
|
|
10
|
+
5. ```renderRequestType``` controller 方法请求配置(请求类型、swagger 描述、必填校验、限流、中间件、钩子)
|
|
11
|
+
6. ```handleClientAdapter``` 持久层适配器,指定数据源并操作对应 model
|
|
12
|
+
7. ```ResultVo``` 返回结果集合
|
|
13
|
+
8. ```AuthController``` 内置登录注册 controller(bcrypt 密码、JWT、验证码)
|
|
14
|
+
9. ```UploadController``` 内置文件上传控制器
|
|
15
|
+
|
|
16
|
+
**扩展与工具**
|
|
17
|
+
- 插件机制(```createPluginManager```、```createRequestLogPlugin```)
|
|
18
|
+
- 数据源注册中心(```createDatasourceRegistry```,支持扩展多数据库)
|
|
19
|
+
- 可观测性(```createMetricsRegistry```、/health、/ready、/metrics)
|
|
20
|
+
- 安全中间件(```createApiKeyMiddleware```、```createHmacSignatureMiddleware```)
|
|
21
|
+
- 配置迁移与弃用告警(```applyConfigMigrations```)
|
|
22
|
+
|
|
23
|
+
**业务层**
|
|
24
|
+
|
|
25
|
+
获得可操作数据库的模型:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// 自定义的model:flow
|
|
29
|
+
const Flow = require('./models/flow')
|
|
30
|
+
let dataSourceKey = ogisticServiceData.authDataSourceKey
|
|
31
|
+
const client = handleClientAdapter(ogisticServiceData, dataSourceKey)
|
|
32
|
+
const flow = new Flow(client, ogisticServiceData)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
简化使用也可以直接使用exmd引出的:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// 自定义的model:flow
|
|
39
|
+
const Flow = require('./models/flow')
|
|
40
|
+
const { getModelInstance } = require('../../exmd')
|
|
41
|
+
const flow = getModelInstance(ogisticServiceData)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
**数据源配置**
|
|
47
|
+
|
|
48
|
+
默认支持 postgresql,可通过 `createDatasourceRegistry` 扩展其他数据库类型
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
module.exports = {
|
|
52
|
+
// 数据库1
|
|
53
|
+
postgresql1: {
|
|
54
|
+
type: 'postgresql',
|
|
55
|
+
data: {
|
|
56
|
+
user: 'bserverx',
|
|
57
|
+
database: 'bserver',
|
|
58
|
+
password: 'bserverx123',
|
|
59
|
+
host: 'localhost',
|
|
60
|
+
port: '5432',
|
|
61
|
+
poolSize: 5,
|
|
62
|
+
poolIdleTimeout: 30000,
|
|
63
|
+
reapIntervalMillis: 10000
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**启动项目**
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
startProject({
|
|
73
|
+
/**
|
|
74
|
+
* 是否开启调试模式
|
|
75
|
+
* 调试模式将会打印sql
|
|
76
|
+
* 调试模式下不会发送邮件
|
|
77
|
+
*/
|
|
78
|
+
isDebugger: true,
|
|
79
|
+
/**
|
|
80
|
+
* api地址路由配置
|
|
81
|
+
*/
|
|
82
|
+
router,
|
|
83
|
+
/**
|
|
84
|
+
* 数据源
|
|
85
|
+
*/
|
|
86
|
+
Datasource,
|
|
87
|
+
/**
|
|
88
|
+
* 用户model
|
|
89
|
+
*/
|
|
90
|
+
authUserModel: UserModel, // 可选, 不传使用内置默认model
|
|
91
|
+
/**
|
|
92
|
+
* 登录注册使用的数据源对应key
|
|
93
|
+
*/
|
|
94
|
+
authDataSourceKey: 'postgresql1',
|
|
95
|
+
/**
|
|
96
|
+
* 注册邮箱使用的内容模板
|
|
97
|
+
* 没有配置将会使用默认模板
|
|
98
|
+
* @param {*} code
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
authEmailContent: (code) => {
|
|
102
|
+
return `
|
|
103
|
+
<p>您好:</p>
|
|
104
|
+
<p>您的验证码是:<strong style="color:orangered;">${code}</strong></p>
|
|
105
|
+
<p>嘿嘿</p>
|
|
106
|
+
`
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* 登录注册验证码超时时间
|
|
110
|
+
*/
|
|
111
|
+
authEmailCodeMaxTime: '1minute',
|
|
112
|
+
// 一个邮箱最多允许绑定多少个用户
|
|
113
|
+
authEmailBindUserMax: 3,
|
|
114
|
+
// token生成私钥(生产环境必填; 支持 EXMD_AUTH_PRIVATE_KEY 环境变量; 调试模式可缺省)
|
|
115
|
+
authPrivateKey: 'abc123',
|
|
116
|
+
// hour/minute/second
|
|
117
|
+
authTokenTime: '1minute',
|
|
118
|
+
/**
|
|
119
|
+
* 发邮件库,
|
|
120
|
+
* 目前只支持 tencenmail
|
|
121
|
+
*/
|
|
122
|
+
emailLib: 'tencenmail',
|
|
123
|
+
/**
|
|
124
|
+
* 邮箱配置
|
|
125
|
+
*/
|
|
126
|
+
emailConfig,
|
|
127
|
+
/**
|
|
128
|
+
* 返回数据的结果集合模板
|
|
129
|
+
*/
|
|
130
|
+
ResultVo,
|
|
131
|
+
// swagger的路径配置
|
|
132
|
+
swaggerUrl: '/swagger/data',
|
|
133
|
+
// 文件上传的保存路径
|
|
134
|
+
uploadBaseUrl: '/Users/wujianfei/test-upload-files',
|
|
135
|
+
/**
|
|
136
|
+
* 文件上传模块划分, 必须传
|
|
137
|
+
* 上传的文件路径:基础路径下 文件名称: [模块名称_时间戳],
|
|
138
|
+
* 比如default下的文件名称:
|
|
139
|
+
* test1_1657007903331.mp4
|
|
140
|
+
*/
|
|
141
|
+
uploadFileDirectorys: [{
|
|
142
|
+
name: 'test1'
|
|
143
|
+
}, {
|
|
144
|
+
name: 'default'
|
|
145
|
+
}],
|
|
146
|
+
// 文件限制配置
|
|
147
|
+
uploadLimits: {
|
|
148
|
+
fileSize: 10 * 1024 * 1024 * 1024
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* 全局中间件(按顺序执行)
|
|
152
|
+
* 签名: async (req, res, context) => {}
|
|
153
|
+
*/
|
|
154
|
+
middlewares: [
|
|
155
|
+
async (req, res) => {
|
|
156
|
+
req.customMark = 'ok'
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
/**
|
|
160
|
+
* 全局生命周期钩子
|
|
161
|
+
*/
|
|
162
|
+
hooks: {
|
|
163
|
+
beforeAuth: [
|
|
164
|
+
async (req, res, context) => {
|
|
165
|
+
// 鉴权前
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
afterAuth: [],
|
|
169
|
+
beforeHandler: [],
|
|
170
|
+
afterHandler: []
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**接口级校验/限流/中间件**
|
|
176
|
+
|
|
177
|
+
`renderRequestType` 支持更细粒度的接口配置:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
exports.login = renderRequestType({
|
|
181
|
+
fun: login,
|
|
182
|
+
type: 'POST',
|
|
183
|
+
desc: '登录',
|
|
184
|
+
requiredFields: ['username', 'password'],
|
|
185
|
+
rateLimit: { max: 10, windowMs: 60 * 1000 },
|
|
186
|
+
middlewares: [
|
|
187
|
+
async (req, res) => {
|
|
188
|
+
// 路由级逻辑
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
hooks: {
|
|
192
|
+
beforeHandler: [
|
|
193
|
+
async (req, res, context) => {
|
|
194
|
+
// 进入controller前
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
afterHandler: [
|
|
198
|
+
async (req, res, context) => {
|
|
199
|
+
// controller执行后
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**安全基线(v0.1)**
|
|
207
|
+
|
|
208
|
+
- **SQL 参数化**:BaseModel 增删改查统一使用参数化查询,避免 SQL 注入
|
|
209
|
+
- **密码哈希**:登录/注册使用 bcrypt,兼容历史 MD5 并自动升级
|
|
210
|
+
- **JWT 配置**:生产环境必须配置 `authPrivateKey` 或 `EXMD_AUTH_PRIVATE_KEY`
|
|
211
|
+
- **请求校验**:`renderRequestType` 支持 `requiredFields` 必填校验
|
|
212
|
+
- **限流**:登录/注册/验证码接口内置限流,可配置 `rateLimit`
|
|
213
|
+
- **全局错误处理**:统一异常中间件,返回 `ResultVo` 并记录日志
|
|
214
|
+
- **请求追踪**:每个请求自动注入 `X-Request-Id`,便于日志排查
|
|
215
|
+
|
|
216
|
+
**配置校验和日志**
|
|
217
|
+
|
|
218
|
+
框架会在 `startProject` 启动时自动校验:
|
|
219
|
+
- `router` 必须是非空数组
|
|
220
|
+
- `Datasource` 必须是非空对象,且每个数据源都包含 `type/data`
|
|
221
|
+
|
|
222
|
+
如果不传 `logger`,框架会默认创建结构化日志器;也可以通过 `createLogger` 自定义:
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
const { startProject, createLogger } = require('exmd')
|
|
226
|
+
|
|
227
|
+
startProject({
|
|
228
|
+
// ...
|
|
229
|
+
logger: createLogger({ level: 'info' }),
|
|
230
|
+
// 慢查询阈值(ms)
|
|
231
|
+
slowQueryThresholdMs: 200
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**插件机制(v0.4)**
|
|
236
|
+
|
|
237
|
+
`startProject` 支持 `plugins`,插件可实现这些生命周期:
|
|
238
|
+
- `setup({ config, datasourceRegistry })`
|
|
239
|
+
- `onConfig({ config, datasourceRegistry })`
|
|
240
|
+
- `onDbReady({ dbClient, datasourceRegistry })`
|
|
241
|
+
- `onServerReady({ app, server, port, swaggerUrl })`
|
|
242
|
+
|
|
243
|
+
插件支持 `priority` 字段(数值越大越先执行)。
|
|
244
|
+
|
|
245
|
+
插件错误策略通过 `pluginErrorStrategy` 配置:
|
|
246
|
+
- `fail-fast`(默认): 任一插件失败,启动流程中断
|
|
247
|
+
- `continue`: 记录错误并继续执行后续插件
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const plugin = {
|
|
251
|
+
name: 'example-plugin',
|
|
252
|
+
setup({ datasourceRegistry }) {
|
|
253
|
+
datasourceRegistry.register('mockdb', (cfg) => {
|
|
254
|
+
return Promise.resolve({
|
|
255
|
+
query: () => Promise.resolve({ rowCount: 0, rows: [] })
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
onServerReady({ port }) {
|
|
260
|
+
console.log('server ready', port)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
startProject({
|
|
265
|
+
// ...
|
|
266
|
+
plugins: [plugin],
|
|
267
|
+
pluginErrorStrategy: 'continue'
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
框架内置了一个官方插件模板 `createRequestLogPlugin`:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const { startProject, createRequestLogPlugin } = require('exmd')
|
|
275
|
+
|
|
276
|
+
startProject({
|
|
277
|
+
// ...
|
|
278
|
+
plugins: [
|
|
279
|
+
createRequestLogPlugin({
|
|
280
|
+
level: 'info',
|
|
281
|
+
priority: 10
|
|
282
|
+
})
|
|
283
|
+
]
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
也可以直接手动创建并注入数据源注册器:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const { startProject, createDatasourceRegistry } = require('exmd')
|
|
291
|
+
const datasourceRegistry = createDatasourceRegistry()
|
|
292
|
+
datasourceRegistry.register('postgresql', postgresqlConnect)
|
|
293
|
+
datasourceRegistry.register('mockdb', connectMock)
|
|
294
|
+
|
|
295
|
+
startProject({
|
|
296
|
+
// ...
|
|
297
|
+
datasourceRegistry
|
|
298
|
+
})
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**事务能力**
|
|
302
|
+
|
|
303
|
+
`BaseModel` 支持事务执行:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
const flow = new Flow(client, ogisticServiceData)
|
|
307
|
+
await flow.withTransaction(async (txFlow) => {
|
|
308
|
+
await txFlow.addItem({ name: 'a' })
|
|
309
|
+
await txFlow.updateItemById('id-1', { name: 'b' })
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**限流增强**
|
|
314
|
+
|
|
315
|
+
`rateLimit` 支持 `keyBy` 维度:
|
|
316
|
+
- `ip`(默认)
|
|
317
|
+
- `user`(优先取 `req.exmdAuth.username/id`)
|
|
318
|
+
- `ip_user`
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
exports.login = renderRequestType({
|
|
322
|
+
fun: login,
|
|
323
|
+
type: 'POST',
|
|
324
|
+
rateLimit: { max: 10, windowMs: 60 * 1000, keyBy: 'user' }
|
|
325
|
+
})
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`startProject` 还支持注入自定义限流存储:
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
const { startProject, createMemoryRateLimitStore } = require('exmd')
|
|
332
|
+
|
|
333
|
+
startProject({
|
|
334
|
+
// ...
|
|
335
|
+
rateLimitStore: createMemoryRateLimitStore()
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**发布质量(v0.5)**
|
|
340
|
+
|
|
341
|
+
1) 配置迁移提示(含移除版本)
|
|
342
|
+
|
|
343
|
+
当前支持自动迁移并告警的旧配置键:
|
|
344
|
+
- `middlewareList -> middlewares`
|
|
345
|
+
- `pluginErrorMode -> pluginErrorStrategy`
|
|
346
|
+
- `dbSlowQueryThreshold -> slowQueryThresholdMs`
|
|
347
|
+
- `datasourceAdapterRegistry -> datasourceRegistry`
|
|
348
|
+
|
|
349
|
+
2) 弃用告警(warn once)
|
|
350
|
+
|
|
351
|
+
同一个弃用键只会提示一次,避免刷屏。
|
|
352
|
+
|
|
353
|
+
3) changelog 生成脚本
|
|
354
|
+
|
|
355
|
+
```shell
|
|
356
|
+
npm run changelog:gen -- --version 1.0.39 --title "v0.5 发布" --changes "feat: 发布质量能力|docs: README更新"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
默认会更新项目根目录 `CHANGELOG.md`,也可通过 `--file` 指定文件。
|
|
360
|
+
|
|
361
|
+
**可观测性(v0.6)**
|
|
362
|
+
|
|
363
|
+
框架默认注入 `metrics` 注册器,包含计数指标:
|
|
364
|
+
- `http_request_total`
|
|
365
|
+
- `http_error_total`
|
|
366
|
+
- `db_slow_query_total`
|
|
367
|
+
|
|
368
|
+
并内置端点(可在 `startProject` 自定义路径):
|
|
369
|
+
- `healthPath`,默认 `/health`
|
|
370
|
+
- `readinessPath`,默认 `/ready`
|
|
371
|
+
- `metricsPath`,默认 `/metrics`
|
|
372
|
+
|
|
373
|
+
可选配置:
|
|
374
|
+
- `readinessDeepCheck: true` 时,`/ready` 会对所有数据源执行 `select 1`
|
|
375
|
+
- `enableMetricsEndpoint: false` 可关闭 `/metrics` 暴露
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
const { startProject, createMetricsRegistry } = require('exmd')
|
|
379
|
+
|
|
380
|
+
startProject({
|
|
381
|
+
// ...
|
|
382
|
+
metrics: createMetricsRegistry(),
|
|
383
|
+
healthPath: '/health',
|
|
384
|
+
readinessPath: '/ready',
|
|
385
|
+
metricsPath: '/metrics',
|
|
386
|
+
readinessDeepCheck: true
|
|
387
|
+
})
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**运维增强(v0.7)**
|
|
391
|
+
|
|
392
|
+
1) readiness draining
|
|
393
|
+
|
|
394
|
+
进入优雅停机后,`/ready` 会返回 `503`,并标记失败原因为 `draining`。
|
|
395
|
+
|
|
396
|
+
2) graceful shutdown
|
|
397
|
+
|
|
398
|
+
服务在 `SIGINT/SIGTERM` 下默认自动执行优雅停机:
|
|
399
|
+
- 停止接收新请求
|
|
400
|
+
- 关闭 DB 客户端
|
|
401
|
+
- 执行插件钩子 `onShutdown`
|
|
402
|
+
|
|
403
|
+
可选配置:
|
|
404
|
+
- `autoHandleProcessSignals: false` 禁用自动信号处理
|
|
405
|
+
- `shutdownTimeoutMs` 优雅停机超时(默认 `10000ms`)
|
|
406
|
+
|
|
407
|
+
`startProject` 返回 `Promise<server>`,可手动触发停机:
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
const server = await startProject({
|
|
411
|
+
// ...
|
|
412
|
+
shutdownTimeoutMs: 15000
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
await server.shutdown('manual')
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**安全增强二期(v0.8)**
|
|
419
|
+
|
|
420
|
+
1) CORS 白名单配置
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
startProject({
|
|
424
|
+
// ...
|
|
425
|
+
cors: {
|
|
426
|
+
allowOrigins: ['https://a.example.com', 'https://b.example.com'],
|
|
427
|
+
allowMethods: 'GET,POST,PUT,DELETE,OPTIONS',
|
|
428
|
+
allowHeaders: 'Content-Type, authorization, x-api-key, x-signature, x-timestamp',
|
|
429
|
+
allowCredentials: true
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
2) Helmet 可配置化
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
startProject({
|
|
438
|
+
// ...
|
|
439
|
+
helmetEnabled: true,
|
|
440
|
+
helmetOptions: {
|
|
441
|
+
contentSecurityPolicy: {
|
|
442
|
+
directives: {
|
|
443
|
+
scriptSrc: ["'self'"]
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
3) API Key / 签名中间件模板
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
const {
|
|
454
|
+
createApiKeyMiddleware,
|
|
455
|
+
createHmacSignatureMiddleware
|
|
456
|
+
} = require('exmd')
|
|
457
|
+
|
|
458
|
+
exports.createOrder = renderRequestType({
|
|
459
|
+
fun: createOrder,
|
|
460
|
+
type: 'POST',
|
|
461
|
+
middlewares: [
|
|
462
|
+
createApiKeyMiddleware({ keys: ['k1', 'k2'] }),
|
|
463
|
+
createHmacSignatureMiddleware({ secret: 'your-secret' })
|
|
464
|
+
]
|
|
465
|
+
})
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**文件上传和登录模块使用**
|
|
469
|
+
|
|
470
|
+
可参考提供的后端模板项目中的路由,看注释即可
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
const { AuthController, UploadController } = require('../../exmd')
|
|
474
|
+
const router = [
|
|
475
|
+
{
|
|
476
|
+
url: '/bserve/',
|
|
477
|
+
controller: UploadController
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
url: '/bserve/',
|
|
481
|
+
controller: AuthController
|
|
482
|
+
}
|
|
483
|
+
...自己定义的controller
|
|
484
|
+
]
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**ResultVo**
|
|
488
|
+
|
|
489
|
+
可以根据该ResultVo自定义
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
/**
|
|
493
|
+
* 返回实体类
|
|
494
|
+
* @param {*} params
|
|
495
|
+
*/
|
|
496
|
+
function ResultVo(params) {
|
|
497
|
+
const { status, data, message } = params
|
|
498
|
+
if (status === undefined) {
|
|
499
|
+
throw new Error('status is undefined')
|
|
500
|
+
}
|
|
501
|
+
if (data === undefined && message === undefined) {
|
|
502
|
+
throw new Error('both and data are undefined')
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
status,
|
|
506
|
+
data,
|
|
507
|
+
message
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
ResultVo.prototype.swaggerDescription = {
|
|
511
|
+
status: '状态 0正常',
|
|
512
|
+
data: '',
|
|
513
|
+
message: '异常信息'
|
|
514
|
+
}
|
|
515
|
+
module.exports = ResultVo
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**UserModel**
|
|
519
|
+
|
|
520
|
+
可以根据该model自定义模板放到models目录下
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
const { BaseModel } = require('../../exmd')
|
|
524
|
+
const fieldsMap = {
|
|
525
|
+
id: {
|
|
526
|
+
type: 'uuid',
|
|
527
|
+
length: 16,
|
|
528
|
+
lengthvar: -1,
|
|
529
|
+
notnull: true,
|
|
530
|
+
comment: null,
|
|
531
|
+
isPrimary: true,
|
|
532
|
+
pk_name: 'omuser_pkey'
|
|
533
|
+
},
|
|
534
|
+
username: {
|
|
535
|
+
type: 'text',
|
|
536
|
+
length: -1,
|
|
537
|
+
lengthvar: -1,
|
|
538
|
+
notnull: true,
|
|
539
|
+
comment: null
|
|
540
|
+
},
|
|
541
|
+
password: {
|
|
542
|
+
type: 'text',
|
|
543
|
+
length: -1,
|
|
544
|
+
lengthvar: -1,
|
|
545
|
+
notnull: true,
|
|
546
|
+
comment: null
|
|
547
|
+
},
|
|
548
|
+
email: {
|
|
549
|
+
type: 'text',
|
|
550
|
+
length: -1,
|
|
551
|
+
lengthvar: -1,
|
|
552
|
+
notnull: true,
|
|
553
|
+
comment: null
|
|
554
|
+
},
|
|
555
|
+
fullName: {
|
|
556
|
+
type: 'text',
|
|
557
|
+
length: -1,
|
|
558
|
+
lengthvar: -1,
|
|
559
|
+
notnull: true,
|
|
560
|
+
comment: null
|
|
561
|
+
},
|
|
562
|
+
isactive: {
|
|
563
|
+
type: 'int4',
|
|
564
|
+
length: 4,
|
|
565
|
+
lengthvar: -1,
|
|
566
|
+
notnull: true,
|
|
567
|
+
comment: null
|
|
568
|
+
},
|
|
569
|
+
realname: {
|
|
570
|
+
type: 'text',
|
|
571
|
+
length: -1,
|
|
572
|
+
lengthvar: -1,
|
|
573
|
+
notnull: false,
|
|
574
|
+
comment: null
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function omuser() {
|
|
579
|
+
const vm = this
|
|
580
|
+
BaseModel.apply(vm, arguments)
|
|
581
|
+
vm.fields = fieldsMap
|
|
582
|
+
vm.tableName = 'omuser'
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
;(function () {
|
|
586
|
+
const TempFun = function () {}
|
|
587
|
+
TempFun.prototype = BaseModel.prototype
|
|
588
|
+
omuser.prototype = new TempFun()
|
|
589
|
+
})()
|
|
590
|
+
|
|
591
|
+
module.exports = omuser
|
|
592
|
+
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**默认UserModel**
|
|
596
|
+
|
|
597
|
+
未传`authUserModel`时,框架会使用内置默认模型`DefaultAuthUserModel`,
|
|
598
|
+
表名为`omuser`,字段如下(仅`username/password/email`为必填):
|
|
599
|
+
|
|
600
|
+
```javascript
|
|
601
|
+
{
|
|
602
|
+
id: { type: 'uuid', isPrimary: true },
|
|
603
|
+
username: { type: 'varchar', length: 64, notnull: true },
|
|
604
|
+
password: { type: 'varchar', length: 64, notnull: true },
|
|
605
|
+
email: { type: 'varchar', length: 128, notnull: true },
|
|
606
|
+
fullName: { type: 'varchar', length: 128 },
|
|
607
|
+
isactive: { type: 'int4' }
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**startProject 配置项汇总**
|
|
612
|
+
|
|
613
|
+
| 分类 | 配置项 | 说明 |
|
|
614
|
+
|------|--------|------|
|
|
615
|
+
| 基础 | router, Datasource, ResultVo | 必填 |
|
|
616
|
+
| 认证 | authUserModel, authDataSourceKey, authPrivateKey, authTokenTime | JWT 与用户模型 |
|
|
617
|
+
| 邮件 | emailLib, emailConfig, authEmailContent, authEmailCodeMaxTime, authEmailBindUserMax | 验证码与注册 |
|
|
618
|
+
| 上传 | uploadBaseUrl, uploadFileDirectorys, uploadLimits | 文件上传 |
|
|
619
|
+
| 工程化 | logger, middlewares, hooks, pluginErrorStrategy | 日志、中间件、钩子 |
|
|
620
|
+
| 插件 | plugins, datasourceRegistry | 插件与数据源扩展 |
|
|
621
|
+
| 安全 | rateLimitStore, cors, helmetEnabled, helmetOptions | 限流、CORS、Helmet |
|
|
622
|
+
| 可观测 | metrics, healthPath, readinessPath, metricsPath, readinessDeepCheck, enableMetricsEndpoint | 健康检查与指标 |
|
|
623
|
+
| 运维 | shutdownTimeoutMs, autoHandleProcessSignals | 优雅停机 |
|
|
624
|
+
|
|
625
|
+
**构建**
|
|
626
|
+
```shell
|
|
627
|
+
npm run build
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**提交所有代码**
|
|
631
|
+
```shell
|
|
632
|
+
npm run push:all
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**发布**
|
|
636
|
+
```shell
|
|
637
|
+
npm run publish:exmd
|
|
638
|
+
npm run publish:aux
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
**Changelog 生成**
|
|
642
|
+
```shell
|
|
643
|
+
npm run changelog:gen -- --version 1.0.39 --title "发布说明" --changes "feat: 功能1|fix: 修复1"
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
源码仓库已提交申请中,请耐心等待
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|
package/bin/build-aux.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
targetFile_1="./dist/auxiliary/package.json"
|
|
4
|
+
targetFile_2="./dist/auxiliary//README.MD"
|
|
5
|
+
sourceFile="./resource-pack/auxiliary/*"
|
|
6
|
+
npm run build &&
|
|
7
|
+
if [ -f "${targetFile_1}" ]; then rm -rf "${targetFile_1}"; fi &&
|
|
8
|
+
if [ -f "${targetFile_2}" ]; then rm -rf "${targetFile_2}"; fi &&
|
|
9
|
+
cp ./resource-pack/auxiliary/* dist/auxiliary &&
|
|
10
|
+
echo "完成文件拷贝"
|
package/bin/build.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# webpack打包
|
|
4
|
+
./node_modules/.bin/webpack --mode=production &&
|
|
5
|
+
# 创建目录
|
|
6
|
+
if [ ! -d dist/auxiliary ]; then mkdir -p dist/auxiliary; fi &&
|
|
7
|
+
# 迁移辅文件
|
|
8
|
+
mv ./dist/exmd/src/auxiliary.js ./dist/auxiliary/index.js &&
|
|
9
|
+
# 拷贝package.json
|
|
10
|
+
cp ./package.json ./dist/exmd/package.json &&
|
|
11
|
+
cp ./resources/index.d.ts ./dist/exmd/index.d.ts
|