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