chanjs 2.5.8 → 2.6.0
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 +26 -68
- package/README.md +2 -13
- package/base/AOP.MD +318 -0
- package/base/Aop.js +178 -0
- package/base/Container.js +81 -0
- package/base/Controller.js +5 -2
- package/base/Event.js +49 -0
- package/base/Service.js +20 -6
- package/helper/index.js +1 -1
- package/helper/loader.js +18 -37
- package/index.js +13 -2
- package/middleware/template.js +4 -3
- package/package.json +1 -1
package/App.js
CHANGED
|
@@ -3,17 +3,14 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
5
|
// 引入基础组件
|
|
6
|
-
import Controller from "./base/Controller.js";
|
|
7
|
-
import Service from "./base/Service.js";
|
|
8
6
|
import DatabaseManager from "./base/Database.js";
|
|
9
7
|
|
|
10
8
|
// 引入配置和工具
|
|
11
9
|
import { Paths } from "./config/index.js";
|
|
12
|
-
import {
|
|
13
|
-
import { loadConfig, loadController, loaderSort, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, getFolders, arrToObj, htmlDecode } from "./helper/index.js";
|
|
10
|
+
import { loadConfig, loaderSort, loadController } from "./helper/index.js";
|
|
14
11
|
import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf, log } from "./middleware/index.js";
|
|
15
|
-
import {
|
|
16
|
-
import { importFile
|
|
12
|
+
import { notFoundResponse, parseDatabaseError, error as responseError } from "./utils/index.js";
|
|
13
|
+
import { importFile } from "./global/import.js";
|
|
17
14
|
|
|
18
15
|
import "./global/index.js";
|
|
19
16
|
|
|
@@ -27,62 +24,6 @@ import "./global/index.js";
|
|
|
27
24
|
*/
|
|
28
25
|
class Chan {
|
|
29
26
|
|
|
30
|
-
// 静态属性:提供全局访问的辅助函数和工具
|
|
31
|
-
static helper = {
|
|
32
|
-
loadController,
|
|
33
|
-
loaderSort,
|
|
34
|
-
loadConfig,
|
|
35
|
-
formatDateFields,
|
|
36
|
-
request,
|
|
37
|
-
delImg,
|
|
38
|
-
getIp,
|
|
39
|
-
setToken,
|
|
40
|
-
getToken,
|
|
41
|
-
verifyToken,
|
|
42
|
-
generateToken,
|
|
43
|
-
getFileTree,
|
|
44
|
-
readFileContent,
|
|
45
|
-
saveFileContent,
|
|
46
|
-
isPathSafe,
|
|
47
|
-
getFolders,
|
|
48
|
-
arrToObj,
|
|
49
|
-
htmlDecode,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// 静态属性:提供全局访问的常用业务函数和工具
|
|
53
|
-
static common = {
|
|
54
|
-
success,
|
|
55
|
-
fail,
|
|
56
|
-
error: responseError,
|
|
57
|
-
getChildrenId,
|
|
58
|
-
CODE: commonCode,
|
|
59
|
-
sendMail,
|
|
60
|
-
genRegEmailHtml,
|
|
61
|
-
genResetPasswordEmail,
|
|
62
|
-
pages,
|
|
63
|
-
getHtmlFilesSync,
|
|
64
|
-
filterBody,
|
|
65
|
-
filterImgFromStr,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// 静态属性:提供全局访问的配置
|
|
69
|
-
static config = {};
|
|
70
|
-
// 静态属性:提供全局访问的数据库
|
|
71
|
-
static db = {};
|
|
72
|
-
// 静态属性:提供全局访问的服务类
|
|
73
|
-
static Service = Service;
|
|
74
|
-
// 静态属性:提供全局访问的控制器类
|
|
75
|
-
static Controller = Controller;
|
|
76
|
-
// 静态属性:提供全局访问的路径配置
|
|
77
|
-
static paths = Paths;
|
|
78
|
-
// 静态属性:提供全局访问的导入函数
|
|
79
|
-
static importFile = importFile;
|
|
80
|
-
static importjs = importjs;
|
|
81
|
-
// 静态属性:提供全局访问的工具函数
|
|
82
|
-
static utils = {
|
|
83
|
-
checkKeywords,
|
|
84
|
-
};
|
|
85
|
-
|
|
86
27
|
/**
|
|
87
28
|
* 构造函数
|
|
88
29
|
* @constructor
|
|
@@ -106,6 +47,7 @@ class Chan {
|
|
|
106
47
|
async start() {
|
|
107
48
|
//加载配置
|
|
108
49
|
await this.config();
|
|
50
|
+
|
|
109
51
|
//加载数据库
|
|
110
52
|
await this.loadDB();
|
|
111
53
|
//加载扩展方法
|
|
@@ -153,7 +95,7 @@ class Chan {
|
|
|
153
95
|
const connection = this.dbManager.add(key, dbConfig, { isDefault: index === 0 });
|
|
154
96
|
|
|
155
97
|
if (index === 0) {
|
|
156
|
-
Chan.db =
|
|
98
|
+
Chan.db = this.dbManager.get();
|
|
157
99
|
}
|
|
158
100
|
} catch (error) {
|
|
159
101
|
console.error(`[DB] 数据库 ${key} 初始化失败: ${error.message}`);
|
|
@@ -181,6 +123,11 @@ class Chan {
|
|
|
181
123
|
for (const { _path, key } of extensions) {
|
|
182
124
|
await this.loadFn(_path, key);
|
|
183
125
|
}
|
|
126
|
+
|
|
127
|
+
if (!Chan.helper) {
|
|
128
|
+
Chan.helper = {};
|
|
129
|
+
}
|
|
130
|
+
Chan.helper.loadController = loadController;
|
|
184
131
|
}
|
|
185
132
|
|
|
186
133
|
/**
|
|
@@ -196,7 +143,10 @@ class Chan {
|
|
|
196
143
|
const files = fs.readdirSync(_path).filter((file) => file.endsWith(".js"));
|
|
197
144
|
for (const file of files) {
|
|
198
145
|
const filePath = path.join(_path, file);
|
|
199
|
-
let helperModule = await
|
|
146
|
+
let helperModule = await importFile(filePath);
|
|
147
|
+
if (!Chan[key]) {
|
|
148
|
+
Chan[key] = {};
|
|
149
|
+
}
|
|
200
150
|
Object.assign(Chan[key], helperModule);
|
|
201
151
|
}
|
|
202
152
|
}
|
|
@@ -292,7 +242,7 @@ class Chan {
|
|
|
292
242
|
|
|
293
243
|
const parsedError = parseDatabaseError(err);
|
|
294
244
|
console.error(`[Global Error Details] Database Error - ${parsedError.msg}`);
|
|
295
|
-
res.status(parsedError.statusCode).json(
|
|
245
|
+
res.status(parsedError.statusCode).json(responseError(err, req));
|
|
296
246
|
});
|
|
297
247
|
}
|
|
298
248
|
|
|
@@ -349,9 +299,17 @@ class Chan {
|
|
|
349
299
|
const configPath = path.join(Paths.appPath, "modules");
|
|
350
300
|
if (fs.existsSync(configPath)) {
|
|
351
301
|
const dirs = loaderSort(Chan.config.modules);
|
|
302
|
+
console.log('路由加载顺序:', dirs);
|
|
303
|
+
console.log('Chan.helper keys:', Object.keys(Chan.helper));
|
|
304
|
+
console.log('Chan.helper.loadController type:', typeof Chan.helper.loadController);
|
|
352
305
|
for (const item of dirs) {
|
|
353
|
-
let
|
|
354
|
-
|
|
306
|
+
let routerFn = await importFile(path.join(Paths.modulesPath, item, "router.js"));
|
|
307
|
+
console.log('路由函数:', routerFn);
|
|
308
|
+
|
|
309
|
+
// 为每个模块创建子路由并添加前缀
|
|
310
|
+
const subRouter = express.Router();
|
|
311
|
+
routerFn(this.app, subRouter, Chan.config);
|
|
312
|
+
this.app.use(`/${item}`, subRouter);
|
|
355
313
|
}
|
|
356
314
|
}
|
|
357
315
|
}
|
|
@@ -363,7 +321,7 @@ class Chan {
|
|
|
363
321
|
* @description 加载 app/router.js 中的公共路由
|
|
364
322
|
*/
|
|
365
323
|
async loadCommonRouter() {
|
|
366
|
-
let router = await
|
|
324
|
+
let router = await importFile(path.join(Paths.appPath, "router.js"));
|
|
367
325
|
if (router) {
|
|
368
326
|
router(this.app, this.router, Chan.config);
|
|
369
327
|
}
|
package/README.md
CHANGED
|
@@ -20,30 +20,19 @@ Chanjs 是一个基于 Express5+ 构建的轻量级 MVC 框架,完全使用 Ja
|
|
|
20
20
|
|
|
21
21
|
```code
|
|
22
22
|
|- app
|
|
23
|
-
|-
|
|
23
|
+
|- modules
|
|
24
24
|
|- module1
|
|
25
25
|
|- controller
|
|
26
26
|
|- service
|
|
27
|
-
|- view
|
|
28
27
|
|- router.js
|
|
29
28
|
|- module2
|
|
30
29
|
|- controller
|
|
31
30
|
|- service
|
|
32
|
-
|- view
|
|
33
31
|
|- router.js
|
|
34
32
|
|- extend
|
|
35
33
|
|- middleware
|
|
36
34
|
|- plugin
|
|
37
|
-
|
|
38
|
-
|- controller
|
|
39
|
-
|- service
|
|
40
|
-
|- view
|
|
41
|
-
|- router.js
|
|
42
|
-
|- module2
|
|
43
|
-
|- controller
|
|
44
|
-
|- service
|
|
45
|
-
|- view
|
|
46
|
-
|- router.js
|
|
35
|
+
|-views
|
|
47
36
|
|- config
|
|
48
37
|
|- public
|
|
49
38
|
|- index.js
|
package/base/AOP.MD
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# AOP 切面编程使用文档
|
|
2
|
+
|
|
3
|
+
## 一、概念说明
|
|
4
|
+
|
|
5
|
+
AOP(面向切面编程)就是在函数执行前后插入额外逻辑,不修改原函数代码。
|
|
6
|
+
|
|
7
|
+
**本质**:给函数加一层"皮",在函数执行前、后、出错时插入自定义逻辑。
|
|
8
|
+
|
|
9
|
+
**优势**:
|
|
10
|
+
- 不改原函数代码
|
|
11
|
+
- 复用逻辑(日志、缓存、权限检查等可以统一处理)
|
|
12
|
+
- 按需开启(想用就用 wrap,不想用就不 wrap)
|
|
13
|
+
- 适用场景 :日志、缓存、权限检查、性能监控、事务管理等
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 二、定义切面函数
|
|
17
|
+
|
|
18
|
+
### 1. 日志切面
|
|
19
|
+
```javascript
|
|
20
|
+
const logAspect = async ({ ctx, methodName, args, result, error }) => {
|
|
21
|
+
if (error) {
|
|
22
|
+
console.error(`[ERROR] ${methodName} 出错:`, error.message);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (result) {
|
|
27
|
+
console.log(`[SUCCESS] ${methodName} 执行完成,结果:`, result);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(`[START] ${methodName} 开始执行,参数:`, args);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. 缓存切面
|
|
35
|
+
```javascript
|
|
36
|
+
const cacheAspect = async ({ ctx, args, methodName, originalMethod, params }) => {
|
|
37
|
+
const key = `${methodName}:${JSON.stringify(args)}`;
|
|
38
|
+
|
|
39
|
+
// 尝试从缓存获取
|
|
40
|
+
const cached = await ctx.cache?.get(key);
|
|
41
|
+
if (cached) {
|
|
42
|
+
console.log(`[CACHE HIT] ${key}`);
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 执行原方法
|
|
47
|
+
const result = await originalMethod.apply(ctx, args);
|
|
48
|
+
|
|
49
|
+
// 写入缓存
|
|
50
|
+
const ttl = params?.ttl || 3600;
|
|
51
|
+
await ctx.cache?.set(key, result, ttl);
|
|
52
|
+
console.log(`[CACHE SET] ${key}, TTL: ${ttl}s`);
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. 权限切面
|
|
59
|
+
```javascript
|
|
60
|
+
const authAspect = async ({ ctx, args, params }) => {
|
|
61
|
+
const requiredRole = params?.role || 'user';
|
|
62
|
+
const userRole = ctx.user?.role || 'guest';
|
|
63
|
+
|
|
64
|
+
if (userRole !== requiredRole) {
|
|
65
|
+
throw new Error(`权限不足,需要: ${requiredRole},当前: ${userRole}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`[AUTH] ${userRole} 通过权限检查`);
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. 性能监控切面
|
|
73
|
+
```javascript
|
|
74
|
+
const perfAspect = async ({ ctx, methodName, args, originalMethod }) => {
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const result = await originalMethod.apply(ctx, args);
|
|
79
|
+
const cost = Date.now() - startTime;
|
|
80
|
+
console.log(`[PERF] ${methodName} 耗时: ${cost}ms`);
|
|
81
|
+
return result;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const cost = Date.now() - startTime;
|
|
84
|
+
console.log(`[PERF] ${methodName} 耗时: ${cost}ms (出错)`);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 三、注册切面
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
import { aop } from 'Chan';
|
|
96
|
+
|
|
97
|
+
// 注册切面
|
|
98
|
+
aop.set('log', logAspect);
|
|
99
|
+
aop.set('cache', cacheAspect);
|
|
100
|
+
aop.set('auth', authAspect);
|
|
101
|
+
aop.set('perf', perfAspect);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 四、绑定切面到实例
|
|
107
|
+
|
|
108
|
+
### 1. 基本绑定
|
|
109
|
+
```javascript
|
|
110
|
+
import UserService from './service/UserService.js';
|
|
111
|
+
import { aop } from 'Chan';
|
|
112
|
+
|
|
113
|
+
const userService = new UserService();
|
|
114
|
+
|
|
115
|
+
// 绑定切面
|
|
116
|
+
aop.wrap(userService, {
|
|
117
|
+
getUser: [
|
|
118
|
+
{ type: 'before', log: true },
|
|
119
|
+
{ type: 'after', log: true }
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 正常调用(切面自动生效)
|
|
124
|
+
const user = await userService.getUser(1);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. 多个切面组合
|
|
128
|
+
```javascript
|
|
129
|
+
aop.wrap(userService, {
|
|
130
|
+
getUser: [
|
|
131
|
+
{ type: 'before', log: true },
|
|
132
|
+
{ type: 'before', auth: { role: 'admin' }},
|
|
133
|
+
{ type: 'after', log: true },
|
|
134
|
+
{ type: 'after', cache: { ttl: 1800 }}
|
|
135
|
+
],
|
|
136
|
+
createUser: [
|
|
137
|
+
{ type: 'before', log: true },
|
|
138
|
+
{ type: 'after', log: true }
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 3. 禁用切面
|
|
144
|
+
```javascript
|
|
145
|
+
aop.wrap(userService, {
|
|
146
|
+
getUser: [
|
|
147
|
+
{ type: 'before', log: true },
|
|
148
|
+
{ type: 'after', log: false } // 禁用 after 日志
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 五、完整使用示例
|
|
156
|
+
|
|
157
|
+
### 场景:用户服务带日志、缓存、权限控制
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// 1. 定义切面
|
|
161
|
+
const logAspect = async ({ ctx, methodName, args, result, error }) => {
|
|
162
|
+
if (error) {
|
|
163
|
+
console.error(`[ERROR] ${methodName}:`, error.message);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`[${methodName}] 参数:`, args, '结果:', result);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const cacheAspect = async ({ ctx, args, methodName, originalMethod, params }) => {
|
|
171
|
+
const key = `${methodName}:${JSON.stringify(args)}`;
|
|
172
|
+
const cached = await ctx.cache?.get(key);
|
|
173
|
+
if (cached) return cached;
|
|
174
|
+
|
|
175
|
+
const result = await originalMethod.apply(ctx, args);
|
|
176
|
+
await ctx.cache?.set(key, result, params?.ttl || 3600);
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const authAspect = async ({ ctx, params }) => {
|
|
181
|
+
if (params?.role && ctx.user?.role !== params.role) {
|
|
182
|
+
throw new Error(`权限不足`);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// 2. 注册切面
|
|
187
|
+
import { aop } from 'Chan';
|
|
188
|
+
aop.set('log', logAspect);
|
|
189
|
+
aop.set('cache', cacheAspect);
|
|
190
|
+
aop.set('auth', authAspect);
|
|
191
|
+
|
|
192
|
+
// 3. 绑定切面
|
|
193
|
+
class UserService {
|
|
194
|
+
constructor(cache, user) {
|
|
195
|
+
this.cache = cache;
|
|
196
|
+
this.user = user;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async getUser(id) {
|
|
200
|
+
return this.db('users').where({ id }).first();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async deleteUser(id) {
|
|
204
|
+
return this.db('users').where({ id }).delete();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const userService = new UserService(cache, currentUser);
|
|
209
|
+
aop.wrap(userService, {
|
|
210
|
+
getUser: [
|
|
211
|
+
{ type: 'before', log: true },
|
|
212
|
+
{ type: 'before', auth: { role: 'admin' }},
|
|
213
|
+
{ type: 'after', cache: { ttl: 1800 }}
|
|
214
|
+
],
|
|
215
|
+
deleteUser: [
|
|
216
|
+
{ type: 'before', log: true },
|
|
217
|
+
{ type: 'before', auth: { role: 'admin' }}
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 4. 正常调用
|
|
222
|
+
const user = await userService.getUser(1); // 自动执行:before → 权限 → 原方法 → 缓存 → after → 日志
|
|
223
|
+
await userService.deleteUser(1); // 自动执行:before → 权限 → 原方法 → after → 日志
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 六、切面类型说明
|
|
229
|
+
|
|
230
|
+
| 类型 | 时机 | 用途 | 参数 |
|
|
231
|
+
|------|------|------|------|
|
|
232
|
+
| `before` | 函数执行前 | 日志、权限检查、参数验证 | `ctx`, `args`, `methodName`, `originalMethod` |
|
|
233
|
+
| `after` | 函数执行后 | 缓存、日志、数据清理 | `ctx`, `args`, `methodName`, `originalMethod`, `result` |
|
|
234
|
+
| `error` | 函数出错时 | 错误日志、错误处理、降级 | `ctx`, `args`, `methodName`, `originalMethod`, `error` |
|
|
235
|
+
|
|
236
|
+
### 切面参数说明
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
{
|
|
240
|
+
ctx, // 上下文对象(实例本身),可访问实例属性和方法
|
|
241
|
+
methodName, // 方法名称
|
|
242
|
+
args, // 方法参数数组
|
|
243
|
+
originalMethod, // 原始方法函数
|
|
244
|
+
result, // 方法执行结果(after/error 类型才有)
|
|
245
|
+
error, // 错误对象(error 类型才有)
|
|
246
|
+
params // 自定义参数(通过配置传递)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 七、配置格式说明
|
|
253
|
+
|
|
254
|
+
### 基本格式
|
|
255
|
+
```javascript
|
|
256
|
+
{
|
|
257
|
+
methodName: {
|
|
258
|
+
type: 'before', // 切面类型:before/after/error
|
|
259
|
+
enabled: true, // 是否启用(默认true,设为false禁用)
|
|
260
|
+
切面名: true, // 启用切面(无参数)
|
|
261
|
+
切面名: { 参数 } // 启用切面(带参数)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 数组格式(多个切面)
|
|
267
|
+
```javascript
|
|
268
|
+
{
|
|
269
|
+
methodName: [
|
|
270
|
+
{ type: 'before', log: true },
|
|
271
|
+
{ type: 'before', auth: { role: 'admin' }},
|
|
272
|
+
{ type: 'after', cache: { ttl: 3600 }}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 八、工具方法
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// 注册切面
|
|
283
|
+
aop.set('log', logAspect);
|
|
284
|
+
|
|
285
|
+
// 获取切面
|
|
286
|
+
const logFn = aop.get('log');
|
|
287
|
+
|
|
288
|
+
// 移除切面
|
|
289
|
+
aop.remove('log');
|
|
290
|
+
|
|
291
|
+
// 清空所有切面
|
|
292
|
+
aop.clear();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 九、注意事项
|
|
298
|
+
|
|
299
|
+
1. **切面函数必须是异步函数**
|
|
300
|
+
- 因为原方法可能是异步的
|
|
301
|
+
- 切面函数需要 `await` 原方法
|
|
302
|
+
|
|
303
|
+
2. **参数浅拷贝**
|
|
304
|
+
- `args: [...args]` 避免修改原参数
|
|
305
|
+
- `params: { ...value }` 避免修改配置对象
|
|
306
|
+
|
|
307
|
+
3. **错误处理**
|
|
308
|
+
- error 切面执行后会重新抛出异常
|
|
309
|
+
- 不会阻断原方法的错误流程
|
|
310
|
+
|
|
311
|
+
4. **切面执行顺序**
|
|
312
|
+
- before 切面按配置顺序执行
|
|
313
|
+
- after 切面按配置顺序执行
|
|
314
|
+
- error 切面按配置顺序执行
|
|
315
|
+
|
|
316
|
+
5. **around 切面已移除**
|
|
317
|
+
- 当前版本仅支持 before/after/error 三种类型
|
|
318
|
+
- around 类型已简化为 before + after 组合
|
package/base/Aop.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description: 轻量级切面类 用于注册和执行切面函数
|
|
3
|
+
* @class Aop
|
|
4
|
+
* @property {Map} aspects - 存储注册的切面函数
|
|
5
|
+
* @property {Set} types - 切面类型集合(内置关键字)
|
|
6
|
+
*/
|
|
7
|
+
export class Aop {
|
|
8
|
+
// 定义内置的切面类型关键字(避免和切面名称冲突)
|
|
9
|
+
static TYPES = new Set(['type', 'enabled']);
|
|
10
|
+
// 支持的切面类型(仅保留常用的3种)
|
|
11
|
+
static SUPPORTED_TYPES = new Set(['before', 'after', 'error']);
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.aspects = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 注册切面
|
|
19
|
+
* @param {string} name - 切面名称
|
|
20
|
+
* @param {Function} fn - 切面函数
|
|
21
|
+
* @returns {Aop} - 当前实例(链式调用)
|
|
22
|
+
* @throws {TypeError} - 入参类型错误时抛出
|
|
23
|
+
*/
|
|
24
|
+
set(name, fn) {
|
|
25
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
26
|
+
throw new TypeError(`切面名称 ${name} 必须是非空字符串`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof fn !== 'function') {
|
|
29
|
+
throw new TypeError(`切面 ${name} 必须是函数,当前类型:${typeof fn}`);
|
|
30
|
+
}
|
|
31
|
+
this.aspects.set(name.trim(), fn);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取已注册的切面函数
|
|
37
|
+
* @param {string} name - 切面名称
|
|
38
|
+
* @returns {Function|null} - 切面函数(不存在则返回null)
|
|
39
|
+
*/
|
|
40
|
+
get(name) {
|
|
41
|
+
return this.aspects.get(name?.trim()) || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 移除指定切面
|
|
46
|
+
* @param {string} name - 切面名称
|
|
47
|
+
* @returns {Aop} - 当前实例
|
|
48
|
+
*/
|
|
49
|
+
remove(name) {
|
|
50
|
+
this.aspects.delete(name?.trim());
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 清空所有切面
|
|
56
|
+
* @returns {Aop} - 当前实例
|
|
57
|
+
*/
|
|
58
|
+
clear() {
|
|
59
|
+
this.aspects.clear();
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 绑定切面到实例方法
|
|
65
|
+
* @param {object} instance - 目标实例(如 new Controller())
|
|
66
|
+
* @param {object} config - 切面配置 { methodName: [{ type: 'before', enabled: true, log: true }] }
|
|
67
|
+
* @returns {object} - 绑定后的实例对象
|
|
68
|
+
* @throws {TypeError} - 入参类型错误时抛出
|
|
69
|
+
*/
|
|
70
|
+
wrap(instance, config) {
|
|
71
|
+
if (typeof instance !== 'object' || instance === null) {
|
|
72
|
+
throw new TypeError('instance 必须是非空对象');
|
|
73
|
+
}
|
|
74
|
+
if (typeof config !== 'object' || config === null) {
|
|
75
|
+
throw new TypeError('config 必须是非空对象');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Object.entries(config).forEach(([methodName, rules]) => {
|
|
79
|
+
const originalMethod = instance[methodName];
|
|
80
|
+
if (typeof originalMethod !== 'function') {
|
|
81
|
+
console.warn(`[AOP警告] 实例不存在方法 ${methodName},跳过切面绑定`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 统一规则为数组 + 过滤禁用规则 + 过滤不支持的类型
|
|
86
|
+
const ruleList = Array.isArray(rules) ? rules : [rules];
|
|
87
|
+
const validRules = ruleList.filter(rule => {
|
|
88
|
+
return rule?.enabled !== false && Aop.SUPPORTED_TYPES.has(rule?.type);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 无有效规则时直接返回原方法
|
|
92
|
+
if (validRules.length === 0) return;
|
|
93
|
+
|
|
94
|
+
// 包装原方法(仅保留 before/after/error 逻辑)
|
|
95
|
+
instance[methodName] = async (...args) => {
|
|
96
|
+
const ctx = instance;
|
|
97
|
+
let result;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// 执行前置切面
|
|
101
|
+
await this._executeAspectByType(ctx, validRules, 'before', args, originalMethod);
|
|
102
|
+
|
|
103
|
+
// 执行原方法
|
|
104
|
+
result = await Reflect.apply(originalMethod, ctx, args);
|
|
105
|
+
|
|
106
|
+
// 执行后置切面(仅无异常时执行)
|
|
107
|
+
await this._executeAspectByType(ctx, validRules, 'after', args, originalMethod, result);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// 执行异常切面
|
|
110
|
+
await this._executeAspectByType(ctx, validRules, 'error', args, originalMethod, null, error);
|
|
111
|
+
throw error; // 重新抛出异常,不阻断流程
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return instance;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 按类型批量执行切面(私有方法)
|
|
123
|
+
* @private
|
|
124
|
+
* @param {object} ctx - 上下文
|
|
125
|
+
* @param {Array} validRules - 有效规则列表
|
|
126
|
+
* @param {string} type - 切面类型(before/after/error)
|
|
127
|
+
* @param {Array} args - 方法参数
|
|
128
|
+
* @param {Function} originalMethod - 原方法
|
|
129
|
+
* @param {*} result - 方法执行结果
|
|
130
|
+
* @param {Error} error - 方法执行错误
|
|
131
|
+
*/
|
|
132
|
+
async _executeAspectByType(ctx, validRules, type, args, originalMethod, result = null, error = null) {
|
|
133
|
+
const rulesOfType = validRules.filter(rule => rule.type === type);
|
|
134
|
+
for (const rule of rulesOfType) {
|
|
135
|
+
await this._executeSingleRule(ctx, rule, args, originalMethod, result, error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 执行单个切面规则(私有方法)
|
|
141
|
+
* @private
|
|
142
|
+
* @param {object} ctx - 上下文
|
|
143
|
+
* @param {object} rule - 单个切面规则
|
|
144
|
+
* @param {Array} args - 方法参数
|
|
145
|
+
* @param {Function} originalMethod - 原方法
|
|
146
|
+
* @param {*} result - 方法执行结果
|
|
147
|
+
* @param {Error} error - 方法执行错误
|
|
148
|
+
*/
|
|
149
|
+
async _executeSingleRule(ctx, rule, args, originalMethod, result = null, error = null) {
|
|
150
|
+
for (const [key, value] of Object.entries(rule)) {
|
|
151
|
+
// 跳过内置关键字 + 空值
|
|
152
|
+
if (Aop.TYPES.has(key) || !value) continue;
|
|
153
|
+
|
|
154
|
+
const aspectFn = this.get(key);
|
|
155
|
+
if (!aspectFn) {
|
|
156
|
+
console.warn(`[AOP警告] 未注册切面 ${key},跳过执行`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 构造切面入参(浅拷贝避免原数据被修改)
|
|
161
|
+
const aspectParams = {
|
|
162
|
+
ctx,
|
|
163
|
+
methodName: originalMethod.name || 'anonymous',
|
|
164
|
+
args: [...args],
|
|
165
|
+
originalMethod,
|
|
166
|
+
result,
|
|
167
|
+
error,
|
|
168
|
+
params: typeof value === 'object' && !Array.isArray(value) ? { ...value } : {}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await aspectFn(aspectParams);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 全局单例
|
|
177
|
+
export const aop = new Aop();
|
|
178
|
+
export default Aop;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Paths } from '../config/paths.js';
|
|
4
|
+
|
|
5
|
+
export class Container {
|
|
6
|
+
constructor(type = 'service') {
|
|
7
|
+
this.map = new Map();
|
|
8
|
+
this.type = type;
|
|
9
|
+
this.baseDir = Paths.modulesPath;
|
|
10
|
+
|
|
11
|
+
// 只添加 config 属性,db 由 Service 自己管理
|
|
12
|
+
this.config = Chan.config;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取模块下的组件实例
|
|
17
|
+
* @param {string} moduleName - 模块名 ,例如:'web','api'
|
|
18
|
+
* @param {string} fileName - 文件名
|
|
19
|
+
* @returns {Promise<any>} - 组件实例
|
|
20
|
+
*/
|
|
21
|
+
async get(moduleName, fileName) {
|
|
22
|
+
const key = `${moduleName}.${fileName}`;
|
|
23
|
+
|
|
24
|
+
if (this.map.has(key)) {
|
|
25
|
+
return this.map.get(key);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await this.loadFile(moduleName, fileName);
|
|
30
|
+
const obj = this.map.get(key);
|
|
31
|
+
if (!obj) {
|
|
32
|
+
console.log(`文件 ${fileName}.js 加载后组件实例为空`);
|
|
33
|
+
}
|
|
34
|
+
return obj;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(`获取模块 ${moduleName} 下文件 ${fileName}.js 对应的组件失败:${err.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 设置模块下的组件实例
|
|
42
|
+
* @param {string} moduleName - 模块名 ,例如:'web','api'
|
|
43
|
+
* @param {string} fileName - 文件名
|
|
44
|
+
* @param {any} obj - 组件实例
|
|
45
|
+
* @returns {Container} - 当前实例
|
|
46
|
+
*/
|
|
47
|
+
set(moduleName, fileName, obj) {
|
|
48
|
+
const key = `${moduleName}.${fileName}`;
|
|
49
|
+
this.map.set(key, obj);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 加载模块下的组件实例
|
|
55
|
+
* @param {string} moduleName - 模块名 ,例如:'web','api'
|
|
56
|
+
* @param {string} fileName - 文件名
|
|
57
|
+
* @returns {Promise<any>} - 组件实例
|
|
58
|
+
*/
|
|
59
|
+
async loadFile(moduleName, fileName) {
|
|
60
|
+
const filePath = path.resolve(
|
|
61
|
+
this.baseDir,
|
|
62
|
+
moduleName,
|
|
63
|
+
this.type,
|
|
64
|
+
`${fileName}.js`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(filePath)) {
|
|
68
|
+
console.log(`模块 ${moduleName} 下 ${this.type} 目录中未找到文件:${fileName}.js`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const module = await import(`file://${filePath}`);
|
|
73
|
+
const obj = module.default;
|
|
74
|
+
|
|
75
|
+
const key = `${moduleName}.${fileName}`;
|
|
76
|
+
this.map.set(key, obj);
|
|
77
|
+
return obj;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default Container;
|
package/base/Controller.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { success, fail } from "../utils/response.js";
|
|
2
|
+
import Container from "./Container.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 控制器基类
|
|
5
6
|
* 提供统一的响应格式和常用方法
|
|
6
7
|
*/
|
|
7
|
-
export default class Controller {
|
|
8
|
-
constructor() {
|
|
8
|
+
export default class Controller extends Container {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('controller');
|
|
11
|
+
}
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* 返回成功响应
|
package/base/Event.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
export class Event extends EventEmitter {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 注册事件监听器
|
|
10
|
+
* @param {string} eventName - 事件名
|
|
11
|
+
* @param {*} listener - 监听器函数
|
|
12
|
+
* @returns {Event} - 当前实例
|
|
13
|
+
*/
|
|
14
|
+
on(eventName, listener) {
|
|
15
|
+
return super.on(eventName, listener);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 触发事件
|
|
20
|
+
* @param {string} eventName - 事件名
|
|
21
|
+
* @param {*} data - 事件数据
|
|
22
|
+
* @returns {Event} - 当前实例
|
|
23
|
+
*/
|
|
24
|
+
emit(eventName, data) {
|
|
25
|
+
return super.emit(eventName, data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 注销事件监听器
|
|
30
|
+
* @param {string} eventName - 事件名
|
|
31
|
+
* @param {*} listener - 监听器函数
|
|
32
|
+
* @returns {Event} - 当前实例
|
|
33
|
+
*/
|
|
34
|
+
off(eventName, listener) {
|
|
35
|
+
return super.off(eventName, listener);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 注销所有事件监听器
|
|
40
|
+
* @param {string} eventName - 事件名
|
|
41
|
+
* @returns {Event} - 当前实例
|
|
42
|
+
*/
|
|
43
|
+
removeAllListeners(eventName) {
|
|
44
|
+
return super.removeAllListeners(eventName);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const event = new Event();
|
|
49
|
+
export default Event;
|
package/base/Service.js
CHANGED
|
@@ -2,21 +2,35 @@
|
|
|
2
2
|
* 数据库服务基类
|
|
3
3
|
* 提供常用的数据库操作方法,包括增删改查、分页查询、事务等
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { formatDateFields } from "../helper/time.js";
|
|
6
|
+
import Container from "./Container.js";
|
|
6
7
|
|
|
7
|
-
class Service {
|
|
8
|
+
class Service extends Container {
|
|
8
9
|
/**
|
|
9
10
|
* 构造函数
|
|
10
|
-
* @param {Object} knex - Knex数据库实例
|
|
11
11
|
* @param {string} tableName - 表名
|
|
12
|
+
* @param {string} dbName - 数据库名称,默认使用默认数据库
|
|
12
13
|
*/
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
constructor(tableName = null, dbName = null) {
|
|
15
|
+
super('service');
|
|
16
|
+
|
|
17
|
+
// 根据数据库名称获取,否则使用默认数据库
|
|
18
|
+
if (dbName) {
|
|
19
|
+
this.db = Chan.dbManager.get(dbName);
|
|
20
|
+
} else {
|
|
21
|
+
this.db = Chan.db;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (tableName) {
|
|
16
25
|
this.tableName = tableName;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
this._dateFields = ['created_at', 'updated_at', 'deleted_at', 'publish_time', 'start_time', 'end_time', 'login_time', 'created_date', 'updated_date', 'createdAt', 'updatedAt', 'deletedAt', 'publishTime', 'startTime', 'endTime', 'loginTime', 'createdDate', 'updatedDate'];
|
|
29
|
+
|
|
30
|
+
// 自动注册事件监听器(如果子类定义了 on 方法)
|
|
31
|
+
if (typeof this.on === 'function') {
|
|
32
|
+
this.on();
|
|
33
|
+
}
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
/**
|
package/helper/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { loaderSort, loadConfig, clearConfigCache,
|
|
1
|
+
export { loaderSort, loadConfig, clearConfigCache, loadController } from "./loader.js";
|
|
2
2
|
export { formatTime } from "./time.js";
|
|
3
3
|
export { formatDateFields } from "./time.js";
|
|
4
4
|
export { default as Cache } from "./cache.js";
|
package/helper/loader.js
CHANGED
|
@@ -70,41 +70,6 @@ export function clearConfigCache() {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
/**
|
|
74
|
-
* 绑定实例的所有方法到实例本身
|
|
75
|
-
* @param {Object} instance - 需要绑定的实例对象
|
|
76
|
-
* @returns {Object} 绑定后的实例
|
|
77
|
-
* @description
|
|
78
|
-
* 遍历实例原型链上的所有方法,将它们绑定到实例本身
|
|
79
|
-
* 这样可以确保方法中的 this 始终指向正确的实例
|
|
80
|
-
* @example
|
|
81
|
-
* class MyController {
|
|
82
|
-
* constructor() {
|
|
83
|
-
* this.name = 'test';
|
|
84
|
-
* }
|
|
85
|
-
* getName() {
|
|
86
|
-
* return this.name;
|
|
87
|
-
* }
|
|
88
|
-
* }
|
|
89
|
-
* const controller = new MyController();
|
|
90
|
-
* const bound = bindInstance(controller);
|
|
91
|
-
* const getName = bound.getName;
|
|
92
|
-
* console.log(getName()); // 正确输出 'test'
|
|
93
|
-
*/
|
|
94
|
-
export function bindInstance(instance) {
|
|
95
|
-
Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).forEach(
|
|
96
|
-
(methodName) => {
|
|
97
|
-
if (
|
|
98
|
-
methodName !== "constructor" &&
|
|
99
|
-
typeof instance[methodName] === "function"
|
|
100
|
-
) {
|
|
101
|
-
instance[methodName] = instance[methodName].bind(instance);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
return instance;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
73
|
/**
|
|
109
74
|
* 加载指定模块的所有控制器
|
|
110
75
|
* @async
|
|
@@ -135,8 +100,24 @@ export async function loadController(moduleName) {
|
|
|
135
100
|
|
|
136
101
|
try {
|
|
137
102
|
const module = await import(`file://${filePath}`);
|
|
138
|
-
let
|
|
139
|
-
|
|
103
|
+
let instance = module?.default || module;
|
|
104
|
+
|
|
105
|
+
// 绑定实例的所有方法
|
|
106
|
+
if (instance && typeof instance === 'object') {
|
|
107
|
+
const proto = Object.getPrototypeOf(instance);
|
|
108
|
+
if (proto) {
|
|
109
|
+
Object.getOwnPropertyNames(proto).forEach((methodName) => {
|
|
110
|
+
if (
|
|
111
|
+
methodName !== "constructor" &&
|
|
112
|
+
typeof instance[methodName] === "function"
|
|
113
|
+
) {
|
|
114
|
+
instance[methodName] = instance[methodName].bind(instance);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
controller[name] = instance;
|
|
140
121
|
} catch (e) {
|
|
141
122
|
console.error(`加载控制器失败: ${filePath}`, e);
|
|
142
123
|
}
|
package/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// 核心基础类导出
|
|
2
2
|
export { default as Controller } from "./base/Controller.js";
|
|
3
3
|
export { default as Service } from "./base/Service.js";
|
|
4
|
-
export { default as
|
|
4
|
+
export { default as DB } from "./base/Database.js";
|
|
5
|
+
export { Container } from "./base/Container.js";
|
|
6
|
+
export { Aop, aop } from "./base/Aop.js";
|
|
7
|
+
export { Event, event } from "./base/Event.js";
|
|
5
8
|
|
|
9
|
+
// 工具模块导出
|
|
6
10
|
export * as helper from "./helper/index.js";
|
|
7
11
|
export * as utils from "./utils/index.js";
|
|
8
12
|
export * as middleware from "./middleware/index.js";
|
|
@@ -10,5 +14,12 @@ export * as config from "./config/index.js";
|
|
|
10
14
|
export * as common from "./common/index.js";
|
|
11
15
|
export * as extend from "./extend/index.js";
|
|
12
16
|
|
|
17
|
+
// 常用工具函数直接导出
|
|
18
|
+
export { loadConfig, loaderSort, loadController } from "./helper/index.js";
|
|
19
|
+
|
|
20
|
+
// 路径配置
|
|
21
|
+
export { Paths } from "./config/paths.js";
|
|
22
|
+
|
|
23
|
+
// 默认导出应用主类
|
|
13
24
|
import Chan from "./App.js";
|
|
14
25
|
export default Chan;
|
package/middleware/template.js
CHANGED
|
@@ -18,15 +18,16 @@ import { importjs } from "../global/import.js";
|
|
|
18
18
|
* @example
|
|
19
19
|
* setTemplate(app, {
|
|
20
20
|
* views: ['./views'],
|
|
21
|
-
* NODE_ENV: '
|
|
21
|
+
* NODE_ENV: 'development'
|
|
22
22
|
* });
|
|
23
23
|
*/
|
|
24
24
|
export let setTemplate = (app, config) => {
|
|
25
25
|
const { views, NODE_ENV } = config;
|
|
26
|
+
console.log("cache", NODE_ENV === "production");
|
|
26
27
|
const all = [...views];
|
|
27
28
|
app.set("view options", {
|
|
28
|
-
debug: NODE_ENV === "
|
|
29
|
-
cache:
|
|
29
|
+
debug: NODE_ENV === "development",
|
|
30
|
+
cache: NODE_ENV === "production",
|
|
30
31
|
minimize: true,
|
|
31
32
|
});
|
|
32
33
|
app.set("view engine", "html");
|