chanjs 2.5.3 → 2.5.4

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/App.js CHANGED
@@ -10,8 +10,8 @@ import DatabaseManager from "./base/Database.js";
10
10
  // 引入配置和工具
11
11
  import { Paths } from "./config/index.js";
12
12
  import { getChildrenId, filterBody, filterImgFromStr, CODE as commonCode, sendMail, genRegEmailHtml, genResetPasswordEmail, pages, getHtmlFilesSync } from "./common/index.js";
13
- import { loadConfig, loadController, loaderSort, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, arrToObj, htmlDecode } from "./helper/index.js";
14
- import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf } from "./middleware/index.js";
13
+ import { loadConfig, loadController, loaderSort, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, getFolders, arrToObj, htmlDecode } from "./helper/index.js";
14
+ import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf, log } from "./middleware/index.js";
15
15
  import { errorResponse, notFoundResponse, parseDatabaseError, error as responseError, fail, success, checkKeywords } from "./utils/index.js";
16
16
  import { importFile, importjs } from "./global/import.js";
17
17
 
@@ -44,6 +44,7 @@ class Chan {
44
44
  readFileContent,
45
45
  saveFileContent,
46
46
  isPathSafe,
47
+ getFolders,
47
48
  arrToObj,
48
49
  htmlDecode,
49
50
  };
@@ -219,6 +220,7 @@ class Chan {
219
220
  statics = [],
220
221
  cors = {},
221
222
  PROXY = "false",
223
+ logger = {},
222
224
  waf: wafConfig = { enabled: false }
223
225
  } = config;
224
226
 
@@ -228,6 +230,7 @@ class Chan {
228
230
  setCookie(this.app, cookieKey);
229
231
  setBody(this.app, BODY_LIMIT);
230
232
  Cors(this.app, cors);
233
+ log(this.app, logger);
231
234
  setTemplate(this.app, { views, NODE_ENV });
232
235
  setHeader(this.app, { APP_NAME, APP_VERSION });
233
236
  }
@@ -1,5 +1,8 @@
1
1
  import template from "art-template";
2
2
  import dayjs from "dayjs";
3
+ import { createRequire } from 'module';
4
+ const require = createRequire(import.meta.url);
5
+ const { marked } = require('marked');
3
6
 
4
7
  //template.defaults.native = false; // 禁用原生模板引擎 防止模板直接调用nodejs语法
5
8
  //template.defaults.debug = false; // 禁用调试模式
@@ -45,3 +48,22 @@ template.defaults.imports.truncate = (str, length = 10) => {
45
48
  template.defaults.imports.safeStringify = (obj) => {
46
49
  return JSON.stringify(obj, null, 2);
47
50
  };
51
+
52
+ /**
53
+ * Markdown 渲染过滤器
54
+ * 自动检测内容是否为 Markdown 格式,如果是则渲染为 HTML
55
+ * @param {string} content - 文章内容
56
+ * @returns {string} 渲染后的 HTML
57
+ */
58
+ template.defaults.imports.renderMarkdown = (content) => {
59
+ if (!content || typeof content !== 'string') {
60
+ return content || '';
61
+ }
62
+
63
+ try {
64
+ return marked.parse(content);
65
+ } catch (err) {
66
+ console.error('[renderMarkdown] Markdown 渲染失败:', err.message);
67
+ return content;
68
+ }
69
+ };
package/helper/file.js CHANGED
@@ -156,3 +156,38 @@ export function isPathSafe(targetPath, basePath) {
156
156
  const relativePath = path.relative(normalizedBase, normalizedTarget);
157
157
  return !relativePath.startsWith('..');
158
158
  }
159
+
160
+ /**
161
+ * 获取指定文件夹下的所有文件夹
162
+ * @param {string} folderPath - 文件夹路径
163
+ * @returns {Array<string>} 文件夹名称数组
164
+ * @description
165
+ * 同步获取指定文件夹下的所有文件夹(不包含隐藏文件)
166
+ * @example
167
+ * const folders = getFolders('/path/to/directory');
168
+ * // 返回: ['default', 'test', ...]
169
+ */
170
+ export function getFolders(folderPath) {
171
+ if (!fs.existsSync(folderPath)) {
172
+ return [];
173
+ }
174
+
175
+ const items = fs.readdirSync(folderPath);
176
+ const folders = [];
177
+
178
+ for (const item of items) {
179
+ if (item.startsWith('.')) continue;
180
+
181
+ const itemPath = path.join(folderPath, item);
182
+ try {
183
+ const stat = fs.statSync(itemPath);
184
+ if (stat.isDirectory()) {
185
+ folders.push(item);
186
+ }
187
+ } catch (err) {
188
+ continue;
189
+ }
190
+ }
191
+
192
+ return folders;
193
+ }
package/helper/index.js CHANGED
@@ -8,6 +8,7 @@ export { getFileTree } from "./file.js";
8
8
  export { readFileContent } from "./file.js";
9
9
  export { saveFileContent } from "./file.js";
10
10
  export { isPathSafe } from "./file.js";
11
+ export { getFolders } from "./file.js";
11
12
  export { htmlDecode } from "./html.js";
12
13
  export { getIp } from "./ip.js";
13
14
  export { verifyToken } from "./jwt.js";
package/helper/ip.js CHANGED
@@ -37,14 +37,11 @@ export function getIp(req) {
37
37
  ip = ip.substring(7);
38
38
  }
39
39
 
40
- const isLocalAddress = ip === '::1' ||
41
- ip === '127.0.0.1' ||
42
- ip === '0.0.0.0' ||
43
- ip === 'localhost';
44
-
45
- if (!isLocalAddress) {
46
- return ip;
40
+ if (ip === '::1') {
41
+ ip = '127.0.0.1';
47
42
  }
43
+
44
+ return ip;
48
45
  }
49
46
 
50
47
  const headers = req.headers || {};
@@ -11,3 +11,4 @@ export { setHeader } from "./header.js";
11
11
  export { setTemplate } from "./template.js";
12
12
  export { Cors } from "./cors.js";
13
13
  export { waf } from "./waf.js";
14
+ export { log } from "./log.js";
@@ -0,0 +1,41 @@
1
+ import morgan from "morgan";
2
+ import { getIp } from "../helper/ip.js";
3
+
4
+ // 自定义 IP 令牌
5
+ morgan.token("ip", (req, res) => {
6
+ return getIp(req);
7
+ });
8
+
9
+ // 自定义用户信息令牌
10
+ morgan.token("user", (req, res) => {
11
+ if (req.user) {
12
+ return `${req.user.uid}:${req.user.username}`;
13
+ }
14
+ return "-";
15
+ });
16
+
17
+ // 自定义时间令牌(带日期)
18
+ morgan.token("datetime", (req, res) => {
19
+ return new Date().toISOString();
20
+ });
21
+
22
+ // 自定义 morgan 格式:时间 IP 用户 Method URL Status Length - Response-Time ms
23
+ morgan.format("chancms", (tokens, req, res) => {
24
+ return [
25
+ tokens.datetime(req, res),
26
+ tokens.ip(req, res),
27
+ tokens.user(req, res),
28
+ tokens.method(req, res),
29
+ tokens.url(req, res),
30
+ tokens.status(req, res),
31
+ tokens.res(req, res, "content-length") || "-",
32
+ "-",
33
+ tokens["response-time"](req, res),
34
+ "ms",
35
+ ].join(" ");
36
+ });
37
+
38
+ export const log = (app, logger) => {
39
+ const level = logger?.level || "chancms";
40
+ app.use(morgan(level === "chancms" ? "chancms" : level));
41
+ };
package/middleware/waf.js CHANGED
@@ -104,7 +104,7 @@ function createWafMiddleware(wafConfig) {
104
104
 
105
105
  if (matched) {
106
106
  const { category, keyword } = matched;
107
- console.error(`[WAF 拦截] IP:${clientIp} 路径:${requestPath} 关键词:${keyword} 类别:${category}`);
107
+ console.warn(`[WAF 拦截] IP:${clientIp} 路径:${requestPath} 关键词:${keyword} 类别:${category}`);
108
108
 
109
109
  const htmlResponse = `
110
110
  <!DOCTYPE html>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.5.3",
4
+ "version": "2.5.4",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
package/utils/keywords.js CHANGED
@@ -16,7 +16,7 @@ export const KEYWORD_RULES = {
16
16
  */
17
17
  extensions: [
18
18
  ".php", ".asp", ".aspx", ".jsp", ".jspx", ".do", ".action", ".cgi",
19
- ".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql"
19
+ ".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql",".env",".git"
20
20
  ],
21
21
  /**
22
22
  * 敏感目录名称