chanjs 2.6.8 → 2.6.11
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 +0 -1
- package/README.md +115 -100
- package/helper/cache.js +23 -19
- package/index.js +1 -1
- package/middleware/template.js +1 -1
- package/package.json +1 -1
package/App.js
CHANGED
package/README.md
CHANGED
|
@@ -2,69 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
Chanjs 是一个基于 Express 5+ 构建的轻量级 MVC 框架,完全使用 JavaScript 开发。它体现了函数式编程的概念,提供了卓越的性能、清晰的代码和易于遵循的过程,确保了高可维护性。
|
|
4
4
|
|
|
5
|
+
ChanJS 是一个基于 Express 5+ 构建的轻量级 MVC 框架,完全使用纯 JavaScript 开发,强调函数式编程理念,具有高性能、代码清晰、易维护等特点。
|
|
6
|
+
|
|
5
7
|
## 特点
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- 路径处理
|
|
65
|
-
- 树形结构转换
|
|
66
|
-
- 代码生成
|
|
67
|
-
- 过滤器
|
|
9
|
+
### 核心架构
|
|
10
|
+
- ✅ Express 5+ 原生
|
|
11
|
+
- ✅ Node.js 24+
|
|
12
|
+
- ✅ ES Modules (import / export)
|
|
13
|
+
- ✅ 多模块 MVC 架构
|
|
14
|
+
- ✅ 轻量级内核
|
|
15
|
+
- ✅ 约定优于配置
|
|
16
|
+
|
|
17
|
+
### 模块化
|
|
18
|
+
- ✅ 多模块路由
|
|
19
|
+
- ✅ 模块化 views
|
|
20
|
+
- ✅ 模块化 controllers
|
|
21
|
+
- ✅ 模块化 services
|
|
22
|
+
- ✅ 模块化 middleware
|
|
23
|
+
|
|
24
|
+
### 数据库
|
|
25
|
+
- ✅ Knex.js 查询构建器
|
|
26
|
+
- ✅ SQLite / MySQL / PostgreSQL
|
|
27
|
+
- ✅ 连接池
|
|
28
|
+
- ✅ 事务
|
|
29
|
+
- ✅ 自动时间格式化
|
|
30
|
+
|
|
31
|
+
### 安全能力
|
|
32
|
+
- ✅ WAF 防火墙
|
|
33
|
+
- ✅ XSS 防护
|
|
34
|
+
- ✅ 关键词过滤
|
|
35
|
+
- ✅ 请求限流
|
|
36
|
+
- ✅ 路由白名单
|
|
37
|
+
- ✅ Cookie 安全
|
|
38
|
+
|
|
39
|
+
### 中间件生态
|
|
40
|
+
- ✅ CORS
|
|
41
|
+
- ✅ 请求解析
|
|
42
|
+
- ✅ Cookie
|
|
43
|
+
- ✅ 静态资源
|
|
44
|
+
- ✅ 日志
|
|
45
|
+
- ✅ Art-template 模板引擎
|
|
46
|
+
|
|
47
|
+
### 高级特性
|
|
48
|
+
- ✅ DI 依赖注入(Map 实现)
|
|
49
|
+
- ✅ AOP 切面(before/after/error)
|
|
50
|
+
- ✅ 事件系统
|
|
51
|
+
- ✅ 自动加载控制器
|
|
52
|
+
- ✅ 定时任务
|
|
53
|
+
- ✅ WebSocket
|
|
54
|
+
- ✅ 缓存系统(Redis 和 内存缓存 map 实现)
|
|
55
|
+
- ✅ 分页器
|
|
56
|
+
- ✅ 国际化 i18n
|
|
57
|
+
- ✅ 请求验证器
|
|
58
|
+
- ✅ 全局异常处理
|
|
59
|
+
- ✅ 接口文档自动生成
|
|
60
|
+
- ✅ 文件上传 / OSS 扩展口
|
|
61
|
+
|
|
62
|
+
### 开发体验
|
|
63
|
+
- ✅ 多环境配置
|
|
64
|
+
- ✅ 统一响应
|
|
65
|
+
- ✅ 工具函数
|
|
68
66
|
|
|
69
67
|
## 约定优于配置
|
|
70
68
|
|
|
@@ -100,29 +98,61 @@ Chanjs 是一个基于 Express 5+ 构建的轻量级 MVC 框架,完全使用 J
|
|
|
100
98
|
|- package.json
|
|
101
99
|
```
|
|
102
100
|
|
|
101
|
+
|
|
102
|
+
## npm 包参考
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"art-template": "^4.13.4",
|
|
107
|
+
"body-parser": "^2.2.0",
|
|
108
|
+
"cookie-parser": "^1.4.7",
|
|
109
|
+
"cors": "^2.8.5",
|
|
110
|
+
"dotenv": "^17.2.1",
|
|
111
|
+
"dayjs": "^1.11.13",
|
|
112
|
+
"express": "^5.1.0",
|
|
113
|
+
"express-art-template": "^1.0.1",
|
|
114
|
+
"knex": "^3.1.0",
|
|
115
|
+
"morgan": "^1.10.0",
|
|
116
|
+
"mysql2": "^3.14.1",
|
|
117
|
+
"marked": "^17.0.3",
|
|
118
|
+
"adm-zip": "^0.5.16",
|
|
119
|
+
"bcryptjs": "^3.0.2",
|
|
120
|
+
"better-sqlite3": "^12.8.0",
|
|
121
|
+
"chanjs": "^2.6.10",
|
|
122
|
+
"cheerio": "^1.1.2",
|
|
123
|
+
"crypto-js": "^4.2.0",
|
|
124
|
+
"iconv-lite": "^0.6.3",
|
|
125
|
+
"jsonwebtoken": "^9.0.2",
|
|
126
|
+
"multer": "^2.0.2",
|
|
127
|
+
"nodemailer": "^7.0.6",
|
|
128
|
+
"qiniu": "^7.14.0",
|
|
129
|
+
"serve-favicon": "^2.5.1",
|
|
130
|
+
"xml2js": "^0.6.2",
|
|
131
|
+
"xss": "^1.0.15",
|
|
132
|
+
"zod": "^4.1.11"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
103
136
|
## 初始化过程
|
|
104
137
|
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
D --> D1[加载common]
|
|
124
|
-
D --> D2[加载helper]
|
|
125
|
-
D --> D3[加载extend]
|
|
138
|
+
```
|
|
139
|
+
初始化流程:
|
|
140
|
+
|
|
141
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
142
|
+
│ 初始化 │───▶│ 加载配置 │───▶│ 加载数据库│───▶│ 加载扩展 │
|
|
143
|
+
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
|
144
|
+
│
|
|
145
|
+
┌─────────┐ ┌─────────┐ │
|
|
146
|
+
│ 加载服务 │ │ 加载控制器│ │
|
|
147
|
+
└────▲────┘ └────▲────┘ │
|
|
148
|
+
│ │ │
|
|
149
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──┴──────┐
|
|
150
|
+
│ 应用路由 │◀───│加载公共路由│◀───│加载模块路由│◀───│加载中间件│
|
|
151
|
+
└────┬────┘ └─────────┘ └────┬────┘ └─────────┘
|
|
152
|
+
│ │
|
|
153
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
154
|
+
└───▶│设置错误处理│───▶│beforeStart│───▶│ run启动 │
|
|
155
|
+
└─────────┘ └─────────┘ └─────────┘
|
|
126
156
|
```
|
|
127
157
|
|
|
128
158
|
## 核心功能
|
|
@@ -141,14 +171,7 @@ class UserService extends Service {
|
|
|
141
171
|
super("user");
|
|
142
172
|
}
|
|
143
173
|
|
|
144
|
-
|
|
145
|
-
const user = await this.findById(userId);
|
|
146
|
-
// 从依赖注入容器中获取 ArticleService 实例
|
|
147
|
-
const articleService = await this.get("article", "Article");
|
|
148
|
-
const articles = await articleService.findByUserId(userId);
|
|
149
|
-
|
|
150
|
-
return { user, articles };
|
|
151
|
-
}
|
|
174
|
+
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
class UserController extends Controller {
|
|
@@ -157,15 +180,7 @@ class UserController extends Controller {
|
|
|
157
180
|
}
|
|
158
181
|
|
|
159
182
|
async getUser(req, res, next) {
|
|
160
|
-
|
|
161
|
-
const { id } = req.params;
|
|
162
|
-
// 从依赖注入容器中获取 UserService 实例
|
|
163
|
-
const userService = await this.get("user", "User");
|
|
164
|
-
const result = await userService.findById(id);
|
|
165
|
-
res.json(this.success({ data: result.data }));
|
|
166
|
-
} catch (err) {
|
|
167
|
-
next(err);
|
|
168
|
-
}
|
|
183
|
+
|
|
169
184
|
}
|
|
170
185
|
}
|
|
171
186
|
```
|
|
@@ -345,4 +360,4 @@ app.run((port) => {
|
|
|
345
360
|
});
|
|
346
361
|
```
|
|
347
362
|
|
|
348
|
-
该框架专为寻求简单与功能之间平衡的开发者设计,为构建 Web 应用程序提供了一个强大的基础。
|
|
363
|
+
该框架专为寻求简单与功能之间平衡的开发者设计,为构建 Web 应用程序提供了一个强大的基础。
|
package/helper/cache.js
CHANGED
|
@@ -34,16 +34,21 @@ class Cache {
|
|
|
34
34
|
* @param {number} [ttl=this.defaultTTL] - 过期时间(毫秒)
|
|
35
35
|
* @description
|
|
36
36
|
* 将键值对存入缓存,如果缓存已满则淘汰最久未使用的条目
|
|
37
|
-
* 每次设置都会更新访问时间
|
|
37
|
+
* 每次设置都会更新访问时间 + 过期时间
|
|
38
38
|
* @example
|
|
39
39
|
* cache.set('key1', 'value1', 60000); // 60 秒后过期
|
|
40
40
|
* cache.set('key2', { data: 123 }); // 使用默认过期时间
|
|
41
41
|
*/
|
|
42
42
|
set(key, value, ttl = this.defaultTTL) {
|
|
43
|
+
// 修复:缓存满 + 是新 key 才淘汰
|
|
43
44
|
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
44
45
|
this._evictLRU();
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
// 修复:无论 key 是否存在,都更新 过期时间 + 访问时间
|
|
46
49
|
this.cache.set(key, { value, expireAt: Date.now() + ttl });
|
|
50
|
+
// LRU:先删除再插入,保证 Map 尾部是最新访问
|
|
51
|
+
this.accessOrder.delete(key);
|
|
47
52
|
this.accessOrder.set(key, Date.now());
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -54,7 +59,7 @@ class Cache {
|
|
|
54
59
|
* @description
|
|
55
60
|
* 获取指定键的缓存值
|
|
56
61
|
* 如果值已过期,会自动删除该条目
|
|
57
|
-
* 每次获取都会更新访问时间
|
|
62
|
+
* 每次获取都会更新访问时间 + 刷新过期时间
|
|
58
63
|
* @example
|
|
59
64
|
* const value = cache.get('key1');
|
|
60
65
|
* if (value !== null) {
|
|
@@ -64,14 +69,22 @@ class Cache {
|
|
|
64
69
|
get(key) {
|
|
65
70
|
const item = this.cache.get(key);
|
|
66
71
|
if (!item) return null;
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
// 已过期直接删除
|
|
75
|
+
if (now > item.expireAt) {
|
|
69
76
|
this.cache.delete(key);
|
|
70
77
|
this.accessOrder.delete(key);
|
|
71
78
|
return null;
|
|
72
79
|
}
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
|
|
75
88
|
return item.value;
|
|
76
89
|
}
|
|
77
90
|
|
|
@@ -141,20 +154,11 @@ class Cache {
|
|
|
141
154
|
* 淘汰最久未使用的条目(内部方法)
|
|
142
155
|
* @private
|
|
143
156
|
* @description
|
|
144
|
-
*
|
|
145
|
-
* 当缓存达到最大容量时自动调用
|
|
157
|
+
* Map 头部就是最早访问,直接删除(O(1))
|
|
146
158
|
*/
|
|
147
159
|
_evictLRU() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
for (const [key, time] of this.accessOrder.entries()) {
|
|
152
|
-
if (time < oldestTime) {
|
|
153
|
-
oldestTime = time;
|
|
154
|
-
oldestKey = key;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
160
|
+
// 修复:Map 有序,keys().next() 直接拿到最久未使用的 key,性能 O(1)
|
|
161
|
+
const oldestKey = this.accessOrder.keys().next().value;
|
|
158
162
|
if (oldestKey) {
|
|
159
163
|
this.cache.delete(oldestKey);
|
|
160
164
|
this.accessOrder.delete(oldestKey);
|
|
@@ -180,4 +184,4 @@ class Cache {
|
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
export const cache = new Cache();
|
|
183
|
-
export default Cache;
|
|
187
|
+
export default Cache;
|
package/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export * as common from "./common/index.js";
|
|
|
14
14
|
export * as extend from "./extend/index.js";
|
|
15
15
|
|
|
16
16
|
// 常用工具函数直接导出
|
|
17
|
-
export { loadConfig, loaderSort, loadController } from "./helper/index.js";
|
|
17
|
+
export { loadConfig, loaderSort, loadController ,cache} from "./helper/index.js";
|
|
18
18
|
|
|
19
19
|
// 路径配置
|
|
20
20
|
export { Paths } from "./config/paths.js";
|
package/middleware/template.js
CHANGED
|
@@ -24,7 +24,7 @@ import { importjs } from "../global/import.js";
|
|
|
24
24
|
export let setTemplate = (app, config) => {
|
|
25
25
|
const { views, NODE_ENV } = config;
|
|
26
26
|
const isProduction = NODE_ENV === "production" || NODE_ENV === "prd";
|
|
27
|
-
console.log("
|
|
27
|
+
console.log("模板缓存->", isProduction);
|
|
28
28
|
const all = [...views];
|
|
29
29
|
app.set("view options", {
|
|
30
30
|
debug: !isProduction,
|