chanjs 2.7.4 → 2.7.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/USAGE.md +533 -0
- package/config/index.js +37 -6
- package/core/App.js +166 -0
- package/core/BaseComponent.js +27 -0
- package/core/Container.js +68 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +323 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +101 -0
- package/core/bootstrap/hook-runner.js +64 -0
- package/core/bootstrap/middleware.js +35 -0
- package/core/bootstrap/router-loader.js +53 -0
- package/core/errors.js +251 -0
- package/core/loader.js +89 -0
- package/core/registry.js +17 -0
- package/doc/Cache.md +279 -106
- package/doc/Common.md +590 -134
- package/doc/Controller.md +166 -95
- package/doc/Help.md +299 -698
- package/doc/QuickStart.md +116 -0
- package/doc/Repository.md +560 -0
- package/doc/Service.md +201 -527
- package/index.js +61 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +15 -17
- package/middleware/header.js +15 -16
- package/middleware/index.js +11 -11
- package/middleware/log.js +26 -56
- package/middleware/static.js +15 -28
- package/middleware/template.js +75 -115
- package/middleware/validate.js +79 -0
- package/middleware/waf.js +176 -197
- package/package.json +9 -2
- package/response/code.js +73 -0
- package/response/index.js +9 -6
- package/response/response.js +82 -236
- package/security/checker.js +26 -74
- package/security/index.js +4 -9
- package/security/jwt.js +84 -139
- package/security/keywords.js +33 -137
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +58 -198
- package/storage/index.js +3 -6
- package/storage/redis.js +124 -181
- package/storage/store.js +163 -188
- package/utils/data-parse.js +42 -186
- package/utils/file.js +73 -244
- package/utils/filter.js +22 -25
- package/utils/html.js +49 -33
- package/utils/index.js +20 -7
- package/utils/ip.js +31 -71
- package/utils/logger.js +117 -0
- package/utils/pages.js +55 -0
- package/utils/paths.js +18 -0
- package/utils/request.js +95 -136
- package/utils/signal.js +87 -0
- package/utils/time.js +33 -75
- package/utils/tree.js +112 -104
- package/App.js +0 -533
- package/base/Aop.js +0 -195
- package/base/Container.js +0 -161
- package/base/Controller.js +0 -65
- package/base/Database.js +0 -133
- package/base/Event.js +0 -61
- package/base/Repository.js +0 -644
- package/common/api.js +0 -35
- package/common/code.js +0 -52
- package/common/email.js +0 -191
- package/common/index.js +0 -5
- package/common/pages.js +0 -120
- package/common/utils.js +0 -73
- package/config/code.js +0 -166
- package/config/paths.js +0 -60
- package/doc/Aop.md +0 -269
- package/doc/Email.md +0 -114
- package/doc/Event.md +0 -232
- package/global/env.js +0 -11
- package/global/import.js +0 -39
- package/global/index.js +0 -8
- package/helper/index.js +0 -79
- package/loader/index.js +0 -6
- package/loader/loader.js +0 -138
- package/middleware/compress.js +0 -185
- package/middleware/setBody.js +0 -32
- package/realtime/index.js +0 -7
- package/realtime/sse.js +0 -424
- package/realtime/websocket.js +0 -540
- package/schedule/index.js +0 -6
- package/schedule/schedule.js +0 -491
package/storage/cache.js
CHANGED
|
@@ -1,258 +1,118 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* incr
|
|
3
|
-
*
|
|
4
|
-
* 与 redis.js 的 DEFAULT_INCR_TTL 保持一致
|
|
2
|
+
* 单Map实现LRU+惰性TTL过期清理,对齐Redis incr行为,限流专用内存存储
|
|
3
|
+
* 特性:访问刷新LRU、自增不续期、批量惰性清理、容量淘汰
|
|
5
4
|
*/
|
|
6
|
-
const
|
|
5
|
+
const CACHE_CONST = Object.freeze({
|
|
6
|
+
DEFAULT_MAX_SIZE: 100000,
|
|
7
|
+
DEFAULT_TTL: 5 * 60 * 1000,
|
|
8
|
+
DEFAULT_INCR_TTL: 60 * 1000,
|
|
9
|
+
CLEANUP_INTERVAL: 10 * 1000,
|
|
10
|
+
CLEANUP_BATCH_LIMIT: 1000,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_INCR_TTL = CACHE_CONST.DEFAULT_INCR_TTL;
|
|
7
14
|
|
|
8
|
-
/**
|
|
9
|
-
* LRU(最近最少使用)缓存类
|
|
10
|
-
* @class Cache
|
|
11
|
-
* @description
|
|
12
|
-
* 基于内存的 LRU 缓存,支持 TTL 和自动过期清理。
|
|
13
|
-
* 改进点:
|
|
14
|
-
* 1. 单 Map 实现 LRU(删除再插入保证顺序),避免双 Map 开销
|
|
15
|
-
* 2. 修复 TTL 续期 bug:get 时仅刷新 LRU 顺序,不再续期 TTL(避免热点 key 永不过期)
|
|
16
|
-
* 3. 默认上限 10 万条(约 20MB),可配置
|
|
17
|
-
* 4. 惰性批量清理:set 时按频率触发过期清理,不使用定时器
|
|
18
|
-
* 5. incr/incrAndExpire:自增不刷新 TTL(与 Redis 行为对齐)
|
|
19
|
-
* @example
|
|
20
|
-
* const cache = new Cache({ maxSize: 100000, defaultTTL: 60000 });
|
|
21
|
-
* cache.set('user:123', { name: '张三' }, 30000);
|
|
22
|
-
* const user = cache.get('user:123');
|
|
23
|
-
*/
|
|
24
15
|
class Cache {
|
|
25
16
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @param {Object} [options={}] - 配置选项
|
|
28
|
-
* @param {number} [options.maxSize=100000] - 最大缓存条目数,默认 10 万条约 20MB
|
|
29
|
-
* @param {number} [options.defaultTTL=300000] - 默认过期时间(毫秒),默认 5 分钟
|
|
17
|
+
* @param {{maxSize?: number; defaultTTL?: number}} opts
|
|
30
18
|
*/
|
|
31
|
-
constructor(
|
|
19
|
+
constructor(opts = {}) {
|
|
32
20
|
this.map = new Map();
|
|
33
|
-
this.maxSize =
|
|
34
|
-
this.defaultTTL =
|
|
35
|
-
// 惰性清理控制
|
|
21
|
+
this.maxSize = opts.maxSize ?? CACHE_CONST.DEFAULT_MAX_SIZE;
|
|
22
|
+
this.defaultTTL = opts.defaultTTL ?? CACHE_CONST.DEFAULT_TTL;
|
|
36
23
|
this._lastCleanup = Date.now();
|
|
37
|
-
this._cleanupInterval =
|
|
38
|
-
this._cleanupBatch = 200; // 每次最多检查 200 条
|
|
24
|
+
this._cleanupInterval = CACHE_CONST.CLEANUP_INTERVAL;
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
/**
|
|
42
|
-
* 设置缓存值
|
|
43
|
-
* @param {string} key - 缓存键
|
|
44
|
-
* @param {*} value - 要缓存的值
|
|
45
|
-
* @param {number} [ttl=this.defaultTTL] - 过期时间(毫秒)
|
|
46
|
-
*/
|
|
27
|
+
/** 写入缓存;ttl<=0 视为永久存储(与 redis.set 行为对齐) */
|
|
47
28
|
set(key, value, ttl = this.defaultTTL) {
|
|
48
29
|
this._maybeCleanup();
|
|
49
|
-
|
|
50
|
-
// 达到上限且是新 key → 淘汰 Map 头部(最久未访问)
|
|
51
|
-
if (this.map.size >= this.maxSize && !this.map.has(key)) {
|
|
52
|
-
this._evictLRU();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 先删除再插入,保证 Map 末尾是最新访问
|
|
30
|
+
if (this.map.size >= this.maxSize && !this.map.has(key)) this._evictLRU();
|
|
56
31
|
this.map.delete(key);
|
|
57
|
-
|
|
32
|
+
const expireAt = ttl > 0 ? Date.now() + ttl : Infinity;
|
|
33
|
+
this.map.set(key, { value, expireAt });
|
|
58
34
|
}
|
|
59
35
|
|
|
60
|
-
/**
|
|
61
|
-
* 获取缓存值
|
|
62
|
-
* @param {string} key - 缓存键
|
|
63
|
-
* @returns {*} 缓存的值,如果键不存在或已过期则返回 null
|
|
64
|
-
* @description
|
|
65
|
-
* 仅刷新 LRU 顺序,不续期 TTL(修复旧版 bug)
|
|
66
|
-
*/
|
|
36
|
+
/** 读取,刷新LRU顺序 */
|
|
67
37
|
get(key) {
|
|
68
|
-
const item = this.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const now = Date.now();
|
|
72
|
-
if (now > item.expireAt) {
|
|
73
|
-
this.map.delete(key);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// LRU:删除再插入,刷新到 Map 末尾
|
|
78
|
-
this.map.delete(key);
|
|
79
|
-
this.map.set(key, item);
|
|
80
|
-
return item.value;
|
|
38
|
+
const item = this._touch(key);
|
|
39
|
+
return item ? item.value : null;
|
|
81
40
|
}
|
|
82
41
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
*/
|
|
86
|
-
del(key) {
|
|
87
|
-
return this.map.delete(key);
|
|
88
|
-
}
|
|
42
|
+
/** 存在性校验,命中同样刷新LRU */
|
|
43
|
+
has(key) { return !!this._touch(key); }
|
|
89
44
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
clear() {
|
|
94
|
-
this.map.clear();
|
|
95
|
-
}
|
|
45
|
+
del = key => this.map.delete(key);
|
|
46
|
+
clear = () => this.map.clear();
|
|
47
|
+
size() { return this.map.size; }
|
|
96
48
|
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
* 与 get 行为对齐:命中也刷新 LRU 访问顺序
|
|
100
|
-
* 避免高频 has 查询的 key 被提前淘汰
|
|
101
|
-
*/
|
|
102
|
-
has(key) {
|
|
49
|
+
/** 访问并刷新LRU,过期项自动清理(单次 delete+set 维护 Map 插入序) */
|
|
50
|
+
_touch(key) {
|
|
103
51
|
const item = this.map.get(key);
|
|
104
|
-
if (!item) return
|
|
52
|
+
if (!item) return null;
|
|
105
53
|
if (Date.now() > item.expireAt) {
|
|
106
54
|
this.map.delete(key);
|
|
107
|
-
return
|
|
55
|
+
return null;
|
|
108
56
|
}
|
|
109
|
-
//
|
|
57
|
+
// 利用 Map 插入序特性:delete+set 将 key 移至末尾(最近访问)
|
|
110
58
|
this.map.delete(key);
|
|
111
59
|
this.map.set(key, item);
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 获取当前缓存大小(触发一次完整清理)
|
|
117
|
-
*/
|
|
118
|
-
size() {
|
|
119
|
-
this._cleanupAll();
|
|
120
|
-
return this.map.size;
|
|
60
|
+
return item;
|
|
121
61
|
}
|
|
122
62
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
* @param {string} key - 缓存键
|
|
126
|
-
* @param {number} [ttlMs=DEFAULT_INCR_TTL] - 新建 key 时的 TTL(毫秒)
|
|
127
|
-
* @returns {number} 自增后的值
|
|
128
|
-
* @description
|
|
129
|
-
* - 新建 key:设置值为 1,TTL = ttlMs
|
|
130
|
-
* - 已存在 key:值 +1,**不刷新 TTL**(与 Redis INCR 行为对齐)
|
|
131
|
-
*
|
|
132
|
-
* 重要:自增不会续期 TTL,这是有意设计。
|
|
133
|
-
* Rate Limit 场景下,每个 IP 的窗口由首次请求决定,后续请求只递增不续期。
|
|
134
|
-
*/
|
|
135
|
-
incr(key, ttlMs = DEFAULT_INCR_TTL) {
|
|
63
|
+
/** 自增,新建key使用默认incrTTL,存量不续期TTL */
|
|
64
|
+
incr(key, ttlMs = CACHE_CONST.DEFAULT_INCR_TTL) {
|
|
136
65
|
this._maybeCleanup();
|
|
137
|
-
|
|
138
|
-
const now = Date.now();
|
|
139
|
-
|
|
140
|
-
if (!item || now > item.expireAt) {
|
|
141
|
-
// 新建 key,设置默认 TTL
|
|
142
|
-
if (this.map.size >= this.maxSize && !this.map.has(key)) {
|
|
143
|
-
this._evictLRU();
|
|
144
|
-
}
|
|
145
|
-
this.map.set(key, { value: 1, expireAt: now + ttlMs });
|
|
146
|
-
return 1;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 已存在:自增,**不刷新 TTL**(保持原 expireAt)
|
|
150
|
-
item.value = (typeof item.value === 'number' ? item.value : 0) + 1;
|
|
151
|
-
return item.value;
|
|
66
|
+
return this._incrInternal(key, ttlMs, false);
|
|
152
67
|
}
|
|
153
68
|
|
|
154
|
-
/**
|
|
155
|
-
* 自增并首次设置过期时间
|
|
156
|
-
* @param {string} key - 缓存键
|
|
157
|
-
* @param {number} ttlMs - TTL 毫秒
|
|
158
|
-
* @returns {number} 自增后的值
|
|
159
|
-
* @description
|
|
160
|
-
* 与 incr 区别:新建时用调用方指定的 ttlMs,而非默认 TTL
|
|
161
|
-
* - 新建 key:设置值为 1,TTL = ttlMs
|
|
162
|
-
* - 已存在 key:值 +1,**不刷新 TTL**
|
|
163
|
-
*
|
|
164
|
-
* 用于 Rate Limit 场景:第一次访问设置窗口 TTL,后续访问只递增不续期
|
|
165
|
-
*/
|
|
69
|
+
/** 自增并刷新过期时间(限流专用) */
|
|
166
70
|
incrAndExpire(key, ttlMs) {
|
|
167
71
|
this._maybeCleanup();
|
|
168
|
-
|
|
169
|
-
|
|
72
|
+
return this._incrInternal(key, ttlMs, true);
|
|
73
|
+
}
|
|
170
74
|
|
|
75
|
+
_incrInternal(key, ttlMs, refreshTTL) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const item = this.map.get(key);
|
|
171
78
|
if (!item || now > item.expireAt) {
|
|
172
|
-
|
|
173
|
-
if (this.map.size >= this.maxSize && !this.map.has(key)) {
|
|
174
|
-
this._evictLRU();
|
|
175
|
-
}
|
|
79
|
+
if (this.map.size >= this.maxSize && !this.map.has(key)) this._evictLRU();
|
|
176
80
|
this.map.set(key, { value: 1, expireAt: now + ttlMs });
|
|
177
81
|
return 1;
|
|
178
82
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
item.value = (typeof item.value === 'number' ? item.value : 0) + 1;
|
|
83
|
+
item.value = (Number.isFinite(item.value) ? item.value : 0) + 1;
|
|
84
|
+
if (refreshTTL) item.expireAt = now + ttlMs;
|
|
182
85
|
return item.value;
|
|
183
86
|
}
|
|
184
87
|
|
|
185
|
-
/**
|
|
186
|
-
* 刷新过期时间
|
|
187
|
-
* @param {string} key - 缓存键
|
|
188
|
-
* @param {number} ttlMs - TTL 毫秒
|
|
189
|
-
* @returns {boolean} 是否设置成功
|
|
190
|
-
*/
|
|
88
|
+
/** 主动刷新指定key过期时间 */
|
|
191
89
|
expire(key, ttlMs) {
|
|
192
90
|
const item = this.map.get(key);
|
|
193
|
-
if (!item || Date.now() > item.expireAt)
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
91
|
+
if (!item || Date.now() > item.expireAt) return false;
|
|
196
92
|
item.expireAt = Date.now() + ttlMs;
|
|
197
93
|
return true;
|
|
198
94
|
}
|
|
199
95
|
|
|
200
|
-
/**
|
|
201
|
-
* 循环淘汰最久未使用的条目,直到 size < maxSize
|
|
202
|
-
* 修复旧版只淘汰 1 条导致批量插入超出上限的问题
|
|
203
|
-
* Map 的 keys().next() 返回最早插入的 key(O(1))
|
|
204
|
-
* @private
|
|
205
|
-
*/
|
|
96
|
+
/** LRU淘汰:删除最久未访问key(Map 头部即最早插入) */
|
|
206
97
|
_evictLRU() {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.map.delete(oldestKey);
|
|
211
|
-
}
|
|
98
|
+
if (this.map.size < this.maxSize) return;
|
|
99
|
+
const oldest = this.map.keys().next().value;
|
|
100
|
+
if (oldest !== undefined) this.map.delete(oldest);
|
|
212
101
|
}
|
|
213
102
|
|
|
214
|
-
/**
|
|
215
|
-
* 惰性批量清理过期项
|
|
216
|
-
* 控制频率:每 10 秒最多清理一次
|
|
217
|
-
* 控制单轮清理量:每轮最多 1000 条,避免大缓存全量遍历阻塞事件循环
|
|
218
|
-
*
|
|
219
|
-
* 修复说明:
|
|
220
|
-
* 旧版在遇到第一个未过期条目时 break,假设 Map 按过期时间有序插入。
|
|
221
|
-
* 实际上 Map 按插入顺序遍历,与过期时间无关。
|
|
222
|
-
* 后插入但 TTL 短的条目可能先过期,导致旧版无法清理被前面长 TTL 条目挡住的过期条目。
|
|
223
|
-
* 现改为分批清理(每轮最多 1000 条),剩余的会在下次触发时继续清理。
|
|
224
|
-
* @private
|
|
225
|
-
*/
|
|
103
|
+
/** 惰性批量清理:间隔10s一次,单次最多清理1000条防阻塞事件循环 */
|
|
226
104
|
_maybeCleanup() {
|
|
227
105
|
const now = Date.now();
|
|
228
106
|
if (now - this._lastCleanup < this._cleanupInterval) return;
|
|
229
107
|
this._lastCleanup = now;
|
|
230
|
-
|
|
231
|
-
// 分批清理,单轮最多 1000 条,避免大缓存全量遍历阻塞事件循环
|
|
232
108
|
let count = 0;
|
|
233
|
-
for (const [
|
|
234
|
-
if (now >
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
if (++count >= 1000) break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* 完整清理所有过期项
|
|
243
|
-
* 仅在调用 size() 时触发
|
|
244
|
-
* @private
|
|
245
|
-
*/
|
|
246
|
-
_cleanupAll() {
|
|
247
|
-
const now = Date.now();
|
|
248
|
-
for (const [key, item] of this.map) {
|
|
249
|
-
if (now > item.expireAt) {
|
|
250
|
-
this.map.delete(key);
|
|
251
|
-
}
|
|
109
|
+
for (const [k, v] of this.map) {
|
|
110
|
+
if (now > v.expireAt) this.map.delete(k);
|
|
111
|
+
if (++count >= CACHE_CONST.CLEANUP_BATCH_LIMIT) break;
|
|
252
112
|
}
|
|
253
113
|
}
|
|
254
114
|
}
|
|
255
115
|
|
|
256
|
-
|
|
116
|
+
// 全局单例实例
|
|
257
117
|
export const cache = new Cache();
|
|
258
|
-
export default Cache;
|
|
118
|
+
export default Cache;
|
package/storage/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* - redis: 异步 Redis 后端
|
|
5
|
-
* - store: 统一异步 API,自动选 Redis/内存 + 降级兜底
|
|
2
|
+
* storage 模块聚合导出
|
|
3
|
+
* 对外暴露:store(适配层单例)/ cache(内存缓存单例)
|
|
6
4
|
*/
|
|
7
|
-
export { cache, DEFAULT_INCR_TTL } from "./cache.js";
|
|
8
|
-
export { default as RedisBackend } from "./redis.js";
|
|
9
5
|
export { store } from "./store.js";
|
|
6
|
+
export { cache } from "./cache.js";
|