@zhin.js/http 1.0.4 → 1.0.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/app/index.ts +732 -0
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +237 -105
- package/lib/index.js.map +1 -1
- package/lib/router.d.ts.map +1 -1
- package/lib/router.js.map +1 -1
- package/package.json +7 -3
- package/CHANGELOG.md +0 -32
- package/src/index.ts +0 -556
- package/tsconfig.json +0 -23
- /package/{src → app}/router.ts +0 -0
package/lib/index.js
CHANGED
|
@@ -1,35 +1,43 @@
|
|
|
1
|
-
import { register, useApp, useDatabase } from
|
|
2
|
-
import { createServer } from
|
|
3
|
-
import os from
|
|
4
|
-
import Koa from
|
|
5
|
-
import auth from
|
|
6
|
-
import KoaBodyParser from
|
|
7
|
-
import { Router } from
|
|
8
|
-
import * as process from
|
|
9
|
-
export * from
|
|
1
|
+
import { register, defineSchema, Schema, usePlugin, useApp, useDatabase, } from "@zhin.js/core";
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import Koa from "koa";
|
|
5
|
+
import auth from "koa-basic-auth";
|
|
6
|
+
import KoaBodyParser from "koa-bodyparser";
|
|
7
|
+
import { Router } from "./router.js";
|
|
8
|
+
import * as process from "process";
|
|
9
|
+
export * from "./router.js";
|
|
10
|
+
const plugin = usePlugin();
|
|
11
|
+
const schema = defineSchema(Schema.object({
|
|
12
|
+
port: Schema.number("port").default(8086).description("HTTP 服务端口"),
|
|
13
|
+
username: Schema.string("username").description("HTTP 基本认证用户名, 默认为当前系统用户名"),
|
|
14
|
+
password: Schema.string("password").description("HTTP 基本认证密码, 默认为随机生成的6位字符串"),
|
|
15
|
+
base: Schema.string("base")
|
|
16
|
+
.default("/api")
|
|
17
|
+
.description("HTTP 路由前缀, 默认为 /api"),
|
|
18
|
+
}));
|
|
10
19
|
const koa = new Koa();
|
|
11
20
|
const server = createServer(koa.callback());
|
|
12
|
-
const router = new Router(server, { prefix: process.env.routerPrefix ||
|
|
21
|
+
const router = new Router(server, { prefix: process.env.routerPrefix || "" });
|
|
13
22
|
// 获取当前计算机登录用户名
|
|
14
23
|
const getCurrentUsername = () => {
|
|
15
24
|
try {
|
|
16
25
|
return os.userInfo().username;
|
|
17
26
|
}
|
|
18
27
|
catch {
|
|
19
|
-
return
|
|
28
|
+
return "admin"; // 如果获取失败,使用默认用户名
|
|
20
29
|
}
|
|
21
30
|
};
|
|
22
31
|
// 生成6位随机密码
|
|
23
32
|
const generateRandomPassword = () => {
|
|
24
|
-
const chars =
|
|
25
|
-
let result =
|
|
33
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
34
|
+
let result = "";
|
|
26
35
|
for (let i = 0; i < 6; i++) {
|
|
27
36
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
28
37
|
}
|
|
29
38
|
return result;
|
|
30
39
|
};
|
|
31
|
-
const
|
|
32
|
-
const password = process.env.password || generateRandomPassword();
|
|
40
|
+
const { port = 8086, username = getCurrentUsername(), password = generateRandomPassword(), base = "/api", } = schema(plugin.config, "http");
|
|
33
41
|
const app = useApp();
|
|
34
42
|
koa.use(auth({
|
|
35
43
|
name: username,
|
|
@@ -39,7 +47,7 @@ koa.use(auth({
|
|
|
39
47
|
// API 路由
|
|
40
48
|
// ============================================================================
|
|
41
49
|
// 系统状态 API
|
|
42
|
-
router.get(
|
|
50
|
+
router.get(`${base}/system/status`, async (ctx) => {
|
|
43
51
|
try {
|
|
44
52
|
ctx.body = {
|
|
45
53
|
success: true,
|
|
@@ -50,8 +58,8 @@ router.get('/api/system/status', async (ctx) => {
|
|
|
50
58
|
platform: process.platform,
|
|
51
59
|
nodeVersion: process.version,
|
|
52
60
|
pid: process.pid,
|
|
53
|
-
timestamp: new Date().toISOString()
|
|
54
|
-
}
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
},
|
|
55
63
|
};
|
|
56
64
|
}
|
|
57
65
|
catch (error) {
|
|
@@ -60,25 +68,25 @@ router.get('/api/system/status', async (ctx) => {
|
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
// 健康检查 API
|
|
63
|
-
router.get(
|
|
71
|
+
router.get(`${base}/health`, async (ctx) => {
|
|
64
72
|
ctx.body = {
|
|
65
73
|
success: true,
|
|
66
|
-
status:
|
|
67
|
-
timestamp: new Date().toISOString()
|
|
74
|
+
status: "ok",
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
68
76
|
};
|
|
69
77
|
});
|
|
70
78
|
// 统计信息 API
|
|
71
|
-
router.get(
|
|
79
|
+
router.get(`${base}/stats`, async (ctx) => {
|
|
72
80
|
try {
|
|
73
81
|
// 统计插件数量
|
|
74
82
|
const pluginCount = app.dependencyList.length;
|
|
75
|
-
const activePluginCount = app.dependencyList.filter(dep => dep.mounted).length;
|
|
83
|
+
const activePluginCount = app.dependencyList.filter((dep) => dep.mounted).length;
|
|
76
84
|
// 统计机器人数量
|
|
77
85
|
let botCount = 0;
|
|
78
86
|
let onlineBotCount = 0;
|
|
79
87
|
for (const context of app.contextList) {
|
|
80
88
|
const adapter = app.getContext(context.name);
|
|
81
|
-
if (adapter && typeof adapter ===
|
|
89
|
+
if (adapter && typeof adapter === "object" && "bots" in adapter) {
|
|
82
90
|
const adapterBots = adapter.bots;
|
|
83
91
|
if (adapterBots && adapterBots instanceof Map) {
|
|
84
92
|
botCount += adapterBots.size;
|
|
@@ -105,7 +113,7 @@ router.get('/api/stats', async (ctx) => {
|
|
|
105
113
|
components: componentCount,
|
|
106
114
|
uptime: process.uptime(),
|
|
107
115
|
memory: process.memoryUsage().heapUsed / 1024 / 1024, // MB
|
|
108
|
-
}
|
|
116
|
+
},
|
|
109
117
|
};
|
|
110
118
|
}
|
|
111
119
|
catch (error) {
|
|
@@ -114,18 +122,18 @@ router.get('/api/stats', async (ctx) => {
|
|
|
114
122
|
}
|
|
115
123
|
});
|
|
116
124
|
// 插件管理 API
|
|
117
|
-
router.get(
|
|
125
|
+
router.get(`${base}/plugins`, async (ctx) => {
|
|
118
126
|
try {
|
|
119
127
|
// 获取详细的插件数据
|
|
120
|
-
const plugins = app.dependencyList.map(dep => {
|
|
128
|
+
const plugins = app.dependencyList.map((dep) => {
|
|
121
129
|
return {
|
|
122
130
|
name: dep.name,
|
|
123
|
-
status: dep.mounted ?
|
|
131
|
+
status: dep.mounted ? "active" : "inactive",
|
|
124
132
|
commandCount: dep.commands.length,
|
|
125
133
|
componentCount: dep.components.size,
|
|
126
134
|
middlewareCount: dep.middlewares.length,
|
|
127
135
|
contextCount: dep.contexts.size,
|
|
128
|
-
description: dep.description ||
|
|
136
|
+
description: dep.description || "无描述",
|
|
129
137
|
};
|
|
130
138
|
});
|
|
131
139
|
ctx.body = { success: true, data: plugins, total: plugins.length };
|
|
@@ -136,68 +144,68 @@ router.get('/api/plugins', async (ctx) => {
|
|
|
136
144
|
}
|
|
137
145
|
});
|
|
138
146
|
// 插件详情 API
|
|
139
|
-
router.get(
|
|
147
|
+
router.get("/api/plugins/:name", async (ctx) => {
|
|
140
148
|
try {
|
|
141
149
|
const pluginName = ctx.params.name;
|
|
142
|
-
const plugin = app.dependencyList.find(dep => dep.name === pluginName);
|
|
150
|
+
const plugin = app.dependencyList.find((dep) => dep.name === pluginName);
|
|
143
151
|
if (!plugin) {
|
|
144
152
|
ctx.status = 404;
|
|
145
|
-
ctx.body = { success: false, error:
|
|
153
|
+
ctx.body = { success: false, error: "插件不存在" };
|
|
146
154
|
return;
|
|
147
155
|
}
|
|
148
156
|
// 获取命令详情
|
|
149
|
-
const commands = plugin.commands.map(cmd => ({
|
|
150
|
-
name: cmd.pattern
|
|
157
|
+
const commands = plugin.commands.map((cmd) => ({
|
|
158
|
+
name: cmd.pattern,
|
|
151
159
|
}));
|
|
152
160
|
// 获取组件详情
|
|
153
161
|
const components = Array.from(plugin.components.entries()).map(([name, comp]) => ({
|
|
154
162
|
name,
|
|
155
163
|
props: comp.props || {},
|
|
156
|
-
type: typeof comp
|
|
164
|
+
type: typeof comp,
|
|
157
165
|
}));
|
|
158
166
|
// 获取中间件详情
|
|
159
167
|
const middlewares = plugin.middlewares.map((_, index) => ({
|
|
160
168
|
id: `middleware-${index}`,
|
|
161
|
-
type:
|
|
169
|
+
type: "function",
|
|
162
170
|
}));
|
|
163
171
|
// 获取上下文详情
|
|
164
172
|
const contexts = Array.from(plugin.contexts.entries()).map(([name, ctx]) => ({
|
|
165
173
|
name,
|
|
166
|
-
description: ctx.description ||
|
|
174
|
+
description: ctx.description || "无描述",
|
|
167
175
|
}));
|
|
168
176
|
// 获取定时任务详情
|
|
169
177
|
const crons = plugin.crons.map((cron, index) => ({
|
|
170
178
|
id: `cron-${index}`,
|
|
171
|
-
pattern: cron.pattern ||
|
|
172
|
-
running: cron.running || false
|
|
179
|
+
pattern: cron.pattern || "unknown",
|
|
180
|
+
running: cron.running || false,
|
|
173
181
|
}));
|
|
174
182
|
// 获取数据模型详情
|
|
175
|
-
const
|
|
183
|
+
const definitions = Array.from(plugin.definitions.entries()).map(([name, definition]) => ({
|
|
176
184
|
name,
|
|
177
|
-
fields: Object.keys(
|
|
185
|
+
fields: Object.keys(definition),
|
|
178
186
|
}));
|
|
179
187
|
ctx.body = {
|
|
180
188
|
success: true,
|
|
181
189
|
data: {
|
|
182
190
|
name: plugin.name,
|
|
183
191
|
filename: plugin.filename,
|
|
184
|
-
status: plugin.mounted ?
|
|
185
|
-
description: plugin.description ||
|
|
192
|
+
status: plugin.mounted ? "active" : "inactive",
|
|
193
|
+
description: plugin.description || "无描述",
|
|
186
194
|
commands,
|
|
187
195
|
components,
|
|
188
196
|
middlewares,
|
|
189
197
|
contexts,
|
|
190
198
|
crons,
|
|
191
|
-
|
|
199
|
+
definitions,
|
|
192
200
|
statistics: {
|
|
193
201
|
commandCount: commands.length,
|
|
194
202
|
componentCount: components.length,
|
|
195
203
|
middlewareCount: middlewares.length,
|
|
196
204
|
contextCount: contexts.length,
|
|
197
205
|
cronCount: crons.length,
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
}
|
|
206
|
+
definitionCount: definitions.length,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
201
209
|
};
|
|
202
210
|
}
|
|
203
211
|
catch (error) {
|
|
@@ -206,13 +214,13 @@ router.get('/api/plugins/:name', async (ctx) => {
|
|
|
206
214
|
}
|
|
207
215
|
});
|
|
208
216
|
// 适配器和机器人状态 API
|
|
209
|
-
router.get(
|
|
217
|
+
router.get(`${base}/bots`, async (ctx) => {
|
|
210
218
|
try {
|
|
211
219
|
const bots = [];
|
|
212
220
|
// 遍历所有上下文,查找适配器
|
|
213
221
|
for (const context of app.contextList) {
|
|
214
222
|
const adapter = app.getContext(context.name);
|
|
215
|
-
if (adapter && typeof adapter ===
|
|
223
|
+
if (adapter && typeof adapter === "object" && "bots" in adapter) {
|
|
216
224
|
const adapterBots = adapter.bots;
|
|
217
225
|
if (adapterBots && adapterBots instanceof Map) {
|
|
218
226
|
for (const [botName, bot] of adapterBots.entries()) {
|
|
@@ -220,7 +228,7 @@ router.get('/api/bots', async (ctx) => {
|
|
|
220
228
|
name: botName,
|
|
221
229
|
adapter: context.name,
|
|
222
230
|
connected: bot.$connected || false,
|
|
223
|
-
status: bot.$connected ?
|
|
231
|
+
status: bot.$connected ? "online" : "offline",
|
|
224
232
|
// 移除 config 字段以保护隐私
|
|
225
233
|
});
|
|
226
234
|
}
|
|
@@ -235,7 +243,7 @@ router.get('/api/bots', async (ctx) => {
|
|
|
235
243
|
}
|
|
236
244
|
});
|
|
237
245
|
// 框架配置信息 API
|
|
238
|
-
router.get(
|
|
246
|
+
router.get(`${base}/config`, async (ctx) => {
|
|
239
247
|
try {
|
|
240
248
|
const config = app.getConfig();
|
|
241
249
|
ctx.body = { success: true, data: config };
|
|
@@ -245,8 +253,126 @@ router.get('/api/config', async (ctx) => {
|
|
|
245
253
|
ctx.body = { success: false, error: error.message };
|
|
246
254
|
}
|
|
247
255
|
});
|
|
256
|
+
// 获取所有插件的 Schema API
|
|
257
|
+
router.get(`${base}/schemas`, async (ctx) => {
|
|
258
|
+
try {
|
|
259
|
+
const schemas = {};
|
|
260
|
+
// 获取 App 的 Schema
|
|
261
|
+
const appSchema = app.schema.toJSON();
|
|
262
|
+
if (appSchema) {
|
|
263
|
+
schemas["app"] = appSchema;
|
|
264
|
+
}
|
|
265
|
+
// 获取所有插件的 Schema
|
|
266
|
+
for (const plugin of app.dependencyList) {
|
|
267
|
+
const schema = plugin.schema.toJSON();
|
|
268
|
+
if (schema) {
|
|
269
|
+
schemas[plugin.name] = schema;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
ctx.body = {
|
|
273
|
+
success: true,
|
|
274
|
+
data: schemas,
|
|
275
|
+
total: Object.keys(schemas).length,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
ctx.status = 500;
|
|
280
|
+
ctx.body = { success: false, error: error.message };
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
// 获取单个插件的 Schema API
|
|
284
|
+
router.get(`${base}/schemas/:name`, async (ctx) => {
|
|
285
|
+
try {
|
|
286
|
+
const { name } = ctx.params;
|
|
287
|
+
if (name === "app") {
|
|
288
|
+
const schema = app.schema?.toJSON();
|
|
289
|
+
if (!schema) {
|
|
290
|
+
ctx.status = 404;
|
|
291
|
+
ctx.body = { success: false, error: "App schema not found" };
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
ctx.body = { success: true, data: schema };
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const plugin = app.findPluginByName(name);
|
|
298
|
+
if (!plugin) {
|
|
299
|
+
ctx.status = 404;
|
|
300
|
+
ctx.body = { success: false, error: `Plugin ${name} not found` };
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const schema = plugin.schema.toJSON();
|
|
304
|
+
if (!schema) {
|
|
305
|
+
ctx.status = 404;
|
|
306
|
+
ctx.body = {
|
|
307
|
+
success: false,
|
|
308
|
+
error: `Schema for plugin ${name} not found`,
|
|
309
|
+
};
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
ctx.body = { success: true, data: schema };
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
ctx.status = 500;
|
|
316
|
+
ctx.body = { success: false, error: error.message };
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
// 获取插件配置 API
|
|
320
|
+
router.get(`${base}/config/:name`, async (ctx) => {
|
|
321
|
+
try {
|
|
322
|
+
const { name } = ctx.params;
|
|
323
|
+
if (name === "app") {
|
|
324
|
+
const config = app.getConfig();
|
|
325
|
+
ctx.body = { success: true, data: config };
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const plugin = app.findPluginByName(name);
|
|
329
|
+
if (!plugin) {
|
|
330
|
+
ctx.status = 404;
|
|
331
|
+
ctx.body = { success: false, error: `Plugin ${name} not found` };
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const config = plugin.config;
|
|
335
|
+
ctx.body = { success: true, data: config };
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
ctx.status = 500;
|
|
339
|
+
ctx.body = { success: false, error: error.message };
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
// 更新插件配置 API
|
|
343
|
+
router.post(`${base}/config/:name`, async (ctx) => {
|
|
344
|
+
try {
|
|
345
|
+
const { name } = ctx.params;
|
|
346
|
+
const newConfig = ctx.request.body;
|
|
347
|
+
if (name === "app") {
|
|
348
|
+
app.config = newConfig;
|
|
349
|
+
ctx.body = {
|
|
350
|
+
success: true,
|
|
351
|
+
message: "App configuration updated successfully",
|
|
352
|
+
data: app.getConfig(),
|
|
353
|
+
};
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const plugin = app.findPluginByName(name);
|
|
357
|
+
if (!plugin) {
|
|
358
|
+
ctx.status = 404;
|
|
359
|
+
ctx.body = { success: false, error: `Plugin ${name} not found` };
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
plugin.config = newConfig;
|
|
363
|
+
ctx.body = {
|
|
364
|
+
success: true,
|
|
365
|
+
message: `Plugin ${name} configuration updated successfully`,
|
|
366
|
+
data: plugin.config,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
ctx.status = 500;
|
|
371
|
+
ctx.body = { success: false, error: error.message };
|
|
372
|
+
}
|
|
373
|
+
});
|
|
248
374
|
// 消息发送 API
|
|
249
|
-
router.post(
|
|
375
|
+
router.post(`${base}/message/send`, async (ctx) => {
|
|
250
376
|
try {
|
|
251
377
|
const body = ctx.request.body;
|
|
252
378
|
const { context, bot, id, type, content } = body;
|
|
@@ -254,7 +380,7 @@ router.post('/api/message/send', async (ctx) => {
|
|
|
254
380
|
ctx.status = 400;
|
|
255
381
|
ctx.body = {
|
|
256
382
|
success: false,
|
|
257
|
-
error:
|
|
383
|
+
error: "Missing required fields: context, bot, id, type, content",
|
|
258
384
|
};
|
|
259
385
|
return;
|
|
260
386
|
}
|
|
@@ -262,8 +388,15 @@ router.post('/api/message/send', async (ctx) => {
|
|
|
262
388
|
// console.log 已替换为注释
|
|
263
389
|
ctx.body = {
|
|
264
390
|
success: true,
|
|
265
|
-
message:
|
|
266
|
-
data: {
|
|
391
|
+
message: "Message sent successfully",
|
|
392
|
+
data: {
|
|
393
|
+
context,
|
|
394
|
+
bot,
|
|
395
|
+
id,
|
|
396
|
+
type,
|
|
397
|
+
content,
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
},
|
|
267
400
|
};
|
|
268
401
|
}
|
|
269
402
|
catch (error) {
|
|
@@ -272,43 +405,45 @@ router.post('/api/message/send', async (ctx) => {
|
|
|
272
405
|
}
|
|
273
406
|
});
|
|
274
407
|
// 日志 API - 获取日志
|
|
275
|
-
router.get(
|
|
408
|
+
router.get(`${base}/logs`, async (ctx) => {
|
|
276
409
|
try {
|
|
277
410
|
const database = useDatabase();
|
|
278
411
|
if (!database) {
|
|
279
412
|
ctx.status = 503;
|
|
280
|
-
ctx.body = { success: false, error:
|
|
413
|
+
ctx.body = { success: false, error: "Database not available" };
|
|
281
414
|
return;
|
|
282
415
|
}
|
|
283
416
|
// 获取查询参数
|
|
284
417
|
const limit = parseInt(ctx.query.limit) || 100;
|
|
285
418
|
const level = ctx.query.level;
|
|
286
|
-
const LogModel = database.model(
|
|
419
|
+
const LogModel = database.model("SystemLog");
|
|
287
420
|
if (!LogModel) {
|
|
288
421
|
ctx.status = 500;
|
|
289
|
-
ctx.body = { success: false, error:
|
|
422
|
+
ctx.body = { success: false, error: "SystemLog model not found" };
|
|
290
423
|
return;
|
|
291
424
|
}
|
|
292
425
|
// 查询日志
|
|
293
426
|
let selection = LogModel.select();
|
|
294
427
|
// 按级别过滤
|
|
295
|
-
if (level && level !==
|
|
428
|
+
if (level && level !== "all") {
|
|
296
429
|
selection = selection.where({ level });
|
|
297
430
|
}
|
|
298
431
|
// 按时间倒序,限制数量
|
|
299
|
-
const logs = await selection.orderBy(
|
|
432
|
+
const logs = await selection.orderBy("timestamp", "DESC").limit(limit);
|
|
300
433
|
// 格式化返回数据
|
|
301
434
|
const formattedLogs = logs.map((log) => ({
|
|
302
435
|
level: log.level,
|
|
303
436
|
name: log.name,
|
|
304
437
|
message: log.message,
|
|
305
438
|
source: log.source,
|
|
306
|
-
timestamp: log.timestamp instanceof Date
|
|
439
|
+
timestamp: log.timestamp instanceof Date
|
|
440
|
+
? log.timestamp.toISOString()
|
|
441
|
+
: log.timestamp,
|
|
307
442
|
}));
|
|
308
443
|
ctx.body = {
|
|
309
444
|
success: true,
|
|
310
445
|
data: formattedLogs,
|
|
311
|
-
total: formattedLogs.length
|
|
446
|
+
total: formattedLogs.length,
|
|
312
447
|
};
|
|
313
448
|
}
|
|
314
449
|
catch (error) {
|
|
@@ -317,25 +452,25 @@ router.get('/api/logs', async (ctx) => {
|
|
|
317
452
|
}
|
|
318
453
|
});
|
|
319
454
|
// 日志 API - 清空日志
|
|
320
|
-
router.delete(
|
|
455
|
+
router.delete(`${base}/logs`, async (ctx) => {
|
|
321
456
|
try {
|
|
322
457
|
const database = useDatabase();
|
|
323
458
|
if (!database) {
|
|
324
459
|
ctx.status = 503;
|
|
325
|
-
ctx.body = { success: false, error:
|
|
460
|
+
ctx.body = { success: false, error: "Database not available" };
|
|
326
461
|
return;
|
|
327
462
|
}
|
|
328
|
-
const LogModel = database.model(
|
|
463
|
+
const LogModel = database.model("SystemLog");
|
|
329
464
|
if (!LogModel) {
|
|
330
465
|
ctx.status = 500;
|
|
331
|
-
ctx.body = { success: false, error:
|
|
466
|
+
ctx.body = { success: false, error: "SystemLog model not found" };
|
|
332
467
|
return;
|
|
333
468
|
}
|
|
334
469
|
// 删除所有日志
|
|
335
470
|
await LogModel.delete({});
|
|
336
471
|
ctx.body = {
|
|
337
472
|
success: true,
|
|
338
|
-
message:
|
|
473
|
+
message: "日志已清空",
|
|
339
474
|
};
|
|
340
475
|
}
|
|
341
476
|
catch (error) {
|
|
@@ -344,49 +479,46 @@ router.delete('/api/logs', async (ctx) => {
|
|
|
344
479
|
}
|
|
345
480
|
});
|
|
346
481
|
// 日志 API - 获取日志统计
|
|
347
|
-
router.get(
|
|
482
|
+
router.get(`${base}/logs/stats`, async (ctx) => {
|
|
348
483
|
try {
|
|
349
484
|
const database = useDatabase();
|
|
350
485
|
if (!database) {
|
|
351
486
|
ctx.status = 503;
|
|
352
|
-
ctx.body = { success: false, error:
|
|
487
|
+
ctx.body = { success: false, error: "Database not available" };
|
|
353
488
|
return;
|
|
354
489
|
}
|
|
355
|
-
const LogModel = database.model(
|
|
490
|
+
const LogModel = database.model("SystemLog");
|
|
356
491
|
if (!LogModel) {
|
|
357
492
|
ctx.status = 500;
|
|
358
|
-
ctx.body = { success: false, error:
|
|
493
|
+
ctx.body = { success: false, error: "SystemLog model not found" };
|
|
359
494
|
return;
|
|
360
495
|
}
|
|
361
496
|
// 获取总日志数
|
|
362
497
|
const total = await LogModel.select();
|
|
363
498
|
const totalCount = total.length;
|
|
364
499
|
// 获取各级别日志数
|
|
365
|
-
const levels = [
|
|
500
|
+
const levels = ["info", "warn", "error"];
|
|
366
501
|
const levelCounts = {};
|
|
367
502
|
for (const level of levels) {
|
|
368
|
-
const count = await LogModel
|
|
369
|
-
.select()
|
|
370
|
-
.where({ level });
|
|
503
|
+
const count = await LogModel.select().where({ level });
|
|
371
504
|
levelCounts[level] = count.length;
|
|
372
505
|
}
|
|
373
506
|
// 获取最旧日志时间
|
|
374
|
-
const oldestLog = await LogModel
|
|
375
|
-
.
|
|
376
|
-
.orderBy('timestamp', 'ASC')
|
|
507
|
+
const oldestLog = await LogModel.select("timestamp")
|
|
508
|
+
.orderBy("timestamp", "ASC")
|
|
377
509
|
.limit(1);
|
|
378
510
|
const oldestTimestamp = oldestLog.length > 0
|
|
379
|
-
?
|
|
511
|
+
? oldestLog[0].timestamp instanceof Date
|
|
380
512
|
? oldestLog[0].timestamp.toISOString()
|
|
381
|
-
: oldestLog[0].timestamp
|
|
513
|
+
: oldestLog[0].timestamp
|
|
382
514
|
: null;
|
|
383
515
|
ctx.body = {
|
|
384
516
|
success: true,
|
|
385
517
|
data: {
|
|
386
518
|
total: totalCount,
|
|
387
519
|
byLevel: levelCounts,
|
|
388
|
-
oldestTimestamp
|
|
389
|
-
}
|
|
520
|
+
oldestTimestamp,
|
|
521
|
+
},
|
|
390
522
|
};
|
|
391
523
|
}
|
|
392
524
|
catch (error) {
|
|
@@ -395,38 +527,36 @@ router.get('/api/logs/stats', async (ctx) => {
|
|
|
395
527
|
}
|
|
396
528
|
});
|
|
397
529
|
// 日志 API - 清理旧日志(手动触发)
|
|
398
|
-
router.post(
|
|
530
|
+
router.post(`${base}/logs/cleanup`, async (ctx) => {
|
|
399
531
|
try {
|
|
400
532
|
const database = useDatabase();
|
|
401
533
|
if (!database) {
|
|
402
534
|
ctx.status = 503;
|
|
403
|
-
ctx.body = { success: false, error:
|
|
535
|
+
ctx.body = { success: false, error: "Database not available" };
|
|
404
536
|
return;
|
|
405
537
|
}
|
|
406
|
-
const LogModel = database.model(
|
|
538
|
+
const LogModel = database.model("SystemLog");
|
|
407
539
|
if (!LogModel) {
|
|
408
540
|
ctx.status = 500;
|
|
409
|
-
ctx.body = { success: false, error:
|
|
541
|
+
ctx.body = { success: false, error: "SystemLog model not found" };
|
|
410
542
|
return;
|
|
411
543
|
}
|
|
412
544
|
const { days, maxRecords } = ctx.request.body || {};
|
|
413
545
|
let deletedCount = 0;
|
|
414
546
|
// 按天数清理
|
|
415
|
-
if (days && typeof days ===
|
|
547
|
+
if (days && typeof days === "number" && days > 0) {
|
|
416
548
|
const cutoffDate = new Date();
|
|
417
549
|
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
418
|
-
const deleted = await LogModel
|
|
419
|
-
.delete({ timestamp: { $lt: cutoffDate } });
|
|
550
|
+
const deleted = await LogModel.delete({ timestamp: { $lt: cutoffDate } });
|
|
420
551
|
deletedCount += deleted || 0;
|
|
421
552
|
}
|
|
422
553
|
// 按数量清理
|
|
423
|
-
if (maxRecords && typeof maxRecords ===
|
|
554
|
+
if (maxRecords && typeof maxRecords === "number" && maxRecords > 0) {
|
|
424
555
|
const totalCount = await LogModel.select();
|
|
425
556
|
if (totalCount.length > maxRecords) {
|
|
426
557
|
const excessCount = totalCount - maxRecords;
|
|
427
|
-
const oldestLogs = await LogModel
|
|
428
|
-
.
|
|
429
|
-
.orderBy('timestamp', 'ASC')
|
|
558
|
+
const oldestLogs = await LogModel.select("id", "timestamp")
|
|
559
|
+
.orderBy("timestamp", "ASC")
|
|
430
560
|
.limit(excessCount);
|
|
431
561
|
const idsToDelete = oldestLogs.map((log) => log.id);
|
|
432
562
|
if (idsToDelete.length > 0) {
|
|
@@ -440,7 +570,7 @@ router.post('/api/logs/cleanup', async (ctx) => {
|
|
|
440
570
|
ctx.body = {
|
|
441
571
|
success: true,
|
|
442
572
|
message: `已清理 ${deletedCount} 条日志`,
|
|
443
|
-
deletedCount
|
|
573
|
+
deletedCount,
|
|
444
574
|
};
|
|
445
575
|
}
|
|
446
576
|
catch (error) {
|
|
@@ -452,38 +582,40 @@ router.post('/api/logs/cleanup', async (ctx) => {
|
|
|
452
582
|
// 上下文注册
|
|
453
583
|
// ============================================================================
|
|
454
584
|
register({
|
|
455
|
-
name:
|
|
585
|
+
name: "server",
|
|
456
586
|
description: "http server",
|
|
457
587
|
mounted(p) {
|
|
458
588
|
return new Promise((resolve) => {
|
|
459
589
|
server.listen({
|
|
460
|
-
host:
|
|
461
|
-
port:
|
|
590
|
+
host: "0.0.0.0",
|
|
591
|
+
port: port,
|
|
462
592
|
}, () => {
|
|
463
593
|
const address = server.address();
|
|
464
594
|
if (!address)
|
|
465
595
|
return;
|
|
466
|
-
const visitAddress = typeof address ===
|
|
596
|
+
const visitAddress = typeof address === "string"
|
|
597
|
+
? address
|
|
598
|
+
: `${address.address}:${address.port}`;
|
|
467
599
|
p.logger.info(`server is running at http://${visitAddress}`);
|
|
468
|
-
p.logger.info(
|
|
469
|
-
p.logger.info(
|
|
600
|
+
p.logger.info("your username is:", username);
|
|
601
|
+
p.logger.info("your password is:", password);
|
|
470
602
|
resolve(server);
|
|
471
603
|
});
|
|
472
604
|
});
|
|
473
605
|
},
|
|
474
606
|
dispose(s) {
|
|
475
607
|
s.close();
|
|
476
|
-
}
|
|
608
|
+
},
|
|
477
609
|
});
|
|
478
610
|
register({
|
|
479
611
|
name: "koa",
|
|
480
612
|
description: "koa instance",
|
|
481
|
-
value: koa
|
|
613
|
+
value: koa,
|
|
482
614
|
});
|
|
483
615
|
register({
|
|
484
|
-
name:
|
|
616
|
+
name: "router",
|
|
485
617
|
description: "koa router",
|
|
486
|
-
value: router
|
|
618
|
+
value: router,
|
|
487
619
|
});
|
|
488
620
|
koa.use(KoaBodyParser()).use(router.routes()).use(router.allowedMethods());
|
|
489
621
|
//# sourceMappingURL=index.js.map
|