chanjs 2.7.4 → 2.7.5

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 (93) hide show
  1. package/USAGE.md +533 -0
  2. package/config/index.js +37 -6
  3. package/core/App.js +166 -0
  4. package/core/Container.js +77 -0
  5. package/core/Controller.js +29 -0
  6. package/core/Database.js +93 -0
  7. package/core/Repository.js +327 -0
  8. package/core/Service.js +11 -0
  9. package/core/bootstrap/error-handler.js +104 -0
  10. package/core/bootstrap/hook-runner.js +64 -0
  11. package/core/bootstrap/middleware.js +35 -0
  12. package/core/bootstrap/router-loader.js +53 -0
  13. package/core/errors.js +224 -0
  14. package/core/loader.js +89 -0
  15. package/core/registry.js +17 -0
  16. package/doc/Cache.md +279 -106
  17. package/doc/Common.md +590 -134
  18. package/doc/Controller.md +166 -95
  19. package/doc/Help.md +299 -698
  20. package/doc/QuickStart.md +116 -0
  21. package/doc/Repository.md +560 -0
  22. package/doc/Service.md +201 -527
  23. package/index.js +75 -37
  24. package/middleware/body.js +17 -0
  25. package/middleware/cookie.js +7 -15
  26. package/middleware/cors.js +9 -27
  27. package/middleware/favicon.js +7 -17
  28. package/middleware/header.js +15 -16
  29. package/middleware/index.js +11 -11
  30. package/middleware/log.js +26 -56
  31. package/middleware/static.js +15 -28
  32. package/middleware/template.js +75 -115
  33. package/middleware/validate.js +79 -0
  34. package/middleware/waf.js +174 -197
  35. package/package.json +9 -2
  36. package/response/code.js +73 -0
  37. package/response/index.js +9 -6
  38. package/response/response.js +82 -236
  39. package/security/checker.js +26 -74
  40. package/security/index.js +4 -9
  41. package/security/jwt.js +69 -142
  42. package/security/keywords.js +32 -136
  43. package/security/rate-limit.js +38 -80
  44. package/security/sign.js +83 -176
  45. package/security/xss-filter.js +21 -53
  46. package/storage/cache.js +57 -196
  47. package/storage/index.js +3 -6
  48. package/storage/redis.js +123 -181
  49. package/storage/store.js +163 -188
  50. package/utils/data-parse.js +42 -186
  51. package/utils/file.js +73 -244
  52. package/utils/filter.js +22 -25
  53. package/utils/html.js +49 -33
  54. package/utils/index.js +21 -7
  55. package/utils/ip.js +31 -71
  56. package/utils/logger.js +117 -0
  57. package/utils/pages.js +55 -0
  58. package/utils/paths.js +18 -0
  59. package/utils/request.js +94 -136
  60. package/utils/signal.js +87 -0
  61. package/utils/time.js +33 -75
  62. package/utils/tree.js +112 -104
  63. package/App.js +0 -533
  64. package/base/Aop.js +0 -195
  65. package/base/Container.js +0 -161
  66. package/base/Controller.js +0 -65
  67. package/base/Database.js +0 -133
  68. package/base/Event.js +0 -61
  69. package/base/Repository.js +0 -644
  70. package/common/api.js +0 -35
  71. package/common/code.js +0 -52
  72. package/common/email.js +0 -191
  73. package/common/index.js +0 -5
  74. package/common/pages.js +0 -120
  75. package/common/utils.js +0 -73
  76. package/config/code.js +0 -166
  77. package/config/paths.js +0 -60
  78. package/doc/Aop.md +0 -269
  79. package/doc/Email.md +0 -114
  80. package/doc/Event.md +0 -232
  81. package/global/env.js +0 -11
  82. package/global/import.js +0 -39
  83. package/global/index.js +0 -8
  84. package/helper/index.js +0 -79
  85. package/loader/index.js +0 -6
  86. package/loader/loader.js +0 -138
  87. package/middleware/compress.js +0 -185
  88. package/middleware/setBody.js +0 -32
  89. package/realtime/index.js +0 -7
  90. package/realtime/sse.js +0 -424
  91. package/realtime/websocket.js +0 -540
  92. package/schedule/index.js +0 -6
  93. package/schedule/schedule.js +0 -491
@@ -1,491 +0,0 @@
1
- /**
2
- * 轻量级定时任务调度器
3
- *
4
- * ============================================================
5
- * 使用方法
6
- * ============================================================
7
- *
8
- * 零依赖、单进程内调度。基于 setTimeout + 递归调度实现。
9
- * 适合 Web 应用内的周期性任务(清理、统计、轮询、健康检查)。
10
- *
11
- * 1. 固定间隔(最常用)
12
- * import { schedule } from 'chanjs/helper/schedule.js';
13
- * schedule.every('clean-log', 5 * 60 * 1000, async () => {
14
- * await cleanOldLogs();
15
- * });
16
- *
17
- * 2. Cron 表达式(5 字段:分 时 日 月 周)
18
- * schedule.cron('daily-report', '0 2 * * *', async () => {
19
- * await sendDailyReport(); // 每天凌晨 2 点
20
- * });
21
- *
22
- * 3. 一次性延迟
23
- * schedule.delay('warmup', 10 * 1000, async () => {
24
- * await warmUpCache();
25
- * });
26
- *
27
- * 4. 任务控制
28
- * schedule.stop('clean-log'); // 停止
29
- * schedule.start('clean-log'); // 启动
30
- * schedule.remove('clean-log'); // 移除
31
- * schedule.list(); // 查看所有任务
32
- *
33
- * 5. 优雅停机
34
- * process.on('SIGTERM', () => schedule.shutdown());
35
- *
36
- * ============================================================
37
- *
38
- * 设计要点:
39
- * 1. 错误隔离:单任务异常不影响其他任务,记录错误后继续下次调度
40
- * 2. 防重叠:同一任务上一次未完成不会启动下一次(避免积压)
41
- * 3. 漂移修正:固定间隔模式按"上次完成时间 + 间隔"调度,避免误差累积
42
- * 4. 优雅停机:shutdown() 清理所有定时器,未完成的任务标记后不重启
43
- * 5. Cron 解析:5 字段标准语法,支持 * / - , 四种操作符
44
- *
45
- * Cron 字段说明:
46
- * 字段 允许值 允许的特殊字符
47
- * 分钟 0-59 星号 / 减号 逗号
48
- * 小时 0-23 星号 / 减号 逗号
49
- * 日 1-31 星号 / 减号 逗号
50
- * 月 1-12 星号 / 减号 逗号
51
- * 周 0-6 (0=周日) 星号 / 减号 逗号
52
- *
53
- * Cron 示例(实际使用时把"星号"替换为 *):
54
- * 星号/5 * * * * 每 5 分钟
55
- * 0 星号 * * * 每小时整点
56
- * 0 2 * * * 每天凌晨 2 点
57
- * 0 0 * * 1 每周一凌晨
58
- * 0 0 1 * * 每月 1 号凌晨
59
- * 0 0,30 9-17 * * 工作时间每半点
60
- */
61
-
62
- /**
63
- * Cron 字段定义:[字段名, 最小值, 最大值]
64
- * @private
65
- */
66
- const CRON_FIELDS = [
67
- ['minute', 0, 59],
68
- ['hour', 0, 23],
69
- ['dayOfMonth', 1, 31],
70
- ['month', 1, 12],
71
- ['dayOfWeek', 0, 6], // 0=周日
72
- ];
73
-
74
- /**
75
- * 解析 Cron 单个字段,返回允许值集合
76
- * 支持:* / - ,
77
- * @param {string} field - 字段表达式
78
- * @param {number} min - 最小值
79
- * @param {number} max - 最大值
80
- * @returns {Set<number>} 允许值集合
81
- * @private
82
- */
83
- function parseCronField(field, min, max) {
84
- const result = new Set();
85
- const parts = field.split(',').map(s => s.trim());
86
-
87
- for (const part of parts) {
88
- // */n → 每 n 个单位
89
- if (part.startsWith('*/')) {
90
- const step = parseInt(part.slice(2), 10);
91
- if (!step || step <= 0) throw new Error(`Cron 步长无效: ${part}`);
92
- for (let i = min; i <= max; i += step) result.add(i);
93
- }
94
- // a-b → 区间
95
- else if (part.includes('-')) {
96
- const [startStr, endStr] = part.split('-');
97
- const start = parseInt(startStr, 10);
98
- const end = parseInt(endStr, 10);
99
- if (isNaN(start) || isNaN(end) || start > end) {
100
- throw new Error(`Cron 区间无效: ${part}`);
101
- }
102
- for (let i = start; i <= end; i++) result.add(i);
103
- }
104
- // * → 全部
105
- else if (part === '*') {
106
- for (let i = min; i <= max; i++) result.add(i);
107
- }
108
- // 单值
109
- else {
110
- const val = parseInt(part, 10);
111
- if (isNaN(val)) throw new Error(`Cron 值无效: ${part}`);
112
- if (val < min || val > max) throw new Error(`Cron 值超出范围: ${part} (${min}-${max})`);
113
- result.add(val);
114
- }
115
- }
116
-
117
- return result;
118
- }
119
-
120
- /**
121
- * 解析 Cron 表达式
122
- * @param {string} expr - 5 字段 cron 表达式
123
- * @returns {Object} { minute:Set, hour:Set, dayOfMonth:Set, month:Set, dayOfWeek:Set }
124
- * @private
125
- */
126
- function parseCronExpression(expr) {
127
- const fields = expr.trim().split(/\s+/);
128
- if (fields.length !== 5) {
129
- throw new Error(`Cron 表达式必须是 5 个字段: "${expr}"`);
130
- }
131
-
132
- const parsed = {};
133
- for (let i = 0; i < 5; i++) {
134
- const [name, min, max] = CRON_FIELDS[i];
135
- parsed[name] = parseCronField(fields[i], min, max);
136
- }
137
- return parsed;
138
- }
139
-
140
- /**
141
- * 计算下次执行时间
142
- * 从 from 的下一分钟开始,逐分钟递增,找到第一个匹配的时间
143
- * 最长搜索 4 年(覆盖闰年周期)或最多 10000 次迭代,找不到返回 null
144
- * @param {Object} cron - parseCronExpression 返回值
145
- * @param {Date} from - 起始时间
146
- * @returns {Date|null} 下次执行时间
147
- * @private
148
- */
149
- function nextCronTime(cron, from = new Date()) {
150
- const next = new Date(from);
151
- next.setSeconds(0, 0); // 重置到当前分钟开始
152
- next.setMinutes(next.getMinutes() + 1); // 下一分钟开始
153
-
154
- // 搜索上限:4 年(覆盖闰年)
155
- const limit = new Date(from);
156
- limit.setFullYear(limit.getFullYear() + 4);
157
-
158
- // 最大迭代次数限制,避免某些边界 cron 表达式导致 CPU 突刺
159
- // 4 年 ≈ 2,107,680 分钟,10000 次迭代约等于一周扫描范围,正常 cron 都能匹配到
160
- const MAX_ITERATIONS = 10000;
161
- let iterations = 0;
162
-
163
- while (next.getTime() < limit.getTime() && iterations < MAX_ITERATIONS) {
164
- iterations++;
165
- // 注意:JS getMonth() 返回 0-11,cron month 是 1-12
166
- // 注意:JS getDay() 返回 0-6(0=周日),cron dow 也是 0-6
167
- if (
168
- cron.month.has(next.getMonth() + 1) &&
169
- cron.dayOfMonth.has(next.getDate()) &&
170
- cron.dayOfWeek.has(next.getDay()) &&
171
- cron.hour.has(next.getHours()) &&
172
- cron.minute.has(next.getMinutes())
173
- ) {
174
- return next;
175
- }
176
- next.setMinutes(next.getMinutes() + 1);
177
- }
178
-
179
- if (iterations >= MAX_ITERATIONS) {
180
- console.warn(`[Schedule] nextCronTime 已达最大迭代次数 ${MAX_ITERATIONS},可能 cron 表达式不合理`);
181
- }
182
-
183
- return null;
184
- }
185
-
186
- /**
187
- * 定时任务调度器
188
- * @class Schedule
189
- */
190
- class Schedule {
191
- constructor() {
192
- /** @type {Map<string, Object>} 任务表 name -> task */
193
- this._tasks = new Map();
194
- /** 是否已 shutdown(shutdown 后不再接受新任务) */
195
- this._shuttingDown = false;
196
- }
197
-
198
- /**
199
- * 添加任务(底层方法)
200
- * @param {string} name - 任务名(唯一)
201
- * @param {Object} options - 任务配置
202
- * @param {Function} options.fn - 任务函数
203
- * @param {string} [options.type] - 类型:'every' | 'cron' | 'delay'
204
- * @param {number} [options.intervalMs] - every 模式的间隔毫秒
205
- * @param {string} [options.cron] - cron 表达式
206
- * @param {number} [options.delayMs] - delay 模式的延迟毫秒
207
- * @param {boolean} [options.runOnInit=false] - 是否立即执行一次
208
- * @param {boolean} [options.autoStart=true] - 是否自动启动
209
- * @returns {Object} 任务对象
210
- */
211
- add(name, options) {
212
- if (this._shuttingDown) {
213
- throw new Error('[Schedule] 已 shutdown,无法添加任务');
214
- }
215
- if (this._tasks.has(name)) {
216
- throw new Error(`[Schedule] 任务已存在: ${name}`);
217
- }
218
- if (typeof options.fn !== 'function') {
219
- throw new Error(`[Schedule] fn 必须是函数: ${name}`);
220
- }
221
-
222
- const task = {
223
- name,
224
- fn: options.fn,
225
- type: options.type,
226
- intervalMs: options.intervalMs,
227
- cron: options.cron ? parseCronExpression(options.cron) : null,
228
- cronExpr: options.cron || null,
229
- delayMs: options.delayMs,
230
- runOnInit: options.runOnInit || false,
231
- // 运行时状态
232
- running: false, // 是否正在执行
233
- timer: null, // setTimeout 句柄
234
- lastRun: null, // 上次执行时间
235
- nextRun: null, // 下次执行时间
236
- lastError: null, // 上次错误
237
- runCount: 0, // 累计执行次数
238
- errorCount: 0, // 累计错误次数
239
- started: false, // 是否已启动
240
- };
241
-
242
- this._tasks.set(name, task);
243
-
244
- if (options.autoStart !== false) {
245
- this._startTask(task);
246
- }
247
-
248
- return task;
249
- }
250
-
251
- /**
252
- * 固定间隔任务
253
- * @param {string} name - 任务名
254
- * @param {number} intervalMs - 间隔毫秒
255
- * @param {Function} fn - 任务函数
256
- * @param {Object} [options] - 额外选项(runOnInit / autoStart)
257
- */
258
- every(name, intervalMs, fn, options = {}) {
259
- if (!intervalMs || intervalMs <= 0) {
260
- throw new Error(`[Schedule] intervalMs 必须大于 0: ${name}`);
261
- }
262
- return this.add(name, { ...options, type: 'every', intervalMs, fn });
263
- }
264
-
265
- /**
266
- * Cron 表达式任务
267
- * @param {string} name - 任务名
268
- * @param {string} expression - 5 字段 cron 表达式
269
- * @param {Function} fn - 任务函数
270
- * @param {Object} [options] - 额外选项(runOnInit / autoStart)
271
- */
272
- cron(name, expression, fn, options = {}) {
273
- return this.add(name, { ...options, type: 'cron', cron: expression, fn });
274
- }
275
-
276
- /**
277
- * 一次性延迟任务
278
- * @param {string} name - 任务名
279
- * @param {number} delayMs - 延迟毫秒
280
- * @param {Function} fn - 任务函数
281
- */
282
- delay(name, delayMs, fn) {
283
- if (!delayMs || delayMs <= 0) {
284
- throw new Error(`[Schedule] delayMs 必须大于 0: ${name}`);
285
- }
286
- return this.add(name, { type: 'delay', delayMs, fn, autoStart: true });
287
- }
288
-
289
- /**
290
- * 启动任务
291
- * @param {string} name - 任务名
292
- * @returns {boolean} 是否成功启动
293
- */
294
- start(name) {
295
- const task = this._tasks.get(name);
296
- if (!task) return false;
297
- if (task.started) return false;
298
- this._startTask(task);
299
- return true;
300
- }
301
-
302
- /**
303
- * 启动所有任务
304
- */
305
- startAll() {
306
- for (const task of this._tasks.values()) {
307
- if (!task.started) this._startTask(task);
308
- }
309
- }
310
-
311
- /**
312
- * 启动单个任务(内部)
313
- * @private
314
- */
315
- _startTask(task) {
316
- if (task.started || this._shuttingDown) return;
317
- task.started = true;
318
-
319
- // 立即执行一次
320
- if (task.runOnInit) {
321
- this._run(task);
322
- }
323
-
324
- this._scheduleNext(task);
325
- }
326
-
327
- /**
328
- * 调度下次执行
329
- * @private
330
- */
331
- _scheduleNext(task) {
332
- if (!task.started || this._shuttingDown) return;
333
-
334
- let delay;
335
- if (task.type === 'every') {
336
- delay = task.intervalMs;
337
- task.nextRun = new Date(Date.now() + delay);
338
- } else if (task.type === 'cron') {
339
- const nextTime = nextCronTime(task.cron, task.lastRun || new Date());
340
- if (!nextTime) {
341
- console.warn(`[Schedule] 任务 ${task.name} 在 4 年内找不到下次执行时间,已停止`);
342
- return;
343
- }
344
- delay = nextTime.getTime() - Date.now();
345
- task.nextRun = nextTime;
346
- } else if (task.type === 'delay') {
347
- delay = task.delayMs;
348
- task.nextRun = new Date(Date.now() + delay);
349
- } else {
350
- return;
351
- }
352
-
353
- // 防御:delay 过小(<0)会导致立即触发,可能死循环
354
- if (delay < 0) delay = 0;
355
-
356
- task.timer = setTimeout(() => {
357
- this._run(task).then(() => {
358
- // delay 类型是一次性,执行完不再调度
359
- if (task.type !== 'delay') {
360
- this._scheduleNext(task);
361
- } else {
362
- task.started = false;
363
- }
364
- });
365
- }, delay);
366
- }
367
-
368
- /**
369
- * 执行任务(错误隔离 + 防重叠)
370
- * @private
371
- */
372
- async _run(task) {
373
- // 防重叠:上次还没执行完
374
- if (task.running) {
375
- console.warn(`[Schedule] 任务 ${task.name} 上次未完成,跳过本次执行`);
376
- return;
377
- }
378
-
379
- task.running = true;
380
- task.lastRun = new Date();
381
- task.runCount++;
382
-
383
- try {
384
- await task.fn();
385
- task.lastError = null;
386
- } catch (err) {
387
- task.lastError = err;
388
- task.errorCount++;
389
- // 错误隔离:打印日志但不中断后续调度
390
- console.error(`[Schedule] 任务 ${task.name} 执行失败:`, err.message);
391
- } finally {
392
- task.running = false;
393
- }
394
- }
395
-
396
- /**
397
- * 停止任务(保留任务,可再启动)
398
- * @param {string} name - 任务名
399
- * @returns {boolean} 是否成功停止
400
- */
401
- stop(name) {
402
- const task = this._tasks.get(name);
403
- if (!task) return false;
404
- if (task.timer) {
405
- clearTimeout(task.timer);
406
- task.timer = null;
407
- }
408
- task.started = false;
409
- task.nextRun = null;
410
- return true;
411
- }
412
-
413
- /**
414
- * 停止所有任务
415
- */
416
- stopAll() {
417
- for (const name of this._tasks.keys()) {
418
- this.stop(name);
419
- }
420
- }
421
-
422
- /**
423
- * 移除任务
424
- * @param {string} name - 任务名
425
- * @returns {boolean} 是否成功移除
426
- */
427
- remove(name) {
428
- const task = this._tasks.get(name);
429
- if (!task) return false;
430
- this.stop(name);
431
- return this._tasks.delete(name);
432
- }
433
-
434
- /**
435
- * 任务是否存在
436
- */
437
- has(name) {
438
- return this._tasks.has(name);
439
- }
440
-
441
- /**
442
- * 获取单个任务状态
443
- */
444
- get(name) {
445
- const task = this._tasks.get(name);
446
- if (!task) return null;
447
- return this._taskStatus(task);
448
- }
449
-
450
- /**
451
- * 获取所有任务状态
452
- */
453
- list() {
454
- return Array.from(this._tasks.values()).map(t => this._taskStatus(t));
455
- }
456
-
457
- /**
458
- * 生成任务状态对象
459
- * @private
460
- */
461
- _taskStatus(task) {
462
- return {
463
- name: task.name,
464
- type: task.type,
465
- cron: task.cronExpr,
466
- intervalMs: task.intervalMs,
467
- delayMs: task.delayMs,
468
- started: task.started,
469
- running: task.running,
470
- lastRun: task.lastRun,
471
- nextRun: task.nextRun,
472
- lastError: task.lastError?.message || null,
473
- runCount: task.runCount,
474
- errorCount: task.errorCount,
475
- };
476
- }
477
-
478
- /**
479
- * 优雅停机:停止所有任务,拒绝后续添加
480
- */
481
- shutdown() {
482
- this._shuttingDown = true;
483
- this.stopAll();
484
- console.log(`[Schedule] 已关闭所有定时任务(共 ${this._tasks.size} 个)`);
485
- }
486
- }
487
-
488
- // 全局单例
489
- export const schedule = new Schedule();
490
- export { Schedule };
491
- export default Schedule;