chanjs 2.0.3 → 2.0.5
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/common/api.js +20 -0
- package/common/code.js +20 -0
- package/extend/api.js +21 -0
- package/extend/file.js +92 -0
- package/extend/upload.js +80 -0
- package/extend/utils.js +342 -0
- package/global/env.js +5 -0
- package/global/global.js +27 -0
- package/global/import.js +46 -0
- package/global/index.js +3 -0
- package/helper/api.js +14 -0
- package/helper/bind.js +21 -0
- package/helper/dataparse.js +24 -0
- package/helper/db.js +75 -0
- package/helper/file.js +202 -0
- package/helper/html.js +19 -0
- package/helper/index.js +25 -0
- package/helper/ip.js +36 -0
- package/helper/jwt.js +41 -0
- package/helper/loader.js +65 -0
- package/helper/response.js +20 -0
- package/helper/safe.js +263 -0
- package/helper/time.js +28 -0
- package/helper/tree.js +32 -0
- package/index.js +12 -6
- package/middleware/header.js +3 -3
- package/middleware/safe.js +144 -0
- package/middleware/waf.js +197 -0
- package/package.json +1 -1
- package/utils/dataparse.js +24 -0
- package/utils/db.js +66 -68
- package/utils/loader.js +23 -24
package/common/api.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
export const success = {
|
2
|
+
code: 200,
|
3
|
+
msg: "success",
|
4
|
+
};
|
5
|
+
|
6
|
+
export const fail = {
|
7
|
+
code: 201,
|
8
|
+
msg: "error",
|
9
|
+
};
|
10
|
+
|
11
|
+
export const error = {
|
12
|
+
code: 500,
|
13
|
+
msg: "error",
|
14
|
+
}
|
15
|
+
|
16
|
+
export default {
|
17
|
+
success,
|
18
|
+
fail,
|
19
|
+
error,
|
20
|
+
}
|
package/common/code.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
// 业务状态码(仅标识业务逻辑结果)
|
3
|
+
export const API_CODE = {
|
4
|
+
SUCCESS: 200, // 成功
|
5
|
+
FAIL: 201, // 失败
|
6
|
+
ERROR: 500, // 错误
|
7
|
+
// 参数相关
|
8
|
+
PARAM_INVALID: 10001, // 参数无效
|
9
|
+
PARAM_MISSING: 10002, // 参数缺失
|
10
|
+
// 认证相关
|
11
|
+
AUTH_FAILED: 20001, // 认证失败
|
12
|
+
TOKEN_EXPIRED: 20002, // 令牌过期
|
13
|
+
// 资源相关
|
14
|
+
RESOURCE_NOT_FOUND: 30001, // 资源不存在
|
15
|
+
RESOURCE_LOCKED: 30002, // 资源锁定
|
16
|
+
// 系统相关
|
17
|
+
SYSTEM_ERROR: 50001, // 系统错误
|
18
|
+
SERVICE_BUSY: 50002 // 服务繁忙
|
19
|
+
};
|
20
|
+
|
package/extend/api.js
ADDED
package/extend/file.js
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
import fs from 'fs/promises';
|
2
|
+
import path from 'path';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 获取文件树
|
6
|
+
* @param {string} basePath
|
7
|
+
* @returns {Promise<Array>}
|
8
|
+
*/
|
9
|
+
export const getFileTree = async (basePath,deep = true) => {
|
10
|
+
try {
|
11
|
+
const stats = await fs.stat(basePath);
|
12
|
+
if (!stats.isDirectory()) {
|
13
|
+
return [];
|
14
|
+
}
|
15
|
+
|
16
|
+
const items = await fs.readdir(basePath);
|
17
|
+
const tree = [];
|
18
|
+
|
19
|
+
for (const item of items) {
|
20
|
+
const itemPath = path.join(basePath, item);
|
21
|
+
const itemStats = await fs.stat(itemPath);
|
22
|
+
|
23
|
+
const treeItem = {
|
24
|
+
name: item,
|
25
|
+
path: itemPath,
|
26
|
+
relativePath: path.relative(APP_PATH, itemPath),
|
27
|
+
type: itemStats.isDirectory() ? 'directory' : 'file',
|
28
|
+
size: itemStats.size,
|
29
|
+
modified: itemStats.mtime,
|
30
|
+
depth: itemPath.split(path.sep).length - 1
|
31
|
+
};
|
32
|
+
|
33
|
+
if (treeItem.type === 'directory' && deep) {
|
34
|
+
treeItem.children = await getFileTree(itemPath);
|
35
|
+
}
|
36
|
+
|
37
|
+
tree.push(treeItem);
|
38
|
+
}
|
39
|
+
|
40
|
+
// 排序:文件夹在前,文件在后
|
41
|
+
return tree.sort((a, b) => {
|
42
|
+
if (a.type === b.type) {
|
43
|
+
return a.name.localeCompare(b.name);
|
44
|
+
}
|
45
|
+
return a.type === 'directory' ? -1 : 1;
|
46
|
+
});
|
47
|
+
} catch (error) {
|
48
|
+
console.error(`获取文件树失败: ${basePath}`, error);
|
49
|
+
throw error;
|
50
|
+
}
|
51
|
+
};
|
52
|
+
|
53
|
+
/**
|
54
|
+
* 读取文件内容
|
55
|
+
* @param {string} filePath
|
56
|
+
* @returns {Promise<string>}
|
57
|
+
*/
|
58
|
+
export const readFileContent = async (filePath) => {
|
59
|
+
try {
|
60
|
+
return await fs.readFile(filePath, 'utf8');
|
61
|
+
} catch (error) {
|
62
|
+
console.error(`读取文件失败: ${filePath}`, error);
|
63
|
+
throw error;
|
64
|
+
}
|
65
|
+
};
|
66
|
+
|
67
|
+
/**
|
68
|
+
* 保存文件内容
|
69
|
+
* @param {string} filePath
|
70
|
+
* @param {string} content
|
71
|
+
* @returns {Promise<void>}
|
72
|
+
*/
|
73
|
+
export const saveFileContent = async (filePath, content) => {
|
74
|
+
try {
|
75
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
76
|
+
await fs.writeFile(filePath, content, 'utf8');
|
77
|
+
} catch (error) {
|
78
|
+
console.error(`保存文件失败: ${filePath}`, error);
|
79
|
+
throw error;
|
80
|
+
}
|
81
|
+
};
|
82
|
+
|
83
|
+
/**
|
84
|
+
* 路径安全验证
|
85
|
+
* @param {string} requestedPath
|
86
|
+
* @param {string} basePath
|
87
|
+
* @returns {boolean}
|
88
|
+
*/
|
89
|
+
export const isPathSafe = (requestedPath, basePath) => {
|
90
|
+
const resolvedPath = path.resolve(requestedPath);
|
91
|
+
return resolvedPath.startsWith(path.resolve(basePath));
|
92
|
+
};
|
package/extend/upload.js
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
import multer from "multer";
|
2
|
+
import dayjs from "dayjs";
|
3
|
+
import fs from "fs";
|
4
|
+
import path from "path";
|
5
|
+
function createStorage(dir = "uploads", changeDir = true) {
|
6
|
+
// 模板
|
7
|
+
return multer.diskStorage({
|
8
|
+
// 目的地
|
9
|
+
destination: async function (req, file, cb) {
|
10
|
+
const template = Chan.config.template;
|
11
|
+
let destinationDir = "";
|
12
|
+
if (changeDir) {
|
13
|
+
const date = dayjs(Date.now()).format("YYYY/MM/DD");
|
14
|
+
destinationDir = path.join(`public/`, dir, template, date);
|
15
|
+
} else {
|
16
|
+
destinationDir = path.join(`public/`, dir, template);
|
17
|
+
}
|
18
|
+
// 确保目录存在
|
19
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
20
|
+
cb(null, destinationDir);
|
21
|
+
},
|
22
|
+
// 文件名
|
23
|
+
filename: (req, file, cb) => {
|
24
|
+
// 根据时间戳生成文件名
|
25
|
+
cb(null, Date.now() + `_source_${file.originalname}`);
|
26
|
+
},
|
27
|
+
});
|
28
|
+
}
|
29
|
+
|
30
|
+
// logo
|
31
|
+
export const logo = () => {
|
32
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
33
|
+
const upload = multer({
|
34
|
+
storage: createStorage("uploads", false), // 使用自定义的存储引擎
|
35
|
+
limits: { fileSize: Chan.config.upload.logoSize }, // 限制文件大小为5MB
|
36
|
+
fileFilter: (req, file, cb) => {
|
37
|
+
if (allowedTypes.includes(file.mimetype)) {
|
38
|
+
cb(null, true); // 允许上传
|
39
|
+
} else {
|
40
|
+
cb(new Error("不支持的文件类型")); // 拒绝上传
|
41
|
+
}
|
42
|
+
},
|
43
|
+
});
|
44
|
+
|
45
|
+
return upload.single("file");
|
46
|
+
};
|
47
|
+
|
48
|
+
// 单图上传
|
49
|
+
export const singleUpload = () => {
|
50
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
51
|
+
const upload = multer({
|
52
|
+
storage: createStorage("uploads"), // 使用自定义的存储引擎
|
53
|
+
limits: { fileSize: Chan.config.upload.imgSize }, // 限制文件大小为5MB
|
54
|
+
fileFilter: (req, file, cb) => {
|
55
|
+
if (allowedTypes.includes(file.mimetype)) {
|
56
|
+
cb(null, true); // 允许上传
|
57
|
+
} else {
|
58
|
+
cb(new Error("不支持的文件类型")); // 拒绝上传
|
59
|
+
}
|
60
|
+
},
|
61
|
+
});
|
62
|
+
return upload.single("file");
|
63
|
+
};
|
64
|
+
|
65
|
+
// 多图上传
|
66
|
+
export const multiUpload = () => {
|
67
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
68
|
+
const upload = multer({
|
69
|
+
storage: createStorage("uploads"), // 使用自定义的存储引擎
|
70
|
+
limits: { fileSize: Chan.config.upload.imgSize }, // 限制文件大小为200kb
|
71
|
+
fileFilter: (req, file, cb) => {
|
72
|
+
if (allowedTypes.includes(file.mimetype)) {
|
73
|
+
cb(null, true); // 允许上传
|
74
|
+
} else {
|
75
|
+
cb(new Error("不支持的文件类型")); // 拒绝上传
|
76
|
+
}
|
77
|
+
},
|
78
|
+
});
|
79
|
+
return upload.array("file", 5);
|
80
|
+
};
|
package/extend/utils.js
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
import jwt from "jsonwebtoken";
|
2
|
+
import fs from "fs";
|
3
|
+
import path from "path";
|
4
|
+
import bcrypt from 'bcryptjs';
|
5
|
+
import crypto from "crypto";
|
6
|
+
import dayjs from 'dayjs';
|
7
|
+
import 'dayjs/locale/zh-cn.js';
|
8
|
+
import relativeTime from 'dayjs/plugin/relativeTime.js';
|
9
|
+
|
10
|
+
dayjs.extend(relativeTime);
|
11
|
+
dayjs.locale('zh-cn');
|
12
|
+
|
13
|
+
// 无限极分类tree
|
14
|
+
export function tree(arr, pid = 0) {
|
15
|
+
if(arr.length === 0){
|
16
|
+
return []
|
17
|
+
}
|
18
|
+
let result = [];
|
19
|
+
arr.forEach((item) => {
|
20
|
+
if (item.pid === pid) {
|
21
|
+
let children = tree(arr, item.id);
|
22
|
+
if (children.length) {
|
23
|
+
item.children = children;
|
24
|
+
}
|
25
|
+
item.level = 1;
|
26
|
+
result.push(item);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
return result;
|
30
|
+
}
|
31
|
+
|
32
|
+
// 返回id父级所有栏目 位置
|
33
|
+
export function treeById(id, source) {
|
34
|
+
const arr = [];
|
35
|
+
const findId = (id, source) => {
|
36
|
+
for (let i = 0, item; i < source.length; i++) {
|
37
|
+
item = source[i];
|
38
|
+
if (item.id == id) {
|
39
|
+
arr.unshift(item);
|
40
|
+
if (item.pid != 0) {
|
41
|
+
findId(item.pid, source);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
};
|
46
|
+
findId(id, source);
|
47
|
+
const _path = [];
|
48
|
+
arr.forEach((item) => {
|
49
|
+
_path.push("/" + item.pinyin);
|
50
|
+
item.path = _path.join("");
|
51
|
+
});
|
52
|
+
return arr;
|
53
|
+
}
|
54
|
+
|
55
|
+
// 获取子栏目
|
56
|
+
export function getChildrenId(py, source) {
|
57
|
+
let cate = {};
|
58
|
+
let id = "";
|
59
|
+
source.forEach((item) => {
|
60
|
+
if (item.pinyin == py || item.id == py) {
|
61
|
+
cate = item;
|
62
|
+
id = item.id;
|
63
|
+
}
|
64
|
+
});
|
65
|
+
return { cate, id };
|
66
|
+
}
|
67
|
+
|
68
|
+
// 设置token
|
69
|
+
export function setToken(data, key, time) {
|
70
|
+
const token = jwt.sign(data, key, {
|
71
|
+
expiresIn: time,
|
72
|
+
});
|
73
|
+
return token;
|
74
|
+
}
|
75
|
+
|
76
|
+
// 获取token
|
77
|
+
export async function getToken(token, key) {
|
78
|
+
return new Promise((resolve, reject) => {
|
79
|
+
jwt.verify(token, key, async (err, decode) => {
|
80
|
+
if (err) {
|
81
|
+
reject(err);
|
82
|
+
console.error(err);
|
83
|
+
} else {
|
84
|
+
resolve(decode);
|
85
|
+
}
|
86
|
+
});
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
// md5加密
|
91
|
+
export function md5(str) {
|
92
|
+
return crypto.createHash("md5").update(str).digest("hex");
|
93
|
+
}
|
94
|
+
|
95
|
+
//过滤body标签
|
96
|
+
export function filterBody(str) {
|
97
|
+
const result = /<body[^>]*>([\s\S]*)<\/body>/.exec(str);
|
98
|
+
if (result && result.length === 2) return result[1];
|
99
|
+
return str;
|
100
|
+
}
|
101
|
+
|
102
|
+
export function pc(str) {
|
103
|
+
if (
|
104
|
+
str.match(
|
105
|
+
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
|
106
|
+
)
|
107
|
+
) {
|
108
|
+
return false;
|
109
|
+
}
|
110
|
+
return true;
|
111
|
+
}
|
112
|
+
|
113
|
+
// 获取图片
|
114
|
+
export function filterImgFromStr(str) {
|
115
|
+
if (!str) {
|
116
|
+
return [];
|
117
|
+
}
|
118
|
+
const imgReg = /<img.*?(?:>|\/>)/gi;
|
119
|
+
const srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
|
120
|
+
const arr = str.match(imgReg);
|
121
|
+
const imgArr = [];
|
122
|
+
if (arr) {
|
123
|
+
for (let i = 0; i < arr.length; i++) {
|
124
|
+
const src = arr[i].match(srcReg);
|
125
|
+
if (src[1]) {
|
126
|
+
imgArr.push(src[1]);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
return imgArr;
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* @description 删除上传的图片
|
135
|
+
* @param {*} link 字符串
|
136
|
+
*/
|
137
|
+
export function delImg(link) {
|
138
|
+
try {
|
139
|
+
fs.accessSync(link);
|
140
|
+
fs.unlinkSync(link);
|
141
|
+
return true;
|
142
|
+
} catch (err) {
|
143
|
+
console.error(err);
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
//生成目录,异步改同步
|
149
|
+
export function mkdirsSync(dirname) {
|
150
|
+
if (fs.existsSync(dirname)) {
|
151
|
+
return true;
|
152
|
+
} else {
|
153
|
+
if (mkdirsSync(path.dirname(dirname))) {
|
154
|
+
fs.mkdirSync(dirname);
|
155
|
+
return true;
|
156
|
+
}
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* @example [{name:'yanyutao',age:33}] => {yanyutao:33}
|
162
|
+
* @description 数组变对象:将数组中的key作为对象的key,其余作为value
|
163
|
+
*/
|
164
|
+
export function convertArrayToObject(array, key) {
|
165
|
+
if (!Array.isArray(array) || array.length === 0) {
|
166
|
+
return {};
|
167
|
+
}
|
168
|
+
const result = {};
|
169
|
+
for (const item of array) {
|
170
|
+
const { [key]: mark, content } = item;
|
171
|
+
if (mark) {
|
172
|
+
result[mark] = content;
|
173
|
+
}
|
174
|
+
}
|
175
|
+
return result;
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* @description 过滤非必要字段
|
180
|
+
* @param {Array} data 原始数组数据
|
181
|
+
* @param {Array} fields 需要的字段
|
182
|
+
* @returns {Array} 返回最终的数组
|
183
|
+
*/
|
184
|
+
export function filterFields(data, fields) {
|
185
|
+
if (!Array.isArray(data) || data.length === 0) {
|
186
|
+
return [];
|
187
|
+
}
|
188
|
+
return data.map((item) => {
|
189
|
+
const filteredItem = {};
|
190
|
+
for (const field of fields) {
|
191
|
+
if (item.hasOwnProperty(field)) {
|
192
|
+
filteredItem[field] = item[field];
|
193
|
+
}
|
194
|
+
}
|
195
|
+
return filteredItem;
|
196
|
+
});
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* @description 格式化时间
|
201
|
+
* @param {Array} data 数组
|
202
|
+
* @param {Boolean} time 是否开启具体时间
|
203
|
+
* @param {String} format YYYY-MM-DD HH:mm:ss
|
204
|
+
* @returns 返回处理过的数组
|
205
|
+
*/
|
206
|
+
export function formatDay(data, time = true, format = "YYYY-MM-DD") {
|
207
|
+
data.forEach((item) => {
|
208
|
+
if (item.createdAt) {
|
209
|
+
item.createdAt = time
|
210
|
+
? dayjs(item.createdAt).format(format)
|
211
|
+
: dayjs(item.createdAt).fromNow().replace(" ", "");
|
212
|
+
}
|
213
|
+
});
|
214
|
+
return data;
|
215
|
+
}
|
216
|
+
|
217
|
+
/**
|
218
|
+
* @description 输出分页标签
|
219
|
+
* @param {*} current 当前页面
|
220
|
+
* @param {*} total 总条数
|
221
|
+
* @param {*} pageSize 每页数量
|
222
|
+
* @param {*} href 跳转路由
|
223
|
+
* @param {*} query 查询参数
|
224
|
+
* @returns 返回分页html
|
225
|
+
*/
|
226
|
+
export function pages(current, total, pageSize, href,query='') {
|
227
|
+
let pageTemp = [];
|
228
|
+
let totalPage = Math.ceil(total / pageSize);
|
229
|
+
if(totalPage<=1){
|
230
|
+
return '';
|
231
|
+
}
|
232
|
+
|
233
|
+
let pageStr = `<p>共${total}条记录,共${totalPage},当前${current}</p>`;
|
234
|
+
//上一页
|
235
|
+
if (current == 1) {
|
236
|
+
pageTemp.push(`<li class="disabled">上一页</li>`);
|
237
|
+
} else {
|
238
|
+
pageTemp.push(`<li><a href='${href}${parseInt(current) - 1}.html${query}'>上一页</a></li>`);
|
239
|
+
}
|
240
|
+
//中间的 小于8页面
|
241
|
+
if (totalPage <= 8) {
|
242
|
+
for (let i = 0; i < totalPage; i++) {
|
243
|
+
if (current == i + 1) {
|
244
|
+
pageTemp.push(
|
245
|
+
`<li class="current"><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`
|
246
|
+
);
|
247
|
+
} else {
|
248
|
+
pageTemp.push(`<li><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`);
|
249
|
+
}
|
250
|
+
}
|
251
|
+
} else {
|
252
|
+
//获取前3
|
253
|
+
for (let i = 0; i < 3; i++) {
|
254
|
+
if (current == i + 1) {
|
255
|
+
pageTemp.push(
|
256
|
+
`<li class="current"><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`
|
257
|
+
);
|
258
|
+
} else {
|
259
|
+
pageTemp.push(`<li><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`);
|
260
|
+
}
|
261
|
+
}
|
262
|
+
pageTemp.push(`<li><a href='${href}${3 + 1}.html${query}'>...</a></li>`);
|
263
|
+
for (let i = totalPage - 3; i < totalPage; i++) {
|
264
|
+
if (current == i + 1) {
|
265
|
+
pageTemp.push(
|
266
|
+
`<li class="current"><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`
|
267
|
+
);
|
268
|
+
} else {
|
269
|
+
pageTemp.push(`<li><a href='${href}${i + 1}.html${query}'>${i + 1}</a></li>`);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
//下一页
|
274
|
+
if (current == totalPage) {
|
275
|
+
pageTemp.push(`<li class="disabled">下一页</li>`);
|
276
|
+
} else {
|
277
|
+
pageTemp.push(`<li><a href='${href}${parseInt(current) + 1}.html${query}'>下一页</a></li>`);
|
278
|
+
}
|
279
|
+
return pageTemp.join("");
|
280
|
+
}
|
281
|
+
|
282
|
+
/**
|
283
|
+
* @description 获取模板文件
|
284
|
+
* @param {*} folderPath
|
285
|
+
* @returns 获取模板文件
|
286
|
+
*/
|
287
|
+
export function getHtmlFilesSync(folderPath) {
|
288
|
+
const files = fs.readdirSync(folderPath);
|
289
|
+
const htmlFiles = [];
|
290
|
+
files.forEach((file) => {
|
291
|
+
const filePath = path.join(folderPath, file);
|
292
|
+
const stats = fs.statSync(filePath);
|
293
|
+
if (stats.isFile() && path.extname(file) === ".html") {
|
294
|
+
htmlFiles.push(file);
|
295
|
+
}
|
296
|
+
});
|
297
|
+
return htmlFiles;
|
298
|
+
}
|
299
|
+
|
300
|
+
/**
|
301
|
+
* @description 获取用户登录ip
|
302
|
+
* @param {*} req
|
303
|
+
* @returns 返回ip地址
|
304
|
+
*/
|
305
|
+
export function getIp(req) {
|
306
|
+
let ip =
|
307
|
+
req.headers['x-forwarded-for'] ||
|
308
|
+
req.ip ||
|
309
|
+
req.headers['x-real-ip'] ||
|
310
|
+
req?.connection?.remoteAddress ||
|
311
|
+
req?.socket?.remoteAddress ||
|
312
|
+
req?.connection?.socket?.remoteAddress ||
|
313
|
+
'';
|
314
|
+
|
315
|
+
// 如果是字符串且包含逗号,取第一个IP
|
316
|
+
if (typeof ip === "string") {
|
317
|
+
ip = ip.split(",").shift().trim();
|
318
|
+
}
|
319
|
+
// 处理IPv6环回地址转换为IPv4
|
320
|
+
if (ip === "::1") {
|
321
|
+
ip = "127.0.0.1";
|
322
|
+
}
|
323
|
+
// 如果remoteAddress是以::ffff:开头,去除前缀
|
324
|
+
if (ip.startsWith("::ffff:")) {
|
325
|
+
ip = ip.substring(7);
|
326
|
+
}
|
327
|
+
return ip;
|
328
|
+
}
|
329
|
+
|
330
|
+
export function htmlDecode(str) {
|
331
|
+
var s = "";
|
332
|
+
if (str.length == 0) return "";
|
333
|
+
s = str.replace(/&/g, "&");
|
334
|
+
s = s.replace(/</g, "<");
|
335
|
+
s = s.replace(/>/g, ">");
|
336
|
+
s = s.replace(/ /g, " ");
|
337
|
+
s = s.replace(/'/g, "'");
|
338
|
+
s = s.replace(/"/g, '"');
|
339
|
+
return s;
|
340
|
+
}
|
341
|
+
|
342
|
+
export { bcrypt };
|
package/global/env.js
ADDED
package/global/global.js
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
import { pathToFileURL, fileURLToPath } from "url";
|
2
|
+
import path from "path";
|
3
|
+
const ROOT_PATH = process.cwd();
|
4
|
+
|
5
|
+
const APP_PATH = path.join(ROOT_PATH, "app");
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const globals = {
|
8
|
+
__filename,
|
9
|
+
__dirname: path.dirname(__filename),
|
10
|
+
ROOT_PATH,
|
11
|
+
APP_PATH,
|
12
|
+
CONFIG_PATH: path.join(ROOT_PATH, "config"),
|
13
|
+
EXTEND_PATH: path.join(APP_PATH, "extend"),
|
14
|
+
PUBLIC_PATH: path.join(ROOT_PATH, "public"),
|
15
|
+
MODULES_PATH: path.join(APP_PATH, "modules"),
|
16
|
+
COMMON_PATH: path.join(APP_PATH, "common"),
|
17
|
+
HELPER_PATH: path.join(APP_PATH, "helper"),
|
18
|
+
};
|
19
|
+
|
20
|
+
for (const [key, value] of Object.entries(globals)) {
|
21
|
+
Object.defineProperty(global, key, {
|
22
|
+
value,
|
23
|
+
writable: false, // 禁止修改
|
24
|
+
configurable: false, // 禁止删除或重新定义
|
25
|
+
enumerable: true, // 可枚举(调试时可见)
|
26
|
+
});
|
27
|
+
}
|
package/global/import.js
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
import fs from "fs/promises";
|
3
|
+
import { pathToFileURL } from "url";
|
4
|
+
import { createRequire } from "module";
|
5
|
+
|
6
|
+
|
7
|
+
/**
|
8
|
+
* @description 安全导入ES模块文件
|
9
|
+
* @param {string} filepath - 文件路径
|
10
|
+
* @returns {Promise<object|null>} 模块对象或null
|
11
|
+
*/
|
12
|
+
const importFile = async (filepath) => {
|
13
|
+
if (!filepath || typeof filepath !== 'string') {
|
14
|
+
console.error('错误: 文件路径必须是有效的字符串');
|
15
|
+
return null;
|
16
|
+
}
|
17
|
+
|
18
|
+
try {
|
19
|
+
// 检查文件是否存在
|
20
|
+
await fs.access(filepath);
|
21
|
+
|
22
|
+
// 转换为文件URL以支持ES模块导入
|
23
|
+
const fileUrl = pathToFileURL(filepath).href;
|
24
|
+
const module = await import(fileUrl);
|
25
|
+
|
26
|
+
// 返回默认导出或整个模块
|
27
|
+
return module.default || module;
|
28
|
+
} catch (error) {
|
29
|
+
if (error.code === 'ENOENT') {
|
30
|
+
console.error(`文件不存在: ${filepath}`);
|
31
|
+
} else if (error.code === 'EACCES') {
|
32
|
+
console.error(`没有权限访问文件: ${filepath}`);
|
33
|
+
} else {
|
34
|
+
console.error(`导入文件时出错 [${filepath}]:`, error.message);
|
35
|
+
}
|
36
|
+
return null;
|
37
|
+
}
|
38
|
+
};
|
39
|
+
|
40
|
+
/**
|
41
|
+
* @description: 兼容低版本 Node 的 CommonJS 模块加载
|
42
|
+
*/
|
43
|
+
export const importjs = createRequire(import.meta.url);
|
44
|
+
|
45
|
+
global.requirejs = importjs;
|
46
|
+
global.importFile = importFile;
|
package/global/index.js
ADDED
package/helper/api.js
ADDED
package/helper/bind.js
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
/**
|
3
|
+
* @description 实例化一个类,并将该类的所有方法绑定到一个新的对象上。
|
4
|
+
* @param {Function} className - 需要实例化的类。
|
5
|
+
*@returns {Object} 包含绑定方法的对象。
|
6
|
+
*/
|
7
|
+
export const bindClass = function(className) {
|
8
|
+
let obj = {};
|
9
|
+
const cls = new className();
|
10
|
+
Object.getOwnPropertyNames(cls.constructor.prototype).forEach(
|
11
|
+
(methodName) => {
|
12
|
+
if (
|
13
|
+
methodName !== "constructor" &&
|
14
|
+
typeof cls[methodName] === "function"
|
15
|
+
) {
|
16
|
+
obj[methodName] = cls[methodName].bind(cls);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
);
|
20
|
+
return obj;
|
21
|
+
}
|