chanjs 2.1.1 → 2.3.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 +387 -0
- package/base/Context.js +78 -0
- package/base/Controller.js +137 -0
- package/base/Database.js +314 -0
- package/base/Service.js +539 -0
- package/common/api.js +25 -0
- package/common/category.js +22 -0
- package/common/code.js +42 -0
- package/common/email.js +110 -0
- package/common/index.js +7 -0
- package/common/pages.js +86 -0
- package/common/sms.js +104 -0
- package/common/utils.js +73 -0
- package/config/code.js +110 -52
- package/config/index.js +10 -0
- package/config/paths.js +60 -0
- package/extend/art-template.js +46 -28
- package/extend/index.js +6 -0
- package/global/env.js +11 -5
- package/global/global.js +63 -39
- package/global/import.js +43 -39
- package/global/index.js +8 -3
- package/helper/cache.js +182 -0
- package/helper/data-parse.js +121 -37
- package/helper/db.js +71 -83
- package/helper/file.js +158 -208
- package/helper/filter.js +34 -0
- package/helper/html.js +30 -47
- package/helper/index.js +29 -5
- package/helper/ip.js +48 -31
- package/helper/jwt.js +78 -11
- package/helper/loader.js +93 -50
- package/helper/request.js +41 -144
- package/helper/sign.js +96 -33
- package/helper/time.js +89 -74
- package/helper/tree.js +77 -0
- package/index.js +15 -181
- package/middleware/cookie.js +20 -4
- package/middleware/cors.js +20 -0
- package/middleware/favicon.js +21 -5
- package/middleware/header.js +26 -9
- package/middleware/index.js +14 -23
- package/middleware/preventRetry.js +30 -0
- package/middleware/setBody.js +24 -10
- package/middleware/static.js +31 -10
- package/middleware/template.js +34 -14
- package/middleware/validator.js +43 -23
- package/middleware/waf.js +147 -287
- package/package.json +1 -1
- package/utils/checker.js +68 -0
- package/utils/error-handler.js +115 -0
- package/utils/error.js +81 -0
- package/utils/index.js +6 -0
- package/utils/keywords.js +126 -0
- package/utils/rate-limit.js +116 -0
- package/utils/response.js +103 -64
- package/utils/xss-filter.js +42 -0
- package/core/controller.js +0 -33
- package/core/index.js +0 -3
- package/core/service.js +0 -307
- package/middleware/log.js +0 -21
package/App.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
import AppContext from "./base/Context.js";
|
|
6
|
+
import Controller from "./base/Controller.js";
|
|
7
|
+
import DatabaseManager from "./base/Database.js";
|
|
8
|
+
|
|
9
|
+
import Service from "./base/Service.js";
|
|
10
|
+
|
|
11
|
+
import { Paths } from "./config/index.js";
|
|
12
|
+
import { getChildrenId, filterBody, filterImgFromStr, CODE as commonCode, sendMail, genRegEmailHtml, genResetPasswordEmail, pages, getHtmlFilesSync } from "./common/index.js";
|
|
13
|
+
import { db, loadConfig, loadController, loaderSort, prefixDbConfig, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, arrToObj, htmlDecode } from "./helper/index.js";
|
|
14
|
+
import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf } from "./middleware/index.js";
|
|
15
|
+
import { errorResponse, notFoundResponse, parseDatabaseError } from "./utils/error-handler.js";
|
|
16
|
+
import { error as responseError, fail, success } from "./utils/response.js";
|
|
17
|
+
|
|
18
|
+
import "./global/index.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Chan 应用核心类
|
|
22
|
+
* @class Chan
|
|
23
|
+
* @description 管理应用程序的路由、中间件、数据库连接和服务
|
|
24
|
+
* @example
|
|
25
|
+
* const app = new Chan({ port: 3000, env: "production" });
|
|
26
|
+
* await app.start();
|
|
27
|
+
*/
|
|
28
|
+
class Chan {
|
|
29
|
+
static helper = {
|
|
30
|
+
loadController,
|
|
31
|
+
db,
|
|
32
|
+
prefixDbConfig,
|
|
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
|
+
arrToObj,
|
|
48
|
+
htmlDecode,
|
|
49
|
+
};
|
|
50
|
+
static common = {
|
|
51
|
+
success,
|
|
52
|
+
fail,
|
|
53
|
+
error: responseError,
|
|
54
|
+
getChildrenId,
|
|
55
|
+
CODE: commonCode,
|
|
56
|
+
sendMail,
|
|
57
|
+
genRegEmailHtml,
|
|
58
|
+
genResetPasswordEmail,
|
|
59
|
+
pages,
|
|
60
|
+
getHtmlFilesSync,
|
|
61
|
+
filterBody,
|
|
62
|
+
filterImgFromStr,
|
|
63
|
+
};
|
|
64
|
+
static config = {};
|
|
65
|
+
static Service = Service;
|
|
66
|
+
static Controller = Controller;
|
|
67
|
+
static paths = Paths;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 构造函数
|
|
71
|
+
* @constructor
|
|
72
|
+
* @param {Object} options - 应用配置选项
|
|
73
|
+
* @param {number} [options.port=3000] - 服务器端口
|
|
74
|
+
* @param {string} [options.env] - 运行环境
|
|
75
|
+
* @description 初始化 Express 应用和核心组件
|
|
76
|
+
*/
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.app = express();
|
|
79
|
+
this.context = new AppContext();
|
|
80
|
+
this.dbManager = new DatabaseManager();
|
|
81
|
+
|
|
82
|
+
this.options = {
|
|
83
|
+
port: options.port || 3000,
|
|
84
|
+
env: options.env || process.env.NODE_ENV || "development",
|
|
85
|
+
...options,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this._isStarted = false;
|
|
89
|
+
this.router = express.Router();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 启动应用程序
|
|
94
|
+
* @async
|
|
95
|
+
* @description 执行完整的启动流程
|
|
96
|
+
* 包括:初始化配置、数据库、扩展、中间件、路由、错误处理
|
|
97
|
+
* @returns {Promise<void>}
|
|
98
|
+
*/
|
|
99
|
+
async start() {
|
|
100
|
+
if (this._isStarted) {
|
|
101
|
+
console.warn("App already started");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
global.appContext = this.context;
|
|
106
|
+
global.Chan = Chan;
|
|
107
|
+
|
|
108
|
+
await this.initConfig();
|
|
109
|
+
await this.initDB();
|
|
110
|
+
await this.initExtend();
|
|
111
|
+
await this.initMiddleware();
|
|
112
|
+
|
|
113
|
+
this._applyConfig();
|
|
114
|
+
|
|
115
|
+
await this.loadRouter();
|
|
116
|
+
await this.loadCommonRouter();
|
|
117
|
+
|
|
118
|
+
this._applyRoutes();
|
|
119
|
+
this._applyErrorHandling();
|
|
120
|
+
|
|
121
|
+
await this._listen();
|
|
122
|
+
|
|
123
|
+
this._isStarted = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 初始化配置
|
|
128
|
+
* @async
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
* @description 加载应用配置并设置到上下文
|
|
131
|
+
*/
|
|
132
|
+
async initConfig() {
|
|
133
|
+
let config = await loadConfig();
|
|
134
|
+
Chan.config = config;
|
|
135
|
+
this.context.set("config", config);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 初始化数据库
|
|
140
|
+
* @async
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
* @description 根据配置初始化所有数据库连接
|
|
143
|
+
*/
|
|
144
|
+
async initDB() {
|
|
145
|
+
const dbList = Chan.config?.db || [];
|
|
146
|
+
const connections = this.dbManager.getConnections();
|
|
147
|
+
|
|
148
|
+
for (const [index, item] of dbList.entries()) {
|
|
149
|
+
const key = item.key || String(index);
|
|
150
|
+
try {
|
|
151
|
+
const dbConfig = prefixDbConfig(key) || item;
|
|
152
|
+
if (!dbConfig) throw new Error("未找到配置");
|
|
153
|
+
|
|
154
|
+
const connection = this.dbManager.add(key, dbConfig, { isDefault: index === 0 });
|
|
155
|
+
this.context.set(`db:${key}`, connection);
|
|
156
|
+
|
|
157
|
+
if (index === 0) {
|
|
158
|
+
Chan.db = connection;
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error(`[DB] 数据库 ${key} 初始化失败: ${error.message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(
|
|
166
|
+
`[DB] 初始化完成,已加载 ${dbList.length} 个数据库,成功 ${Object.keys(connections).length} 个`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 初始化扩展模块
|
|
172
|
+
* @async
|
|
173
|
+
* @returns {Promise<void>}
|
|
174
|
+
* @description 加载 common、helper、extend 目录下的扩展模块
|
|
175
|
+
*/
|
|
176
|
+
async initExtend() {
|
|
177
|
+
const extensions = [
|
|
178
|
+
{ _path: Paths.commonPath, key: "common" },
|
|
179
|
+
{ _path: Paths.helperPath, key: "helper" },
|
|
180
|
+
{ _path: Paths.extendPath, key: "extend" },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
for (const { _path, key } of extensions) {
|
|
184
|
+
await this.loadFn(_path, key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 加载模块文件
|
|
190
|
+
* @async
|
|
191
|
+
* @param {string} _path - 模块目录路径
|
|
192
|
+
* @param {string} key - 模块键名
|
|
193
|
+
* @returns {Promise<void>}
|
|
194
|
+
* @description 从指定目录加载所有 JS 模块并合并到 Chan 静态属性
|
|
195
|
+
*/
|
|
196
|
+
async loadFn(_path, key) {
|
|
197
|
+
if (fs.existsSync(_path)) {
|
|
198
|
+
const files = fs.readdirSync(_path).filter((file) => file.endsWith(".js"));
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
const filePath = path.join(_path, file);
|
|
201
|
+
let helperModule = await importFile(filePath);
|
|
202
|
+
Object.assign(Chan[key], helperModule);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 初始化中间件
|
|
209
|
+
* @async
|
|
210
|
+
* @returns {Promise<void>}
|
|
211
|
+
* @description 配置并注册所有应用中间件
|
|
212
|
+
*/
|
|
213
|
+
async initMiddleware() {
|
|
214
|
+
const config = Chan.config;
|
|
215
|
+
const { views = [], env = "development", APP_NAME = "ChanCMS", APP_VERSION = "1.0.0", cookieKey, BODY_LIMIT = "10mb", statics = [], cors = {}, PROXY = "false", waf: wafConfig = { enabled: false } } = config;
|
|
216
|
+
|
|
217
|
+
await waf(this.app, wafConfig);
|
|
218
|
+
setFavicon(this.app);
|
|
219
|
+
setStatic(this.app, statics);
|
|
220
|
+
setCookie(this.app, cookieKey);
|
|
221
|
+
setBody(this.app, BODY_LIMIT);
|
|
222
|
+
Cors(this.app, cors);
|
|
223
|
+
setTemplate(this.app, { views, NODE_ENV: env });
|
|
224
|
+
setHeader(this.app, { APP_NAME, APP_VERSION });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 应用配置
|
|
229
|
+
* @private
|
|
230
|
+
* @description 设置 Express 应用配置
|
|
231
|
+
*/
|
|
232
|
+
_applyConfig() {
|
|
233
|
+
this.app.set("trust proxy", Chan.config.PROXY === "true");
|
|
234
|
+
this.app.set("env", this.options.env);
|
|
235
|
+
this.app.disable("x-powered-by");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 应用路由
|
|
240
|
+
* @private
|
|
241
|
+
* @description 将所有路由应用到 Express 应用
|
|
242
|
+
*/
|
|
243
|
+
_applyRoutes() {
|
|
244
|
+
this.app.use(this.router);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 应用错误处理
|
|
249
|
+
* @private
|
|
250
|
+
* @description 配置全局错误处理中间件
|
|
251
|
+
*/
|
|
252
|
+
_applyErrorHandling() {
|
|
253
|
+
this.app.use((req, res) => {
|
|
254
|
+
res.status(404).json(notFoundResponse(req));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.app.use((err, req, res, next) => {
|
|
258
|
+
if (res.headersSent) return next(err);
|
|
259
|
+
|
|
260
|
+
const errorInfo = err?.stack ? this._parseErrorStack(err.stack) : { message: err?.message || err };
|
|
261
|
+
const requestInfo = `${req.method} ${req.originalUrl}`;
|
|
262
|
+
console.error(`[Global Error] ${requestInfo} - ${errorInfo.file}:${errorInfo.line || "?"} - ${errorInfo.message}`);
|
|
263
|
+
|
|
264
|
+
if (err?.isError && err.message) {
|
|
265
|
+
const statusCode = err.statusCode >= 500 ? 500 : 400;
|
|
266
|
+
console.error(`[Global Error Details] Code: ${err.code || 500}, Status: ${statusCode}`);
|
|
267
|
+
return res.status(statusCode).json({
|
|
268
|
+
success: false,
|
|
269
|
+
msg: err.message,
|
|
270
|
+
code: err.code || 500,
|
|
271
|
+
data: process.env.NODE_ENV === "development" ? {
|
|
272
|
+
stack: err.stack,
|
|
273
|
+
file: errorInfo.file,
|
|
274
|
+
line: errorInfo.line,
|
|
275
|
+
column: errorInfo.column,
|
|
276
|
+
url: req.originalUrl,
|
|
277
|
+
method: req.method
|
|
278
|
+
} : {},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const parsedError = parseDatabaseError(err);
|
|
283
|
+
console.error(`[Global Error Details] Database Error - ${parsedError.msg}`);
|
|
284
|
+
res.status(parsedError.statusCode).json(errorResponse(err, req));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 解析错误堆栈
|
|
290
|
+
* @private
|
|
291
|
+
* @param {string} stack - 错误堆栈字符串
|
|
292
|
+
* @returns {Object} 解析后的错误信息
|
|
293
|
+
* @description 从错误堆栈中提取文件名、行号、列号等信息
|
|
294
|
+
*/
|
|
295
|
+
_parseErrorStack(stack) {
|
|
296
|
+
if (!stack) return { message: '未知错误' };
|
|
297
|
+
|
|
298
|
+
const stackLines = stack.split('\n');
|
|
299
|
+
if (stackLines.length < 2) {
|
|
300
|
+
return { message: stackLines[0] || '未知错误' };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const errorLine = stackLines[1].trim();
|
|
304
|
+
const match = errorLine.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
305
|
+
|
|
306
|
+
if (match) {
|
|
307
|
+
return {
|
|
308
|
+
message: stackLines[0] || '未知错误',
|
|
309
|
+
function: match[1],
|
|
310
|
+
file: match[2],
|
|
311
|
+
line: match[3],
|
|
312
|
+
column: match[4]
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const fileMatch = errorLine.match(/at\s+(.+?):(\d+):(\d+)/);
|
|
317
|
+
if (fileMatch) {
|
|
318
|
+
return {
|
|
319
|
+
message: stackLines[0] || '未知错误',
|
|
320
|
+
file: fileMatch[1],
|
|
321
|
+
line: fileMatch[2],
|
|
322
|
+
column: fileMatch[3]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { message: stackLines[0] || '未知错误', file: errorLine };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 启动服务器监听
|
|
331
|
+
* @async
|
|
332
|
+
* @private
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
* @description 启动 HTTP 服务器监听指定端口
|
|
335
|
+
*/
|
|
336
|
+
async _listen() {
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
const server = this.app.listen(this.options.port, () => {
|
|
339
|
+
console.log(`Server running on port ${this.options.port}`);
|
|
340
|
+
resolve(server);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
server.on("error", reject);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 加载模块路由
|
|
349
|
+
* @async
|
|
350
|
+
* @returns {Promise<void>}
|
|
351
|
+
* @description 从 app/modules 目录加载所有模块的路由
|
|
352
|
+
*/
|
|
353
|
+
async loadRouter() {
|
|
354
|
+
const configPath = path.join(Paths.appPath, "modules");
|
|
355
|
+
if (fs.existsSync(configPath)) {
|
|
356
|
+
const dirs = loaderSort(Chan.config.modules);
|
|
357
|
+
for (const item of dirs) {
|
|
358
|
+
let router = await importFile(`app/modules/${item}/router.js`);
|
|
359
|
+
router(this.app, this.router, Chan.config);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 加载公共路由
|
|
366
|
+
* @async
|
|
367
|
+
* @returns {Promise<void>}
|
|
368
|
+
* @description 加载 app/router.js 中的公共路由
|
|
369
|
+
*/
|
|
370
|
+
async loadCommonRouter() {
|
|
371
|
+
let router = await importFile("app/router.js");
|
|
372
|
+
if (router) {
|
|
373
|
+
router(this.app, this.router, Chan.config);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 准备启动
|
|
379
|
+
* @param {Function} [cb] - 回调函数
|
|
380
|
+
* @description 在启动前执行回调,传递端口号
|
|
381
|
+
*/
|
|
382
|
+
ready(cb) {
|
|
383
|
+
cb?.(this.options.port);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export default Chan;
|
package/base/Context.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 应用上下文类
|
|
3
|
+
* 用于存储和管理应用的配置和模型实例
|
|
4
|
+
*/
|
|
5
|
+
class AppContext {
|
|
6
|
+
/**
|
|
7
|
+
* 构造函数
|
|
8
|
+
* 初始化配置和模型的Map存储
|
|
9
|
+
*/
|
|
10
|
+
constructor() {
|
|
11
|
+
this._config = new Map();
|
|
12
|
+
this._models = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 设置配置项
|
|
17
|
+
* @param {string} key - 配置键名
|
|
18
|
+
* @param {*} value - 配置值
|
|
19
|
+
*/
|
|
20
|
+
set(key, value) {
|
|
21
|
+
this._config.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取配置项
|
|
26
|
+
* @param {string} key - 配置键名
|
|
27
|
+
* @param {*} defaultValue - 默认值
|
|
28
|
+
* @returns {*} 配置值或默认值
|
|
29
|
+
*/
|
|
30
|
+
get(key, defaultValue) {
|
|
31
|
+
return this._config.get(key) ?? defaultValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 检查配置项是否存在
|
|
36
|
+
* @param {string} key - 配置键名
|
|
37
|
+
* @returns {boolean} 是否存在
|
|
38
|
+
*/
|
|
39
|
+
has(key) {
|
|
40
|
+
return this._config.has(key);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 设置模型实例
|
|
45
|
+
* @param {string} name - 模型名称
|
|
46
|
+
* @param {Object} instance - 模型实例
|
|
47
|
+
*/
|
|
48
|
+
setModel(name, instance) {
|
|
49
|
+
this._models.set(name, instance);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 获取模型实例
|
|
54
|
+
* @param {string} name - 模型名称
|
|
55
|
+
* @returns {Object|undefined} 模型实例
|
|
56
|
+
*/
|
|
57
|
+
getModel(name) {
|
|
58
|
+
return this._models.get(name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 获取所有模型实例
|
|
63
|
+
* @returns {Object} 包含所有模型的对象
|
|
64
|
+
*/
|
|
65
|
+
getAllModels() {
|
|
66
|
+
return Object.fromEntries(this._models.entries());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 清空所有配置和模型
|
|
71
|
+
*/
|
|
72
|
+
clear() {
|
|
73
|
+
this._config.clear();
|
|
74
|
+
this._models.clear();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default AppContext;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { success, fail, error as responseError } from "../utils/response.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 控制器基类
|
|
5
|
+
* 提供统一的响应格式和常用方法
|
|
6
|
+
*/
|
|
7
|
+
export default class Controller {
|
|
8
|
+
/**
|
|
9
|
+
* 构造函数
|
|
10
|
+
* @param {Object} context - 应用上下文实例
|
|
11
|
+
*/
|
|
12
|
+
constructor(context = null) {
|
|
13
|
+
if (context) {
|
|
14
|
+
this.context = context;
|
|
15
|
+
} else if (global.appContext) {
|
|
16
|
+
this.context = global.appContext;
|
|
17
|
+
} else {
|
|
18
|
+
this.context = { get: () => null, set: () => null };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 标准化返回结果
|
|
24
|
+
* @private
|
|
25
|
+
* @param {*} result - 原始结果
|
|
26
|
+
* @param {string} defaultMsg - 默认消息
|
|
27
|
+
* @returns {Object} 标准化的结果对象
|
|
28
|
+
*/
|
|
29
|
+
_normalizeResult(result, defaultMsg = "操作成功") {
|
|
30
|
+
if (result === null || result === undefined) {
|
|
31
|
+
return { success: false, code: 5001, msg: "Service 返回空值", data: {} };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof result === 'object' && 'success' in result) {
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof result === 'object' || Array.isArray(result)) {
|
|
39
|
+
return { success: true, code: 200, msg: defaultMsg, data: result };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { success: true, code: 200, msg: defaultMsg, data: result };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 返回成功响应
|
|
47
|
+
* @param {*} options - 响应选项或数据
|
|
48
|
+
* @param {*} options.data - 响应数据
|
|
49
|
+
* @param {string} options.msg - 响应消息,默认"操作成功"
|
|
50
|
+
* @returns {Object} 标准成功响应
|
|
51
|
+
*/
|
|
52
|
+
success(options, msg = "操作成功") {
|
|
53
|
+
let data, message = msg, extra = {};
|
|
54
|
+
|
|
55
|
+
if (options === null || options === undefined) {
|
|
56
|
+
data = undefined;
|
|
57
|
+
} else if (typeof options === 'object' && options !== null && !Array.isArray(options)) {
|
|
58
|
+
if ('data' in options) {
|
|
59
|
+
data = options.data;
|
|
60
|
+
message = options.msg || msg;
|
|
61
|
+
extra = { ...options };
|
|
62
|
+
delete extra.data;
|
|
63
|
+
delete extra.msg;
|
|
64
|
+
} else if ('success' in options && 'code' in options) {
|
|
65
|
+
return options;
|
|
66
|
+
} else {
|
|
67
|
+
data = options;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
data = options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (data instanceof Error) {
|
|
74
|
+
return this.error({ err: data });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const normalized = this._normalizeResult(data, message);
|
|
78
|
+
|
|
79
|
+
if (!normalized.success) {
|
|
80
|
+
return this.fail({ msg: normalized.msg, data: normalized.data, code: normalized.code });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
code: 200,
|
|
86
|
+
msg: normalized.msg,
|
|
87
|
+
data: normalized.data,
|
|
88
|
+
...extra
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 返回失败响应
|
|
94
|
+
* @param {Object} options - 响应选项
|
|
95
|
+
* @param {string} options.msg - 失败消息,默认"操作失败"
|
|
96
|
+
* @param {*} options.data - 响应数据
|
|
97
|
+
* @param {number} options.code - 错误码,默认201
|
|
98
|
+
* @returns {Object} 标准失败响应
|
|
99
|
+
*/
|
|
100
|
+
fail({ msg = "操作失败", data = {}, code = 201 } = {}) {
|
|
101
|
+
return fail({ msg, data, code });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 返回错误响应
|
|
106
|
+
* @param {Object} options - 响应选项
|
|
107
|
+
* @param {Error} options.err - 错误对象
|
|
108
|
+
* @param {*} options.data - 响应数据
|
|
109
|
+
* @param {number} options.code - 错误码,默认500
|
|
110
|
+
* @returns {Object} 标准错误响应
|
|
111
|
+
*/
|
|
112
|
+
error({ err, data = {}, code = 500 } = {}) {
|
|
113
|
+
return responseError({ err, data, code });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 返回分页响应
|
|
118
|
+
* @param {Array} list - 数据列表
|
|
119
|
+
* @param {number} total - 总记录数
|
|
120
|
+
* @param {number} current - 当前页码
|
|
121
|
+
* @param {number} pageSize - 每页大小
|
|
122
|
+
* @returns {Object} 标准分页响应
|
|
123
|
+
*/
|
|
124
|
+
paginate(list, total, current, pageSize) {
|
|
125
|
+
return success({
|
|
126
|
+
data: {
|
|
127
|
+
list,
|
|
128
|
+
pagination: {
|
|
129
|
+
total,
|
|
130
|
+
current,
|
|
131
|
+
pageSize,
|
|
132
|
+
totalPages: Math.ceil(total / pageSize),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|