chanjs 2.0.17 → 2.0.19
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/config/code.js +6 -2
- package/core/controller.js +5 -1
- package/core/service.js +161 -118
- package/extend/art-template.js +1 -1
- package/global/import.js +5 -6
- package/helper/data-parse.js +63 -46
- package/helper/db.js +16 -13
- package/helper/file.js +33 -33
- package/helper/html.js +29 -21
- package/helper/index.js +4 -8
- package/helper/ip.js +18 -17
- package/helper/jwt.js +15 -16
- package/helper/loader.js +46 -6
- package/helper/time.js +49 -3
- package/index.js +7 -5
- package/middleware/cookie.js +2 -2
- package/middleware/cors.js +3 -3
- package/middleware/header.js +1 -1
- package/middleware/index.js +21 -21
- package/middleware/setBody.js +2 -3
- package/middleware/template.js +4 -4
- package/middleware/validator.js +2 -2
- package/middleware/waf.js +133 -25
- package/package.json +1 -1
- package/utils/response.js +23 -5
package/helper/db.js
CHANGED
|
@@ -19,7 +19,7 @@ const getDefaultErrorMessage = (error) => {
|
|
|
19
19
|
}
|
|
20
20
|
return "数据库发生未知错误,请稍后重试。";
|
|
21
21
|
};
|
|
22
|
-
export const db =({
|
|
22
|
+
export const db = ({
|
|
23
23
|
client = "mysql2",
|
|
24
24
|
host = "127.0.0.1",
|
|
25
25
|
user = "root",
|
|
@@ -30,8 +30,8 @@ export const db =({
|
|
|
30
30
|
charset = "utf8mb4",
|
|
31
31
|
min = 0,
|
|
32
32
|
max = 2,
|
|
33
|
-
filename=
|
|
34
|
-
}) =>{
|
|
33
|
+
filename = "",
|
|
34
|
+
}) => {
|
|
35
35
|
let config = {
|
|
36
36
|
client,
|
|
37
37
|
connection: {
|
|
@@ -40,20 +40,23 @@ export const db =({
|
|
|
40
40
|
user,
|
|
41
41
|
password,
|
|
42
42
|
database,
|
|
43
|
-
charset
|
|
43
|
+
charset,
|
|
44
44
|
},
|
|
45
45
|
debug,
|
|
46
46
|
pool: {
|
|
47
47
|
//默认为{min: 2, max: 10},连接池配置
|
|
48
48
|
min,
|
|
49
49
|
max,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
conn.on(
|
|
53
|
-
console.error(
|
|
50
|
+
// 添加连接池错误处理
|
|
51
|
+
afterCreate: (conn, done) => {
|
|
52
|
+
conn.on("error", (error) => {
|
|
53
|
+
console.error(
|
|
54
|
+
`[连接池错误] 连接出错: ${getDefaultErrorMessage(error)}`,
|
|
55
|
+
error
|
|
56
|
+
);
|
|
54
57
|
});
|
|
55
58
|
done(null, conn);
|
|
56
|
-
}
|
|
59
|
+
},
|
|
57
60
|
},
|
|
58
61
|
log: {
|
|
59
62
|
warn(message) {
|
|
@@ -79,10 +82,10 @@ export const db =({
|
|
|
79
82
|
},
|
|
80
83
|
},
|
|
81
84
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
|
|
86
|
+
if (client === "sqlite3" || client === "better-sqlite3") {
|
|
87
|
+
config.connection.filename = filename;
|
|
88
|
+
}
|
|
86
89
|
|
|
87
90
|
return knex(config);
|
|
88
91
|
};
|
package/helper/file.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import { accessSync, unlinkSync, existsSync, mkdirSync } from
|
|
3
|
-
import path from
|
|
4
|
-
import { fileURLToPath } from
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import { accessSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
5
|
|
|
6
|
-
export const dirname = (url) =>{
|
|
6
|
+
export const dirname = (url) => {
|
|
7
7
|
const __filename = fileURLToPath(url);
|
|
8
8
|
return path.dirname(__filename);
|
|
9
|
-
}
|
|
10
|
-
|
|
9
|
+
};
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* 获取指定路径的文件树结构
|
|
14
|
-
*
|
|
13
|
+
*
|
|
15
14
|
* @param {string} basePath - 要扫描的基础路径
|
|
16
15
|
* @param {boolean} [deep=true] - 是否深度遍历子目录
|
|
17
16
|
* @returns {Promise<Array<Object>>} 文件树数组,每个元素包含文件/目录信息
|
|
@@ -43,13 +42,15 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
|
43
42
|
name: item,
|
|
44
43
|
path: itemPath,
|
|
45
44
|
relativePath: path.relative(ROOT_PATH, itemPath),
|
|
46
|
-
type: itemStats.isDirectory() ?
|
|
45
|
+
type: itemStats.isDirectory() ? "directory" : "file",
|
|
47
46
|
size: itemStats.size,
|
|
48
47
|
modified: itemStats.mtime,
|
|
49
|
-
depth:
|
|
48
|
+
depth:
|
|
49
|
+
itemPath.split(path.sep).length -
|
|
50
|
+
path.resolve(ROOT_PATH).split(path.sep).length,
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
if (treeItem.type ===
|
|
53
|
+
if (treeItem.type === "directory" && deep) {
|
|
53
54
|
treeItem.children = await getFileTree(itemPath, deep);
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -59,9 +60,9 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
|
59
60
|
// 排序:文件夹在前,文件在后,名称按字母顺序排列
|
|
60
61
|
return tree.sort((a, b) => {
|
|
61
62
|
if (a.type === b.type) {
|
|
62
|
-
return a.name.localeCompare(b.name, undefined, { sensitivity:
|
|
63
|
+
return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
|
|
63
64
|
}
|
|
64
|
-
return a.type ===
|
|
65
|
+
return a.type === "directory" ? -1 : 1;
|
|
65
66
|
});
|
|
66
67
|
} catch (error) {
|
|
67
68
|
console.error(`获取文件树失败: ${basePath}`, error);
|
|
@@ -71,7 +72,7 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* 读取文件内容(UTF-8编码)
|
|
74
|
-
*
|
|
75
|
+
*
|
|
75
76
|
* @param {string} filePath - 要读取的文件路径
|
|
76
77
|
* @returns {Promise<string>} 文件内容字符串
|
|
77
78
|
* @throws {Error} 当文件不存在、无法读取或不是文件时抛出错误
|
|
@@ -79,17 +80,17 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
|
79
80
|
export const readFileContent = async (filePath) => {
|
|
80
81
|
try {
|
|
81
82
|
// 先检查路径是否安全
|
|
82
|
-
if (!isPathSafe(filePath,APP_PATH)
|
|
83
|
+
if (!isPathSafe(filePath, APP_PATH) && !isPathSafe(filePath, ROOT_PATH)) {
|
|
83
84
|
throw new Error(`路径不安全: ${filePath}`);
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
+
|
|
86
87
|
// 检查是否为文件
|
|
87
88
|
const stats = await fs.stat(filePath);
|
|
88
89
|
if (!stats.isFile()) {
|
|
89
90
|
throw new Error(`不是文件: ${filePath}`);
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
return await fs.readFile(filePath,
|
|
92
|
+
|
|
93
|
+
return await fs.readFile(filePath, "utf8");
|
|
93
94
|
} catch (error) {
|
|
94
95
|
console.error(`读取文件失败: ${filePath}`, error);
|
|
95
96
|
throw error;
|
|
@@ -98,7 +99,7 @@ export const readFileContent = async (filePath) => {
|
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
101
|
* 保存内容到文件(UTF-8编码)
|
|
101
|
-
*
|
|
102
|
+
*
|
|
102
103
|
* @param {string} filePath - 要保存的文件路径
|
|
103
104
|
* @param {string} content - 要写入的内容
|
|
104
105
|
* @returns {Promise<void>} 无返回值
|
|
@@ -107,16 +108,16 @@ export const readFileContent = async (filePath) => {
|
|
|
107
108
|
export const saveFileContent = async (filePath, content) => {
|
|
108
109
|
try {
|
|
109
110
|
// 先检查路径是否安全
|
|
110
|
-
if (!isPathSafe(filePath,APP_PATH)
|
|
111
|
+
if (!isPathSafe(filePath, APP_PATH) && !isPathSafe(filePath, ROOT_PATH)) {
|
|
111
112
|
throw new Error(`路径不安全: ${filePath}`);
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
+
|
|
114
115
|
// 确保目录存在
|
|
115
116
|
const dirname = path.dirname(filePath);
|
|
116
117
|
await fs.mkdir(dirname, { recursive: true });
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
// 写入文件
|
|
119
|
-
await fs.writeFile(filePath, content,
|
|
120
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
120
121
|
} catch (error) {
|
|
121
122
|
console.error(`保存文件失败: ${filePath}`, error);
|
|
122
123
|
throw error;
|
|
@@ -125,7 +126,7 @@ export const saveFileContent = async (filePath, content) => {
|
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
128
|
* 验证路径是否在基础路径范围内(防止路径遍历攻击)
|
|
128
|
-
*
|
|
129
|
+
*
|
|
129
130
|
* @param {string} requestedPath - 要验证的路径
|
|
130
131
|
* @param {string} basePath - 基础路径,requestedPath必须在此路径下
|
|
131
132
|
* @returns {boolean} 如果路径安全则返回true,否则返回false
|
|
@@ -139,7 +140,7 @@ export const isPathSafe = (requestedPath, basePath) => {
|
|
|
139
140
|
|
|
140
141
|
/**
|
|
141
142
|
* 删除指定路径的图片文件
|
|
142
|
-
*
|
|
143
|
+
*
|
|
143
144
|
* @param {string} link - 图片文件的路径
|
|
144
145
|
* @returns {boolean} 成功删除返回true,否则返回false
|
|
145
146
|
* @description 同步操作,会检查文件是否存在后再删除
|
|
@@ -151,10 +152,10 @@ export function delImg(link) {
|
|
|
151
152
|
console.error(`路径不安全: ${link}`);
|
|
152
153
|
return false;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
+
|
|
155
156
|
// 检查文件是否存在
|
|
156
157
|
accessSync(link);
|
|
157
|
-
|
|
158
|
+
|
|
158
159
|
// 删除文件
|
|
159
160
|
unlinkSync(link);
|
|
160
161
|
return true;
|
|
@@ -166,7 +167,7 @@ export function delImg(link) {
|
|
|
166
167
|
|
|
167
168
|
/**
|
|
168
169
|
* 递归创建目录(同步操作)
|
|
169
|
-
*
|
|
170
|
+
*
|
|
170
171
|
* @param {string} dirname - 要创建的目录路径
|
|
171
172
|
* @returns {boolean} 成功创建或目录已存在返回true,否则返回false
|
|
172
173
|
* @description 类似于mkdir -p命令,会创建所有不存在的父目录
|
|
@@ -178,18 +179,18 @@ export function mkdirsSync(dirname) {
|
|
|
178
179
|
console.error(`路径不安全: ${dirname}`);
|
|
179
180
|
return false;
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
+
|
|
182
183
|
if (existsSync(dirname)) {
|
|
183
184
|
return true;
|
|
184
185
|
}
|
|
185
|
-
|
|
186
|
+
|
|
186
187
|
// 递归创建父目录
|
|
187
188
|
const parentDir = path.dirname(dirname);
|
|
188
189
|
if (mkdirsSync(parentDir)) {
|
|
189
190
|
mkdirSync(dirname);
|
|
190
191
|
return true;
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
+
|
|
193
194
|
return false;
|
|
194
195
|
} catch (err) {
|
|
195
196
|
console.error(`创建目录失败: ${dirname}`, err);
|
|
@@ -197,7 +198,6 @@ export function mkdirsSync(dirname) {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
|
|
201
201
|
export default {
|
|
202
202
|
mkdirsSync,
|
|
203
203
|
delImg,
|
|
@@ -205,4 +205,4 @@ export default {
|
|
|
205
205
|
saveFileContent,
|
|
206
206
|
readFileContent,
|
|
207
207
|
getFileTree,
|
|
208
|
-
}
|
|
208
|
+
};
|
package/helper/html.js
CHANGED
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const htmlEncode = (str) => {
|
|
7
7
|
// 非字符串直接返回,避免报错
|
|
8
|
-
if (typeof str !==
|
|
9
|
-
|
|
8
|
+
if (typeof str !== "string") return str;
|
|
9
|
+
|
|
10
10
|
// 一次性替换所有特殊字符,减少函数调用
|
|
11
|
-
return str.replace(
|
|
12
|
-
'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
return str.replace(
|
|
12
|
+
/[&<>"' ]/g,
|
|
13
|
+
(match) =>
|
|
14
|
+
({
|
|
15
|
+
"&": "&",
|
|
16
|
+
"<": "<",
|
|
17
|
+
">": ">",
|
|
18
|
+
'"': """,
|
|
19
|
+
"'": "'",
|
|
20
|
+
" ": " ",
|
|
21
|
+
}[match])
|
|
22
|
+
);
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -25,15 +29,19 @@ export const htmlEncode = (str) => {
|
|
|
25
29
|
*/
|
|
26
30
|
export const htmlDecode = (str) => {
|
|
27
31
|
// 非字符串直接返回,避免报错
|
|
28
|
-
if (typeof str !==
|
|
29
|
-
|
|
32
|
+
if (typeof str !== "string") return str;
|
|
33
|
+
|
|
30
34
|
// 一次性替换所有常见实体,减少函数调用
|
|
31
|
-
return str.replace(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
return str.replace(
|
|
36
|
+
/&|<|>| |'|"/g,
|
|
37
|
+
(match) =>
|
|
38
|
+
({
|
|
39
|
+
"&": "&",
|
|
40
|
+
"<": "<",
|
|
41
|
+
">": ">",
|
|
42
|
+
" ": " ",
|
|
43
|
+
"'": "'",
|
|
44
|
+
""": '"',
|
|
45
|
+
}[match])
|
|
46
|
+
);
|
|
47
|
+
};
|
package/helper/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { loaderSort, loadConfig } from "./loader.js";
|
|
3
|
+
import { formatTime, formatDateFields } from "./time.js";
|
|
1
4
|
|
|
2
|
-
|
|
3
|
-
import { loaderSort, loadConfig } from './loader.js'
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
db,
|
|
7
|
-
loaderSort,
|
|
8
|
-
loadConfig,
|
|
9
|
-
}
|
|
5
|
+
export { db, loaderSort, loadConfig, formatTime, formatDateFields };
|
package/helper/ip.js
CHANGED
|
@@ -3,34 +3,35 @@
|
|
|
3
3
|
* @param {Object} req - Express请求对象
|
|
4
4
|
* @returns {string} 格式化后的IP地址,默认返回空字符串
|
|
5
5
|
*/
|
|
6
|
-
export const getIp = (req) =>{
|
|
7
|
-
|
|
6
|
+
export const getIp = (req) => {
|
|
8
7
|
// 优先级从高到低获取可能的IP来源
|
|
9
8
|
const ipSources = [
|
|
10
|
-
req.headers[
|
|
11
|
-
req.headers[
|
|
12
|
-
req.ip,
|
|
13
|
-
req.connection?.remoteAddress,
|
|
14
|
-
req.socket?.remoteAddress,
|
|
15
|
-
req.connection?.socket?.remoteAddress
|
|
9
|
+
req.headers["x-forwarded-for"], // 代理场景下的真实IP(可能多个,逗号分隔)
|
|
10
|
+
req.headers["x-real-ip"], // 部分代理服务器使用
|
|
11
|
+
req.ip, // Express内置的IP获取(已处理代理)
|
|
12
|
+
req.connection?.remoteAddress, // 底层连接的远程地址
|
|
13
|
+
req.socket?.remoteAddress, // 套接字的远程地址
|
|
14
|
+
req.connection?.socket?.remoteAddress, // 连接套接字的远程地址
|
|
16
15
|
];
|
|
17
16
|
|
|
18
17
|
// 从来源中找到第一个有效IP字符串
|
|
19
|
-
let ip = ipSources.find(
|
|
20
|
-
|
|
18
|
+
let ip = ipSources.find(
|
|
19
|
+
(source) => typeof source === "string" && source.trim() !== ""
|
|
20
|
+
);
|
|
21
|
+
|
|
21
22
|
// 若未找到有效IP,直接返回空
|
|
22
|
-
if (!ip) return
|
|
23
|
+
if (!ip) return "";
|
|
23
24
|
|
|
24
25
|
// 处理多IP情况(取第一个)
|
|
25
|
-
ip = ip.split(
|
|
26
|
+
ip = ip.split(",").shift().trim();
|
|
26
27
|
|
|
27
28
|
// IPv6转IPv4处理
|
|
28
|
-
if (ip ===
|
|
29
|
-
return
|
|
29
|
+
if (ip === "::1") {
|
|
30
|
+
return "127.0.0.1"; // 本地环回地址
|
|
30
31
|
}
|
|
31
|
-
if (ip.startsWith(
|
|
32
|
-
return ip.slice(7);
|
|
32
|
+
if (ip.startsWith("::ffff:")) {
|
|
33
|
+
return ip.slice(7); // 去除IPv6映射的IPv4前缀
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
return ip;
|
|
36
|
-
}
|
|
37
|
+
};
|
package/helper/jwt.js
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
*
|
|
6
5
|
* @param {Object} data - 要存储在令牌中的数据(不建议存放敏感信息)
|
|
7
6
|
* @param {string} secretKey - 用于签名的密钥
|
|
8
7
|
* @param {string} time - 令牌过期时间,如 '1h'、'7d' 或秒数
|
|
9
8
|
* @returns {string} 生成的JWT令牌
|
|
10
9
|
*/
|
|
11
|
-
export function setToken(data={}, secretKey=
|
|
10
|
+
export function setToken(data = {}, secretKey = "chancms", time = "7d") {
|
|
12
11
|
try {
|
|
13
12
|
return jwt.sign(data, secretKey, {
|
|
14
13
|
expiresIn: time,
|
|
15
|
-
algorithm:
|
|
14
|
+
algorithm: "HS256",
|
|
16
15
|
});
|
|
17
16
|
} catch (error) {
|
|
18
|
-
console.error(
|
|
17
|
+
console.error("令牌生成失败:", error.message);
|
|
19
18
|
throw new Error(`生成令牌失败: ${error.message}`);
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
// 获取token
|
|
24
|
-
export async function getToken(token, secretKey=
|
|
23
|
+
export async function getToken(token, secretKey = "chancms") {
|
|
25
24
|
return new Promise((resolve, reject) => {
|
|
26
|
-
jwt.verify(token, secretKey,(err, decode) => {
|
|
25
|
+
jwt.verify(token, secretKey, (err, decode) => {
|
|
27
26
|
if (err) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
let errorMessage = "令牌验证失败";
|
|
28
|
+
if (err.name === "TokenExpiredError") {
|
|
29
|
+
errorMessage = "令牌已过期";
|
|
30
|
+
} else if (err.name === "JsonWebTokenError") {
|
|
31
|
+
errorMessage = "无效的令牌";
|
|
32
|
+
}
|
|
33
|
+
console.error(errorMessage, "令牌异常信息:", err.message);
|
|
34
|
+
return reject(new Error(errorMessage));
|
|
36
35
|
} else {
|
|
37
36
|
resolve(decode);
|
|
38
37
|
}
|
|
39
38
|
});
|
|
40
39
|
});
|
|
41
|
-
}
|
|
40
|
+
}
|
package/helper/loader.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @description 实例化一个类,并将该类的所有方法绑定到一个新的对象上。
|
|
7
|
+
* @param {Function} className - 需要实例化的类。
|
|
8
|
+
*@returns {Object} 包含绑定方法的对象。
|
|
9
|
+
*/
|
|
10
|
+
export const bindClass = function (className) {
|
|
11
|
+
let obj = {};
|
|
12
|
+
const cls = new className();
|
|
13
|
+
Object.getOwnPropertyNames(cls.constructor.prototype).forEach(
|
|
14
|
+
(methodName) => {
|
|
15
|
+
if (
|
|
16
|
+
methodName !== "constructor" &&
|
|
17
|
+
typeof cls[methodName] === "function"
|
|
18
|
+
) {
|
|
19
|
+
obj[methodName] = cls[methodName].bind(cls);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
return obj;
|
|
24
|
+
};
|
|
3
25
|
|
|
4
26
|
/**
|
|
5
27
|
*
|
|
@@ -26,6 +48,26 @@ export const loadConfig = async function () {
|
|
|
26
48
|
return config;
|
|
27
49
|
};
|
|
28
50
|
|
|
51
|
+
/**
|
|
52
|
+
* 绑定已实例化对象的方法
|
|
53
|
+
* @param {Object} instance - 已实例化的对象
|
|
54
|
+
* @returns {Object} 绑定方法后的对象
|
|
55
|
+
*/
|
|
56
|
+
export const bindInstance = function (instance) {
|
|
57
|
+
Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).forEach(
|
|
58
|
+
(methodName) => {
|
|
59
|
+
if (
|
|
60
|
+
methodName !== "constructor" &&
|
|
61
|
+
typeof instance[methodName] === "function"
|
|
62
|
+
) {
|
|
63
|
+
instance[methodName] = instance[methodName].bind(instance);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
return instance;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
|
|
29
71
|
/**
|
|
30
72
|
* 加载指定模块名下的所有控制器文件
|
|
31
73
|
* @param {string} moduleName - 模块名称
|
|
@@ -45,17 +87,15 @@ export const loadController = async function (moduleName) {
|
|
|
45
87
|
|
|
46
88
|
for (const file of files) {
|
|
47
89
|
const filePath = path.join(dir, file);
|
|
48
|
-
const name = file.replace(/\.js$/i, "");
|
|
90
|
+
const name = file.replace(/\.js$/i, "");
|
|
49
91
|
|
|
50
92
|
try {
|
|
51
93
|
const module = await importFile(filePath);
|
|
52
|
-
let obj = module
|
|
53
|
-
|
|
54
|
-
controller[name] = obj;
|
|
94
|
+
let obj = module?.default || module;
|
|
95
|
+
// 使用 bindClass 确保方法绑定了正确的 this
|
|
96
|
+
controller[name] = bindInstance(obj);
|
|
55
97
|
} catch (e) {
|
|
56
98
|
console.error(`加载控制器失败: ${filePath}`, e);
|
|
57
|
-
// 可选:抛出错误或继续加载其他文件
|
|
58
|
-
// throw e;
|
|
59
99
|
}
|
|
60
100
|
}
|
|
61
101
|
|
package/helper/time.js
CHANGED
|
@@ -21,8 +21,54 @@ export const formatDay = (data, time = true, format = "YYYY-MM-DD") => {
|
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
23
|
return data;
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
25
|
|
|
26
26
|
export const formatTime = (data, format = "YYYY-MM-DD HH:mm:ss") => {
|
|
27
|
-
return dayjs(data).format(
|
|
28
|
-
}
|
|
27
|
+
return dayjs(data).format(format);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 格式化对象或数组中的日期字段
|
|
32
|
+
* @param {Object|Array} data - 要格式化的数据
|
|
33
|
+
* @param {Object} options - 配置选项
|
|
34
|
+
* @param {Array} options.fields - 要格式化的字段名,默认 ['createdAt', 'updatedAt']
|
|
35
|
+
* @param {String} options.format - 日期格式,默认 'YYYY-MM-DD HH:mm:ss'
|
|
36
|
+
* @returns {Object|Array} - 格式化后的数据
|
|
37
|
+
*/
|
|
38
|
+
export const formatDateFields = (data, options = {}) => {
|
|
39
|
+
const {
|
|
40
|
+
fields = ['createdAt', 'updatedAt'],
|
|
41
|
+
format = 'YYYY-MM-DD HH:mm:ss',
|
|
42
|
+
} = options;
|
|
43
|
+
|
|
44
|
+
const isPlainObject = (val) =>
|
|
45
|
+
Object.prototype.toString.call(val) === '[object Object]';
|
|
46
|
+
|
|
47
|
+
if (isPlainObject(data)) {
|
|
48
|
+
const result = { ...data };
|
|
49
|
+
fields.forEach(field => {
|
|
50
|
+
const value = result[field];
|
|
51
|
+
if (value != null) {
|
|
52
|
+
const type = typeof value;
|
|
53
|
+
if (type === 'string' || type === 'number' || value instanceof Date) {
|
|
54
|
+
try {
|
|
55
|
+
result[field] = formatTime(value, format);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn(`格式化字段 ${field} 失败:`, e.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(data)) {
|
|
66
|
+
return data.map(item =>
|
|
67
|
+
typeof item === 'object' && item !== null
|
|
68
|
+
? formatDateFields(item, options)
|
|
69
|
+
: item
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return data;
|
|
74
|
+
};;
|
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
waf,
|
|
18
18
|
} from "./middleware/index.js";
|
|
19
19
|
import { db, loaderSort, loadConfig } from "./helper/index.js";
|
|
20
|
-
import {dirname} from "./helper/file.js";
|
|
20
|
+
import { dirname } from "./helper/file.js";
|
|
21
21
|
|
|
22
22
|
class Chan {
|
|
23
23
|
//版本号
|
|
@@ -29,6 +29,7 @@ class Chan {
|
|
|
29
29
|
static Controller = Controller; //控制器
|
|
30
30
|
static extend = {}; //组件扩展
|
|
31
31
|
static middleware = {}; //中间件
|
|
32
|
+
static modules = {}; //模块
|
|
32
33
|
|
|
33
34
|
constructor() {
|
|
34
35
|
this.app = express();
|
|
@@ -65,8 +66,8 @@ class Chan {
|
|
|
65
66
|
logger,
|
|
66
67
|
cors,
|
|
67
68
|
} = Chan.config;
|
|
68
|
-
|
|
69
|
-
this.app.set(
|
|
69
|
+
|
|
70
|
+
this.app.set("trust proxy", true);
|
|
70
71
|
log(this.app, logger);
|
|
71
72
|
setFavicon(this.app);
|
|
72
73
|
setCookie(this.app, cookieKey);
|
|
@@ -134,7 +135,9 @@ class Chan {
|
|
|
134
135
|
|
|
135
136
|
async loadFn(_path, key) {
|
|
136
137
|
if (fs.existsSync(_path)) {
|
|
137
|
-
const files = fs
|
|
138
|
+
const files = fs
|
|
139
|
+
.readdirSync(_path)
|
|
140
|
+
.filter((file) => file.endsWith(".js"));
|
|
138
141
|
for (const file of files) {
|
|
139
142
|
const filePath = path.join(_path, file);
|
|
140
143
|
let helperModule = await importFile(filePath);
|
|
@@ -162,7 +165,6 @@ class Chan {
|
|
|
162
165
|
methods: Object.keys(r.route.methods),
|
|
163
166
|
}));
|
|
164
167
|
|
|
165
|
-
console.log(routes);
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
run(cb) {
|
package/middleware/cookie.js
CHANGED
package/middleware/cors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import cors from "cors";
|
|
2
|
-
export const Cors = (app,_cors) => {
|
|
3
|
-
|
|
4
|
-
};
|
|
2
|
+
export const Cors = (app, _cors) => {
|
|
3
|
+
app.use(cors(_cors));
|
|
4
|
+
};
|
package/middleware/header.js
CHANGED