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/index.js
CHANGED
|
@@ -1,37 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export
|
|
1
|
+
/**
|
|
2
|
+
* chanjs 包入口
|
|
3
|
+
*
|
|
4
|
+
* 默认导出:Chan 应用主类
|
|
5
|
+
* import Chan from "chanjs";
|
|
6
|
+
* const chan = new Chan();
|
|
7
|
+
* await chan.start();
|
|
8
|
+
*
|
|
9
|
+
* 命名导出:
|
|
10
|
+
* import { Controller, Repository, Service, cache, Paths, loader, utils, getApp } from "chanjs";
|
|
11
|
+
* import { validate, validateAll } from "chanjs"; // zod 校验中间件
|
|
12
|
+
* import { AppError, NotFoundError, ValidationError } from "chanjs"; // 错误类
|
|
13
|
+
*
|
|
14
|
+
* 多实例:
|
|
15
|
+
* 每个 Chan 实例持有独立 config/db/paths/dbManager(注册表管理),
|
|
16
|
+
* 业务代码统一通过 getApp() 或 this.app(Controller/Service 内)访问当前实例。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ===================== 核心基础类 =====================
|
|
20
|
+
export { BaseComponent } from "./core/BaseComponent.js";
|
|
21
|
+
export { default as Controller } from "./core/Controller.js";
|
|
22
|
+
export { default as Repository } from "./core/Repository.js";
|
|
23
|
+
export { Service } from "./core/Service.js";
|
|
24
|
+
|
|
25
|
+
// ===================== 错误体系 =====================
|
|
26
|
+
export {
|
|
27
|
+
AppError, AuthError, TokenExpiredError, ForbiddenError,
|
|
28
|
+
NotFoundError, ConflictError, ValidationError, ParamMissingError,
|
|
29
|
+
BusinessError, RateLimitError, BlockedError, SystemError, ServiceBusyError,
|
|
30
|
+
describeError, errorExtraProps, parseStack, isAppError, wrapDbError,
|
|
31
|
+
} from "./core/errors.js";
|
|
32
|
+
|
|
33
|
+
// ===================== 统一响应工具(纯函数,可复用于接口 / 定时任务 / RPC)=====================
|
|
34
|
+
export { success, fail, routeNotFound, serializeError, buildErrorHtml, respondError } from "./response/index.js";
|
|
35
|
+
|
|
36
|
+
// ===================== 安全工具 =====================
|
|
37
|
+
export { setToken, getToken, verifyToken } from "./security/jwt.js";
|
|
38
|
+
export { aesEncrypt, aesDecrypt } from "./security/sign.js";
|
|
39
|
+
export { createRateLimitMiddleware } from "./security/rate-limit.js";
|
|
40
|
+
export { filterXSS, checkKeywords } from "./security/index.js";
|
|
41
|
+
|
|
42
|
+
// ===================== 统一日志 =====================
|
|
43
|
+
export { default as logger, createLogger } from "./utils/logger.js";
|
|
44
|
+
|
|
45
|
+
// ===================== 校验中间件 =====================
|
|
46
|
+
export { validate, validateAll } from "./middleware/validate.js";
|
|
47
|
+
|
|
48
|
+
// ===================== 应用实例(单实例)=====================
|
|
49
|
+
export { setApp, getApp } from "./core/registry.js";
|
|
50
|
+
|
|
51
|
+
// ===================== 命名空间导出(业务侧 router/service 使用)=====================
|
|
52
|
+
export * as loader from "./core/loader.js";
|
|
53
|
+
export * as utils from "./utils/index.js";
|
|
54
|
+
|
|
55
|
+
// ===================== 常用工具直接导出 =====================
|
|
56
|
+
export { cache, store } from "./storage/index.js";
|
|
57
|
+
export { Paths } from "./utils/paths.js";
|
|
58
|
+
|
|
59
|
+
// ===================== 默认导出应用主类 =====================
|
|
60
|
+
import Chan from "./core/App.js";
|
|
61
|
+
export default Chan;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description 请求体解析中间件,支持 JSON/URL-XML
|
|
5
|
+
* 安全限制:请求体上限、参数数量防攻击,支持嵌套对象
|
|
6
|
+
* @param {express.Application} app Express实例
|
|
7
|
+
* @param {string} [bodyLimit='10mb'] 请求体大小限制
|
|
8
|
+
*/
|
|
9
|
+
export function body(app, bodyLimit = '10mb') {
|
|
10
|
+
app.use(express.raw({ type: "application/xml", limit: bodyLimit }));
|
|
11
|
+
app.use(express.json({ limit: bodyLimit }));
|
|
12
|
+
app.use(express.urlencoded({
|
|
13
|
+
extended: true,
|
|
14
|
+
limit: bodyLimit,
|
|
15
|
+
parameterLimit: 100,
|
|
16
|
+
}));
|
|
17
|
+
}
|
package/middleware/cookie.js
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import cookieParser from "cookie-parser";
|
|
2
|
+
import logger from "../utils/logger.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Cookie
|
|
5
|
-
*
|
|
5
|
+
* Cookie解析中间件,支持签名校验
|
|
6
|
+
* @param {express.Application} app Express实例
|
|
7
|
+
* @param {string} [cookieKey] Cookie签名密钥
|
|
6
8
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* 设置 Cookie 解析中间件
|
|
10
|
-
* @param {Object} app - Express 应用实例
|
|
11
|
-
* @param {string} cookieKey - Cookie 签名密钥
|
|
12
|
-
* @description
|
|
13
|
-
* 为 Express 应用配置 cookie-parser 中间件
|
|
14
|
-
* 使用密钥对 cookie 进行签名验证
|
|
15
|
-
* @example
|
|
16
|
-
* setCookie(app, 'my-secret-key');
|
|
17
|
-
*/
|
|
18
|
-
export const setCookie = (app, cookieKey) => {
|
|
9
|
+
export const cookie = (app, cookieKey) => {
|
|
10
|
+
!cookieKey && logger.warn("[Cookie] 未配置签名密钥,Cookie无法加密签名");
|
|
19
11
|
app.use(cookieParser(cookieKey));
|
|
20
|
-
};
|
|
12
|
+
};
|
package/middleware/cors.js
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
import cors from "cors";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* CORS
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 设置 CORS 中间件
|
|
10
|
-
* @param {Object} app - Express 应用实例
|
|
11
|
-
* @param {Object} [_cors={}] - CORS 配置选项
|
|
12
|
-
* @description
|
|
13
|
-
* 为 Express 应用配置 CORS 中间件
|
|
14
|
-
*
|
|
15
|
-
* 安全改进:
|
|
16
|
-
* - 默认拒绝跨域(origin=false),避免未配置时全开放
|
|
17
|
-
* - 默认仅允许 GET/POST 方法
|
|
18
|
-
* - credentials 默认 false,避免 Cookie 跨域携带
|
|
19
|
-
* - 业务侧需显式开放时再传入完整 _cors 配置
|
|
20
|
-
* @example
|
|
21
|
-
* Cors(app, {
|
|
22
|
-
* origin: 'https://example.com',
|
|
23
|
-
* methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
24
|
-
* credentials: true
|
|
25
|
-
* });
|
|
4
|
+
* CORS跨域中间件,内置安全默认值
|
|
5
|
+
* @param {express.Application} app Express实例
|
|
6
|
+
* @param {cors.CorsOptions} [_cors={}] 自定义跨域配置
|
|
7
|
+
* 默认安全策略:禁止跨域、仅GET/POST、不携带Cookie
|
|
26
8
|
*/
|
|
27
9
|
export const Cors = (app, _cors = {}) => {
|
|
28
10
|
const safeOptions = {
|
|
29
|
-
origin: _cors.origin
|
|
30
|
-
methods: _cors.methods
|
|
31
|
-
allowedHeaders: _cors.allowedHeaders
|
|
11
|
+
origin: _cors.origin ?? false,
|
|
12
|
+
methods: _cors.methods ?? ["GET", "POST"],
|
|
13
|
+
allowedHeaders: _cors.allowedHeaders ?? ["Content-Type", "Authorization"],
|
|
32
14
|
credentials: !!_cors.credentials,
|
|
33
|
-
maxAge: _cors.maxAge
|
|
15
|
+
maxAge: _cors.maxAge ?? 600,
|
|
34
16
|
};
|
|
35
17
|
app.use(cors(safeOptions));
|
|
36
|
-
};
|
|
18
|
+
};
|
package/middleware/favicon.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
+
import fs from "fs";
|
|
1
2
|
import path from "path";
|
|
2
|
-
import
|
|
3
|
-
import { Paths } from "../
|
|
3
|
+
import serveFavicon from "serve-favicon";
|
|
4
|
+
import { Paths } from "../utils/paths.js";
|
|
5
|
+
import logger from "../utils/logger.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* 网站图标中间件,读取public目录favicon.ico
|
|
9
|
+
* 文件不存在时静默跳过,不阻断启动
|
|
10
|
+
* @param {express.Application} app Express实例
|
|
8
11
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* setFavicon(app);
|
|
18
|
-
*/
|
|
19
|
-
export const setFavicon = (app) => {
|
|
20
|
-
app.use(favicon(path.join(Paths.publicPath, "favicon.ico")));
|
|
21
|
-
};
|
|
12
|
+
export const favicon = app => {
|
|
13
|
+
const icoPath = path.join(Paths.publicPath, "favicon.ico");
|
|
14
|
+
if (!fs.existsSync(icoPath)) {
|
|
15
|
+
logger.info("[Favicon] favicon.ico 不存在,跳过图标中间件");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
app.use(serveFavicon(icoPath));
|
|
19
|
+
};
|
package/middleware/header.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 自定义响应头中间件,隐藏原生Express标识,统一输出ChanCMS
|
|
3
|
+
* @param {express.Application} app Express实例
|
|
4
|
+
* @param {{APP_NAME?: string, APP_VERSION?: string}} [opts] 配置选项
|
|
4
5
|
*/
|
|
6
|
+
export const header = (app, opts = {}) => {
|
|
7
|
+
const appName = opts.APP_NAME || "ChanCMS";
|
|
8
|
+
const appVer = opts.APP_VERSION || "";
|
|
9
|
+
const poweredBy = appVer ? `${appName}/${appVer}` : appName;
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* @example
|
|
14
|
-
* setHeader(app, { APP_NAME: 'MyApp' });
|
|
15
|
-
*/
|
|
16
|
-
export let setHeader = (app) => {
|
|
17
|
-
app.use((req, res, next) => {
|
|
18
|
-
res.setHeader("X-Powered-By", "ChanCMS");
|
|
11
|
+
app.use((_, res, next) => {
|
|
12
|
+
res.setHeader("X-Powered-By", poweredBy);
|
|
13
|
+
// 常驻安全响应头:无论 WAF 是否启用都生效
|
|
14
|
+
// 禁止 MIME 嗅探(防 XSS 利用 Content-Type 歧义)
|
|
15
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
16
|
+
// 防点击劫持:仅允许同源 iframe 嵌套(如需被第三方嵌入请按需放宽)
|
|
17
|
+
res.setHeader("Content-Security-Policy", "frame-ancestors 'self'");
|
|
19
18
|
next();
|
|
20
19
|
});
|
|
21
|
-
};
|
|
20
|
+
};
|
package/middleware/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* middleware 模块聚合导出
|
|
3
|
+
*
|
|
4
|
+
* 说明:响应压缩(compress)已从框架移除——生产环境由 Nginx gzip 统一处理,
|
|
5
|
+
* 无需在 Node 层重复实现。
|
|
4
6
|
*/
|
|
5
|
-
|
|
6
|
-
export { setCookie } from "./cookie.js";
|
|
7
|
-
export { setFavicon } from "./favicon.js";
|
|
8
|
-
export { setBody } from "./setBody.js";
|
|
9
|
-
export { setStatic } from "./static.js";
|
|
10
|
-
export { setHeader } from "./header.js";
|
|
11
|
-
export { setTemplate } from "./template.js";
|
|
12
7
|
export { Cors } from "./cors.js";
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
8
|
+
export { body } from "./body.js";
|
|
9
|
+
export { cookie } from "./cookie.js";
|
|
10
|
+
export { favicon } from "./favicon.js";
|
|
11
|
+
export { header } from "./header.js";
|
|
12
|
+
export { staticMw } from "./static.js";
|
|
13
|
+
export { template } from "./template.js";
|
|
14
|
+
export { waf, wafBody } from "./waf.js";
|
|
15
15
|
export { log } from "./log.js";
|
package/middleware/log.js
CHANGED
|
@@ -1,65 +1,35 @@
|
|
|
1
1
|
import morgan from "morgan";
|
|
2
2
|
import { getIp } from "../utils/ip.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* 仅允许使用以下格式,避免任意 level 注入导致 morgan 内部异常
|
|
7
|
-
*/
|
|
8
|
-
const ALLOWED_FORMATS = new Set([
|
|
9
|
-
'chancms',
|
|
10
|
-
'combined',
|
|
11
|
-
'common',
|
|
12
|
-
'dev',
|
|
13
|
-
'short',
|
|
14
|
-
'tiny',
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
// 自定义 IP 令牌
|
|
18
|
-
morgan.token("ip", (req, res) => {
|
|
19
|
-
return getIp(req);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// 自定义用户信息令牌
|
|
23
|
-
morgan.token("user", (req, res) => {
|
|
24
|
-
if (req.user) {
|
|
25
|
-
return `${req.user.uid}:${req.user.username}`;
|
|
26
|
-
}
|
|
27
|
-
return "-";
|
|
28
|
-
});
|
|
4
|
+
// 日志格式白名单,防止非法格式注入
|
|
5
|
+
const ALLOWED_FORMATS = new Set(["chancms", "combined", "common", "dev", "short", "tiny"]);
|
|
29
6
|
|
|
30
|
-
//
|
|
31
|
-
morgan.token("
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
// 自定义token
|
|
8
|
+
morgan.token("ip", req => getIp(req));
|
|
9
|
+
morgan.token("user", req => req.user ? `${req.user.uid}:${req.user.username}` : "-");
|
|
10
|
+
morgan.token("datetime", () => new Date().toISOString());
|
|
34
11
|
|
|
35
|
-
// 自定义
|
|
36
|
-
morgan.format("chancms", (tokens, req, res) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
].join(" ");
|
|
49
|
-
});
|
|
12
|
+
// 自定义ChanCMS日志输出模板
|
|
13
|
+
morgan.format("chancms", (tokens, req, res) => [
|
|
14
|
+
tokens.datetime(req, res),
|
|
15
|
+
tokens.ip(req, res),
|
|
16
|
+
tokens.user(req, res),
|
|
17
|
+
tokens.method(req, res),
|
|
18
|
+
tokens.url(req, res),
|
|
19
|
+
tokens.status(req, res),
|
|
20
|
+
tokens.res(req, res, "content-length") || "-",
|
|
21
|
+
"-",
|
|
22
|
+
tokens["response-time"](req, res),
|
|
23
|
+
"ms"
|
|
24
|
+
].join(" "));
|
|
50
25
|
|
|
51
26
|
/**
|
|
52
|
-
*
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {string} [logger.level='chancms'] - 日志格式,仅允许白名单内的格式
|
|
56
|
-
* @description
|
|
57
|
-
* 安全改进:
|
|
58
|
-
* - level 必须在白名单内,未配置或非法值默认 'chancms'
|
|
59
|
-
* - 避免任意字符串传入 morgan 导致格式解析异常
|
|
27
|
+
* 请求日志中间件(morgan),内置格式白名单防注入
|
|
28
|
+
* @param {express.Application} app Express实例
|
|
29
|
+
* @param {{level?: string}} [logger={}] 日志配置
|
|
60
30
|
*/
|
|
61
|
-
export const log = (app, logger) => {
|
|
62
|
-
const level = logger
|
|
63
|
-
const format = ALLOWED_FORMATS.has(level) ? level :
|
|
31
|
+
export const log = (app, logger = {}) => {
|
|
32
|
+
const level = logger.level;
|
|
33
|
+
const format = ALLOWED_FORMATS.has(level) ? level : "chancms";
|
|
64
34
|
app.use(morgan(format));
|
|
65
|
-
};
|
|
35
|
+
};
|
package/middleware/static.js
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import { safePath } from "../utils/file.js";
|
|
3
|
+
import logger from "../utils/logger.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* 批量挂载静态资源中间件,内置目录安全校验
|
|
7
|
+
* @param {express.Application} app Express实例
|
|
8
|
+
* @param {Array<{prefix:string;dir:string;maxAge?:number}>} statics 静态目录配置数组
|
|
6
9
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* 每个静态资源可以指定不同的 URL 前缀和目录
|
|
18
|
-
* @example
|
|
19
|
-
* setStatic(app, [
|
|
20
|
-
* { prefix: '/public', dir: './public', maxAge: 3600 },
|
|
21
|
-
* { prefix: '/uploads', dir: './uploads', maxAge: 0 }
|
|
22
|
-
* ]);
|
|
23
|
-
*/
|
|
24
|
-
export const setStatic = async function (app, statics) {
|
|
25
|
-
if (statics.length > 0) {
|
|
26
|
-
statics.forEach((item) => {
|
|
27
|
-
const { prefix, dir, maxAge } = item;
|
|
28
|
-
// dotfile: 'deny' 显式拒绝访问点文件(.env / .git/config 等),避免敏感配置泄漏
|
|
29
|
-
app.use(prefix, express.static(dir, { maxAge: maxAge || 0, dotfile: 'deny' }));
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
};
|
|
10
|
+
export const staticMw = async (app, statics) => {
|
|
11
|
+
if (!Array.isArray(statics) || !statics.length) return;
|
|
12
|
+
statics.forEach(({ prefix, dir, maxAge }) => {
|
|
13
|
+
if (!safePath(dir)) {
|
|
14
|
+
logger.error(`[Static] 不安全目录拦截:${dir}`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
app.use(prefix, express.static(dir, { maxAge: maxAge ?? 0, dotfile: "deny" }));
|
|
18
|
+
});
|
|
19
|
+
};
|