chanjs 2.7.2 → 2.7.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.
Files changed (67) hide show
  1. package/App.js +232 -16
  2. package/base/Aop.js +20 -3
  3. package/base/Container.js +80 -3
  4. package/base/Controller.js +38 -9
  5. package/base/Database.js +50 -0
  6. package/base/Event.js +12 -0
  7. package/base/{Service.js → Repository.js} +644 -539
  8. package/common/api.js +18 -8
  9. package/common/code.js +25 -15
  10. package/common/email.js +98 -17
  11. package/common/index.js +1 -1
  12. package/config/code.js +138 -82
  13. package/global/index.js +1 -1
  14. package/helper/index.js +43 -41
  15. package/index.js +19 -6
  16. package/loader/index.js +6 -0
  17. package/{helper → loader}/loader.js +41 -27
  18. package/middleware/compress.js +185 -0
  19. package/middleware/cors.js +36 -24
  20. package/middleware/header.js +5 -10
  21. package/middleware/index.js +1 -0
  22. package/middleware/log.js +27 -3
  23. package/middleware/setBody.js +9 -1
  24. package/middleware/static.js +2 -1
  25. package/middleware/template.js +139 -4
  26. package/middleware/waf.js +136 -76
  27. package/package.json +4 -4
  28. package/realtime/index.js +7 -0
  29. package/realtime/sse.js +424 -0
  30. package/realtime/websocket.js +540 -0
  31. package/response/index.js +12 -0
  32. package/response/response.js +258 -0
  33. package/schedule/index.js +6 -0
  34. package/schedule/schedule.js +491 -0
  35. package/{helper → security}/checker.js +23 -8
  36. package/security/index.js +14 -0
  37. package/{helper → security}/jwt.js +175 -107
  38. package/security/keywords.js +179 -0
  39. package/security/rate-limit.js +105 -0
  40. package/security/sign.js +210 -0
  41. package/security/xss-filter.js +63 -0
  42. package/storage/cache.js +258 -0
  43. package/storage/index.js +9 -0
  44. package/storage/redis.js +258 -0
  45. package/storage/store.js +266 -0
  46. package/{helper → utils}/file.js +106 -15
  47. package/{helper → utils}/filter.js +2 -1
  48. package/{helper → utils}/html.js +19 -1
  49. package/utils/index.js +34 -0
  50. package/{helper → utils}/ip.js +25 -16
  51. package/utils/request.js +172 -0
  52. package/{helper → utils}/time.js +1 -1
  53. package/utils/tree.js +121 -0
  54. package/common/category.js +0 -22
  55. package/common/sms.js +0 -104
  56. package/extend/art-template.js +0 -129
  57. package/extend/index.js +0 -6
  58. package/global/global.js +0 -63
  59. package/helper/cache.js +0 -187
  60. package/helper/keywords.js +0 -132
  61. package/helper/rate-limit.js +0 -116
  62. package/helper/request.js +0 -47
  63. package/helper/response.js +0 -180
  64. package/helper/sign.js +0 -96
  65. package/helper/tree.js +0 -77
  66. package/helper/xss-filter.js +0 -42
  67. /package/{helper → utils}/data-parse.js +0 -0
@@ -1,22 +0,0 @@
1
- /**
2
- * 分类工具函数
3
- * 提供分类相关的辅助方法
4
- */
5
-
6
- /**
7
- * 根据拼音或ID获取分类信息
8
- * @param {string|number} py - 拼音或ID
9
- * @param {Array} source - 分类数据源
10
- * @returns {Object} 包含分类对象和ID的对象
11
- */
12
- export function getChildrenId(py, source) {
13
- let cate = {};
14
- let id = "";
15
- source.forEach((item) => {
16
- if (item.pinyin == py || item.id == py) {
17
- cate = item;
18
- id = item.id;
19
- }
20
- });
21
- return { cate, id };
22
- }
package/common/sms.js DELETED
@@ -1,104 +0,0 @@
1
- import crypto from 'crypto';
2
-
3
- /**
4
- * 阿里云短信发送工具
5
- * 提供短信发送客户端创建和发送功能
6
- */
7
-
8
- const API_CONFIG = {
9
- host: 'https://sms.aliyuncs.com/',
10
- action: 'SingleSendSms',
11
- version: '2016-09-27',
12
- format: 'JSON',
13
- signatureMethod: 'HMAC-SHA1',
14
- signatureVersion: '1.0'
15
- };
16
-
17
- /**
18
- * 生成签名
19
- * @private
20
- * @param {Object} params - 请求参数
21
- * @param {string} secret - AccessKeySecret
22
- * @returns {string} Base64编码的签名
23
- */
24
- function generateSignature(params, secret) {
25
- const sortedParams = Object.keys(params).sort()
26
- .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
27
- .join('&');
28
-
29
- const signatureStr = `POST&${encodeURIComponent('/')}&${encodeURIComponent(sortedParams)}`;
30
-
31
- return crypto
32
- .createHmac('sha1', `${secret}&`)
33
- .update(Buffer.from(signatureStr, 'utf8'))
34
- .digest('base64');
35
- }
36
-
37
- /**
38
- * 创建短信客户端
39
- * @param {Object} config - 配置对象
40
- * @param {string} config.accessKeyId - AccessKey ID
41
- * @param {string} config.accessKeySecret - AccessKey Secret
42
- * @param {string} config.signName - 短信签名
43
- * @param {string} config.templateCode - 短信模板代码
44
- * @returns {Object} 短信客户端对象,包含send方法
45
- * @throws {Error} 缺少必要配置时抛出异常
46
- */
47
- export function createSmsClient(config) {
48
- const requiredFields = ['accessKeyId', 'accessKeySecret', 'signName', 'templateCode'];
49
- const missingFields = requiredFields.filter(field => !config[field]);
50
-
51
- if (missingFields.length) {
52
- throw new Error(`缺少必要配置: ${missingFields.join(', ')}`);
53
- }
54
-
55
- return {
56
- /**
57
- * 发送短信
58
- * @param {Object} options - 发送选项
59
- * @param {string} options.phone - 手机号
60
- * @param {Object} options.params - 模板参数
61
- * @returns {Promise<Object>} 发送结果
62
- * @throws {Error} 手机号或模板参数缺失,或发送失败时抛出异常
63
- */
64
- async send({ phone, params }) {
65
- if (!phone || !params) {
66
- throw new Error('手机号和模板参数为必填项');
67
- }
68
-
69
- const requestParams = {
70
- Action: API_CONFIG.action,
71
- Version: API_CONFIG.version,
72
- Format: API_CONFIG.format,
73
- AccessKeyId: config.accessKeyId,
74
- SignatureMethod: API_CONFIG.signatureMethod,
75
- SignatureVersion: API_CONFIG.signatureVersion,
76
- SignatureNonce: crypto.randomUUID(),
77
- Timestamp: new Date().toISOString(),
78
- SignName: config.signName,
79
- TemplateCode: config.templateCode,
80
- RecNum: phone,
81
- ParamString: JSON.stringify(params)
82
- };
83
-
84
- requestParams.Signature = generateSignature(requestParams, config.accessKeySecret);
85
-
86
- const formData = new URLSearchParams();
87
- Object.entries(requestParams).forEach(([key, value]) => formData.append(key, value));
88
-
89
- const response = await fetch(API_CONFIG.host, {
90
- method: 'POST',
91
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
92
- body: formData
93
- });
94
-
95
- const result = await response.json();
96
-
97
- if (result.Code) {
98
- throw new Error(`[${result.Code}] ${result.Message}`);
99
- }
100
-
101
- return result;
102
- }
103
- };
104
- }
@@ -1,129 +0,0 @@
1
- import template from "art-template";
2
- import dayjs from "dayjs";
3
- import relativeTime from "dayjs/plugin/relativeTime.js";
4
- import "dayjs/locale/zh-cn.js";
5
- import { createRequire } from 'module';
6
- const require = createRequire(import.meta.url);
7
- const { marked } = require('marked');
8
-
9
- dayjs.extend(relativeTime);
10
- dayjs.locale('zh-cn');
11
-
12
- template.defaults.native = false; // 禁用原生模板引擎 防止模板直接调用nodejs语法
13
- //template.defaults.debug = false; // 禁用调试模式
14
-
15
-
16
- /**
17
- * 日期格式化过滤器
18
- * @param {Date|string|number} date - 日期对象、日期字符串或时间戳
19
- * @param {string} format - 日期格式字符串
20
- * @returns {string} 格式化后的日期字符串
21
- */
22
- template.defaults.imports.dateFormat = function (date, format) {
23
- if (!date) {
24
- return "";
25
- }
26
- if (
27
- date instanceof Date ||
28
- typeof date === "string" ||
29
- typeof date === "number"
30
- ) {
31
- date = dayjs(date);
32
- } else {
33
- return "";
34
- }
35
- return date.format(format);
36
- };
37
-
38
- /**
39
- * 相对时间过滤器(如"刚刚"、"5分钟前"、"3小时前")
40
- * @param {Date|string|number} date - 日期对象、日期字符串或时间戳
41
- * @returns {string} 相对时间字符串
42
- */
43
- template.defaults.imports.timeAgo = function (date) {
44
- if (!date) return "";
45
- const d = dayjs(date);
46
- if (!d.isValid()) return "";
47
- return d.fromNow();
48
- };
49
-
50
- /**
51
- * 字符串截断过滤器
52
- * @param {string} str - 原始字符串
53
- * @param {number} length - 截断长度,默认10
54
- * @returns {string} 截断后的字符串
55
- */
56
- template.defaults.imports.truncate = (str, length = 10) => {
57
- return str.length > length ? str.slice(0, length) + "..." : str;
58
- };
59
-
60
- /**
61
- * 安全的 JSON 序列化过滤器
62
- * 用于在模板中调试和显示对象内容
63
- * @param {Object} obj - 要序列化的对象
64
- * @param {Array} keys - 可选,只返回指定的字段
65
- * @returns {string} JSON字符串
66
- */
67
- template.defaults.imports.safeStringify = (obj, keys) => {
68
- if (!obj) return 'null';
69
-
70
- // 如果指定了 keys,只返回这些字段
71
- if (keys && Array.isArray(keys) && keys.length > 0) {
72
- const filteredObj = {};
73
- keys.forEach(key => {
74
- if (obj.hasOwnProperty(key)) {
75
- filteredObj[key] = obj[key];
76
- }
77
- });
78
- return JSON.stringify(filteredObj, null, 2);
79
- }
80
-
81
- // 否则返回完整对象
82
- return JSON.stringify(obj, null, 2);
83
- };
84
-
85
- /**
86
- * 获取对象所有key的过滤器,方便调试查看数据结构
87
- * 用法:{{$data | objKeys}} 或 {{$data | objKeys ','}}
88
- * @param {Object} obj - 要获取key的对象
89
- * @param {string} separator - 分隔符,默认为换行
90
- * @returns {string} 所有key的字符串
91
- */
92
- template.defaults.imports.objKeys = (obj, separator) => {
93
- if (!obj || typeof obj !== 'object') return '';
94
- const sep = separator !== undefined ? separator : '\n';
95
- return Object.keys(obj).join(sep);
96
- };
97
-
98
- /**
99
- * Markdown 渲染过滤器
100
- * 自动检测内容是否为 Markdown 格式,如果是则渲染为 HTML
101
- * @param {string} content - 文章内容
102
- * @returns {string} 渲染后的 HTML
103
- */
104
- template.defaults.imports.renderContent = (content, editorType = 'rich', allowScript = 0) => {
105
- if (!content || typeof content !== 'string') {
106
- return content || '';
107
- }
108
-
109
- let html = content;
110
-
111
- // Markdown 需要转换
112
- if (editorType === 'md') {
113
- try {
114
- html = marked.parse(content);
115
- } catch (err) {
116
- console.error('[renderContent] Markdown 渲染失败:', err.message);
117
- html = content;
118
- }
119
- }
120
-
121
- // 如果 allowScript !== 1,转义 script 标签
122
- if (Number(allowScript) !== 1) {
123
- html = html
124
- .replace(/<script\b[^>]*>/gi, '&amp;lt;script&amp;gt;')
125
- .replace(/<\/script>/gi, '&amp;lt;/script&amp;gt;');
126
- }
127
-
128
- return html;
129
- };
package/extend/index.js DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * 扩展模块入口
3
- * 导入所有扩展功能
4
- */
5
-
6
- import "./art-template.js";
package/global/global.js DELETED
@@ -1,63 +0,0 @@
1
- import path from "path";
2
- import { readFileSync } from "fs";
3
- import { pathToFileURL, fileURLToPath } from "url";
4
- import { Paths } from "../config/index.js";
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- /**
10
- * 获取应用版本号
11
- * @returns {string} 版本号
12
- */
13
- const getVersion = () => {
14
- const packageJsonPath = path.join(Paths.rootPath, "package.json");
15
- const packageJson = JSON.parse(
16
- readFileSync(packageJsonPath, "utf8")
17
- );
18
- return packageJson?.version || "1.0.0";
19
- };
20
-
21
- /**
22
- * 全局变量定义
23
- * 将常用变量挂载到global对象上
24
- */
25
- const globals = {
26
- /**
27
- * 当前模块文件所在目录的绝对路径
28
- */
29
- __dirname,
30
- /**
31
- * 当前模块文件的绝对路径
32
- */
33
- __filename,
34
- /**
35
- * 应用版本号
36
- */
37
- APP_VERSION: getVersion(),
38
- /**
39
- * 路径配置对象
40
- */
41
- Paths,
42
- /**
43
- * 通用模块目录路径
44
- */
45
- COMMON_PATH: Paths.commonPath,
46
- /**
47
- * 辅助函数目录路径
48
- */
49
- HELPER_PATH: Paths.helperPath,
50
- /**
51
- * 扩展目录路径
52
- */
53
- EXTEND_PATH: Paths.extendPath,
54
- };
55
-
56
- for (const [key, value] of Object.entries(globals)) {
57
- Object.defineProperty(global, key, {
58
- value,
59
- writable: false,
60
- configurable: false,
61
- enumerable: true,
62
- });
63
- }
package/helper/cache.js DELETED
@@ -1,187 +0,0 @@
1
- /**
2
- * LRU(最近最少使用)缓存类
3
- * @class Cache
4
- * @description
5
- * 实现了一个基于内存的 LRU 缓存,支持 TTL(生存时间)和自动过期清理
6
- * 使用 Map 存储缓存数据,另一个 Map 记录访问顺序以实现 LRU 淘汰策略
7
- * @example
8
- * const cache = new Cache({ maxSize: 500, defaultTTL: 60000 });
9
- * cache.set('user:123', { name: '张三' }, 30000);
10
- * const user = cache.get('user:123');
11
- * console.log(user); // { name: '张三' }
12
- */
13
- class Cache {
14
- /**
15
- * 创建缓存实例
16
- * @param {Object} [options={}] - 配置选项
17
- * @param {number} [options.maxSize=1000] - 最大缓存条目数
18
- * @param {number} [options.defaultTTL=300000] - 默认过期时间(毫秒),默认 5 分钟
19
- * @description
20
- * 初始化缓存实例,设置最大容量和默认过期时间
21
- * 当缓存达到最大容量时,会自动淘汰最久未使用的条目
22
- */
23
- constructor(options = {}) {
24
- this.cache = new Map();
25
- this.accessOrder = new Map();
26
- this.maxSize = options.maxSize || 1000;
27
- this.defaultTTL = options.defaultTTL || 5 * 60 * 1000;
28
- }
29
-
30
- /**
31
- * 设置缓存值
32
- * @param {string} key - 缓存键
33
- * @param {*} value - 要缓存的值
34
- * @param {number} [ttl=this.defaultTTL] - 过期时间(毫秒)
35
- * @description
36
- * 将键值对存入缓存,如果缓存已满则淘汰最久未使用的条目
37
- * 每次设置都会更新访问时间 + 过期时间
38
- * @example
39
- * cache.set('key1', 'value1', 60000); // 60 秒后过期
40
- * cache.set('key2', { data: 123 }); // 使用默认过期时间
41
- */
42
- set(key, value, ttl = this.defaultTTL) {
43
- // 修复:缓存满 + 是新 key 才淘汰
44
- if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
45
- this._evictLRU();
46
- }
47
-
48
- // 修复:无论 key 是否存在,都更新 过期时间 + 访问时间
49
- this.cache.set(key, { value, expireAt: Date.now() + ttl });
50
- // LRU:先删除再插入,保证 Map 尾部是最新访问
51
- this.accessOrder.delete(key);
52
- this.accessOrder.set(key, Date.now());
53
- }
54
-
55
- /**
56
- * 获取缓存值
57
- * @param {string} key - 缓存键
58
- * @returns {*} 缓存的值,如果键不存在或已过期则返回 null
59
- * @description
60
- * 获取指定键的缓存值
61
- * 如果值已过期,会自动删除该条目
62
- * 每次获取都会更新访问时间 + 刷新过期时间
63
- * @example
64
- * const value = cache.get('key1');
65
- * if (value !== null) {
66
- * console.log('缓存命中:', value);
67
- * }
68
- */
69
- get(key) {
70
- const item = this.cache.get(key);
71
- if (!item) return null;
72
-
73
- const now = Date.now();
74
- // 已过期直接删除
75
- if (now > item.expireAt) {
76
- this.cache.delete(key);
77
- this.accessOrder.delete(key);
78
- return null;
79
- }
80
-
81
- // 修复:命中缓存 → 刷新 LRU 顺序 + 刷新 TTL
82
- this.accessOrder.delete(key);
83
- this.accessOrder.set(key, now);
84
- // 重新计算过期时间(访问续命,标准行为)
85
- const ttl = item.expireAt - (now - (item.expireAt - this.defaultTTL));
86
- this.cache.set(key, { value: item.value, expireAt: now + ttl });
87
-
88
- return item.value;
89
- }
90
-
91
- /**
92
- * 删除缓存条目
93
- * @param {string} key - 要删除的缓存键
94
- * @description
95
- * 从缓存中删除指定的键及其访问记录
96
- * @example
97
- * cache.del('key1');
98
- */
99
- del(key) {
100
- this.cache.delete(key);
101
- this.accessOrder.delete(key);
102
- }
103
-
104
- /**
105
- * 清空所有缓存
106
- * @description
107
- * 删除所有缓存条目和访问记录
108
- * @example
109
- * cache.clear();
110
- */
111
- clear() {
112
- this.cache.clear();
113
- this.accessOrder.clear();
114
- }
115
-
116
- /**
117
- * 检查键是否存在
118
- * @param {string} key - 要检查的缓存键
119
- * @returns {boolean} 如果键存在且未过期返回 true,否则返回 false
120
- * @description
121
- * 检查指定的键是否存在于缓存中且未过期
122
- * 如果键已过期,会自动删除该条目
123
- * @example
124
- * if (cache.has('key1')) {
125
- * console.log('缓存存在');
126
- * }
127
- */
128
- has(key) {
129
- const item = this.cache.get(key);
130
- if (!item) return false;
131
- if (Date.now() > item.expireAt) {
132
- this.cache.delete(key);
133
- this.accessOrder.delete(key);
134
- return false;
135
- }
136
- return true;
137
- }
138
-
139
- /**
140
- * 获取当前缓存大小
141
- * @returns {number} 当前缓存中的有效条目数
142
- * @description
143
- * 返回当前缓存中的条目数量
144
- * 会先清理已过期的条目
145
- * @example
146
- * console.log(`当前缓存大小: ${cache.size()}`);
147
- */
148
- size() {
149
- this._cleanupExpired();
150
- return this.cache.size;
151
- }
152
-
153
- /**
154
- * 淘汰最久未使用的条目(内部方法)
155
- * @private
156
- * @description
157
- * Map 头部就是最早访问,直接删除(O(1))
158
- */
159
- _evictLRU() {
160
- // 修复:Map 有序,keys().next() 直接拿到最久未使用的 key,性能 O(1)
161
- const oldestKey = this.accessOrder.keys().next().value;
162
- if (oldestKey) {
163
- this.cache.delete(oldestKey);
164
- this.accessOrder.delete(oldestKey);
165
- }
166
- }
167
-
168
- /**
169
- * 清理已过期的条目(内部方法)
170
- * @private
171
- * @description
172
- * 遍历所有缓存条目,删除已过期的条目
173
- * 在获取缓存大小时自动调用
174
- */
175
- _cleanupExpired() {
176
- const now = Date.now();
177
- for (const [key, item] of this.cache.entries()) {
178
- if (now > item.expireAt) {
179
- this.cache.delete(key);
180
- this.accessOrder.delete(key);
181
- }
182
- }
183
- }
184
- }
185
-
186
- export const cache = new Cache();
187
- export default Cache;
@@ -1,132 +0,0 @@
1
- /**
2
- * 安全关键词规则定义
3
- * 定义各类恶意攻击的关键词检测规则
4
- */
5
-
6
- export const KEYWORD_RULES = {
7
- /**
8
- * 整词匹配规则(需完整匹配)
9
- */
10
- wholeWord: [
11
- "netcat", "nc", "php-cgi", "process", "require","exec","import",
12
- "child_process", "execSync", "mainModule"
13
- ],
14
- /**
15
- * 敏感文件扩展名
16
- */
17
- extensions: [
18
- ".php", ".asp", ".aspx", ".jsp", ".jspx", ".do", ".action", ".cgi",
19
- ".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql",".env",".git"
20
- ],
21
- /**
22
- * 敏感目录名称
23
- */
24
- directories: [
25
- "/administrator", "/wp-admin", "phpMyAdmin", "cgi-bin",
26
- "setup", "staging", "internal", "debug", "metadata", "secret"
27
- ],
28
- /**
29
- * SQL 注入关键词
30
- */
31
- sqlInjection: [
32
- "sleep(", "benchmark(", "concat(", "extractvalue(", "updatexml(", "version(",
33
- "union select", "union all", "select @@", "drop ", "alter ", "truncate ",
34
- "(select", "information_schema", "load_file(", "into outfile", "into dumpfile"
35
- ],
36
- /**
37
- * 命令注入关键词
38
- */
39
- commandInjection: [
40
- "cmd=", "system(", "exec(", "shell_exec(", "passthru(",
41
- "eval(", "assert(", "preg_replace", "bash -i", "rm -rf",
42
- "wget ", "curl ", "chmod ", "base64_decode", "phpinfo()",
43
- "kill ", "killall", "shutdown", "reboot", "halt", "fdisk",
44
- "mkfs", "dd ", "ssh ", "scp ", "rsync", "nc ",
45
- "netcat", "nmap", "iptables", "systemctl", "service",
46
- "crontab", "at ", "su ", "sudo", "useradd",
47
- "userdel", "usermod", "groupadd", "groupdel", "passwd",
48
- "chpasswd", "mount ", "umount", "ln -s"
49
- ],
50
- /**
51
- * 路径遍历关键词
52
- */
53
- pathTraversal: [
54
- "../", "..\\", "/etc/passwd", "/etc/shadow", "/etc/hosts",
55
- "/etc/", "/var/www/", "/app/", "/root/", "__dirname", "__filename"
56
- ],
57
- /**
58
- * XSS 攻击关键词
59
- */
60
- xss: [
61
- "<script", "javascript:", "onerror=", "onload=", "onclick=",
62
- "alert(", "document.cookie", "document.write"
63
- ],
64
- /**
65
- * 编码绕过关键词
66
- */
67
- encoding: [
68
- "0x7e", "UNION%20SELECT", "%27OR%27", "{{", "}}", "${", "1+1"
69
- ],
70
- /**
71
- * 敏感标识符
72
- */
73
- sensitiveIdentifiers: [
74
- "wp-", "smtp", "redirect", "configs", ".well-known/",
75
- "fs.readFile", "fs.existsSync", "process.env", "process.argv"
76
- ],
77
- };
78
-
79
- /**
80
- * 关键词正则表达式缓存
81
- * 将关键词规则编译为正则表达式以提高检测性能
82
- */
83
- export let keywordRegexCache = (() => {
84
- const regexSpecialChars = /[.*+?^${}()|[\]\\]/g;
85
- const cache = {};
86
-
87
- for (const [category, keywords] of Object.entries(KEYWORD_RULES)) {
88
- if (!Array.isArray(keywords)) continue;
89
-
90
- cache[category] = keywords.map((keyword) => {
91
- const escaped = keyword.replace(regexSpecialChars, "\\$&").replace(/ /g, "\\s");
92
- return {
93
- keyword,
94
- regex: new RegExp(
95
- category === 'wholeWord' ? `\\b${escaped}\\b` : escaped,
96
- "i"
97
- ),
98
- };
99
- });
100
- }
101
-
102
- return cache;
103
- })();
104
-
105
- /**
106
- * 更新关键词缓存
107
- * @param {Object} newRules - 新的关键词规则
108
- * @description
109
- * 允许动态添加或更新关键词检测规则
110
- * @example
111
- * updateKeywordCache({
112
- * customKeywords: ['malicious', 'attack']
113
- * });
114
- */
115
- export function updateKeywordCache(newRules = {}) {
116
- const regexSpecialChars = /[.*+?^${}()|[\]\\]/g;
117
-
118
- for (const [category, keywords] of Object.entries(newRules)) {
119
- if (!Array.isArray(keywords)) continue;
120
-
121
- keywordRegexCache[category] = keywords.map((keyword) => {
122
- const escaped = keyword.replace(regexSpecialChars, "\\$&").replace(/ /g, "\\s");
123
- return {
124
- keyword,
125
- regex: new RegExp(
126
- category === 'wholeWord' ? `\\b${escaped}\\b` : escaped,
127
- "i"
128
- ),
129
- };
130
- });
131
- }
132
- }