chanjs 2.7.4 → 2.7.6
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/USAGE.md +533 -0
- package/config/index.js +37 -6
- package/core/App.js +166 -0
- package/core/BaseComponent.js +27 -0
- package/core/Container.js +68 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +323 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +101 -0
- package/core/bootstrap/hook-runner.js +64 -0
- package/core/bootstrap/middleware.js +35 -0
- package/core/bootstrap/router-loader.js +53 -0
- package/core/errors.js +251 -0
- package/core/loader.js +89 -0
- package/core/registry.js +17 -0
- package/doc/Cache.md +279 -106
- package/doc/Common.md +590 -134
- package/doc/Controller.md +166 -95
- package/doc/Help.md +299 -698
- package/doc/QuickStart.md +116 -0
- package/doc/Repository.md +560 -0
- package/doc/Service.md +201 -527
- package/index.js +61 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +15 -17
- package/middleware/header.js +15 -16
- package/middleware/index.js +11 -11
- package/middleware/log.js +26 -56
- package/middleware/static.js +15 -28
- package/middleware/template.js +75 -115
- package/middleware/validate.js +79 -0
- package/middleware/waf.js +176 -197
- package/package.json +9 -2
- package/response/code.js +73 -0
- package/response/index.js +9 -6
- package/response/response.js +82 -236
- package/security/checker.js +26 -74
- package/security/index.js +4 -9
- package/security/jwt.js +84 -139
- package/security/keywords.js +33 -137
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +58 -198
- package/storage/index.js +3 -6
- package/storage/redis.js +124 -181
- package/storage/store.js +163 -188
- package/utils/data-parse.js +42 -186
- package/utils/file.js +73 -244
- package/utils/filter.js +22 -25
- package/utils/html.js +49 -33
- package/utils/index.js +20 -7
- package/utils/ip.js +31 -71
- package/utils/logger.js +117 -0
- package/utils/pages.js +55 -0
- package/utils/paths.js +18 -0
- package/utils/request.js +95 -136
- package/utils/signal.js +87 -0
- package/utils/time.js +33 -75
- package/utils/tree.js +112 -104
- package/App.js +0 -533
- package/base/Aop.js +0 -195
- package/base/Container.js +0 -161
- package/base/Controller.js +0 -65
- package/base/Database.js +0 -133
- package/base/Event.js +0 -61
- package/base/Repository.js +0 -644
- package/common/api.js +0 -35
- package/common/code.js +0 -52
- package/common/email.js +0 -191
- package/common/index.js +0 -5
- package/common/pages.js +0 -120
- package/common/utils.js +0 -73
- package/config/code.js +0 -166
- package/config/paths.js +0 -60
- package/doc/Aop.md +0 -269
- package/doc/Email.md +0 -114
- package/doc/Event.md +0 -232
- package/global/env.js +0 -11
- package/global/import.js +0 -39
- package/global/index.js +0 -8
- package/helper/index.js +0 -79
- package/loader/index.js +0 -6
- package/loader/loader.js +0 -138
- package/middleware/compress.js +0 -185
- package/middleware/setBody.js +0 -32
- package/realtime/index.js +0 -7
- package/realtime/sse.js +0 -424
- package/realtime/websocket.js +0 -540
- package/schedule/index.js +0 -6
- package/schedule/schedule.js +0 -491
package/helper/index.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 聚合导出层 - 从各子模块统一 re-export
|
|
3
|
-
*
|
|
4
|
-
* 框架内部已按职责拆分到 storage/security/realtime/schedule/loader/response/utils
|
|
5
|
-
* 本文件提供扁平化聚合访问,业务侧可用:
|
|
6
|
-
* import { helper } from "chanjs";
|
|
7
|
-
* const { getIp, formatDateFields, filterXSS } = helper;
|
|
8
|
-
*
|
|
9
|
-
* 也可直接从子模块导入(更精确):
|
|
10
|
-
* import { utils, security, storage } from "chanjs";
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// 加载器
|
|
14
|
-
export { loaderSort, loadConfig, clearConfigCache, loadController } from "../loader/index.js";
|
|
15
|
-
|
|
16
|
-
// 时间处理
|
|
17
|
-
export { formatTime, formatDateFields } from "../utils/time.js";
|
|
18
|
-
|
|
19
|
-
// 存储相关
|
|
20
|
-
export { cache, DEFAULT_INCR_TTL } from "../storage/index.js";
|
|
21
|
-
export { store } from "../storage/index.js";
|
|
22
|
-
export { default as RedisBackend } from "../storage/redis.js";
|
|
23
|
-
|
|
24
|
-
// 文件操作
|
|
25
|
-
export {
|
|
26
|
-
dirname,
|
|
27
|
-
delImg,
|
|
28
|
-
getFileTree,
|
|
29
|
-
readFileContent,
|
|
30
|
-
saveFileContent,
|
|
31
|
-
isPathSafe,
|
|
32
|
-
getFolders,
|
|
33
|
-
} from "../utils/file.js";
|
|
34
|
-
|
|
35
|
-
// HTML 处理
|
|
36
|
-
export { htmlDecode, htmlEncode, escapeScript } from "../utils/html.js";
|
|
37
|
-
|
|
38
|
-
// IP 获取
|
|
39
|
-
export { getIp } from "../utils/ip.js";
|
|
40
|
-
|
|
41
|
-
// JWT 令牌
|
|
42
|
-
export { verifyToken, generateToken, setToken, getToken } from "../security/jwt.js";
|
|
43
|
-
|
|
44
|
-
// 签名/加密
|
|
45
|
-
export { signData, verifySign, aesEncrypt, aesDecrypt } from "../security/sign.js";
|
|
46
|
-
|
|
47
|
-
// 网络请求
|
|
48
|
-
export { request } from "../utils/request.js";
|
|
49
|
-
|
|
50
|
-
// 数据解析
|
|
51
|
-
export { dataParse, arrToObj, parseJsonFields, buildTree } from "../utils/data-parse.js";
|
|
52
|
-
|
|
53
|
-
// 树形结构
|
|
54
|
-
export { tree, treeById } from "../utils/tree.js";
|
|
55
|
-
|
|
56
|
-
// 字段过滤
|
|
57
|
-
export { filterFields } from "../utils/filter.js";
|
|
58
|
-
|
|
59
|
-
// 响应格式化
|
|
60
|
-
export {
|
|
61
|
-
success,
|
|
62
|
-
fail,
|
|
63
|
-
error,
|
|
64
|
-
parseDatabaseError,
|
|
65
|
-
notFoundResponse,
|
|
66
|
-
errorResponse,
|
|
67
|
-
} from "../response/index.js";
|
|
68
|
-
|
|
69
|
-
// 安全检查
|
|
70
|
-
export { checkKeywords, isIgnored } from "../security/checker.js";
|
|
71
|
-
export { filterXSS } from "../security/xss-filter.js";
|
|
72
|
-
export { createRateLimitMiddleware } from "../security/rate-limit.js";
|
|
73
|
-
|
|
74
|
-
// 定时任务
|
|
75
|
-
export { schedule } from "../schedule/index.js";
|
|
76
|
-
|
|
77
|
-
// 实时通信
|
|
78
|
-
export { sse, SSEManager } from "../realtime/index.js";
|
|
79
|
-
export { websocket, WebSocketManager } from "../realtime/index.js";
|
package/loader/index.js
DELETED
package/loader/loader.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { Paths } from "../config/paths.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 对模块列表进行排序,将 web 模块移到最后
|
|
7
|
-
* @param {Array<string>} modules - 模块名称数组
|
|
8
|
-
* @returns {Array<string>} 排序后的模块数组
|
|
9
|
-
* @description
|
|
10
|
-
* 确保 web 模块始终在模块列表的最后,以便优先加载其他模块的路由
|
|
11
|
-
* @example
|
|
12
|
-
* const modules = ['api', 'web', 'admin'];
|
|
13
|
-
* const sorted = loaderSort(modules);
|
|
14
|
-
* // 返回: ['api', 'admin', 'web']
|
|
15
|
-
*/
|
|
16
|
-
export function loaderSort(modules = []) {
|
|
17
|
-
const index = modules.indexOf("web");
|
|
18
|
-
if (index !== -1) {
|
|
19
|
-
const web = modules.splice(index, 1);
|
|
20
|
-
modules.push(web[0]);
|
|
21
|
-
}
|
|
22
|
-
return modules;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 加载应用配置文件
|
|
27
|
-
* @async
|
|
28
|
-
* @returns {Promise<Object>} 配置对象
|
|
29
|
-
* @description
|
|
30
|
-
* 从 config/index.js 加载配置
|
|
31
|
-
*
|
|
32
|
-
* 性能优化(P2 #21):
|
|
33
|
-
* 删除 LRU 缓存层,直接依赖 ESM 模块缓存
|
|
34
|
-
* - ESM import 同一文件返回缓存的模块实例(Node.js 内置机制)
|
|
35
|
-
* - 旧版 LRU 缓存有 5 分钟 TTL,配置变更后不生效,需要手动 clearConfigCache
|
|
36
|
-
* - 现在配置变更直接重启进程即可,符合生产实践
|
|
37
|
-
*/
|
|
38
|
-
export async function loadConfig() {
|
|
39
|
-
const configPath = path.join(Paths.configPath, "index.js");
|
|
40
|
-
try {
|
|
41
|
-
const module = await import(`file://${configPath}`);
|
|
42
|
-
return module.default || module;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error("[Config] 加载配置失败", error.message);
|
|
45
|
-
return {};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 清除配置缓存(已废弃)
|
|
51
|
-
* @deprecated P2 #21 优化后,配置直接依赖 ESM 缓存,无需手动清理
|
|
52
|
-
* 保留函数仅为向后兼容,调用为空操作
|
|
53
|
-
* @example
|
|
54
|
-
* clearConfigCache(); // 空操作,无需调用
|
|
55
|
-
*/
|
|
56
|
-
export function clearConfigCache() {
|
|
57
|
-
// ESM 模块缓存由 Node.js 管理,无需手动清理
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 已绑定方法标记(Symbol 幂等)
|
|
62
|
-
* 避免重复 loadController 时对同一方法多次 bind 形成嵌套
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
const BOUND_SYMBOL = Symbol('chanjs:bound');
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 加载指定模块的所有控制器
|
|
69
|
-
* @async
|
|
70
|
-
* @param {string} moduleName - 模块名称
|
|
71
|
-
* @returns {Promise<Object>} 控制器对象,键为控制器名称
|
|
72
|
-
* @description
|
|
73
|
-
* 从 modules/{moduleName}/controller 目录加载所有 .js 文件
|
|
74
|
-
* 每个文件会被加载并绑定实例,然后以文件名作为键存储
|
|
75
|
-
*
|
|
76
|
-
* 性能优化(P2 #18):
|
|
77
|
-
* - 用 Symbol 标记已 bind 过的方法,避免重复 bind 形成嵌套
|
|
78
|
-
* - ESM 模块缓存导致同一实例可能被多次 loadController,原版每次都重新 bind
|
|
79
|
-
* - 现在首次 bind 后标记,后续调用直接跳过
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* const controllers = await loadController('api');
|
|
83
|
-
* console.log(controllers.User); // User 控制器实例
|
|
84
|
-
*/
|
|
85
|
-
export async function loadController(moduleName) {
|
|
86
|
-
const controller = {};
|
|
87
|
-
|
|
88
|
-
const dir = path.join(Paths.modulesPath, moduleName, "controller");
|
|
89
|
-
|
|
90
|
-
if (!fs.existsSync(dir)) {
|
|
91
|
-
console.error(`模块路径不存在,跳过加载控制器: ${dir}`);
|
|
92
|
-
return controller;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const files = fs.readdirSync(dir).filter((file) => file.endsWith(".js"));
|
|
96
|
-
|
|
97
|
-
for (const file of files) {
|
|
98
|
-
const filePath = path.join(dir, file);
|
|
99
|
-
const name = file.replace(/\.js$/i, "");
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const module = await import(`file://${filePath}`);
|
|
103
|
-
let instance = module?.default || module;
|
|
104
|
-
|
|
105
|
-
// 绑定实例的所有方法(Symbol 幂等,避免重复 bind)
|
|
106
|
-
if (instance && typeof instance === 'object') {
|
|
107
|
-
const proto = Object.getPrototypeOf(instance);
|
|
108
|
-
if (proto) {
|
|
109
|
-
Object.getOwnPropertyNames(proto).forEach((methodName) => {
|
|
110
|
-
if (
|
|
111
|
-
methodName !== "constructor" &&
|
|
112
|
-
typeof instance[methodName] === "function"
|
|
113
|
-
) {
|
|
114
|
-
const method = instance[methodName];
|
|
115
|
-
// 已 bind 过的方法直接跳过(避免重复 bind 嵌套)
|
|
116
|
-
if (method[BOUND_SYMBOL]) return;
|
|
117
|
-
// bind 创建新函数,标记后存回实例
|
|
118
|
-
const bound = method.bind(instance);
|
|
119
|
-
Object.defineProperty(bound, BOUND_SYMBOL, {
|
|
120
|
-
value: true,
|
|
121
|
-
enumerable: false,
|
|
122
|
-
writable: false,
|
|
123
|
-
configurable: false,
|
|
124
|
-
});
|
|
125
|
-
instance[methodName] = bound;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
controller[name] = instance;
|
|
132
|
-
} catch (e) {
|
|
133
|
-
console.error(`加载控制器失败: ${filePath}`, e);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return controller;
|
|
138
|
-
}
|
package/middleware/compress.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import zlib from 'zlib';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 响应压缩中间件(零依赖,基于 Node 原生 zlib)
|
|
5
|
-
* 改进点:
|
|
6
|
-
* 1. 支持 br / gzip / deflate,按 Accept-Encoding 优先级选择
|
|
7
|
-
* 2. 仅压缩可压缩文本类型(html/css/js/json/plain/svg+xml)
|
|
8
|
-
* 3. 跳过 SSE(text/event-stream)和 WebSocket 升级请求
|
|
9
|
-
* 4. 阈值 1KB,避免小包压缩负收益
|
|
10
|
-
* 5. 压缩失败自动回退原样输出,不影响请求
|
|
11
|
-
* 6. chunks 缓冲上限 10MB,超出后 bypass 直接放行不压缩,避免内存爆炸
|
|
12
|
-
* 7. 拦截 res.write/res.end 后在 finish/close 时恢复,避免泄漏
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// 压缩阈值:响应体 > 1KB 才压缩
|
|
16
|
-
const DEFAULT_THRESHOLD = 1024;
|
|
17
|
-
|
|
18
|
-
// chunks 缓冲上限:10MB,超出后 bypass 直接放行不压缩
|
|
19
|
-
const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
20
|
-
|
|
21
|
-
// 可压缩的 Content-Type 集合(精确匹配,charset 后缀在判断时已剥离)
|
|
22
|
-
const COMPRESSIBLE_TYPES = new Set([
|
|
23
|
-
'text/html',
|
|
24
|
-
'text/css',
|
|
25
|
-
'application/json',
|
|
26
|
-
'application/javascript',
|
|
27
|
-
'text/plain',
|
|
28
|
-
'image/svg+xml',
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 根据 Accept-Encoding 选择最优编码
|
|
33
|
-
* 优先级:br > gzip > deflate
|
|
34
|
-
* @param {string} acceptEncoding - 请求头 Accept-Encoding 值
|
|
35
|
-
* @returns {'br'|'gzip'|'deflate'|null}
|
|
36
|
-
*/
|
|
37
|
-
function pickEncoding(acceptEncoding) {
|
|
38
|
-
if (!acceptEncoding) return null;
|
|
39
|
-
const enc = acceptEncoding.toLowerCase();
|
|
40
|
-
if (enc.includes('br')) return 'br';
|
|
41
|
-
if (enc.includes('gzip')) return 'gzip';
|
|
42
|
-
if (enc.includes('deflate')) return 'deflate';
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 使用 Node 原生 zlib 同步压缩 Buffer
|
|
48
|
-
* @param {Buffer} buf - 待压缩数据
|
|
49
|
-
* @param {'br'|'gzip'|'deflate'} encoding - 压缩编码
|
|
50
|
-
* @returns {Buffer} 压缩后的 Buffer
|
|
51
|
-
*/
|
|
52
|
-
function compressBuffer(buf, encoding) {
|
|
53
|
-
switch (encoding) {
|
|
54
|
-
case 'br': return zlib.brotliCompressSync(buf);
|
|
55
|
-
case 'gzip': return zlib.gzipSync(buf);
|
|
56
|
-
case 'deflate': return zlib.deflateSync(buf);
|
|
57
|
-
default: return buf;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 创建压缩中间件
|
|
63
|
-
* @param {Object} [options]
|
|
64
|
-
* @param {number} [options.threshold=1024] - 压缩阈值(字节),响应体小于该值不压缩
|
|
65
|
-
* @returns {Function} Express 中间件
|
|
66
|
-
*/
|
|
67
|
-
export function compress(options = {}) {
|
|
68
|
-
const threshold = options.threshold || DEFAULT_THRESHOLD;
|
|
69
|
-
|
|
70
|
-
return (req, res, next) => {
|
|
71
|
-
// WebSocket 升级请求直接跳过(不适用 HTTP 响应压缩)
|
|
72
|
-
if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') {
|
|
73
|
-
return next();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const encoding = pickEncoding(req.headers['accept-encoding'] || '');
|
|
77
|
-
// 客户端不支持任何压缩编码,直接放行
|
|
78
|
-
if (!encoding) return next();
|
|
79
|
-
|
|
80
|
-
// 缓存原方法(bind 避免 this 丢失)
|
|
81
|
-
const originalEnd = res.end.bind(res);
|
|
82
|
-
const originalWrite = res.write.bind(res);
|
|
83
|
-
const chunks = [];
|
|
84
|
-
let totalSize = 0;
|
|
85
|
-
let bypassed = false; // 超过缓冲上限后 bypass 标志
|
|
86
|
-
|
|
87
|
-
// 恢复 res.write/res.end 的函数:在 finish/close 时调用,避免拦截函数泄漏
|
|
88
|
-
const restore = () => {
|
|
89
|
-
res.write = originalWrite;
|
|
90
|
-
res.end = originalEnd;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// 拦截 res.write:收集 buffer 片段(流式输出场景)
|
|
94
|
-
res.write = (chunk, ...args) => {
|
|
95
|
-
if (bypassed) return originalWrite(chunk, ...args);
|
|
96
|
-
if (chunk) {
|
|
97
|
-
if (typeof chunk === 'string') {
|
|
98
|
-
chunk = Buffer.from(chunk, args[0] || 'utf8');
|
|
99
|
-
}
|
|
100
|
-
if (Buffer.isBuffer(chunk)) {
|
|
101
|
-
chunks.push(chunk);
|
|
102
|
-
totalSize += chunk.length;
|
|
103
|
-
// 缓冲超限:bypass 直接放行后续 write/end
|
|
104
|
-
if (totalSize > MAX_BUFFER_BYTES) {
|
|
105
|
-
console.warn(`[compress] 响应体超过 ${MAX_BUFFER_BYTES} 字节,bypass 直接放行不压缩`);
|
|
106
|
-
bypassed = true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// 拦截 res.end:收集最后一片 buffer 后决定是否压缩
|
|
114
|
-
res.end = (chunk, encodingArg) => {
|
|
115
|
-
if (bypassed) {
|
|
116
|
-
// bypass 模式:直接走原 end,恢复后退出
|
|
117
|
-
restore();
|
|
118
|
-
if (chunk) return originalEnd(chunk, encodingArg);
|
|
119
|
-
return originalEnd();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// res.end 多种签名处理:end() / end(chunk) / end(chunk, encoding) / end(callback)
|
|
123
|
-
if (chunk) {
|
|
124
|
-
if (typeof chunk === 'string') {
|
|
125
|
-
const buf = Buffer.from(chunk, typeof encodingArg === 'string' ? encodingArg : 'utf8');
|
|
126
|
-
chunks.push(buf);
|
|
127
|
-
totalSize += buf.length;
|
|
128
|
-
} else if (Buffer.isBuffer(chunk)) {
|
|
129
|
-
chunks.push(chunk);
|
|
130
|
-
totalSize += chunk.length;
|
|
131
|
-
}
|
|
132
|
-
// function 类型为 end(callback) 签名,不收集
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const body = chunks.length ? Buffer.concat(chunks) : Buffer.alloc(0);
|
|
136
|
-
|
|
137
|
-
// 取 Content-Type 主类型(去掉 charset 等参数)
|
|
138
|
-
const contentType = (res.getHeader('Content-Type') || '').toString().split(';')[0].trim().toLowerCase();
|
|
139
|
-
|
|
140
|
-
// 不满足压缩条件:原样输出
|
|
141
|
-
// - 响应体小于阈值
|
|
142
|
-
// - 非可压缩类型
|
|
143
|
-
// - SSE(text/event-stream)需要实时推送,不压缩
|
|
144
|
-
if (
|
|
145
|
-
body.length < threshold ||
|
|
146
|
-
!COMPRESSIBLE_TYPES.has(contentType) ||
|
|
147
|
-
contentType === 'text/event-stream'
|
|
148
|
-
) {
|
|
149
|
-
if (!res.headersSent) {
|
|
150
|
-
res.setHeader('Content-Length', body.length);
|
|
151
|
-
}
|
|
152
|
-
restore();
|
|
153
|
-
originalEnd(body);
|
|
154
|
-
return res;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 执行压缩
|
|
158
|
-
try {
|
|
159
|
-
const compressed = compressBuffer(body, encoding);
|
|
160
|
-
// 移除原 Content-Length,避免与压缩后大小不匹配
|
|
161
|
-
res.removeHeader('Content-Length');
|
|
162
|
-
res.setHeader('Content-Encoding', encoding);
|
|
163
|
-
res.setHeader('Vary', 'Accept-Encoding');
|
|
164
|
-
res.setHeader('Content-Length', compressed.length);
|
|
165
|
-
restore();
|
|
166
|
-
originalEnd(compressed);
|
|
167
|
-
} catch (e) {
|
|
168
|
-
// 压缩失败:回退原样输出,避免请求中断
|
|
169
|
-
console.error('[compress] 压缩失败,回退原样输出:', e.message);
|
|
170
|
-
if (!res.headersSent) {
|
|
171
|
-
res.setHeader('Content-Length', body.length);
|
|
172
|
-
}
|
|
173
|
-
restore();
|
|
174
|
-
originalEnd(body);
|
|
175
|
-
}
|
|
176
|
-
return res;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// 响应结束后恢复,防止 res.write/res.end 拦截泄漏到后续中间件复用
|
|
180
|
-
res.on('finish', restore);
|
|
181
|
-
res.on('close', restore);
|
|
182
|
-
|
|
183
|
-
next();
|
|
184
|
-
};
|
|
185
|
-
}
|
package/middleware/setBody.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 请求体解析中间件配置
|
|
5
|
-
* 配置请求体的解析方式
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 设置请求体解析中间件
|
|
10
|
-
* @param {Object} app - Express 应用实例
|
|
11
|
-
* @param {string} JSON_LIMIT - JSON 请求体大小限制
|
|
12
|
-
* @description
|
|
13
|
-
* 为 Express 应用配置请求体解析中间件
|
|
14
|
-
* 支持 JSON、URL 编码和 XML 格式的请求体
|
|
15
|
-
*
|
|
16
|
-
* 安全改进:
|
|
17
|
-
* - urlencoded 也设置 limit 和 parameterLimit,防止参数爆炸攻击
|
|
18
|
-
* - extended: true 支持嵌套对象(与 qs 行为对齐)
|
|
19
|
-
* @example
|
|
20
|
-
* setBody(app, '10mb');
|
|
21
|
-
*/
|
|
22
|
-
let setBody = function (app, JSON_LIMIT) {
|
|
23
|
-
app.use(express.raw({ type: "application/xml", limit: JSON_LIMIT }));
|
|
24
|
-
app.use(express.json({ limit: JSON_LIMIT }));
|
|
25
|
-
app.use(express.urlencoded({
|
|
26
|
-
extended: true,
|
|
27
|
-
limit: JSON_LIMIT,
|
|
28
|
-
parameterLimit: 100,
|
|
29
|
-
}));
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export { setBody };
|
package/realtime/index.js
DELETED