chanjs 2.1.1 → 2.3.1

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.
Files changed (61) hide show
  1. package/App.js +384 -0
  2. package/base/Context.js +78 -0
  3. package/base/Controller.js +137 -0
  4. package/base/Database.js +314 -0
  5. package/base/Service.js +539 -0
  6. package/common/api.js +25 -0
  7. package/common/category.js +22 -0
  8. package/common/code.js +42 -0
  9. package/common/email.js +110 -0
  10. package/common/index.js +7 -0
  11. package/common/pages.js +86 -0
  12. package/common/sms.js +104 -0
  13. package/common/utils.js +73 -0
  14. package/config/code.js +110 -52
  15. package/config/index.js +10 -0
  16. package/config/paths.js +60 -0
  17. package/extend/art-template.js +46 -28
  18. package/extend/index.js +6 -0
  19. package/global/env.js +11 -5
  20. package/global/global.js +63 -39
  21. package/global/import.js +43 -39
  22. package/global/index.js +8 -3
  23. package/helper/cache.js +182 -0
  24. package/helper/data-parse.js +121 -37
  25. package/helper/db.js +71 -83
  26. package/helper/file.js +158 -208
  27. package/helper/filter.js +34 -0
  28. package/helper/html.js +30 -47
  29. package/helper/index.js +29 -5
  30. package/helper/ip.js +48 -31
  31. package/helper/jwt.js +78 -11
  32. package/helper/loader.js +93 -50
  33. package/helper/request.js +41 -144
  34. package/helper/sign.js +96 -33
  35. package/helper/time.js +89 -74
  36. package/helper/tree.js +77 -0
  37. package/index.js +15 -181
  38. package/middleware/cookie.js +20 -4
  39. package/middleware/cors.js +20 -0
  40. package/middleware/favicon.js +21 -5
  41. package/middleware/header.js +26 -9
  42. package/middleware/index.js +14 -23
  43. package/middleware/preventRetry.js +30 -0
  44. package/middleware/setBody.js +24 -10
  45. package/middleware/static.js +31 -10
  46. package/middleware/template.js +34 -14
  47. package/middleware/validator.js +43 -23
  48. package/middleware/waf.js +147 -287
  49. package/package.json +1 -1
  50. package/utils/checker.js +68 -0
  51. package/utils/error-handler.js +115 -0
  52. package/utils/error.js +81 -0
  53. package/utils/index.js +6 -0
  54. package/utils/keywords.js +126 -0
  55. package/utils/rate-limit.js +116 -0
  56. package/utils/response.js +103 -64
  57. package/utils/xss-filter.js +42 -0
  58. package/core/controller.js +0 -33
  59. package/core/index.js +0 -3
  60. package/core/service.js +0 -307
  61. package/middleware/log.js +0 -21
package/helper/file.js CHANGED
@@ -1,208 +1,158 @@
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
-
6
- export const dirname = (url) => {
7
- const __filename = fileURLToPath(url);
8
- return path.dirname(__filename);
9
- };
10
-
11
- /**
12
- * 获取指定路径的文件树结构
13
- *
14
- * @param {string} basePath - 要扫描的基础路径
15
- * @param {boolean} [deep=true] - 是否深度遍历子目录
16
- * @returns {Promise<Array<Object>>} 文件树数组,每个元素包含文件/目录信息
17
- * @returns {string} return[].name - 名称
18
- * @returns {string} return[].path - 完整路径
19
- * @returns {string} return[].relativePath - 相对于APP_PATH的路径
20
- * @returns {'directory'|'file'} return[].type - 类型(目录或文件)
21
- * @returns {number} return[].size - 大小(字节)
22
- * @returns {Date} return[].modified - 最后修改时间
23
- * @returns {number} return[].depth - 深度
24
- * @returns {Array<Object>} [return[].children] - 子目录内容(仅目录有)
25
- * @throws {Error} 当路径不存在或没有访问权限时抛出错误
26
- */
27
- export const getFileTree = async (basePath, deep = true) => {
28
- try {
29
- const stats = await fs.stat(basePath);
30
- if (!stats.isDirectory()) {
31
- return [];
32
- }
33
-
34
- const items = await fs.readdir(basePath);
35
- const tree = [];
36
-
37
- for (const item of items) {
38
- const itemPath = path.join(basePath, item);
39
- const itemStats = await fs.stat(itemPath);
40
-
41
- const treeItem = {
42
- name: item,
43
- path: itemPath,
44
- relativePath: path.relative(ROOT_PATH, itemPath),
45
- type: itemStats.isDirectory() ? "directory" : "file",
46
- size: itemStats.size,
47
- modified: itemStats.mtime,
48
- depth:
49
- itemPath.split(path.sep).length -
50
- path.resolve(ROOT_PATH).split(path.sep).length,
51
- };
52
-
53
- if (treeItem.type === "directory" && deep) {
54
- treeItem.children = await getFileTree(itemPath, deep);
55
- }
56
-
57
- tree.push(treeItem);
58
- }
59
-
60
- // 排序:文件夹在前,文件在后,名称按字母顺序排列
61
- return tree.sort((a, b) => {
62
- if (a.type === b.type) {
63
- return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
64
- }
65
- return a.type === "directory" ? -1 : 1;
66
- });
67
- } catch (error) {
68
- console.error(`获取文件树失败: ${basePath}`, error);
69
- throw error;
70
- }
71
- };
72
-
73
- /**
74
- * 读取文件内容(UTF-8编码)
75
- *
76
- * @param {string} filePath - 要读取的文件路径
77
- * @returns {Promise<string>} 文件内容字符串
78
- * @throws {Error} 当文件不存在、无法读取或不是文件时抛出错误
79
- */
80
- export const readFileContent = async (filePath) => {
81
- try {
82
- // 先检查路径是否安全
83
- if (!isPathSafe(filePath, APP_PATH) && !isPathSafe(filePath, ROOT_PATH)) {
84
- throw new Error(`路径不安全: ${filePath}`);
85
- }
86
-
87
- // 检查是否为文件
88
- const stats = await fs.stat(filePath);
89
- if (!stats.isFile()) {
90
- throw new Error(`不是文件: ${filePath}`);
91
- }
92
-
93
- return await fs.readFile(filePath, "utf8");
94
- } catch (error) {
95
- console.error(`读取文件失败: ${filePath}`, error);
96
- throw error;
97
- }
98
- };
99
-
100
- /**
101
- * 保存内容到文件(UTF-8编码)
102
- *
103
- * @param {string} filePath - 要保存的文件路径
104
- * @param {string} content - 要写入的内容
105
- * @returns {Promise<void>} 无返回值
106
- * @throws {Error} 当路径不安全、无法写入或发生其他错误时抛出错误
107
- */
108
- export const saveFileContent = async (filePath, content) => {
109
- try {
110
- // 先检查路径是否安全
111
- if (!isPathSafe(filePath, APP_PATH) && !isPathSafe(filePath, ROOT_PATH)) {
112
- throw new Error(`路径不安全: ${filePath}`);
113
- }
114
-
115
- // 确保目录存在
116
- const dirname = path.dirname(filePath);
117
- await fs.mkdir(dirname, { recursive: true });
118
-
119
- // 写入文件
120
- await fs.writeFile(filePath, content, "utf8");
121
- } catch (error) {
122
- console.error(`保存文件失败: ${filePath}`, error);
123
- throw error;
124
- }
125
- };
126
-
127
- /**
128
- * 验证路径是否在基础路径范围内(防止路径遍历攻击)
129
- *
130
- * @param {string} requestedPath - 要验证的路径
131
- * @param {string} basePath - 基础路径,requestedPath必须在此路径下
132
- * @returns {boolean} 如果路径安全则返回true,否则返回false
133
- */
134
- export const isPathSafe = (requestedPath, basePath) => {
135
- const resolvedRequestedPath = path.resolve(requestedPath);
136
- const resolvedBasePath = path.resolve(basePath);
137
- // 检查请求的路径是否以基础路径为前缀
138
- return resolvedRequestedPath.startsWith(resolvedBasePath);
139
- };
140
-
141
- /**
142
- * 删除指定路径的图片文件
143
- *
144
- * @param {string} link - 图片文件的路径
145
- * @returns {boolean} 成功删除返回true,否则返回false
146
- * @description 同步操作,会检查文件是否存在后再删除
147
- */
148
- export function delImg(link) {
149
- try {
150
- // 先检查路径是否安全
151
- if (!isPathSafe(link, APP_PATH)) {
152
- console.error(`路径不安全: ${link}`);
153
- return false;
154
- }
155
-
156
- // 检查文件是否存在
157
- accessSync(link);
158
-
159
- // 删除文件
160
- unlinkSync(link);
161
- return true;
162
- } catch (err) {
163
- console.error(`删除图片失败: ${link}`, err);
164
- return false;
165
- }
166
- }
167
-
168
- /**
169
- * 递归创建目录(同步操作)
170
- *
171
- * @param {string} dirname - 要创建的目录路径
172
- * @returns {boolean} 成功创建或目录已存在返回true,否则返回false
173
- * @description 类似于mkdir -p命令,会创建所有不存在的父目录
174
- */
175
- export function mkdirsSync(dirname) {
176
- try {
177
- // 先检查路径是否安全
178
- if (!isPathSafe(dirname, APP_PATH)) {
179
- console.error(`路径不安全: ${dirname}`);
180
- return false;
181
- }
182
-
183
- if (existsSync(dirname)) {
184
- return true;
185
- }
186
-
187
- // 递归创建父目录
188
- const parentDir = path.dirname(dirname);
189
- if (mkdirsSync(parentDir)) {
190
- mkdirSync(dirname);
191
- return true;
192
- }
193
-
194
- return false;
195
- } catch (err) {
196
- console.error(`创建目录失败: ${dirname}`, err);
197
- return false;
198
- }
199
- }
200
-
201
- export default {
202
- mkdirsSync,
203
- delImg,
204
- isPathSafe,
205
- saveFileContent,
206
- readFileContent,
207
- getFileTree,
208
- };
1
+ import path from "path";
2
+ import fs from "fs";
3
+
4
+ /**
5
+ * 获取当前文件的目录路径
6
+ * @param {string} importMetaUrl - import.meta.url 的值
7
+ * @returns {string} 当前文件的目录路径
8
+ * @description
9
+ * 使用 import.meta.url 获取当前模块的 URL,然后提取目录路径
10
+ * 适用于 ES 模块中获取 __dirname 的替代方案
11
+ * @example
12
+ * const __dirname = dirname(import.meta.url);
13
+ * const configPath = path.join(__dirname, 'config.json');
14
+ */
15
+ export function dirname(importMetaUrl) {
16
+ const url = new URL(importMetaUrl);
17
+ return path.dirname(url.pathname);
18
+ }
19
+
20
+ /**
21
+ * 删除图片文件
22
+ * @param {string} filePath - 相对于项目根目录的文件路径
23
+ * @returns {boolean} 如果文件存在并被删除返回 true,否则返回 false
24
+ * @description
25
+ * 删除指定路径的图片文件
26
+ * 路径是相对于项目根目录(process.cwd())的
27
+ * 如果文件不存在,不会抛出错误,直接返回 false
28
+ * @example
29
+ * const deleted = delImg('/uploads/avatar/123.jpg');
30
+ * if (deleted) {
31
+ * console.log('图片已删除');
32
+ * }
33
+ */
34
+ export function delImg(filePath) {
35
+ const fullPath = path.join(process.cwd(), filePath);
36
+ if (fs.existsSync(fullPath)) {
37
+ fs.unlinkSync(fullPath);
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ /**
44
+ * 获取文件树结构
45
+ * @param {string} dirPath - 目录路径
46
+ * @param {boolean} [recursive=true] - 是否递归获取子目录
47
+ * @returns {Array} 文件树数组
48
+ * @description
49
+ * 递归读取目录下的所有文件和子目录,返回树形结构
50
+ * @example
51
+ * const tree = getFileTree('/path/to/directory');
52
+ * // 返回: [{ name: 'file.html', path: '/path/to/directory/file.html', type: 'file' }, ...]
53
+ */
54
+ export function getFileTree(dirPath, recursive = true, basePath = null) {
55
+ if (!fs.existsSync(dirPath)) {
56
+ return [];
57
+ }
58
+
59
+ const stats = fs.statSync(dirPath);
60
+ if (!stats.isDirectory()) {
61
+ return [];
62
+ }
63
+
64
+ const items = [];
65
+ const files = fs.readdirSync(dirPath);
66
+
67
+ const base = basePath || process.cwd();
68
+
69
+ for (const file of files) {
70
+ const fullPath = path.join(dirPath, file);
71
+ const fileStats = fs.statSync(fullPath);
72
+
73
+ if (fileStats.isDirectory() && recursive) {
74
+ const children = getFileTree(fullPath, recursive, basePath);
75
+ items.push({
76
+ name: file,
77
+ path: fullPath,
78
+ type: 'directory',
79
+ children: children
80
+ });
81
+ } else if (fileStats.isFile()) {
82
+ const relativePath = path.relative(base, fullPath).replace(/\\/g, '/');
83
+ items.push({
84
+ name: file,
85
+ path: fullPath,
86
+ relativePath: relativePath.startsWith('public') ? '/' + relativePath.replace(/^\//, '') : fullPath,
87
+ type: 'file',
88
+ size: fileStats.size
89
+ });
90
+ }
91
+ }
92
+
93
+ return items;
94
+ }
95
+
96
+ /**
97
+ * 读取文件内容
98
+ * @param {string} filePath - 文件路径
99
+ * @returns {string} 文件内容
100
+ * @description
101
+ * 读取指定文件的内容
102
+ * @example
103
+ * const content = readFileContent('/path/to/file.html');
104
+ */
105
+ export function readFileContent(filePath) {
106
+ if (!fs.existsSync(filePath)) {
107
+ throw new Error('文件不存在');
108
+ }
109
+
110
+ const stats = fs.statSync(filePath);
111
+ if (!stats.isFile()) {
112
+ throw new Error('路径不是文件');
113
+ }
114
+
115
+ return fs.readFileSync(filePath, 'utf-8');
116
+ }
117
+
118
+ /**
119
+ * 保存文件内容
120
+ * @param {string} filePath - 文件路径
121
+ * @param {string} content - 文件内容
122
+ * @returns {void}
123
+ * @description
124
+ * 将内容保存到指定文件,如果目录不存在会自动创建
125
+ * @example
126
+ * saveFileContent('/path/to/file.html', '<html>...</html>');
127
+ */
128
+ export function saveFileContent(filePath, content) {
129
+ const dirPath = path.dirname(filePath);
130
+
131
+ if (!fs.existsSync(dirPath)) {
132
+ fs.mkdirSync(dirPath, { recursive: true });
133
+ }
134
+
135
+ fs.writeFileSync(filePath, content, 'utf-8');
136
+ }
137
+
138
+ /**
139
+ * 检查路径是否安全
140
+ * @param {string} targetPath - 目标路径
141
+ * @param {string} basePath - 基础路径
142
+ * @returns {boolean} 路径是否安全
143
+ * @description
144
+ * 检查目标路径是否在基础路径范围内,防止路径遍历攻击
145
+ * @example
146
+ * const safe = isPathSafe('/project/file.html', '/project');
147
+ */
148
+ export function isPathSafe(targetPath, basePath) {
149
+ const normalizedTarget = path.normalize(targetPath);
150
+ const normalizedBase = path.normalize(basePath);
151
+
152
+ if (normalizedTarget.includes('..')) {
153
+ return false;
154
+ }
155
+
156
+ const relativePath = path.relative(normalizedBase, normalizedTarget);
157
+ return !relativePath.startsWith('..');
158
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 从数组中的每个对象筛选指定字段
3
+ * @param {Array<Object>} data - 要处理的对象数组
4
+ * @param {Array<string>} fields - 需要保留的字段名数组
5
+ * @returns {Array<Object>} 只包含指定字段的新对象数组
6
+ * @description
7
+ * 遍历数组中的每个对象,只保留指定的字段
8
+ * 返回一个新的数组,不修改原始数据
9
+ * @example
10
+ * const data = [
11
+ * { id: 1, name: '张三', age: 25, email: 'test@example.com' },
12
+ * { id: 2, name: '李四', age: 30, email: 'demo@example.com' }
13
+ * ];
14
+ * const result = filterFields(data, ['id', 'name']);
15
+ * console.log(result);
16
+ * // [
17
+ * // { id: 1, name: '张三' },
18
+ * // { id: 2, name: '李四' }
19
+ * // ]
20
+ */
21
+ export function filterFields(data, fields) {
22
+ if (!Array.isArray(data) || data.length === 0) {
23
+ return [];
24
+ }
25
+ return data.map((item) => {
26
+ const filteredItem = {};
27
+ for (const field of fields) {
28
+ if (item.hasOwnProperty(field)) {
29
+ filteredItem[field] = item[field];
30
+ }
31
+ }
32
+ return filteredItem;
33
+ });
34
+ }
package/helper/html.js CHANGED
@@ -1,47 +1,30 @@
1
- /**
2
- * 处理最常用的HTML特殊字符实体 - 编码
3
- * @param {string} str - 需要编码的字符串
4
- * @returns {string} 编码后的字符串
5
- */
6
- export const htmlEncode = (str) => {
7
- // 非字符串直接返回,避免报错
8
- if (typeof str !== "string") return str;
9
-
10
- // 一次性替换所有特殊字符,减少函数调用
11
- return str.replace(
12
- /[&<>"' ]/g,
13
- (match) =>
14
- ({
15
- "&": "&amp;",
16
- "<": "&lt;",
17
- ">": "&gt;",
18
- '"': "&quot;",
19
- "'": "&#39;",
20
- " ": "&nbsp;",
21
- }[match])
22
- );
23
- };
24
-
25
- /**
26
- * 处理最常用的HTML特殊字符实体 - 解码
27
- * @param {string} str - 需要解码的字符串
28
- * @returns {string} 解码后的字符串
29
- */
30
- export const htmlDecode = (str) => {
31
- // 非字符串直接返回,避免报错
32
- if (typeof str !== "string") return str;
33
-
34
- // 一次性替换所有常见实体,减少函数调用
35
- return str.replace(
36
- /&amp;|&lt;|&gt;|&nbsp;|&#39;|&quot;/g,
37
- (match) =>
38
- ({
39
- "&amp;": "&",
40
- "&lt;": "<",
41
- "&gt;": ">",
42
- "&nbsp;": " ",
43
- "&#39;": "'",
44
- "&quot;": '"',
45
- }[match])
46
- );
47
- };
1
+ /**
2
+ * 解码 HTML 实体字符
3
+ * @param {string} str - 包含 HTML 实体的字符串
4
+ * @returns {string} 解码后的字符串,如果输入为空则返回空字符串
5
+ * @description
6
+ * HTML 实体字符转换为对应的普通字符
7
+ * 支持的实体字符:
8
+ * - &amp; -> &
9
+ * - &lt; -> <
10
+ * - &gt; -> >
11
+ * - &quot; -> "
12
+ * - &apos; -> '
13
+ * - &nbsp; -> 空格
14
+ * @example
15
+ * const html = '&lt;div&gt;Hello &amp; World&lt;/div&gt;';
16
+ * const decoded = htmlDecode(html);
17
+ * console.log(decoded); // '<div>Hello & World</div>'
18
+ */
19
+ export function htmlDecode(str) {
20
+ if (!str) return "";
21
+ const htmlEntities = {
22
+ "&amp;": "&",
23
+ "&lt;": "<",
24
+ "&gt;": ">",
25
+ "&quot;": '"',
26
+ "&apos;": "'",
27
+ "&nbsp;": " ",
28
+ };
29
+ return str.replace(/&[a-z]+;/gi, (match) => htmlEntities[match] || match);
30
+ }
package/helper/index.js CHANGED
@@ -1,5 +1,29 @@
1
- import { db } from "./db.js";
2
- import { loaderSort, loadConfig } from "./loader.js";
3
- import { formatTime, formatDateFields } from "./time.js";
4
-
5
- export { db, loaderSort, loadConfig, formatTime, formatDateFields };
1
+ export { db } from "./db.js";
2
+ export { prefixDbConfig } from "./db.js";
3
+ export { loaderSort, loadConfig, clearConfigCache, bindInstance, loadController } from "./loader.js";
4
+ export { formatTime } from "./time.js";
5
+ export { formatDateFields } from "./time.js";
6
+ export { default as Cache } from "./cache.js";
7
+ export { dirname } from "./file.js";
8
+ export { delImg } from "./file.js";
9
+ export { getFileTree } from "./file.js";
10
+ export { readFileContent } from "./file.js";
11
+ export { saveFileContent } from "./file.js";
12
+ export { isPathSafe } from "./file.js";
13
+ export { htmlDecode } from "./html.js";
14
+ export { getIp } from "./ip.js";
15
+ export { verifyToken } from "./jwt.js";
16
+ export { generateToken } from "./jwt.js";
17
+ export { setToken } from "./jwt.js";
18
+ export { getToken } from "./jwt.js";
19
+ export { signData } from "./sign.js";
20
+ export { verifySign } from "./sign.js";
21
+ export { aesEncrypt } from "./sign.js";
22
+ export { aesDecrypt } from "./sign.js";
23
+ export { request } from "./request.js";
24
+ export { dataParse } from "./data-parse.js";
25
+ export { arrToObj } from "./data-parse.js";
26
+ export { parseJsonFields } from "./data-parse.js";
27
+ export { buildTree } from "./data-parse.js";
28
+ export { tree, treeById } from "./tree.js";
29
+ export { filterFields } from "./filter.js";
package/helper/ip.js CHANGED
@@ -1,35 +1,52 @@
1
1
  /**
2
- * 获取用户登录IP地址
3
- * @param {Object} req - Express请求对象
4
- * @returns {string} 格式化后的IP地址,默认返回空字符串
2
+ * 从请求对象中获取客户端真实 IP 地址
3
+ * @param {Object} req - Express 请求对象
4
+ * @param {string} [req.ip] - Express 解析的 IP(已包含代理头部解析)
5
+ * @param {Object} [req.headers] - 请求头对象
6
+ * @param {string} [req.headers.cf-connecting-ip] - Cloudflare 连接 IP
7
+ * @returns {string} 客户端 IP 地址,如果无法获取则返回 "0.0.0.0"
8
+ * @description
9
+ * 从 Express 的 req.ip 获取客户端 IP。
10
+ *
11
+ * 注意:需要在 App.js 中设置 trust proxy:
12
+ * app.set("trust proxy", true);
13
+ *
14
+ * 设置 trust proxy 后,Express 会自动从以下头部解析真实 IP:
15
+ * - X-Forwarded-For(反向代理,如 Nginx)
16
+ * - X-Real-IP
17
+ * - CF-Connecting-IP(Cloudflare CDN)
18
+ *
19
+ * 此方法按优先级从以下来源获取 IP 地址:
20
+ * 1. req.ip(Express 自动解析,已包含代理头部处理)
21
+ * 2. cf-connecting-ip 请求头(Cloudflare CDN,优先级更高)
22
+ * 3. 默认值 "0.0.0.0"
23
+ *
24
+ * 此方法适用于部署在反向代理(如 Nginx)或 CDN 后端的应用
25
+ * @example
26
+ * app.use((req, res, next) => {
27
+ * const ip = getIp(req);
28
+ * console.log(`访问 IP: ${ip}`);
29
+ * next();
30
+ * });
5
31
  */
6
- export const getIp = (req) => {
7
- // 优先级从高到低获取可能的IP来源
8
- const ipSources = [
9
- req.ip, // Express内置的IP获取(已处理代理)
10
- req.connection?.remoteAddress, // 底层连接的远程地址
11
- req.socket?.remoteAddress, // 套接字的远程地址
12
- req.connection?.socket?.remoteAddress, // 连接套接字的远程地址
13
- ];
14
-
15
- // 从来源中找到第一个有效IP字符串
16
- let ip = ipSources.find(
17
- (source) => typeof source === "string" && source.trim() !== ""
18
- );
19
-
20
- // 若未找到有效IP,直接返回空
21
- if (!ip) return "";
22
-
23
- // 处理多IP情况(取第一个)
24
- ip = ip.split(",").shift().trim();
25
-
26
- // IPv6转IPv4处理
27
- if (ip === "::1") {
28
- return "127.0.0.1"; // 本地环回地址
32
+ export function getIp(req) {
33
+ if (req.ip) {
34
+ const ip = req.ip;
35
+
36
+ const isLocalAddress = ip === '::1' ||
37
+ ip === '127.0.0.1' ||
38
+ ip === '0.0.0.0' ||
39
+ ip === 'localhost';
40
+
41
+ if (!isLocalAddress) {
42
+ return ip;
43
+ }
29
44
  }
30
- if (ip.startsWith("::ffff:")) {
31
- return ip.slice(7); // 去除IPv6映射的IPv4前缀
45
+
46
+ const headers = req.headers || {};
47
+ if (headers["cf-connecting-ip"]) {
48
+ return headers["cf-connecting-ip"];
32
49
  }
33
-
34
- return ip;
35
- };
50
+
51
+ return null;
52
+ }