@jayfong/x-server 2.118.0 → 2.119.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/lib/_cjs/cli/api_generator.js +26 -6
- package/lib/_cjs/cli/cli.js +0 -0
- package/lib/_cjs/cli/dev_util.js +2 -1
- package/lib/_cjs/core/define_task.js +2 -2
- package/lib/_cjs/core/handler.js +4 -4
- package/lib/_cjs/core/server.js +6 -8
- package/lib/_cjs/plugins/compress.js +1 -1
- package/lib/_cjs/plugins/cookie.js +1 -1
- package/lib/_cjs/plugins/form_body_parser.js +1 -1
- package/lib/_cjs/plugins/ws_parser.js +1 -1
- package/lib/_cjs/plugins/xml_parser.js +1 -1
- package/lib/_cjs/services/captcha.js +3 -3
- package/lib/_cjs/services/dingtalk.js +2 -2
- package/lib/_cjs/services/emoji.js +1 -1
- package/lib/_cjs/services/log.js +2 -2
- package/lib/_cjs/services/pay.js +5 -6
- package/lib/_cjs/services/sensitive_words.js +0 -1
- package/lib/cli/api_generator.js +26 -6
- package/lib/cli/dev_util.js +2 -1
- package/lib/core/ctx_storage.d.ts +1 -1
- package/lib/core/define_bus.d.ts +1 -1
- package/lib/core/define_cron.d.ts +1 -1
- package/lib/core/define_task.js +1 -1
- package/lib/core/get_handler_url.js +2 -2
- package/lib/core/handler.js +4 -4
- package/lib/core/http_method.d.ts +2 -2
- package/lib/core/server.d.ts +1 -1
- package/lib/core/server.js +6 -8
- package/lib/core/types.d.ts +2 -2
- package/lib/plugins/base.d.ts +1 -1
- package/lib/plugins/compress.d.ts +3 -3
- package/lib/plugins/compress.js +1 -1
- package/lib/plugins/cookie.d.ts +3 -3
- package/lib/plugins/cookie.js +1 -1
- package/lib/plugins/cors.d.ts +4 -4
- package/lib/plugins/cors.js +1 -2
- package/lib/plugins/file_parser.d.ts +3 -3
- package/lib/plugins/form_body_parser.d.ts +3 -3
- package/lib/plugins/form_body_parser.js +1 -1
- package/lib/plugins/ws_parser.d.ts +3 -3
- package/lib/plugins/ws_parser.js +1 -1
- package/lib/plugins/xml_parser.d.ts +3 -3
- package/lib/plugins/xml_parser.js +1 -1
- package/lib/services/captcha.d.ts +1 -1
- package/lib/services/captcha.js +3 -3
- package/lib/services/crypto.d.ts +1 -1
- package/lib/services/dingtalk.d.ts +1 -1
- package/lib/services/dingtalk.js +1 -1
- package/lib/services/dispose.d.ts +1 -1
- package/lib/services/emoji.d.ts +1 -1
- package/lib/services/emoji.js +1 -1
- package/lib/services/html.d.ts +1 -1
- package/lib/services/log.d.ts +2 -2
- package/lib/services/log.js +1 -1
- package/lib/services/mail.d.ts +1 -1
- package/lib/services/pay.d.ts +6 -8
- package/lib/services/pay.js +3 -4
- package/lib/services/rate_limit.d.ts +2 -2
- package/lib/services/redis.d.ts +2 -2
- package/lib/services/sensitive_words.d.ts +1 -2
- package/lib/services/sensitive_words.js +0 -1
- package/lib/services/sms.d.ts +1 -1
- package/package.json +34 -33
|
@@ -26,6 +26,7 @@ class ApiGenerator {
|
|
|
26
26
|
// /user/index.ts
|
|
27
27
|
// /post.ts
|
|
28
28
|
// /user/list.ts
|
|
29
|
+
// category 必须从 handlers 目录结构中推导,后续会直接拼接到 URL 前缀
|
|
29
30
|
this.categoryUrlRe = /^.+\/src\/handlers\/(?:index\.ts|(.*)\/index\.ts|(.*)\.ts)$/;
|
|
30
31
|
this.checker = void 0;
|
|
31
32
|
this.cwd = this.options.cwd || process.cwd();
|
|
@@ -36,7 +37,7 @@ class ApiGenerator {
|
|
|
36
37
|
const handlersPath = _nodePath.default.join(this.cwd, 'node_modules/.x/handlers.ts');
|
|
37
38
|
const entryFile = _nodePath.default.join(this.cwd, 'src/generated/handlers.ts');
|
|
38
39
|
|
|
39
|
-
// 仅使用 ts-morph
|
|
40
|
+
// 仅使用 ts-morph 初始化项目,并把运行时生成的 handlers 聚合文件注入到 Program 中
|
|
40
41
|
const moProject = new _tsMorph.default.Project({
|
|
41
42
|
tsConfigFilePath: tsConfigPath
|
|
42
43
|
});
|
|
@@ -45,11 +46,12 @@ class ApiGenerator {
|
|
|
45
46
|
const checker = program.getTypeChecker();
|
|
46
47
|
this.checker = checker;
|
|
47
48
|
const entrySourceFile = program.getSourceFile(entryFile);
|
|
49
|
+
// handlers.ts 的每个 export 对应一个分类(如 user、post)
|
|
48
50
|
const moduleSymbol = checker.getSymbolAtLocation(entrySourceFile);
|
|
49
51
|
const categorySymbols = checker.getExportsOfModule(moduleSymbol);
|
|
50
52
|
const apiData = [];
|
|
51
53
|
for (const categorySymbol of categorySymbols) {
|
|
52
|
-
//
|
|
54
|
+
// 分类的类型:形如 { list: defineHandler<...>, detail: defineHandler<...> }
|
|
53
55
|
const categoryType = checker.getTypeOfSymbol(categorySymbol);
|
|
54
56
|
|
|
55
57
|
// 处理器列表
|
|
@@ -81,7 +83,8 @@ class ApiGenerator {
|
|
|
81
83
|
let handlerPath = [handlerSymbol.getName()];
|
|
82
84
|
const handlerNode = handlerSymbol.getDeclarations()[0];
|
|
83
85
|
|
|
84
|
-
// 支持自定义 requestPath
|
|
86
|
+
// 支持自定义 requestPath,仅识别字符串字面量或字符串数组字面量
|
|
87
|
+
// 这里不会执行表达式(例如变量、模板字符串、函数调用)
|
|
85
88
|
if (
|
|
86
89
|
// const xx = defineHandler
|
|
87
90
|
_tsMorph.ts.isVariableDeclaration(handlerNode) && handlerNode.initializer &&
|
|
@@ -116,6 +119,9 @@ class ApiGenerator {
|
|
|
116
119
|
}
|
|
117
120
|
this.debug('生成接口: %s ...', handlerUrl);
|
|
118
121
|
const handlerType = checker.getTypeAtLocation(handlerNode);
|
|
122
|
+
|
|
123
|
+
// 约定 defineHandler 的泛型顺序固定为 <Request, Response, Method>
|
|
124
|
+
// 若上游定义顺序变更,这里会静默解析错误
|
|
119
125
|
const [requestType, responseType, methodType] = checker.getTypeArguments(handlerType);
|
|
120
126
|
const handlerCategory = (0, _vtils.pascalCase)(categoryUrl) || 'Index';
|
|
121
127
|
const handlerDescription = handlerComment.description || handlerUrl.join(', ');
|
|
@@ -129,6 +135,8 @@ class ApiGenerator {
|
|
|
129
135
|
});
|
|
130
136
|
const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
|
|
131
137
|
const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
|
|
138
|
+
|
|
139
|
+
// 说明文档按“首段标题 + 空行 + 详情”拆分
|
|
132
140
|
const handlerDescriptionDividerIndex = handlerDescription.indexOf('\n\n');
|
|
133
141
|
const apiName = handlerDescriptionDividerIndex === -1 ? handlerDescription : handlerDescription.substring(0, handlerDescriptionDividerIndex);
|
|
134
142
|
const apiDesc = handlerDescriptionDividerIndex === -1 ? '' : handlerDescription.substring(handlerDescriptionDividerIndex + 2);
|
|
@@ -167,6 +175,7 @@ class ApiGenerator {
|
|
|
167
175
|
};
|
|
168
176
|
}
|
|
169
177
|
const description = _tsMorph.ts.displayPartsToString(symbol.getDocumentationComment(this.checker)).trim();
|
|
178
|
+
// JSDoc tag 统一收敛到 Map,方便按 @private 这类标签做开关
|
|
170
179
|
const tags = symbol.getJsDocTags().map(item => {
|
|
171
180
|
return {
|
|
172
181
|
label: item.name,
|
|
@@ -187,6 +196,8 @@ class ApiGenerator {
|
|
|
187
196
|
keySymbol = type.symbol || type.aliasSymbol
|
|
188
197
|
}) {
|
|
189
198
|
const hasTypeFlag = (flag, _type = type) => (_type.flags & flag) === flag;
|
|
199
|
+
|
|
200
|
+
// valueSymbol 用于识别“实际值类型”,keySymbol 用于保留字段名和字段注释
|
|
190
201
|
let valueSymbol = type.symbol || type.aliasSymbol;
|
|
191
202
|
const keySymbolName = keySymbol?.getName() || '';
|
|
192
203
|
let valueSymbolName = valueSymbol?.getName() || '';
|
|
@@ -242,8 +253,8 @@ class ApiGenerator {
|
|
|
242
253
|
};
|
|
243
254
|
}
|
|
244
255
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
256
|
+
// 联合类型:布尔字面量、枚举字面量都会走到这里
|
|
257
|
+
// 处理策略:忽略 null/undefined,用第一个成员推断基础类型,同时把所有字面量收集到 enum
|
|
247
258
|
const rawUnionTypes = type.isUnion() && type.types || [];
|
|
248
259
|
const unionTypes = rawUnionTypes.filter(item => !hasTypeFlag(_tsMorph.ts.TypeFlags.Null, item) && !hasTypeFlag(_tsMorph.ts.TypeFlags.Undefined, item));
|
|
249
260
|
const isUnion = !!unionTypes.length;
|
|
@@ -274,6 +285,8 @@ class ApiGenerator {
|
|
|
274
285
|
const isNumber = hasTypeFlag(_tsMorph.ts.TypeFlags.Number) || isNumberLiteral;
|
|
275
286
|
const isBoolean = hasTypeFlag(_tsMorph.ts.TypeFlags.Boolean) || hasTypeFlag(_tsMorph.ts.TypeFlags.BooleanLiteral);
|
|
276
287
|
const isLiteral = isStringLiteral || isNumberLiteral || isBigIntLiteral;
|
|
288
|
+
|
|
289
|
+
// object 兜底:只要不是数组/基础类型,且 TypeScript 视为 Object,就按对象递归
|
|
277
290
|
const isObject = !isArray && !isString && !isNumber && !isBoolean && hasTypeFlag(_tsMorph.ts.TypeFlags.Object) ||
|
|
278
291
|
// 交集类型视为对象
|
|
279
292
|
isIntersection;
|
|
@@ -284,6 +297,8 @@ class ApiGenerator {
|
|
|
284
297
|
const apiChildren = isArray ? [this.getApiDto({
|
|
285
298
|
type: this.checker.getTypeArguments(type)[0]
|
|
286
299
|
})] : isObject ? (0, _vtils.ii)(() => {
|
|
300
|
+
// 这里使用了 TypeChecker 的内部能力来合并交集类型字段
|
|
301
|
+
// 若 TypeScript 内部 API 变化,这里是高风险点
|
|
287
302
|
const childSymbols = this.checker.getAllPossiblePropertiesOfTypes(intersectionTypes.length ? intersectionTypes : [type]);
|
|
288
303
|
return childSymbols.map(childSymbol => {
|
|
289
304
|
const childType = this.checker.getTypeOfSymbol(childSymbol);
|
|
@@ -303,6 +318,7 @@ class ApiGenerator {
|
|
|
303
318
|
};
|
|
304
319
|
}
|
|
305
320
|
apiDataToJsonSchema(apiData, jsonSchema = {}) {
|
|
321
|
+
// 递归把内部 DTO 结构映射到 JSON Schema(以 object/array/primitive 三类分支处理)
|
|
306
322
|
jsonSchema.description = apiData.desc;
|
|
307
323
|
if (apiData.type === 'object') {
|
|
308
324
|
jsonSchema.type = 'object';
|
|
@@ -327,7 +343,10 @@ class ApiGenerator {
|
|
|
327
343
|
for (const handle of handles) {
|
|
328
344
|
for (const path of handle.path) {
|
|
329
345
|
data[handle.category] = data[handle.category] || [];
|
|
330
|
-
data[handle.category].push(
|
|
346
|
+
data[handle.category].push(
|
|
347
|
+
// GET -> query;FILE -> form-data;其余 -> json body
|
|
348
|
+
// 此处是与 YApi 结构的协议转换层
|
|
349
|
+
handle.handlerMethod === 'GET' ? {
|
|
331
350
|
method: handle.method.toUpperCase(),
|
|
332
351
|
title: handle.name,
|
|
333
352
|
markdown: handle.desc,
|
|
@@ -411,6 +430,7 @@ class ApiGenerator {
|
|
|
411
430
|
tags: [handle.category],
|
|
412
431
|
summary: handle.name,
|
|
413
432
|
description: handle.desc,
|
|
433
|
+
// 目前统一按 application/json 输出;上传类接口未转为 multipart/form-data
|
|
414
434
|
requestBody: {
|
|
415
435
|
content: {
|
|
416
436
|
'application/json': {
|
package/lib/_cjs/cli/cli.js
CHANGED
|
File without changes
|
package/lib/_cjs/cli/dev_util.js
CHANGED
|
@@ -12,7 +12,8 @@ class DevUtil {
|
|
|
12
12
|
const tsxPkgFile = require.resolve('tsx/package.json');
|
|
13
13
|
const tsxJsFile = tsxPkgFile.replace('/package.json', '/dist/cli.js');
|
|
14
14
|
const tsxCjsFile = tsxPkgFile.replace('/package.json', '/dist/cli.cjs');
|
|
15
|
-
|
|
15
|
+
const tsxMjsFile = tsxPkgFile.replace('/package.json', '/dist/cli.mjs');
|
|
16
|
+
return _nodeFs.default.existsSync(tsxCjsFile) ? tsxCjsFile : _nodeFs.default.existsSync(tsxMjsFile) ? tsxMjsFile : tsxJsFile;
|
|
16
17
|
}), options.file], {
|
|
17
18
|
cwd: options.cwd,
|
|
18
19
|
stdio: 'inherit'
|
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
exports.__esModule = true;
|
|
5
5
|
exports.defineSliceTask = defineSliceTask;
|
|
6
6
|
exports.defineTask = defineTask;
|
|
7
|
-
var
|
|
7
|
+
var _nodeAssert = _interopRequireDefault(require("node:assert"));
|
|
8
8
|
var _bull = _interopRequireDefault(require("bull"));
|
|
9
9
|
var _vtils = require("vtils");
|
|
10
10
|
var _x = require("../x");
|
|
@@ -46,7 +46,7 @@ function defineSliceTask(options) {
|
|
|
46
46
|
const duration = addOptions?.duration != null ? (0, _vtils.ms)(addOptions.duration) : typeof options.duration === 'function' ? (0, _vtils.ms)(options.duration(key)) : options.duration && (0, _vtils.ms)(options.duration);
|
|
47
47
|
const threshold = addOptions?.threshold || typeof options.threshold && (typeof options.threshold === 'function' ? options.threshold(key) : options.threshold);
|
|
48
48
|
const thresholdTimeout = addOptions?.thresholdTimeout != null ? (0, _vtils.ms)(addOptions.thresholdTimeout) : typeof options.thresholdTimeout === 'function' ? (0, _vtils.ms)(options.thresholdTimeout(key)) : options.thresholdTimeout && (0, _vtils.ms)(options.thresholdTimeout);
|
|
49
|
-
(0,
|
|
49
|
+
(0, _nodeAssert.default)(duration != null || threshold != null, '参数 threshold 和 duration 必须至少设置 1 个');
|
|
50
50
|
const redisKey = !key ? redisKeyPrefix : `${redisKeyPrefix}_${key}`;
|
|
51
51
|
const res = await _x.x.redis.multi([['llen', redisKey], ['lpush', redisKey, JSON.stringify(data)]]).exec();
|
|
52
52
|
const count = parseInt(res[0][1], 10) + 1;
|
package/lib/_cjs/core/handler.js
CHANGED
|
@@ -131,11 +131,11 @@ class Handler {
|
|
|
131
131
|
};
|
|
132
132
|
this.handleWs = async (_data, ctx) => {
|
|
133
133
|
const dispose = new _dispose.DisposeService();
|
|
134
|
-
ctx.ws.
|
|
134
|
+
ctx.ws.on('message', async payload => {
|
|
135
135
|
try {
|
|
136
136
|
const _ = JSON.parse(payload.toString('utf8'));
|
|
137
137
|
const send = data => {
|
|
138
|
-
ctx.ws.
|
|
138
|
+
ctx.ws.send(JSON.stringify({
|
|
139
139
|
id: _.id,
|
|
140
140
|
data: data
|
|
141
141
|
}));
|
|
@@ -172,14 +172,14 @@ class Handler {
|
|
|
172
172
|
}
|
|
173
173
|
} catch (err) {
|
|
174
174
|
const _err = _http_error.HttpError.isHttpError(err) ? err : new _http_error.HttpError.InternalServerError();
|
|
175
|
-
ctx.ws.
|
|
175
|
+
ctx.ws.send(JSON.stringify({
|
|
176
176
|
statusCode: _err.statusCode,
|
|
177
177
|
error: _err.name,
|
|
178
178
|
message: _err.message
|
|
179
179
|
}));
|
|
180
180
|
}
|
|
181
181
|
});
|
|
182
|
-
ctx.ws.
|
|
182
|
+
ctx.ws.on('close', () => {
|
|
183
183
|
dispose.dispose();
|
|
184
184
|
});
|
|
185
185
|
};
|
package/lib/_cjs/core/server.js
CHANGED
|
@@ -118,7 +118,7 @@ class Server {
|
|
|
118
118
|
url,
|
|
119
119
|
headers: req.headers,
|
|
120
120
|
setHeader: (k, v) => res.header(k, v),
|
|
121
|
-
redirect: (url, permanently) => res.redirect(permanently ? 301 : 302
|
|
121
|
+
redirect: (url, permanently) => res.redirect(url, permanently ? 301 : 302),
|
|
122
122
|
ws: undefined,
|
|
123
123
|
req: req,
|
|
124
124
|
res: res
|
|
@@ -188,15 +188,13 @@ class Server {
|
|
|
188
188
|
if (Array.isArray(_x2.x.env.APP_STATIC_CONTENT)) {
|
|
189
189
|
const staticContent = _x2.x.env.APP_STATIC_CONTENT;
|
|
190
190
|
for (const item of staticContent) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!this.fastify.hasRoute({
|
|
195
|
-
method: item.method,
|
|
191
|
+
const methods = (0, _vtils.castArray)(item.method);
|
|
192
|
+
if (!methods.some(method => this.fastify.hasRoute({
|
|
193
|
+
method,
|
|
196
194
|
url: item.path
|
|
197
|
-
})) {
|
|
195
|
+
}))) {
|
|
198
196
|
this.fastify.route({
|
|
199
|
-
method:
|
|
197
|
+
method: methods,
|
|
200
198
|
url: item.path,
|
|
201
199
|
handler: async (_req, res) => {
|
|
202
200
|
if (!item.contentType && typeof item.content === 'string') {
|
|
@@ -12,7 +12,7 @@ class FormBodyParserPlugin {
|
|
|
12
12
|
this.options = options;
|
|
13
13
|
}
|
|
14
14
|
register(fastify) {
|
|
15
|
-
fastify.register(_formbody.default, this.options);
|
|
15
|
+
fastify.register(_formbody.default, this.options || {});
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
exports.FormBodyParserPlugin = FormBodyParserPlugin;
|
|
@@ -18,7 +18,7 @@ class XmlParserPlugin {
|
|
|
18
18
|
...parseOptions
|
|
19
19
|
} = this.options || {};
|
|
20
20
|
const xmlParser = new _fastXmlParser.XMLParser(parseOptions);
|
|
21
|
-
fastify.addContentTypeParser(contentType, (
|
|
21
|
+
fastify.addContentTypeParser(contentType, (_req, payload, done) => {
|
|
22
22
|
let body = '';
|
|
23
23
|
const handleError = err => done(err);
|
|
24
24
|
const handleData = data => body += data;
|
|
@@ -13,9 +13,9 @@ class CaptchaService {
|
|
|
13
13
|
this.charsetMap = {
|
|
14
14
|
'09': '0123456789'.split(''),
|
|
15
15
|
'19': '123456789'.split(''),
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
az: 'abcdefghijklmnopqrstuvwxyz'.split(''),
|
|
17
|
+
AZ: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
18
|
+
aZ: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
19
19
|
'0z': '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
|
|
20
20
|
'0Z': '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
21
21
|
'1z': '123456789abcdefghijklmnopqrstuvwxyz'.split(''),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
4
|
exports.__esModule = true;
|
|
5
5
|
exports.DingtalkService = void 0;
|
|
6
|
-
var
|
|
6
|
+
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
|
|
7
7
|
var _got = _interopRequireDefault(require("got"));
|
|
8
8
|
class DingtalkService {
|
|
9
9
|
constructor(options) {
|
|
@@ -12,7 +12,7 @@ class DingtalkService {
|
|
|
12
12
|
}
|
|
13
13
|
async send(options) {
|
|
14
14
|
const timestamp = Date.now();
|
|
15
|
-
const sign =
|
|
15
|
+
const sign = _nodeCrypto.default.createHmac('sha256', this.options.secret).update(`${timestamp}\n${this.options.secret}`).digest('base64');
|
|
16
16
|
const data = {
|
|
17
17
|
msgtype: 'markdown',
|
|
18
18
|
markdown: {
|
package/lib/_cjs/services/log.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
4
|
exports.__esModule = true;
|
|
5
5
|
exports.LogService = void 0;
|
|
6
|
-
var
|
|
6
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
7
7
|
var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
8
8
|
var _vtils = require("vtils");
|
|
9
9
|
var _date = require("vtils/date");
|
|
@@ -68,7 +68,7 @@ class LogService {
|
|
|
68
68
|
getLogFile(date) {
|
|
69
69
|
const year = date.getFullYear();
|
|
70
70
|
const month = date.getMonth() + 1;
|
|
71
|
-
return
|
|
71
|
+
return _nodePath.default.join(_x.x.dataDir, `log/${year}/${month < 10 ? `0${month}` : month}.log`);
|
|
72
72
|
}
|
|
73
73
|
get logFile() {
|
|
74
74
|
return this.getLogFile(new Date());
|
package/lib/_cjs/services/pay.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
4
|
exports.__esModule = true;
|
|
5
5
|
exports.PayService = void 0;
|
|
6
|
-
var
|
|
6
|
+
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
|
|
7
7
|
var _alipaySdk = _interopRequireDefault(require("alipay-sdk"));
|
|
8
8
|
var _form = _interopRequireDefault(require("alipay-sdk/lib/form"));
|
|
9
9
|
var _got = _interopRequireDefault(require("got"));
|
|
@@ -44,9 +44,8 @@ class PayService {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
async prepareWepay(options) {
|
|
47
|
-
const appId = options.appId || this.options.wepay.appId;
|
|
48
47
|
const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
|
|
49
|
-
appid: appId,
|
|
48
|
+
appid: this.options.wepay.appId,
|
|
50
49
|
mchid: this.options.wepay.merchantId,
|
|
51
50
|
description: options.goodsName,
|
|
52
51
|
out_trade_no: options.tradeNumber,
|
|
@@ -65,7 +64,7 @@ class PayService {
|
|
|
65
64
|
throw new Error(error);
|
|
66
65
|
}
|
|
67
66
|
const params = {
|
|
68
|
-
appId: appId,
|
|
67
|
+
appId: this.options.wepay.appId,
|
|
69
68
|
timestamp: String(Math.round(Date.now() / 1000)),
|
|
70
69
|
nonceStr: Math.random().toString().substr(2, 12),
|
|
71
70
|
package: `prepay_id=${res.prepay_id}`,
|
|
@@ -145,13 +144,13 @@ class PayService {
|
|
|
145
144
|
};
|
|
146
145
|
}
|
|
147
146
|
wepaySign(data) {
|
|
148
|
-
return
|
|
147
|
+
return _nodeCrypto.default.createSign('RSA-SHA256').update(data.map(v => `${v}\n`).join('')).sign(this.options.wepay.privateKey, 'base64');
|
|
149
148
|
}
|
|
150
149
|
wepayDecrypt(payload) {
|
|
151
150
|
const ciphertextBuffer = Buffer.from(payload.ciphertext, 'base64');
|
|
152
151
|
const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16);
|
|
153
152
|
const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16);
|
|
154
|
-
const decipherIv =
|
|
153
|
+
const decipherIv = _nodeCrypto.default.createDecipheriv('aes-256-gcm', this.options.wepay.secretKey, payload.nonce);
|
|
155
154
|
decipherIv.setAuthTag(Buffer.from(authTag));
|
|
156
155
|
decipherIv.setAAD(Buffer.from(payload.associated_data));
|
|
157
156
|
const decryptStr = decipherIv.update(data, undefined, 'utf8');
|
|
@@ -12,7 +12,6 @@ const toRegExp = (0, _vtils.memoize)(re => new RegExp(re.substring(1, re.length
|
|
|
12
12
|
*/
|
|
13
13
|
class SensitiveWordsService {
|
|
14
14
|
constructor(options) {
|
|
15
|
-
this.options = options;
|
|
16
15
|
this.serviceName = 'sensitiveWords';
|
|
17
16
|
this.sensitiveWords = [];
|
|
18
17
|
this.sensitiveWords = (0, _vtils.uniq)(options.words.map(word => word.toLowerCase()));
|
package/lib/cli/api_generator.js
CHANGED
|
@@ -21,6 +21,7 @@ export class ApiGenerator {
|
|
|
21
21
|
// /user/index.ts
|
|
22
22
|
// /post.ts
|
|
23
23
|
// /user/list.ts
|
|
24
|
+
// category 必须从 handlers 目录结构中推导,后续会直接拼接到 URL 前缀
|
|
24
25
|
this.categoryUrlRe = /^.+\/src\/handlers\/(?:index\.ts|(.*)\/index\.ts|(.*)\.ts)$/;
|
|
25
26
|
this.checker = void 0;
|
|
26
27
|
this.cwd = this.options.cwd || process.cwd();
|
|
@@ -31,7 +32,7 @@ export class ApiGenerator {
|
|
|
31
32
|
const handlersPath = path.join(this.cwd, 'node_modules/.x/handlers.ts');
|
|
32
33
|
const entryFile = path.join(this.cwd, 'src/generated/handlers.ts');
|
|
33
34
|
|
|
34
|
-
// 仅使用 ts-morph
|
|
35
|
+
// 仅使用 ts-morph 初始化项目,并把运行时生成的 handlers 聚合文件注入到 Program 中
|
|
35
36
|
const moProject = new mo.Project({
|
|
36
37
|
tsConfigFilePath: tsConfigPath
|
|
37
38
|
});
|
|
@@ -40,11 +41,12 @@ export class ApiGenerator {
|
|
|
40
41
|
const checker = program.getTypeChecker();
|
|
41
42
|
this.checker = checker;
|
|
42
43
|
const entrySourceFile = program.getSourceFile(entryFile);
|
|
44
|
+
// handlers.ts 的每个 export 对应一个分类(如 user、post)
|
|
43
45
|
const moduleSymbol = checker.getSymbolAtLocation(entrySourceFile);
|
|
44
46
|
const categorySymbols = checker.getExportsOfModule(moduleSymbol);
|
|
45
47
|
const apiData = [];
|
|
46
48
|
for (const categorySymbol of categorySymbols) {
|
|
47
|
-
//
|
|
49
|
+
// 分类的类型:形如 { list: defineHandler<...>, detail: defineHandler<...> }
|
|
48
50
|
const categoryType = checker.getTypeOfSymbol(categorySymbol);
|
|
49
51
|
|
|
50
52
|
// 处理器列表
|
|
@@ -76,7 +78,8 @@ export class ApiGenerator {
|
|
|
76
78
|
let handlerPath = [handlerSymbol.getName()];
|
|
77
79
|
const handlerNode = handlerSymbol.getDeclarations()[0];
|
|
78
80
|
|
|
79
|
-
// 支持自定义 requestPath
|
|
81
|
+
// 支持自定义 requestPath,仅识别字符串字面量或字符串数组字面量
|
|
82
|
+
// 这里不会执行表达式(例如变量、模板字符串、函数调用)
|
|
80
83
|
if (
|
|
81
84
|
// const xx = defineHandler
|
|
82
85
|
ts.isVariableDeclaration(handlerNode) && handlerNode.initializer &&
|
|
@@ -111,6 +114,9 @@ export class ApiGenerator {
|
|
|
111
114
|
}
|
|
112
115
|
this.debug('生成接口: %s ...', handlerUrl);
|
|
113
116
|
const handlerType = checker.getTypeAtLocation(handlerNode);
|
|
117
|
+
|
|
118
|
+
// 约定 defineHandler 的泛型顺序固定为 <Request, Response, Method>
|
|
119
|
+
// 若上游定义顺序变更,这里会静默解析错误
|
|
114
120
|
const [requestType, responseType, methodType] = checker.getTypeArguments(handlerType);
|
|
115
121
|
const handlerCategory = pascalCase(categoryUrl) || 'Index';
|
|
116
122
|
const handlerDescription = handlerComment.description || handlerUrl.join(', ');
|
|
@@ -124,6 +130,8 @@ export class ApiGenerator {
|
|
|
124
130
|
});
|
|
125
131
|
const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
|
|
126
132
|
const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
|
|
133
|
+
|
|
134
|
+
// 说明文档按“首段标题 + 空行 + 详情”拆分
|
|
127
135
|
const handlerDescriptionDividerIndex = handlerDescription.indexOf('\n\n');
|
|
128
136
|
const apiName = handlerDescriptionDividerIndex === -1 ? handlerDescription : handlerDescription.substring(0, handlerDescriptionDividerIndex);
|
|
129
137
|
const apiDesc = handlerDescriptionDividerIndex === -1 ? '' : handlerDescription.substring(handlerDescriptionDividerIndex + 2);
|
|
@@ -162,6 +170,7 @@ export class ApiGenerator {
|
|
|
162
170
|
};
|
|
163
171
|
}
|
|
164
172
|
const description = ts.displayPartsToString(symbol.getDocumentationComment(this.checker)).trim();
|
|
173
|
+
// JSDoc tag 统一收敛到 Map,方便按 @private 这类标签做开关
|
|
165
174
|
const tags = symbol.getJsDocTags().map(item => {
|
|
166
175
|
return {
|
|
167
176
|
label: item.name,
|
|
@@ -182,6 +191,8 @@ export class ApiGenerator {
|
|
|
182
191
|
keySymbol = type.symbol || type.aliasSymbol
|
|
183
192
|
}) {
|
|
184
193
|
const hasTypeFlag = (flag, _type = type) => (_type.flags & flag) === flag;
|
|
194
|
+
|
|
195
|
+
// valueSymbol 用于识别“实际值类型”,keySymbol 用于保留字段名和字段注释
|
|
185
196
|
let valueSymbol = type.symbol || type.aliasSymbol;
|
|
186
197
|
const keySymbolName = keySymbol?.getName() || '';
|
|
187
198
|
let valueSymbolName = valueSymbol?.getName() || '';
|
|
@@ -237,8 +248,8 @@ export class ApiGenerator {
|
|
|
237
248
|
};
|
|
238
249
|
}
|
|
239
250
|
|
|
240
|
-
//
|
|
241
|
-
//
|
|
251
|
+
// 联合类型:布尔字面量、枚举字面量都会走到这里
|
|
252
|
+
// 处理策略:忽略 null/undefined,用第一个成员推断基础类型,同时把所有字面量收集到 enum
|
|
242
253
|
const rawUnionTypes = type.isUnion() && type.types || [];
|
|
243
254
|
const unionTypes = rawUnionTypes.filter(item => !hasTypeFlag(ts.TypeFlags.Null, item) && !hasTypeFlag(ts.TypeFlags.Undefined, item));
|
|
244
255
|
const isUnion = !!unionTypes.length;
|
|
@@ -269,6 +280,8 @@ export class ApiGenerator {
|
|
|
269
280
|
const isNumber = hasTypeFlag(ts.TypeFlags.Number) || isNumberLiteral;
|
|
270
281
|
const isBoolean = hasTypeFlag(ts.TypeFlags.Boolean) || hasTypeFlag(ts.TypeFlags.BooleanLiteral);
|
|
271
282
|
const isLiteral = isStringLiteral || isNumberLiteral || isBigIntLiteral;
|
|
283
|
+
|
|
284
|
+
// object 兜底:只要不是数组/基础类型,且 TypeScript 视为 Object,就按对象递归
|
|
272
285
|
const isObject = !isArray && !isString && !isNumber && !isBoolean && hasTypeFlag(ts.TypeFlags.Object) ||
|
|
273
286
|
// 交集类型视为对象
|
|
274
287
|
isIntersection;
|
|
@@ -279,6 +292,8 @@ export class ApiGenerator {
|
|
|
279
292
|
const apiChildren = isArray ? [this.getApiDto({
|
|
280
293
|
type: this.checker.getTypeArguments(type)[0]
|
|
281
294
|
})] : isObject ? ii(() => {
|
|
295
|
+
// 这里使用了 TypeChecker 的内部能力来合并交集类型字段
|
|
296
|
+
// 若 TypeScript 内部 API 变化,这里是高风险点
|
|
282
297
|
const childSymbols = this.checker.getAllPossiblePropertiesOfTypes(intersectionTypes.length ? intersectionTypes : [type]);
|
|
283
298
|
return childSymbols.map(childSymbol => {
|
|
284
299
|
const childType = this.checker.getTypeOfSymbol(childSymbol);
|
|
@@ -298,6 +313,7 @@ export class ApiGenerator {
|
|
|
298
313
|
};
|
|
299
314
|
}
|
|
300
315
|
apiDataToJsonSchema(apiData, jsonSchema = {}) {
|
|
316
|
+
// 递归把内部 DTO 结构映射到 JSON Schema(以 object/array/primitive 三类分支处理)
|
|
301
317
|
jsonSchema.description = apiData.desc;
|
|
302
318
|
if (apiData.type === 'object') {
|
|
303
319
|
jsonSchema.type = 'object';
|
|
@@ -322,7 +338,10 @@ export class ApiGenerator {
|
|
|
322
338
|
for (const handle of handles) {
|
|
323
339
|
for (const path of handle.path) {
|
|
324
340
|
data[handle.category] = data[handle.category] || [];
|
|
325
|
-
data[handle.category].push(
|
|
341
|
+
data[handle.category].push(
|
|
342
|
+
// GET -> query;FILE -> form-data;其余 -> json body
|
|
343
|
+
// 此处是与 YApi 结构的协议转换层
|
|
344
|
+
handle.handlerMethod === 'GET' ? {
|
|
326
345
|
method: handle.method.toUpperCase(),
|
|
327
346
|
title: handle.name,
|
|
328
347
|
markdown: handle.desc,
|
|
@@ -406,6 +425,7 @@ export class ApiGenerator {
|
|
|
406
425
|
tags: [handle.category],
|
|
407
426
|
summary: handle.name,
|
|
408
427
|
description: handle.desc,
|
|
428
|
+
// 目前统一按 application/json 输出;上传类接口未转为 multipart/form-data
|
|
409
429
|
requestBody: {
|
|
410
430
|
content: {
|
|
411
431
|
'application/json': {
|
package/lib/cli/dev_util.js
CHANGED
|
@@ -7,7 +7,8 @@ export class DevUtil {
|
|
|
7
7
|
const tsxPkgFile = require.resolve('tsx/package.json');
|
|
8
8
|
const tsxJsFile = tsxPkgFile.replace('/package.json', '/dist/cli.js');
|
|
9
9
|
const tsxCjsFile = tsxPkgFile.replace('/package.json', '/dist/cli.cjs');
|
|
10
|
-
|
|
10
|
+
const tsxMjsFile = tsxPkgFile.replace('/package.json', '/dist/cli.mjs');
|
|
11
|
+
return fs.existsSync(tsxCjsFile) ? tsxCjsFile : fs.existsSync(tsxMjsFile) ? tsxMjsFile : tsxJsFile;
|
|
11
12
|
}), options.file], {
|
|
12
13
|
cwd: options.cwd,
|
|
13
14
|
stdio: 'inherit'
|
package/lib/core/define_bus.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
declare class EventBus extends EventEmitter {
|
|
3
3
|
onReturnOff(event: string, listener: (...args: any[]) => any): () => any;
|
|
4
4
|
}
|
|
5
|
-
export declare function defineBus<T extends Record<string, any>>(): StrictEventEmitter<EventBus, T, T, "addEventListener" | "removeEventListener", "on" | "addListener" | "
|
|
5
|
+
export declare function defineBus<T extends Record<string, any>>(): StrictEventEmitter<EventBus, T, T, "addEventListener" | "removeEventListener", "on" | "addListener" | "removeListener" | "once" | "emit" | "off" | "onReturnOff">;
|
|
6
6
|
declare const assignmentCompatibilityHack: unique symbol;
|
|
7
7
|
interface TypeRecord<T, U, V> {
|
|
8
8
|
' _emitterType'?: T;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { XCron } from './types';
|
|
1
|
+
import type { XCron } from './types';
|
|
2
2
|
export declare function defineCron(options: XCron.Options): XCron.Cron;
|
package/lib/core/define_task.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createUrlQueryString } from 'vtils';
|
|
2
|
-
import { x } from "../x";
|
|
3
1
|
// @ts-ignore
|
|
4
2
|
|
|
3
|
+
import { createUrlQueryString } from 'vtils';
|
|
4
|
+
import { x } from "../x";
|
|
5
5
|
export function getHandlerUrl(path, query) {
|
|
6
6
|
return `${x.env.APP_URL.replace(/\/+$/, '')}${path}${query ? `?${createUrlQueryString(query)}` : ''}`;
|
|
7
7
|
}
|
package/lib/core/handler.js
CHANGED
|
@@ -125,11 +125,11 @@ export class Handler {
|
|
|
125
125
|
};
|
|
126
126
|
this.handleWs = async (_data, ctx) => {
|
|
127
127
|
const dispose = new DisposeService();
|
|
128
|
-
ctx.ws.
|
|
128
|
+
ctx.ws.on('message', async payload => {
|
|
129
129
|
try {
|
|
130
130
|
const _ = JSON.parse(payload.toString('utf8'));
|
|
131
131
|
const send = data => {
|
|
132
|
-
ctx.ws.
|
|
132
|
+
ctx.ws.send(JSON.stringify({
|
|
133
133
|
id: _.id,
|
|
134
134
|
data: data
|
|
135
135
|
}));
|
|
@@ -166,14 +166,14 @@ export class Handler {
|
|
|
166
166
|
}
|
|
167
167
|
} catch (err) {
|
|
168
168
|
const _err = HttpError.isHttpError(err) ? err : new HttpError.InternalServerError();
|
|
169
|
-
ctx.ws.
|
|
169
|
+
ctx.ws.send(JSON.stringify({
|
|
170
170
|
statusCode: _err.statusCode,
|
|
171
171
|
error: _err.name,
|
|
172
172
|
message: _err.message
|
|
173
173
|
}));
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
|
-
ctx.ws.
|
|
176
|
+
ctx.ws.on('close', () => {
|
|
177
177
|
dispose.dispose();
|
|
178
178
|
});
|
|
179
179
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { HTTPMethods } from 'fastify';
|
|
2
|
-
import { XHandler } from './types';
|
|
1
|
+
import type { HTTPMethods } from 'fastify';
|
|
2
|
+
import type { XHandler } from './types';
|
|
3
3
|
export declare const HandlerMethodToHttpMethod: Record<XHandler.Method, HTTPMethods>;
|
package/lib/core/server.d.ts
CHANGED
package/lib/core/server.js
CHANGED
|
@@ -113,7 +113,7 @@ export class Server {
|
|
|
113
113
|
url,
|
|
114
114
|
headers: req.headers,
|
|
115
115
|
setHeader: (k, v) => res.header(k, v),
|
|
116
|
-
redirect: (url, permanently) => res.redirect(permanently ? 301 : 302
|
|
116
|
+
redirect: (url, permanently) => res.redirect(url, permanently ? 301 : 302),
|
|
117
117
|
ws: undefined,
|
|
118
118
|
req: req,
|
|
119
119
|
res: res
|
|
@@ -183,15 +183,13 @@ export class Server {
|
|
|
183
183
|
if (Array.isArray(x.env.APP_STATIC_CONTENT)) {
|
|
184
184
|
const staticContent = x.env.APP_STATIC_CONTENT;
|
|
185
185
|
for (const item of staticContent) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!this.fastify.hasRoute({
|
|
190
|
-
method: item.method,
|
|
186
|
+
const methods = castArray(item.method);
|
|
187
|
+
if (!methods.some(method => this.fastify.hasRoute({
|
|
188
|
+
method,
|
|
191
189
|
url: item.path
|
|
192
|
-
})) {
|
|
190
|
+
}))) {
|
|
193
191
|
this.fastify.route({
|
|
194
|
-
method:
|
|
192
|
+
method: methods,
|
|
195
193
|
url: item.path,
|
|
196
194
|
handler: async (_req, res) => {
|
|
197
195
|
if (!item.contentType && typeof item.content === 'string') {
|
package/lib/core/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { IncomingHttpHeaders } from 'node:http';
|
|
2
2
|
import type { MultipartFile } from '@fastify/multipart';
|
|
3
|
-
import type { SocketStream } from '@fastify/websocket';
|
|
4
3
|
import type { Queue } from 'bull';
|
|
5
4
|
import type { CronJob } from 'cron';
|
|
6
5
|
import type { FastifyReply, FastifyRequest, FastifyServerOptions } from 'fastify';
|
|
@@ -8,6 +7,7 @@ import type { MsValue } from 'vtils';
|
|
|
8
7
|
import type { AsyncOrSync, LiteralUnion, OneOrMore, RequiredDeep } from 'vtils/types';
|
|
9
8
|
import type * as vae from 'vtils/vae';
|
|
10
9
|
import type { yup } from 'vtils/validator';
|
|
10
|
+
import type { WebSocket } from 'ws';
|
|
11
11
|
import type { BasePlugin } from '../plugins/base';
|
|
12
12
|
import type { BaseService } from '../services/base';
|
|
13
13
|
import type { DisposeService } from '../services/dispose';
|
|
@@ -92,7 +92,7 @@ export declare namespace XHandler {
|
|
|
92
92
|
/**
|
|
93
93
|
* WS
|
|
94
94
|
*/
|
|
95
|
-
ws:
|
|
95
|
+
ws: WebSocket;
|
|
96
96
|
/**
|
|
97
97
|
* Fastify Request
|
|
98
98
|
*/
|
package/lib/plugins/base.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FastifyCompressOptions } from '@fastify/compress';
|
|
2
|
-
import { FastifyInstance } from 'fastify';
|
|
3
|
-
import { BasePlugin } from './base';
|
|
1
|
+
import { type FastifyCompressOptions } from '@fastify/compress';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export interface CompressPluginOptions extends FastifyCompressOptions {
|
|
5
5
|
}
|
|
6
6
|
/**
|
package/lib/plugins/compress.js
CHANGED
package/lib/plugins/cookie.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FastifyCookieOptions } from '@fastify/cookie';
|
|
2
|
-
import { FastifyInstance } from 'fastify';
|
|
3
|
-
import { BasePlugin } from './base';
|
|
1
|
+
import { type FastifyCookieOptions } from '@fastify/cookie';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export interface CookiePluginOptions extends FastifyCookieOptions {
|
|
5
5
|
}
|
|
6
6
|
/**
|
package/lib/plugins/cookie.js
CHANGED
package/lib/plugins/cors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { FastifyCorsOptions } from '@fastify/cors';
|
|
2
|
-
import { FastifyInstance } from 'fastify';
|
|
3
|
-
import { MsValue } from 'vtils';
|
|
4
|
-
import { BasePlugin } from './base';
|
|
1
|
+
import { type FastifyCorsOptions } from '@fastify/cors';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import { type MsValue } from 'vtils';
|
|
4
|
+
import type { BasePlugin } from './base';
|
|
5
5
|
export interface CorsPluginOptions extends FastifyCorsOptions {
|
|
6
6
|
/**
|
|
7
7
|
* 允许的来源,支持位于开头的 * 通配符
|
package/lib/plugins/cors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FastifyMultipartOptions } from '@fastify/multipart';
|
|
2
|
-
import { FastifyInstance } from 'fastify';
|
|
3
|
-
import { BasePlugin } from './base';
|
|
1
|
+
import { type FastifyMultipartOptions } from '@fastify/multipart';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export { MultipartFile } from '@fastify/multipart';
|
|
5
5
|
export interface FileParserPluginOptions extends FastifyMultipartOptions {
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FastifyFormbodyOptions } from '@fastify/formbody';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { type FastifyFormbodyOptions } from '@fastify/formbody';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export interface FormBodyParserPluginOptions extends FastifyFormbodyOptions {
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { WebsocketPluginOptions } from '@fastify/websocket';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { type WebsocketPluginOptions } from '@fastify/websocket';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export interface WsParserPluginOptions extends WebsocketPluginOptions {
|
|
5
5
|
}
|
|
6
6
|
/**
|
package/lib/plugins/ws_parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FastifyInstance } from 'fastify';
|
|
3
|
-
import {
|
|
1
|
+
import { type X2jOptions } from 'fast-xml-parser';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { BasePlugin } from './base';
|
|
4
4
|
export interface XmlParserPluginOptions extends Partial<X2jOptions> {
|
|
5
5
|
/**
|
|
6
6
|
* 要处理的 `Content-Type`
|
|
@@ -14,7 +14,7 @@ export class XmlParserPlugin {
|
|
|
14
14
|
...parseOptions
|
|
15
15
|
} = this.options || {};
|
|
16
16
|
const xmlParser = new XMLParser(parseOptions);
|
|
17
|
-
fastify.addContentTypeParser(contentType, (
|
|
17
|
+
fastify.addContentTypeParser(contentType, (_req, payload, done) => {
|
|
18
18
|
let body = '';
|
|
19
19
|
const handleError = err => done(err);
|
|
20
20
|
const handleData = data => body += data;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MsValue } from 'vtils';
|
|
2
|
-
import { BaseService } from './base';
|
|
2
|
+
import type { BaseService } from './base';
|
|
3
3
|
export type CaptchaServiceGenerateCharset = '09' | '19' | 'az' | 'AZ' | 'aZ' | '0z' | '0Z' | '1z' | '1Z';
|
|
4
4
|
export interface CaptchaServiceGeneratePayload<T> {
|
|
5
5
|
/**
|
package/lib/services/captcha.js
CHANGED
|
@@ -8,9 +8,9 @@ export class CaptchaService {
|
|
|
8
8
|
this.charsetMap = {
|
|
9
9
|
'09': '0123456789'.split(''),
|
|
10
10
|
'19': '123456789'.split(''),
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
az: 'abcdefghijklmnopqrstuvwxyz'.split(''),
|
|
12
|
+
AZ: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
13
|
+
aZ: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
14
14
|
'0z': '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
|
|
15
15
|
'0Z': '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
16
16
|
'1z': '123456789abcdefghijklmnopqrstuvwxyz'.split(''),
|
package/lib/services/crypto.d.ts
CHANGED
package/lib/services/dingtalk.js
CHANGED
package/lib/services/emoji.d.ts
CHANGED
package/lib/services/emoji.js
CHANGED
package/lib/services/html.d.ts
CHANGED
package/lib/services/log.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Merge } from 'vtils/types';
|
|
2
|
-
import { BaseService } from './base';
|
|
1
|
+
import type { Merge } from 'vtils/types';
|
|
2
|
+
import type { BaseService } from './base';
|
|
3
3
|
export type LogServiceLevel = 'error' | 'info' | 'success';
|
|
4
4
|
export type LogServiceDesc = Record<string, any> | any[];
|
|
5
5
|
export type LogServiceWriter = 'file' | 'console';
|
package/lib/services/log.js
CHANGED
package/lib/services/mail.d.ts
CHANGED
package/lib/services/pay.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseService } from './base';
|
|
1
|
+
import type { BaseService } from './base';
|
|
2
2
|
export interface PayServiceOptions {
|
|
3
3
|
/** 支付宝 */
|
|
4
4
|
alipay?: {
|
|
@@ -9,17 +9,17 @@ export interface PayServiceOptions {
|
|
|
9
9
|
/** 支付宝公钥,用于对返回结果验签 */
|
|
10
10
|
publicKey: string;
|
|
11
11
|
};
|
|
12
|
-
/** 微信支付 */
|
|
12
|
+
/** 微信支付(APIv3),未实现回调验签,因此验证微信支付身份块不需要关心 */
|
|
13
13
|
wepay?: {
|
|
14
14
|
/** 公众号 APPID */
|
|
15
15
|
appId: string;
|
|
16
16
|
/** 商户 ID */
|
|
17
17
|
merchantId: string;
|
|
18
|
-
/**
|
|
18
|
+
/** 商户API证书私钥 */
|
|
19
19
|
privateKey: string;
|
|
20
|
-
/** 证书序列号 */
|
|
20
|
+
/** 商户API证书序列号 */
|
|
21
21
|
certificateSerialNumber: string;
|
|
22
|
-
/**
|
|
22
|
+
/** 密钥,用于解密回调数据 */
|
|
23
23
|
secretKey: string;
|
|
24
24
|
};
|
|
25
25
|
}
|
|
@@ -48,9 +48,7 @@ export interface PayServicePrepareWepayOptions {
|
|
|
48
48
|
totalMoney: number;
|
|
49
49
|
/** 通知地址(POST) */
|
|
50
50
|
notifyUrl: string;
|
|
51
|
-
/**
|
|
52
|
-
appId?: string;
|
|
53
|
-
/** 对应公众号用户的 openid */
|
|
51
|
+
/** 公众号用户的 openid */
|
|
54
52
|
openid: string;
|
|
55
53
|
}
|
|
56
54
|
export interface PayServicePrepareWepayResult {
|
package/lib/services/pay.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
2
|
import AlipaySdk from 'alipay-sdk';
|
|
3
3
|
import AlipayFormData from 'alipay-sdk/lib/form';
|
|
4
4
|
import got from 'got';
|
|
@@ -39,9 +39,8 @@ export class PayService {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
async prepareWepay(options) {
|
|
42
|
-
const appId = options.appId || this.options.wepay.appId;
|
|
43
42
|
const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
|
|
44
|
-
appid: appId,
|
|
43
|
+
appid: this.options.wepay.appId,
|
|
45
44
|
mchid: this.options.wepay.merchantId,
|
|
46
45
|
description: options.goodsName,
|
|
47
46
|
out_trade_no: options.tradeNumber,
|
|
@@ -60,7 +59,7 @@ export class PayService {
|
|
|
60
59
|
throw new Error(error);
|
|
61
60
|
}
|
|
62
61
|
const params = {
|
|
63
|
-
appId: appId,
|
|
62
|
+
appId: this.options.wepay.appId,
|
|
64
63
|
timestamp: String(Math.round(Date.now() / 1000)),
|
|
65
64
|
nonceStr: Math.random().toString().substr(2, 12),
|
|
66
65
|
package: `prepay_id=${res.prepay_id}`,
|
package/lib/services/redis.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Redis, type RedisOptions } from 'ioredis';
|
|
2
|
+
import type { BaseService } from './base';
|
|
3
3
|
export interface RedisServiceOptions extends RedisOptions {
|
|
4
4
|
}
|
|
5
5
|
export declare class RedisService extends Redis implements BaseService {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseService } from './base';
|
|
1
|
+
import type { BaseService } from './base';
|
|
2
2
|
export interface SensitiveWordsServiceOptions {
|
|
3
3
|
/** 敏感词列表 */
|
|
4
4
|
words: string[];
|
|
@@ -7,7 +7,6 @@ export interface SensitiveWordsServiceOptions {
|
|
|
7
7
|
* 敏感词服务。
|
|
8
8
|
*/
|
|
9
9
|
export declare class SensitiveWordsService implements BaseService {
|
|
10
|
-
private options;
|
|
11
10
|
serviceName: string;
|
|
12
11
|
private sensitiveWords;
|
|
13
12
|
constructor(options: SensitiveWordsServiceOptions);
|
|
@@ -8,7 +8,6 @@ const toRegExp = memoize(re => new RegExp(re.substring(1, re.length - 1)), re =>
|
|
|
8
8
|
*/
|
|
9
9
|
export class SensitiveWordsService {
|
|
10
10
|
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
11
|
this.serviceName = 'sensitiveWords';
|
|
13
12
|
this.sensitiveWords = [];
|
|
14
13
|
this.sensitiveWords = uniq(options.words.map(word => word.toLowerCase()));
|
package/lib/services/sms.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayfong/x-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.119.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "lib/_cjs/index.js",
|
|
@@ -12,64 +12,55 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"lib"
|
|
14
14
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "haoma compile",
|
|
17
|
-
"build_test_pkg": "tyn build && rm -rf ./lib_test && mkdir -p ./lib_test && cp -r ./lib ./lib_test/lib && cp ./package.json ./lib_test/package.json && cd ./tests/app && tyn add file:../../lib_test",
|
|
18
|
-
"test": "tsc --noEmit -p ./tsconfig.build.json && jest \"$(pwd)/src/\"",
|
|
19
|
-
"test_all": "tsc --noEmit -p ./tsconfig.build.json && jest",
|
|
20
|
-
"test_api_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
|
|
21
|
-
"test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
|
|
22
|
-
"up_prisma": "tpm dlx npm-check-updates '/.*prisma.*/' -i --target semver"
|
|
23
|
-
},
|
|
24
15
|
"dependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"@aws-sdk/
|
|
27
|
-
"@
|
|
28
|
-
"@fastify/
|
|
29
|
-
"@fastify/
|
|
30
|
-
"@fastify/
|
|
31
|
-
"@fastify/
|
|
32
|
-
"@fastify/
|
|
33
|
-
"@
|
|
34
|
-
"@paralleldrive/cuid2": "^3.0
|
|
16
|
+
"@aws-sdk/client-s3": "^3.1166.0",
|
|
17
|
+
"@aws-sdk/s3-request-presigner": "^3.1166.0",
|
|
18
|
+
"@fastify/compress": "^9.3.0",
|
|
19
|
+
"@fastify/cookie": "^11.1.5",
|
|
20
|
+
"@fastify/cors": "^11.4.1",
|
|
21
|
+
"@fastify/formbody": "^9.0.1",
|
|
22
|
+
"@fastify/multipart": "^10.1.8",
|
|
23
|
+
"@fastify/websocket": "^11.3.7",
|
|
24
|
+
"@jayfong/x-request": "2.119.0",
|
|
25
|
+
"@paralleldrive/cuid2": "^3.3.0",
|
|
35
26
|
"@prisma/adapter-mariadb": "^7.10.1",
|
|
36
27
|
"@prisma/client": "^7.10.1",
|
|
37
28
|
"@types/busboy": "^0.3.2",
|
|
38
|
-
"@types/http-errors": "^
|
|
29
|
+
"@types/http-errors": "^2.0.5",
|
|
39
30
|
"@types/jsonwebtoken": "^8.5.8",
|
|
40
31
|
"@types/nodemailer": "^6.4.4",
|
|
41
|
-
"@types/ws": "^8.
|
|
32
|
+
"@types/ws": "^8.18.2",
|
|
42
33
|
"alipay-sdk": "^3.2.0",
|
|
43
34
|
"bufferutil": "^4.0.6",
|
|
44
|
-
"bull": "^4.
|
|
35
|
+
"bull": "^4.16.5",
|
|
45
36
|
"chokidar": "^3.5.3",
|
|
46
37
|
"comment-parser": "^1.3.1",
|
|
47
38
|
"compressing": "^1.5.1",
|
|
48
|
-
"cron": "^
|
|
39
|
+
"cron": "^4.4.0",
|
|
49
40
|
"cuid": "^2.1.8",
|
|
50
41
|
"debug": "^4.3.4",
|
|
51
|
-
"esbuild": "^0.
|
|
42
|
+
"esbuild": "^0.28.2",
|
|
52
43
|
"esbuild-register": "^3.6.0",
|
|
53
44
|
"execa": "^5.1.1",
|
|
54
45
|
"exit-hook": "^2.2.1",
|
|
55
46
|
"fast-escape-html": "^1.1.0",
|
|
56
|
-
"fast-xml-parser": "^
|
|
57
|
-
"fastify": "^
|
|
47
|
+
"fast-xml-parser": "^5.12.0",
|
|
48
|
+
"fastify": "^5.12.8",
|
|
58
49
|
"fs-extra": "^10.0.1",
|
|
59
50
|
"globby": "^11",
|
|
60
51
|
"got": "^11.8.2",
|
|
61
|
-
"http-errors": "^2.0.
|
|
52
|
+
"http-errors": "^2.0.1",
|
|
62
53
|
"inquirer": "^12.5.0",
|
|
63
54
|
"inquirer-autocomplete-standalone": "^0.8.1",
|
|
64
55
|
"ioredis": "^5.3.2",
|
|
65
56
|
"jsonwebtoken": "^8.5.1",
|
|
66
57
|
"lru-cache": "^10.0.0",
|
|
67
58
|
"lz-string": "^1.4.4",
|
|
68
|
-
"mime": "^4.1.
|
|
59
|
+
"mime": "^4.1.2",
|
|
69
60
|
"mini-svg-data-uri": "^1.4.4",
|
|
70
61
|
"node-ssh": "^13.1.0",
|
|
71
62
|
"nodemailer": "^6.7.3",
|
|
72
|
-
"pino-pretty": "^
|
|
63
|
+
"pino-pretty": "^13.2.0",
|
|
73
64
|
"pinyin-pro": "^3.26.0",
|
|
74
65
|
"pirates": "^4.0.6",
|
|
75
66
|
"prisma": "^7.10.1",
|
|
@@ -80,12 +71,12 @@
|
|
|
80
71
|
"svg-captcha": "^1.4.0",
|
|
81
72
|
"tencentcloud-sdk-nodejs-sms": "^4.0.681",
|
|
82
73
|
"ts-morph": "^27.0.2",
|
|
83
|
-
"tsx": "^4.
|
|
74
|
+
"tsx": "^4.23.15",
|
|
84
75
|
"unplugin-macros": "^0.16.0",
|
|
85
76
|
"utf-8-validate": "^5.0.9",
|
|
86
77
|
"vscode-generate-index-standalone": "^1.7.1",
|
|
87
78
|
"vtils": "^4.138.0",
|
|
88
|
-
"yaml": "^2.
|
|
79
|
+
"yaml": "^2.9.1",
|
|
89
80
|
"yargs": "^17.4.1"
|
|
90
81
|
},
|
|
91
82
|
"devDependencies": {
|
|
@@ -104,5 +95,15 @@
|
|
|
104
95
|
},
|
|
105
96
|
"publishConfig": {
|
|
106
97
|
"access": "public"
|
|
98
|
+
},
|
|
99
|
+
"scripts": {
|
|
100
|
+
"build": "mv ./node_modules/.x ./node_modules/.x2 || true && haoma compile && mv ./node_modules/.x2 ./node_modules/.x || true",
|
|
101
|
+
"build_test_pkg": "tyn build && rm -rf ./lib_test && mkdir -p ./lib_test && cp -r ./lib ./lib_test/lib && cp ./package.json ./lib_test/package.json && cd ./tests/app && tyn add file:../../lib_test",
|
|
102
|
+
"test": "tsc --noEmit -p ./tsconfig.build.json && jest \"$(pwd)/src/\"",
|
|
103
|
+
"test_all": "tsc --noEmit -p ./tsconfig.build.json && jest",
|
|
104
|
+
"test_api_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
|
|
105
|
+
"test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
|
|
106
|
+
"up_prisma": "tpm dlx npm-check-updates '/.*prisma.*/' -i --target semver",
|
|
107
|
+
"up_fastify": "tpm dlx npm-check-updates '/.*fastify.*/' -i --target latest"
|
|
107
108
|
}
|
|
108
|
-
}
|
|
109
|
+
}
|