chanjs 2.0.5 → 2.0.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/global/global.js +0 -4
- package/global/import.js +0 -6
- package/helper/data-parse.js +85 -0
- package/helper/file.js +11 -5
- package/helper/index.js +8 -24
- package/helper/loader.js +0 -2
- package/helper/validate.js +2 -0
- package/index.js +35 -27
- package/middleware/index.js +3 -1
- package/middleware/waf.js +9 -11
- package/package.json +1 -1
- package/common/api.js +0 -20
- package/common/code.js +0 -20
- package/common/global.js +0 -61
- package/extend/api.js +0 -21
- package/extend/file.js +0 -92
- package/extend/import.js +0 -6
- package/extend/upload.js +0 -80
- package/extend/utils.js +0 -342
- package/helper/api.js +0 -14
- package/helper/bind.js +0 -21
- package/helper/dataparse.js +0 -24
- package/helper/response.js +0 -20
- package/helper/safe.js +0 -263
- package/helper/tree.js +0 -32
- package/middleware/safe.js +0 -144
- package/utils/bind.js +0 -21
- package/utils/dataparse.js +0 -24
- package/utils/db.js +0 -75
- package/utils/index.js +0 -12
- package/utils/loader.js +0 -65
- package/utils/response.js +0 -20
package/global/global.js
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
import { pathToFileURL, fileURLToPath } from "url";
|
2
2
|
import path from "path";
|
3
3
|
const ROOT_PATH = process.cwd();
|
4
|
-
|
5
4
|
const APP_PATH = path.join(ROOT_PATH, "app");
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
7
5
|
const globals = {
|
8
|
-
__filename,
|
9
|
-
__dirname: path.dirname(__filename),
|
10
6
|
ROOT_PATH,
|
11
7
|
APP_PATH,
|
12
8
|
CONFIG_PATH: path.join(ROOT_PATH, "config"),
|
package/global/import.js
CHANGED
@@ -3,7 +3,6 @@ import fs from "fs/promises";
|
|
3
3
|
import { pathToFileURL } from "url";
|
4
4
|
import { createRequire } from "module";
|
5
5
|
|
6
|
-
|
7
6
|
/**
|
8
7
|
* @description 安全导入ES模块文件
|
9
8
|
* @param {string} filepath - 文件路径
|
@@ -16,14 +15,9 @@ const importFile = async (filepath) => {
|
|
16
15
|
}
|
17
16
|
|
18
17
|
try {
|
19
|
-
// 检查文件是否存在
|
20
18
|
await fs.access(filepath);
|
21
|
-
|
22
|
-
// 转换为文件URL以支持ES模块导入
|
23
19
|
const fileUrl = pathToFileURL(filepath).href;
|
24
20
|
const module = await import(fileUrl);
|
25
|
-
|
26
|
-
// 返回默认导出或整个模块
|
27
21
|
return module.default || module;
|
28
22
|
} catch (error) {
|
29
23
|
if (error.code === 'ENOENT') {
|
@@ -0,0 +1,85 @@
|
|
1
|
+
/**
|
2
|
+
* 将数组转换为键值对对象
|
3
|
+
* @param {Array} arr - 包含键值对的数组
|
4
|
+
* @param {string} keyField - 作为键的字段名
|
5
|
+
* @param {string} valueField - 作为值的字段名
|
6
|
+
* @returns {Object} 转换后的对象
|
7
|
+
*/
|
8
|
+
export const arrToObj = (arr, keyField = 'config_key', valueField = 'config_value') => {
|
9
|
+
// 输入验证
|
10
|
+
if (!Array.isArray(arr)) {
|
11
|
+
throw new Error('arrToObj 期望接收数组作为第一个参数');
|
12
|
+
}
|
13
|
+
|
14
|
+
return arr.reduce((result, item) => {
|
15
|
+
if (item && typeof item === 'object') {
|
16
|
+
const key = item[keyField];
|
17
|
+
const value = item[valueField];
|
18
|
+
if (key !== undefined && value !== undefined) {
|
19
|
+
result[key] = value;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return result;
|
23
|
+
}, {});
|
24
|
+
};
|
25
|
+
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @description 将字符串中的 JSON 字符串转换为对象
|
29
|
+
* @param {*} obj - 包含 JSON 字符串的对象
|
30
|
+
* @returns
|
31
|
+
*/
|
32
|
+
export function parseJsonFields(obj) {
|
33
|
+
const result = {};
|
34
|
+
for (const key in obj) {
|
35
|
+
if (!obj.hasOwnProperty(key)) continue;
|
36
|
+
const value = obj[key];
|
37
|
+
// 如果是字符串,并且看起来像 JSON(以 { 或 [ 开头)
|
38
|
+
if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
|
39
|
+
try {
|
40
|
+
result[key] = JSON.parse(value);
|
41
|
+
} catch (e) {
|
42
|
+
console.warn(`JSON parse failed for field: ${key}`, e);
|
43
|
+
result[key] = value; // 保留原始值
|
44
|
+
}
|
45
|
+
} else {
|
46
|
+
result[key] = value;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
return result;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
/**
|
55
|
+
* @param {Array} arr - 原始数据数组
|
56
|
+
* @param {number|string} [pid=0] - 根节点父ID
|
57
|
+
* @param {string} [idKey='id'] - ID字段名
|
58
|
+
* @param {string} [pidKey='pid'] - 父ID字段名
|
59
|
+
* @param {string} [childrenKey='children'] - 子节点字段名
|
60
|
+
* @returns {Array} 树形结构数组
|
61
|
+
*/
|
62
|
+
export function buildTree(arr, pid = 0, idKey = 'id', pidKey = 'pid', childrenKey = 'children') {
|
63
|
+
// 基础参数校验
|
64
|
+
if (!Array.isArray(arr)) return [];
|
65
|
+
|
66
|
+
const tree = [];
|
67
|
+
|
68
|
+
for (let i = 0; i < arr.length; i++) {
|
69
|
+
const item = arr[i];
|
70
|
+
// 找到当前层级的节点
|
71
|
+
if (item[pidKey] === pid) {
|
72
|
+
// 递归查找子节点(通过slice创建子数组,避免重复遍历已处理项)
|
73
|
+
const children = buildTree(arr.slice(i + 1), item[idKey], idKey, pidKey, childrenKey);
|
74
|
+
|
75
|
+
// 有子节点则添加,避免空数组
|
76
|
+
if (children.length) {
|
77
|
+
item[childrenKey] = children;
|
78
|
+
}
|
79
|
+
|
80
|
+
tree.push(item);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
return tree;
|
85
|
+
}
|
package/helper/file.js
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
import fs from 'fs/promises';
|
2
2
|
import { accessSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
3
3
|
import path from 'path';
|
4
|
+
import { fileURLToPath } from 'url';
|
5
|
+
|
6
|
+
export const dirname = (url) =>{
|
7
|
+
const __filename = fileURLToPath(url);
|
8
|
+
return path.dirname(__filename);
|
9
|
+
}
|
10
|
+
|
4
11
|
|
5
12
|
/**
|
6
13
|
* 获取指定路径的文件树结构
|
@@ -35,11 +42,11 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
35
42
|
const treeItem = {
|
36
43
|
name: item,
|
37
44
|
path: itemPath,
|
38
|
-
relativePath: path.relative(
|
45
|
+
relativePath: path.relative(ROOT_PATH, itemPath),
|
39
46
|
type: itemStats.isDirectory() ? 'directory' : 'file',
|
40
47
|
size: itemStats.size,
|
41
48
|
modified: itemStats.mtime,
|
42
|
-
depth: itemPath.split(path.sep).length - path.resolve(
|
49
|
+
depth: itemPath.split(path.sep).length - path.resolve(ROOT_PATH).split(path.sep).length
|
43
50
|
};
|
44
51
|
|
45
52
|
if (treeItem.type === 'directory' && deep) {
|
@@ -72,7 +79,7 @@ export const getFileTree = async (basePath, deep = true) => {
|
|
72
79
|
export const readFileContent = async (filePath) => {
|
73
80
|
try {
|
74
81
|
// 先检查路径是否安全
|
75
|
-
if (!isPathSafe(filePath,
|
82
|
+
if (!isPathSafe(filePath,APP_PATH) && !isPathSafe(filePath,ROOT_PATH)) {
|
76
83
|
throw new Error(`路径不安全: ${filePath}`);
|
77
84
|
}
|
78
85
|
|
@@ -100,7 +107,7 @@ export const readFileContent = async (filePath) => {
|
|
100
107
|
export const saveFileContent = async (filePath, content) => {
|
101
108
|
try {
|
102
109
|
// 先检查路径是否安全
|
103
|
-
if (!isPathSafe(filePath,
|
110
|
+
if (!isPathSafe(filePath,APP_PATH) && !isPathSafe(filePath,ROOT_PATH)) {
|
104
111
|
throw new Error(`路径不安全: ${filePath}`);
|
105
112
|
}
|
106
113
|
|
@@ -126,7 +133,6 @@ export const saveFileContent = async (filePath, content) => {
|
|
126
133
|
export const isPathSafe = (requestedPath, basePath) => {
|
127
134
|
const resolvedRequestedPath = path.resolve(requestedPath);
|
128
135
|
const resolvedBasePath = path.resolve(basePath);
|
129
|
-
|
130
136
|
// 检查请求的路径是否以基础路径为前缀
|
131
137
|
return resolvedRequestedPath.startsWith(resolvedBasePath);
|
132
138
|
};
|
package/helper/index.js
CHANGED
@@ -1,25 +1,9 @@
|
|
1
|
-
import {
|
2
|
-
getFileTree,
|
3
|
-
readFileContent,
|
4
|
-
saveFileContent,
|
5
|
-
isPathSafe,
|
6
|
-
} from "./file.js";
|
7
|
-
import { setToken, getToken } from "./token.js";
|
8
|
-
import { formatDay, fromatTime } from "./time.js";
|
9
|
-
import { buildTree } from "./tree.js";
|
10
|
-
import { htmlDecode } from "./html.js";
|
11
|
-
import { getIp } from "./ip.js";
|
12
1
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
fromatTime,
|
22
|
-
buildTree,
|
23
|
-
htmlDecode,
|
24
|
-
getIp
|
25
|
-
};
|
2
|
+
import { db } from './db.js'
|
3
|
+
import { loaderSort, loadConfig } from './loader.js'
|
4
|
+
|
5
|
+
export {
|
6
|
+
db,
|
7
|
+
loaderSort,
|
8
|
+
loadConfig,
|
9
|
+
}
|
package/helper/loader.js
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import fs from "fs";
|
2
2
|
import path from "path";
|
3
|
-
import { bindClass } from "./bind.js";
|
4
3
|
|
5
4
|
/**
|
6
5
|
*
|
@@ -24,7 +23,6 @@ export const getPackage = async function () {
|
|
24
23
|
|
25
24
|
export const loadConfig = async function () {
|
26
25
|
let config = await importFile("config/index.js");
|
27
|
-
console.log("config", config);
|
28
26
|
return config;
|
29
27
|
};
|
30
28
|
|
package/index.js
CHANGED
@@ -1,15 +1,8 @@
|
|
1
1
|
import "./global/index.js";
|
2
2
|
import path from "path";
|
3
3
|
import fs from "fs";
|
4
|
-
import
|
5
|
-
|
6
|
-
db,
|
7
|
-
loaderSort,
|
8
|
-
getPackage,
|
9
|
-
loadConfig,
|
10
|
-
loadController,
|
11
|
-
} from "./utils/index.js";
|
12
|
-
import { express, z } from "./extend/import.js";
|
4
|
+
import express from "express";
|
5
|
+
|
13
6
|
import { Controller, Service } from "./core/index.js";
|
14
7
|
import {
|
15
8
|
log,
|
@@ -21,23 +14,16 @@ import {
|
|
21
14
|
setTemplate,
|
22
15
|
Cors,
|
23
16
|
validator,
|
17
|
+
waf,
|
24
18
|
} from "./middleware/index.js";
|
19
|
+
import { db, loaderSort, loadConfig } from "./helper/index.js";
|
20
|
+
import {dirname} from "./helper/file.js";
|
25
21
|
|
26
22
|
class Chan {
|
27
|
-
|
28
23
|
//版本号
|
29
24
|
#version = "0.0.0";
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
static helper = {
|
34
|
-
bindClass,
|
35
|
-
getPackage,
|
36
|
-
loadController,
|
37
|
-
db,
|
38
|
-
z,
|
39
|
-
validator,
|
40
|
-
};
|
25
|
+
static helper = {};
|
26
|
+
static common = {}; //公共方法
|
41
27
|
static config = {}; //配置
|
42
28
|
static Service = Service; //服务
|
43
29
|
static Controller = Controller; //控制器
|
@@ -84,10 +70,12 @@ class Chan {
|
|
84
70
|
setFavicon(this.app);
|
85
71
|
setCookie(this.app, cookieKey);
|
86
72
|
setBody(this.app, BODY_LIMIT);
|
73
|
+
waf(this.app);
|
87
74
|
setTemplate(this.app, { views, env });
|
88
75
|
setStatic(this.app, statics);
|
89
76
|
Cors(this.app, cors);
|
90
77
|
setHeader(this.app, { APP_NAME, APP_VERSION });
|
78
|
+
|
91
79
|
}
|
92
80
|
|
93
81
|
//数据库操作
|
@@ -125,14 +113,34 @@ class Chan {
|
|
125
113
|
}
|
126
114
|
|
127
115
|
async loadExtend() {
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
116
|
+
let arr = [
|
117
|
+
{
|
118
|
+
_path: COMMON_PATH,
|
119
|
+
key: "common",
|
120
|
+
},
|
121
|
+
{
|
122
|
+
_path: HELPER_PATH,
|
123
|
+
key: "helper",
|
124
|
+
},
|
125
|
+
{
|
126
|
+
_path: path.join(dirname(import.meta.url), "helper"),
|
127
|
+
key: "helper",
|
128
|
+
},
|
129
|
+
];
|
130
|
+
for (let item of arr) {
|
131
|
+
await this.loadFn(item._path, item.key);
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
async loadFn(_path, key) {
|
136
|
+
if (fs.existsSync(_path)) {
|
137
|
+
const files = fs.readdirSync(_path).filter((file) => file.endsWith(".js"));
|
132
138
|
for (const file of files) {
|
133
|
-
const filePath = path.join(
|
139
|
+
const filePath = path.join(_path, file);
|
134
140
|
let helperModule = await importFile(filePath);
|
135
|
-
Chan.
|
141
|
+
// Chan.common[file.replace(".js", "")] = helperModule;
|
142
|
+
// 将模块导出的所有方法/属性,直接混入到 Chan.common 上
|
143
|
+
Object.assign(Chan[key], helperModule);
|
136
144
|
}
|
137
145
|
}
|
138
146
|
}
|
package/middleware/index.js
CHANGED
@@ -7,6 +7,7 @@ import {setHeader} from "./header.js";
|
|
7
7
|
import {setTemplate} from "./template.js";
|
8
8
|
import {validator} from "./validator.js";
|
9
9
|
import {Cors} from "./cors.js";
|
10
|
+
import {waf} from "./waf.js";
|
10
11
|
|
11
12
|
export {
|
12
13
|
log,
|
@@ -17,5 +18,6 @@ export {
|
|
17
18
|
setHeader,
|
18
19
|
setTemplate,
|
19
20
|
Cors,
|
20
|
-
validator
|
21
|
+
validator,
|
22
|
+
waf
|
21
23
|
}
|
package/middleware/waf.js
CHANGED
@@ -27,7 +27,7 @@ const keywords = [
|
|
27
27
|
".bak",
|
28
28
|
".aws",
|
29
29
|
".database",
|
30
|
-
".cookie",
|
30
|
+
// ".cookie",
|
31
31
|
".location",
|
32
32
|
".dump",
|
33
33
|
".ftp",
|
@@ -72,7 +72,6 @@ const keywords = [
|
|
72
72
|
"/php-cgi/",
|
73
73
|
"/wp-",
|
74
74
|
"/backup/",
|
75
|
-
"password",
|
76
75
|
"redirect",
|
77
76
|
"/phpMyAdmin/",
|
78
77
|
"/setup/",
|
@@ -86,12 +85,8 @@ const keywords = [
|
|
86
85
|
"a%",
|
87
86
|
"union",
|
88
87
|
"drop",
|
89
|
-
"update",
|
90
|
-
"insert",
|
91
|
-
"delete",
|
92
88
|
"alter",
|
93
89
|
"truncate",
|
94
|
-
"create",
|
95
90
|
"exec",
|
96
91
|
];
|
97
92
|
|
@@ -128,10 +123,10 @@ const { combinedStrRegex, combinedRegRegex } = (() => {
|
|
128
123
|
const safe = (req, res, next) => {
|
129
124
|
try {
|
130
125
|
// 1. 设置安全头(保持不变)
|
131
|
-
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
132
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
133
|
-
res.setHeader("Referrer-Policy", "no-referrer-when-downgrade");
|
134
|
-
res.removeHeader("Server");
|
126
|
+
// res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
127
|
+
// res.setHeader("X-Content-Type-Options", "nosniff");
|
128
|
+
// res.setHeader("Referrer-Policy", "no-referrer-when-downgrade");
|
129
|
+
// res.removeHeader("Server");
|
135
130
|
|
136
131
|
// 2. 构建检查文本:req.path(仅路径)+ query(非空才加)(优化冗余)
|
137
132
|
let checkText = req.path || "";
|
@@ -165,6 +160,8 @@ const safe = (req, res, next) => {
|
|
165
160
|
// 合并完整文本(无需 toLowerCase)
|
166
161
|
const fullText = checkText + bodyText;
|
167
162
|
|
163
|
+
|
164
|
+
|
168
165
|
// 4. 高效匹配:两次正则 test 替代 N 次循环(核心优化)
|
169
166
|
let foundMatch = false;
|
170
167
|
// 先检查字符串关键词合并正则(更快,因为无复杂正则逻辑)
|
@@ -182,7 +179,8 @@ const safe = (req, res, next) => {
|
|
182
179
|
ip: getIp(req),
|
183
180
|
userAgent: req.get("User-Agent") || "",
|
184
181
|
});
|
185
|
-
|
182
|
+
|
183
|
+
return res.status(403).send("非法风险请求,已拦截");
|
186
184
|
}
|
187
185
|
|
188
186
|
next();
|
package/package.json
CHANGED
package/common/api.js
DELETED
@@ -1,20 +0,0 @@
|
|
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
DELETED
@@ -1,20 +0,0 @@
|
|
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/common/global.js
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
|
2
|
-
import path from "path";
|
3
|
-
import fs from "fs";
|
4
|
-
import { pathToFileURL } from 'url'; // 新增顶部导入
|
5
|
-
import dotenv from "dotenv";
|
6
|
-
|
7
|
-
const ROOT_PATH = process.cwd();
|
8
|
-
const APP_PATH = path.join(ROOT_PATH, "app");
|
9
|
-
const CONFIG_PATH = path.join(APP_PATH, "config");
|
10
|
-
const EXTEND_PATH = path.join(APP_PATH, "extend");
|
11
|
-
const PUBLIC_PATH = path.join(APP_PATH, "public");
|
12
|
-
const MODULES_PATH = path.join(APP_PATH, "modules");
|
13
|
-
// 兼容低版本node common包
|
14
|
-
import { createRequire } from "module";
|
15
|
-
const requirejs = createRequire(import.meta.url);
|
16
|
-
|
17
|
-
//let user = getFilePath('app/controller/user.js')
|
18
|
-
|
19
|
-
//实现dirname
|
20
|
-
global.__dirname = path.dirname(new URL(import.meta.url).pathname);
|
21
|
-
//实现__filename
|
22
|
-
global.__filename = new URL(import.meta.url).pathname;
|
23
|
-
|
24
|
-
//加载环境变量
|
25
|
-
const envFile = process.env.ENV_FILE || '.env.prd'
|
26
|
-
dotenv.config({ path: path.join(ROOT_PATH, envFile) })
|
27
|
-
|
28
|
-
// app
|
29
|
-
global.APP_PATH = APP_PATH;
|
30
|
-
// config
|
31
|
-
global.CONFIG_PATH = CONFIG_PATH;
|
32
|
-
// run root path
|
33
|
-
global.ROOT_PATH = ROOT_PATH;
|
34
|
-
// extend
|
35
|
-
global.EXTEND_PATH = EXTEND_PATH;
|
36
|
-
// public
|
37
|
-
global.PUBLIC_PATH = PUBLIC_PATH;
|
38
|
-
// modules
|
39
|
-
global.MODULES_PATH = MODULES_PATH;
|
40
|
-
// require 兼容低版本node common包
|
41
|
-
global.requirejs = requirejs;
|
42
|
-
//解决多重...问题
|
43
|
-
const importRootFile = async (str) => {
|
44
|
-
let filepath = path.join(global.ROOT_PATH, str);
|
45
|
-
if (fs.existsSync(filepath)) {
|
46
|
-
const fileUrl = pathToFileURL(filepath).href; // 新增转换
|
47
|
-
const module = await import(fileUrl);
|
48
|
-
return module.default || module;
|
49
|
-
}
|
50
|
-
};
|
51
|
-
|
52
|
-
const importFile = async (filepath) => {
|
53
|
-
if (fs.existsSync(filepath)) {
|
54
|
-
const fileUrl = pathToFileURL(filepath).href; // 新增转换
|
55
|
-
const module = await import(fileUrl);
|
56
|
-
return module.default || module;
|
57
|
-
}
|
58
|
-
};
|
59
|
-
|
60
|
-
global.importFile = importFile;
|
61
|
-
global.importRootFile = importRootFile;
|
package/extend/api.js
DELETED
package/extend/file.js
DELETED
@@ -1,92 +0,0 @@
|
|
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
|
-
};
|