@onebots/core 1.0.0 → 1.0.5

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.
@@ -15,8 +15,8 @@ describe('Integration Tests', () => {
15
15
  const validated = ConfigValidator.validateWithDefaults(config, BaseAppConfigSchema);
16
16
  expect(validated.port).toBe(6727);
17
17
  expect(validated.database).toBe('onebots.db');
18
- expect(validated.username).toBe('admin');
19
- expect(validated.password).toBe('123456');
18
+ expect(validated.username).toBeUndefined();
19
+ expect(validated.password).toBeUndefined();
20
20
  expect(validated.log_level).toBe('info');
21
21
  });
22
22
  it('should throw ValidationError on invalid config', () => {
package/lib/account.d.ts CHANGED
@@ -36,7 +36,10 @@ export declare class Account<P extends keyof Adapter.Configs = keyof Adapter.Con
36
36
  stop(force?: boolean): Promise<void>;
37
37
  getGroupList(): Promise<Adapter.GroupInfo[]>;
38
38
  getFriendList(): Promise<Adapter.FriendInfo[]>;
39
- dispatch(commonEvent: CommonEvent.Base): Promise<void>;
39
+ /**
40
+ * 将通用事件同步派发到本账号绑定的各协议(协议内自行 catch,避免一次失败阻断其它协议)
41
+ */
42
+ dispatch(commonEvent: CommonEvent.Base): void;
40
43
  }
41
44
  export declare enum AccountStatus {
42
45
  Pending = "pending",// 上线中
package/lib/account.js CHANGED
@@ -85,9 +85,11 @@ export class Account extends EventEmitter {
85
85
  getFriendList() {
86
86
  return this.adapter.getFriendList(this.account_id);
87
87
  }
88
- async dispatch(commonEvent) {
88
+ /**
89
+ * 将通用事件同步派发到本账号绑定的各协议(协议内自行 catch,避免一次失败阻断其它协议)
90
+ */
91
+ dispatch(commonEvent) {
89
92
  this.logger.debug(`Dispatching event: ${commonEvent.type} to ${this.protocols.length} protocol(s)`);
90
- // Each protocol instance formats the common event to its own standard
91
93
  for (const protocol of this.protocols) {
92
94
  this.logger.debug(`Dispatching to protocol: ${protocol.name}/${protocol.version}`);
93
95
  protocol.dispatch(commonEvent);
package/lib/adapter.d.ts CHANGED
@@ -21,10 +21,24 @@ export declare abstract class Adapter<C = any, T extends keyof Adapter.Configs =
21
21
  accounts: Map<string, Account<T, C>>;
22
22
  icon: string;
23
23
  get db(): SqliteDB;
24
+ /**
25
+ * id_map 表名:平台名可能含 `-`(如 wechat-ilink),不能直接拼进 SQL,需规范为合法标识符。
26
+ */
24
27
  get tableName(): string;
25
28
  protected constructor(app: I, platform: T);
26
29
  createId(id: string | number): CommonTypes.Id;
27
- resolveId(id: string | number): CommonTypes.Id;
30
+ /**
31
+ * 将 string / number / 已是框架层的 Id 归一到当前适配器的 Id。
32
+ * - 已带有 string+number 的 Id:原样返回(避免被当成 string 键查错)。
33
+ * - string / number:查 id_map,无则 createId。
34
+ */
35
+ resolveId(id: string | number | CommonTypes.Id): CommonTypes.Id;
36
+ /**
37
+ * 将协议传入的 scene_id / user_id 归一为 CommonTypes.Id。
38
+ * - 事件侧经 createId 上报的 Id:直接可用,.string / .source 存平台原始标识。
39
+ * - Milky 等若传入 JSON 原始 string/number:需 resolveId 查表或建档,才能与 passiveReply 等场景用的 openid 一致。
40
+ */
41
+ protected coerceId(value: CommonTypes.Id | string | number): CommonTypes.Id;
28
42
  /**
29
43
  * 1. 发送消息
30
44
  * OneBot V11: send_private_msg, send_group_msg, send_msg
@@ -493,6 +507,15 @@ export declare abstract class Adapter<C = any, T extends keyof Adapter.Configs =
493
507
  };
494
508
  setOnline(uin: string): Promise<void>;
495
509
  setOffline(uin: string): Promise<void>;
510
+ /**
511
+ * Web 验证提交 - 可选实现。支持 Web 端完成登录验证的适配器实现此方法,
512
+ * 根据 type 将 data 转交给平台 Bot(如 submitSlider / submitSmsCode)。
513
+ */
514
+ submitVerification?(accountId: string, type: string, data: Record<string, unknown>): void | Promise<void>;
515
+ /**
516
+ * 请求发送短信验证码 - 可选实现。设备锁带手机号时,用户选择短信验证前需先调用此方法。
517
+ */
518
+ requestSmsCode?(accountId: string): void | Promise<void>;
496
519
  /**
497
520
  * 创建账号 - 必须由平台适配器实现
498
521
  */
@@ -508,6 +531,57 @@ export type AdapterClient<T extends Adapter = Adapter> = T extends Adapter<infer
508
531
  export declare namespace Adapter {
509
532
  interface Configs extends Record<string, any> {
510
533
  }
534
+ /**
535
+ * 验证请求的展示块(Web 按 type 通用渲染,适配器按需组合)
536
+ */
537
+ type VerificationBlock = {
538
+ type: 'image';
539
+ base64: string;
540
+ alt?: string;
541
+ }
542
+ /** 远程图片 URL(如微信 CDN),Web 端用 img 的 src 直接展示 */
543
+ | {
544
+ type: 'image_url';
545
+ url: string;
546
+ alt?: string;
547
+ } | {
548
+ type: 'link';
549
+ url: string;
550
+ label?: string;
551
+ } | {
552
+ type: 'text';
553
+ content: string;
554
+ } | {
555
+ type: 'input';
556
+ key: string;
557
+ placeholder?: string;
558
+ maxLength?: number;
559
+ secret?: boolean;
560
+ };
561
+ /**
562
+ * 验证请求的展示配置(全平台通用,由适配器提供)
563
+ */
564
+ interface VerificationRequestOptions {
565
+ blocks?: VerificationBlock[];
566
+ }
567
+ /**
568
+ * 统一登录验证请求(适配器 emit('verification:request', payload) 时使用)
569
+ * hint、options 由适配器提供,Web 仅做通用展示;onApprove/onReject 由前端绑定。
570
+ */
571
+ interface VerificationRequest {
572
+ platform: string;
573
+ account_id: string;
574
+ type: string;
575
+ /** 说明文案,由适配器提供,适用于全平台 */
576
+ hint: string;
577
+ /** 展示配置(链接、图片、输入框等),由适配器提供 */
578
+ options?: VerificationRequestOptions;
579
+ /** 为 true 时 Web 显示「发送验证码」按钮,需配合 requestSmsCode 使用 */
580
+ requestSmsAvailable?: boolean;
581
+ /** 扩展数据,可选 */
582
+ data?: Record<string, unknown>;
583
+ request_id?: string;
584
+ }
511
585
  interface SendMessageParams {
512
586
  scene_type: CommonTypes.Scene;
513
587
  scene_id: CommonTypes.Id;
@@ -552,6 +626,7 @@ export declare namespace Adapter {
552
626
  sender_name: string;
553
627
  scene_name: string;
554
628
  }
629
+ /** 单条消息详情(getMessage 等);time 为 **Unix 秒**,与 OneBot 等协议常见约定一致 */
555
630
  interface MessageInfo {
556
631
  message_id: CommonTypes.Id;
557
632
  time: number;
package/lib/adapter.js CHANGED
@@ -19,8 +19,12 @@ export class Adapter extends EventEmitter {
19
19
  get db() {
20
20
  return this.app.db;
21
21
  }
22
+ /**
23
+ * id_map 表名:平台名可能含 `-`(如 wechat-ilink),不能直接拼进 SQL,需规范为合法标识符。
24
+ */
22
25
  get tableName() {
23
- return `id_map_${this.platform}`;
26
+ const safe = String(this.platform).replace(/[^a-zA-Z0-9_]/g, "_");
27
+ return `id_map_${safe}`;
24
28
  }
25
29
  constructor(app, platform) {
26
30
  super();
@@ -36,6 +40,9 @@ export class Adapter extends EventEmitter {
36
40
  // ID 管理方法
37
41
  // ============================================
38
42
  createId(id) {
43
+ if (id === undefined || id === null) {
44
+ throw new Error('createId: id 不能为 undefined 或 null');
45
+ }
39
46
  if (typeof id === "number")
40
47
  return { string: id.toString(), number: id, source: id };
41
48
  const [existData] = this.db.select('*').from(this.tableName).where({
@@ -57,13 +64,37 @@ export class Adapter extends EventEmitter {
57
64
  this.db.insert(this.tableName).values(newId).run();
58
65
  return newId;
59
66
  }
67
+ /**
68
+ * 将 string / number / 已是框架层的 Id 归一到当前适配器的 Id。
69
+ * - 已带有 string+number 的 Id:原样返回(避免被当成 string 键查错)。
70
+ * - string / number:查 id_map,无则 createId。
71
+ */
60
72
  resolveId(id) {
61
- const [dbRecord] = this.db.select('*').from(this.tableName).where({
62
- [typeof id === "number" ? "number" : "string"]: id
63
- }).run();
73
+ if (typeof id === "object" &&
74
+ id !== null &&
75
+ typeof id.string === "string" &&
76
+ typeof id.number === "number") {
77
+ return id;
78
+ }
79
+ const primitive = id;
80
+ const [dbRecord] = this.db
81
+ .select("*")
82
+ .from(this.tableName)
83
+ .where({
84
+ [typeof primitive === "number" ? "number" : "string"]: primitive,
85
+ })
86
+ .run();
64
87
  if (dbRecord)
65
88
  return dbRecord;
66
- return this.createId(id);
89
+ return this.createId(primitive);
90
+ }
91
+ /**
92
+ * 将协议传入的 scene_id / user_id 归一为 CommonTypes.Id。
93
+ * - 事件侧经 createId 上报的 Id:直接可用,.string / .source 存平台原始标识。
94
+ * - Milky 等若传入 JSON 原始 string/number:需 resolveId 查表或建档,才能与 passiveReply 等场景用的 openid 一致。
95
+ */
96
+ coerceId(value) {
97
+ return this.resolveId(value);
67
98
  }
68
99
  // ============================================
69
100
  // 消息相关方法 (Message - 7个)
package/lib/base-app.d.ts CHANGED
@@ -64,6 +64,14 @@ export declare class BaseApp extends Koa {
64
64
  * 设置健康检查端点
65
65
  */
66
66
  private setupHealthEndpoints;
67
+ /**
68
+ * 解析 public_static_dir:相对路径基于配置文件目录(configDir),禁止相对路径穿越出 configDir;绝对路径按原样使用
69
+ */
70
+ private resolvePublicStaticDirectory;
71
+ /**
72
+ * 管理端 API:当前解析后的站点根静态目录(未配置或无效时为 null)
73
+ */
74
+ getPublicStaticRoot(): string | null;
67
75
  get adapterConfigs(): Map<string, Account.Config[]>;
68
76
  private initAdapters;
69
77
  addAccount<P extends keyof Adapter.Configs>(config: Account.Config<P>): void;
@@ -71,6 +79,10 @@ export declare class BaseApp extends Koa {
71
79
  removeAccount(p: string, uin: string, force?: boolean): void;
72
80
  get accounts(): Account<string | number, any>[];
73
81
  findOrCreateAdapter<P extends keyof Adapter.Configs>(platform: P): Adapter<any, string | number, BaseApp>;
82
+ /**
83
+ * 适配器首次创建后的钩子,子类可覆写以订阅该适配器事件(如 verification:request)
84
+ */
85
+ protected onAdapterCreated(_adapter: Adapter): void;
74
86
  start(): Promise<void>;
75
87
  reload(config: BaseApp.Config): Promise<void>;
76
88
  stop(): Promise<void>;
@@ -86,7 +98,11 @@ export declare namespace BaseApp {
86
98
  timeout?: number;
87
99
  username?: string;
88
100
  password?: string;
101
+ /** 管理端 Bearer 鉴权码,配置后可使用 Authorization: Bearer <access_token> 访问 API,无需用户名密码 */
102
+ access_token?: string;
89
103
  log_level?: LogLevel;
104
+ /** 站点根静态目录,相对配置文件所在目录(configDir)或绝对路径;用于企业微信等可信域名校验文件 */
105
+ public_static_dir?: string;
90
106
  general?: Protocol.Configs;
91
107
  } & KoaOptions & AdapterConfig;
92
108
  const defaultConfig: Config;
package/lib/base-app.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import Koa from "koa";
2
2
  import * as os from "os";
3
3
  import "reflect-metadata";
4
+ import * as fs from "fs";
4
5
  import { writeFileSync } from "fs";
5
6
  import log4js from "log4js";
6
7
  import { createServer } from "http";
7
8
  import yaml from "js-yaml";
8
9
  import KoaBody from "koa-body";
9
- import basicAuth from "koa-basic-auth";
10
+ import koaStatic from "koa-static";
10
11
  const { configure, connectLogger, getLogger } = log4js;
11
12
  import { deepClone, deepMerge } from "./utils.js";
12
13
  import { Router } from "./router.js";
@@ -14,11 +15,15 @@ import * as path from "path";
14
15
  import process from "process";
15
16
  import { SqliteDB } from "./db.js";
16
17
  import pkg from "../package.json" with { type: "json" };
17
- import { AdapterRegistry, ProtocolRegistry } from "./registry.js";
18
+ import { AdapterRegistry } from "./registry.js";
18
19
  import { ConfigValidator, BaseAppConfigSchema } from "./config-validator.js";
19
20
  import { LifecycleManager } from "./lifecycle.js";
20
21
  import { ErrorHandler, ConfigError } from "./errors.js";
21
22
  import { createLogger } from "./logger.js";
23
+ import { initSecurityAudit, securityAudit, closeSecurityAudit } from "./middleware/security-audit.js";
24
+ import { defaultRateLimit } from "./middleware/rate-limit.js";
25
+ import { metricsCollector } from "./middleware/metrics-collector.js";
26
+ import { metrics } from "./metrics.js";
22
27
  export { configure, yaml, connectLogger };
23
28
  export class BaseApp extends Koa {
24
29
  config;
@@ -102,24 +107,33 @@ export class BaseApp extends Koa {
102
107
  this.httpServer.close(() => resolve());
103
108
  });
104
109
  });
110
+ // 初始化安全审计日志
111
+ initSecurityAudit(path.join(BaseApp.dataDir, 'audit'));
105
112
  // 注册健康检查端点(无需认证)
106
113
  this.setupHealthEndpoints();
107
- this.use(KoaBody())
108
- .use(async (ctx, next) => {
109
- // 健康检查端点跳过认证
110
- if (ctx.path === '/health' || ctx.path === '/ready' || ctx.path === '/metrics') {
111
- return next();
112
- }
113
- // 检查是否是协议路径格式: /{platform}/{accountId}/{protocol}/{version}/...
114
- const pathParts = ctx.path?.split("/").filter(p => p) || [];
115
- const [_platform, _accountId, protocol, version] = pathParts;
116
- if (ProtocolRegistry.has(protocol, version)) {
117
- return next();
118
- }
119
- return await basicAuth({
120
- name: this.config.username,
121
- pass: this.config.password,
122
- })(ctx, next);
114
+ // 用户配置的站点根静态目录(需在 Router 等功能路由之前,便于 GET /xxx.txt 等直出)
115
+ const publicStaticDir = this.resolvePublicStaticDirectory();
116
+ if (publicStaticDir) {
117
+ this.enhancedLogger.info('已启用站点根静态目录', { dir: publicStaticDir });
118
+ this.use(koaStatic(publicStaticDir));
119
+ }
120
+ // 中间件链(multipart:管理端上传站点静态文件等)
121
+ this.use(KoaBody({
122
+ multipart: true,
123
+ formidable: {
124
+ maxFileSize: 2 * 1024 * 1024,
125
+ keepExtensions: true,
126
+ },
127
+ }))
128
+ // 性能指标收集(最早执行,以便记录所有请求)
129
+ .use(metricsCollector())
130
+ // 安全审计日志
131
+ .use(securityAudit())
132
+ // 速率限制(在认证之前,防止暴力破解)
133
+ .use(defaultRateLimit)
134
+ .use(async (_ctx, next) => {
135
+ // 本层不做鉴权。管理端鉴权仅针对 /api(由 onebots 应用层负责);各平台对外 API(如 /{platform}/{accountId}/onebot/v11/...)由各自协议/适配器单独鉴权。
136
+ return next();
123
137
  })
124
138
  .use(this.router.routes())
125
139
  .use(this.router.allowedMethods());
@@ -189,33 +203,33 @@ export class BaseApp extends Koa {
189
203
  },
190
204
  };
191
205
  });
192
- // /metrics - 简单的 Prometheus 格式指标
206
+ // /metrics - Prometheus 格式指标
193
207
  this.router.get('/metrics', (ctx) => {
194
- const metrics = [];
195
- const now = Date.now();
208
+ const metricLines = [];
196
209
  // 基础指标
197
- metrics.push(`# HELP onebots_info OneBots application info`);
198
- metrics.push(`# TYPE onebots_info gauge`);
199
- metrics.push(`onebots_info{version="${pkg.version}"} 1`);
200
- metrics.push(`# HELP onebots_uptime_seconds Application uptime in seconds`);
201
- metrics.push(`# TYPE onebots_uptime_seconds gauge`);
202
- metrics.push(`onebots_uptime_seconds ${process.uptime()}`);
203
- metrics.push(`# HELP onebots_started Whether the application is started`);
204
- metrics.push(`# TYPE onebots_started gauge`);
205
- metrics.push(`onebots_started ${this.isStarted ? 1 : 0}`);
210
+ metricLines.push(`# HELP onebots_info OneBots application info`);
211
+ metricLines.push(`# TYPE onebots_info gauge`);
212
+ metricLines.push(`onebots_info{version="${pkg.version}"} 1`);
213
+ metricLines.push(`# HELP onebots_uptime_seconds Application uptime in seconds`);
214
+ metricLines.push(`# TYPE onebots_uptime_seconds gauge`);
215
+ metricLines.push(`onebots_uptime_seconds ${process.uptime()}`);
216
+ metricLines.push(`# HELP onebots_started Whether the application is started`);
217
+ metricLines.push(`# TYPE onebots_started gauge`);
218
+ metricLines.push(`onebots_started ${this.isStarted ? 1 : 0}`);
206
219
  // 内存使用
207
220
  const memUsage = process.memoryUsage();
208
- metrics.push(`# HELP onebots_memory_bytes Memory usage in bytes`);
209
- metrics.push(`# TYPE onebots_memory_bytes gauge`);
210
- metrics.push(`onebots_memory_bytes{type="rss"} ${memUsage.rss}`);
211
- metrics.push(`onebots_memory_bytes{type="heapTotal"} ${memUsage.heapTotal}`);
212
- metrics.push(`onebots_memory_bytes{type="heapUsed"} ${memUsage.heapUsed}`);
221
+ metricLines.push(`# HELP onebots_memory_bytes Memory usage in bytes`);
222
+ metricLines.push(`# TYPE onebots_memory_bytes gauge`);
223
+ metricLines.push(`onebots_memory_bytes{type="rss"} ${memUsage.rss}`);
224
+ metricLines.push(`onebots_memory_bytes{type="heapTotal"} ${memUsage.heapTotal}`);
225
+ metricLines.push(`onebots_memory_bytes{type="heapUsed"} ${memUsage.heapUsed}`);
226
+ metricLines.push(`onebots_memory_bytes{type="external"} ${memUsage.external}`);
213
227
  // 适配器和账号指标
214
- metrics.push(`# HELP onebots_adapters_total Total number of adapters`);
215
- metrics.push(`# TYPE onebots_adapters_total gauge`);
216
- metrics.push(`onebots_adapters_total ${this.adapters.size}`);
217
- metrics.push(`# HELP onebots_accounts_total Total accounts by platform and status`);
218
- metrics.push(`# TYPE onebots_accounts_total gauge`);
228
+ metricLines.push(`# HELP onebots_adapters_total Total number of adapters`);
229
+ metricLines.push(`# TYPE onebots_adapters_total gauge`);
230
+ metricLines.push(`onebots_adapters_total ${this.adapters.size}`);
231
+ metricLines.push(`# HELP onebots_accounts_total Total accounts by platform and status`);
232
+ metricLines.push(`# TYPE onebots_accounts_total gauge`);
219
233
  for (const [platform, adapter] of this.adapters) {
220
234
  let online = 0;
221
235
  let offline = 0;
@@ -225,13 +239,69 @@ export class BaseApp extends Koa {
225
239
  else
226
240
  offline++;
227
241
  }
228
- metrics.push(`onebots_accounts_total{platform="${platform}",status="online"} ${online}`);
229
- metrics.push(`onebots_accounts_total{platform="${platform}",status="offline"} ${offline}`);
242
+ metricLines.push(`onebots_accounts_total{platform="${platform}",status="online"} ${online}`);
243
+ metricLines.push(`onebots_accounts_total{platform="${platform}",status="offline"} ${offline}`);
244
+ }
245
+ // 添加性能指标
246
+ const prometheusMetrics = metrics.exportPrometheus();
247
+ if (prometheusMetrics.trim()) {
248
+ metricLines.push('\n# Performance metrics');
249
+ metricLines.push(prometheusMetrics);
230
250
  }
231
251
  ctx.type = 'text/plain; charset=utf-8';
232
- ctx.body = metrics.join('\n') + '\n';
252
+ ctx.body = metricLines.join('\n') + '\n';
233
253
  });
234
254
  }
255
+ /**
256
+ * 解析 public_static_dir:相对路径基于配置文件目录(configDir),禁止相对路径穿越出 configDir;绝对路径按原样使用
257
+ */
258
+ resolvePublicStaticDirectory() {
259
+ const raw = this.config.public_static_dir;
260
+ if (raw == null || String(raw).trim() === '')
261
+ return null;
262
+ const trimmed = String(raw).trim();
263
+ const configDirResolved = path.resolve(BaseApp.configDir);
264
+ const abs = path.isAbsolute(trimmed)
265
+ ? path.resolve(trimmed)
266
+ : path.resolve(configDirResolved, trimmed);
267
+ if (!path.isAbsolute(trimmed)) {
268
+ const rel = path.relative(configDirResolved, abs);
269
+ // 禁止 `.`、等价空相对路径,避免整个配置目录被当作静态站点根
270
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
271
+ this.enhancedLogger.warn('public_static_dir 必须为配置目录下的子目录(不能为 . 或路径穿越),已忽略', {
272
+ configured: trimmed,
273
+ resolved: abs,
274
+ configDir: configDirResolved,
275
+ });
276
+ return null;
277
+ }
278
+ }
279
+ let st;
280
+ try {
281
+ st = fs.statSync(abs);
282
+ }
283
+ catch {
284
+ try {
285
+ fs.mkdirSync(abs, { recursive: true });
286
+ st = fs.statSync(abs);
287
+ }
288
+ catch (e) {
289
+ this.enhancedLogger.warn('public_static_dir 无法创建或访问,静态托管已跳过', { abs, error: e });
290
+ return null;
291
+ }
292
+ }
293
+ if (!st.isDirectory()) {
294
+ this.enhancedLogger.warn('public_static_dir 不是目录,已忽略', { abs });
295
+ return null;
296
+ }
297
+ return abs;
298
+ }
299
+ /**
300
+ * 管理端 API:当前解析后的站点根静态目录(未配置或无效时为 null)
301
+ */
302
+ getPublicStaticRoot() {
303
+ return this.resolvePublicStaticDirectory();
304
+ }
235
305
  get adapterConfigs() {
236
306
  const map = new Map();
237
307
  Object.keys(this.config).forEach(key => {
@@ -311,8 +381,13 @@ export class BaseApp extends Koa {
311
381
  return this.adapters.get(platform);
312
382
  const adapter = AdapterRegistry.create(`${platform}`, this);
313
383
  this.adapters.set(platform, adapter);
384
+ this.onAdapterCreated(adapter);
314
385
  return adapter;
315
386
  }
387
+ /**
388
+ * 适配器首次创建后的钩子,子类可覆写以订阅该适配器事件(如 verification:request)
389
+ */
390
+ onAdapterCreated(_adapter) { }
316
391
  async start() {
317
392
  const stopTimer = this.enhancedLogger.start('Application start');
318
393
  try {
@@ -371,6 +446,8 @@ export class BaseApp extends Koa {
371
446
  this.adapters.clear();
372
447
  // 清理资源
373
448
  await this.lifecycle.cleanup();
449
+ // 关闭安全审计日志
450
+ closeSecurityAudit();
374
451
  this.emit("close");
375
452
  this.isStarted = false;
376
453
  stopTimer();
@@ -387,8 +464,6 @@ export class BaseApp extends Koa {
387
464
  BaseApp.defaultConfig = {
388
465
  port: 6727,
389
466
  database: "onebots.db",
390
- username: "admin",
391
- password: "123456",
392
467
  timeout: 30,
393
468
  general: {},
394
469
  log_level: "info",
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 熔断器模式实现
3
+ * 防止级联故障,提高系统稳定性
4
+ */
5
+ export declare enum CircuitState {
6
+ /** 关闭状态:正常处理请求 */
7
+ CLOSED = "CLOSED",
8
+ /** 开启状态:拒绝所有请求 */
9
+ OPEN = "OPEN",
10
+ /** 半开状态:尝试恢复,允许部分请求通过 */
11
+ HALF_OPEN = "HALF_OPEN"
12
+ }
13
+ export interface CircuitBreakerOptions {
14
+ /** 失败阈值:连续失败多少次后开启熔断 */
15
+ failureThreshold?: number;
16
+ /** 成功阈值:半开状态下成功多少次后关闭熔断 */
17
+ successThreshold?: number;
18
+ /** 超时时间:开启状态持续多久后进入半开状态(毫秒) */
19
+ timeout?: number;
20
+ /** 监控窗口大小(毫秒) */
21
+ monitoringPeriod?: number;
22
+ /** 最小请求数:在监控窗口内至少需要多少请求才触发熔断 */
23
+ minimumRequests?: number;
24
+ /** 错误率阈值:错误率超过多少时开启熔断(0-1) */
25
+ errorRateThreshold?: number;
26
+ }
27
+ /**
28
+ * 熔断器类
29
+ */
30
+ export declare class CircuitBreaker {
31
+ private state;
32
+ private failureCount;
33
+ private successCount;
34
+ private lastFailureTime;
35
+ private records;
36
+ private options;
37
+ constructor(options?: CircuitBreakerOptions);
38
+ /**
39
+ * 执行函数,带熔断保护
40
+ */
41
+ execute<T>(fn: () => Promise<T>): Promise<T>;
42
+ /**
43
+ * 成功回调
44
+ */
45
+ private onSuccess;
46
+ /**
47
+ * 失败回调
48
+ */
49
+ private onFailure;
50
+ /**
51
+ * 判断是否应该开启熔断
52
+ */
53
+ private shouldOpen;
54
+ /**
55
+ * 获取错误率
56
+ */
57
+ private getErrorRate;
58
+ /**
59
+ * 获取最近的请求记录
60
+ */
61
+ private getRecentRequests;
62
+ /**
63
+ * 清理过期记录
64
+ */
65
+ private cleanupOldRecords;
66
+ /**
67
+ * 获取当前状态
68
+ */
69
+ getState(): CircuitState;
70
+ /**
71
+ * 获取统计信息
72
+ */
73
+ getStats(): {
74
+ state: CircuitState;
75
+ failureCount: number;
76
+ successCount: number;
77
+ errorRate: number;
78
+ totalRequests: number;
79
+ };
80
+ /**
81
+ * 手动重置熔断器
82
+ */
83
+ reset(): void;
84
+ }
85
+ /**
86
+ * 熔断器开启错误
87
+ */
88
+ export declare class CircuitBreakerOpenError extends Error {
89
+ constructor(message: string);
90
+ }
91
+ /**
92
+ * 创建带熔断器的函数包装器
93
+ */
94
+ export declare function withCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, breaker: CircuitBreaker): T;