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 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(APP_PATH, itemPath),
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(APP_PATH).split(path.sep).length
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, APP_PATH)) {
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, APP_PATH)) {
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
- export default {
14
- getFileTree,
15
- readFileContent,
16
- saveFileContent,
17
- isPathSafe,
18
- setToken,
19
- getToken,
20
- formatDay,
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
 
@@ -0,0 +1,2 @@
1
+ import { z } from "zod";
2
+ export { z };
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
- bindClass,
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
- if (fs.existsSync(EXTEND_PATH)) {
129
- const files = fs
130
- .readdirSync(EXTEND_PATH)
131
- .filter((file) => file.endsWith(".js"));
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(EXTEND_PATH, file);
139
+ const filePath = path.join(_path, file);
134
140
  let helperModule = await importFile(filePath);
135
- Chan.helper[file.replace(".js", "")] = helperModule;
141
+ // Chan.common[file.replace(".js", "")] = helperModule;
142
+ // 将模块导出的所有方法/属性,直接混入到 Chan.common 上
143
+ Object.assign(Chan[key], helperModule);
136
144
  }
137
145
  }
138
146
  }
@@ -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
- return res.status(403).send("请求被安全策略拦截");
182
+
183
+ return res.status(403).send("非法风险请求,已拦截");
186
184
  }
187
185
 
188
186
  next();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.0.5",
4
+ "version": "2.0.6",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
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
@@ -1,21 +0,0 @@
1
- let success = {
2
- code: 200,
3
- msg: "success",
4
- };
5
-
6
- let fail = {
7
- code: 201,
8
- msg: "error",
9
- };
10
-
11
- const error = {
12
- code: 500,
13
- msg: "error",
14
- }
15
-
16
-
17
- export default {
18
- success,
19
- fail,
20
- error,
21
- }
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
- };
package/extend/import.js DELETED
@@ -1,6 +0,0 @@
1
- import express from "express";
2
-
3
- import { z } from 'zod';
4
-
5
-
6
- export { express, z };