日期:2026-11-14 环境:NAS(Debian 12,x86_64,4 核 7.7G)· Docker · QQ Linux 3.2.28-48517 测试群:大肥鱼测试群(1134381782) 结论:Nekolux 功能完整可用,压力与边界表现良好
| 测试项 | 结果 |
|---|---|
| 压力测试(并发 20 × 100 条) | ✅ 100/100,2.17s |
| 边界测试(14 个异常用例) | ✅ 11 个精确报错,3 个正常通过 |
| 关键词插件端到端 | ✅ 事件 → 匹配 → 回复 |
| 多账号 | ⏸ 未做(需第二个 QQ) |
容器内直接走 OneBot HTTP(排除 SSH + docker exec 开销),
用固定并发 worker 池发送 send_group_msg。
// tools/stress.cjs
const workers = Number(process.argv[3] ?? 5);
async function worker() {
while (next <= n) {
const i = next++;
results.push(await send(i));
}
}
| 并发 | 条数 | 成功 | 总耗时 | min | p50 | p95 | max |
|---|---|---|---|---|---|---|---|
| 5 | 20 | 20/20 | 1.14s | 196ms | 268ms | 330ms | 330ms |
| 10 | 50 | 50/50 | 2.47s | 160ms | 279ms | 1092ms | 1244ms |
| 20 | 100 | 100/100 | 2.17s | 172ms | 313ms | 761ms | 800ms |
全部成功,无丢失、无重复。
用 SSH + docker exec 调同一个 action,延迟是 7.3–7.9 秒:
并发 10 条 成功 10/10 总耗时 15.5s
延迟: min=7.30s p50=7.65s max=7.85s
这 7.5 秒全是 ssh 建连 + docker exec 进程启动的开销,不是 Nekolux 的性能。
真实延迟看容器内数据(p50 313ms)。
14 个异常用例,全部按预期处理。
未知 action 1404 unknown action
缺必需参数 1400 message: is required
群号非法(-1) 1400 group_id: must be >= 1
空消息 1400 message segment array must not be empty
非法消息类型 1400 unknown message segment type: not_a_type
负时长禁言 1400 duration: must be >= 0
错误信息都指出了具体字段和原因,不是笼统的「参数错误」。
消息ID不存在 100 message not found
不存在的文件 100 ENOENT: no such file or directory, realpath '/tmp/no-such-file.txt'
超大禁言时长 100 OIDB error 120101203 on 0x1253_1:
OIDB error 带上了 cmd 和错误码,排障友好。
| 用例 | 结果 |
|---|---|
| 超长文本(5000 字) | ✅ 发送成功,无截断 |
| 特殊字符 + emoji + NUL | ✅ 发送成功,无崩溃 |
CQ 码字符串 [CQ:face,id=1] |
✅ 自动解析 |
get_group_info(999999999) → {"status":"ok","data":{"group_name":"","member_count":0,...}}
get_stranger_info(1) → {"status":"ok","data":{"nickname":"","uid":"",...}}
不存在的群/用户返回 ok + 空结构。这是 QQ 服务端的行为——它对不存在的目标
返回空对象而不是错误码,Nekolux 如实透传。
影响:调用方需要自己判断「字段全空」= 不存在。可以改进(group_name === '' 时
返回 1404),但这是语义选择,需要权衡兼容性。
unified/examples/plugins/keyword-reply/
export async function plugin_onmessage(ctx, event) {
if (event.message_type !== 'group') return;
const text = extractText(event.message);
const matched = (ctx.getConfig().keywords ?? ['你好']).find((k) => text.includes(k));
if (!matched) return;
// 防刷屏:同群同关键词 5 秒内只回一次
if (Date.now() - (lastReply.get(key) ?? 0) < 5000) return;
await ctx.callAction('send_group_msg', {
group_id: event.group_id,
message: [
{ type: 'reply', data: { id: String(event.message_id) } },
{ type: 'text', data: { text: ctx.getConfig().reply ?? '收到~' } },
],
});
}
04:34:53 [Event] 群 [大肥鱼测试群(1134381782)] | [寒拂晴ColdBreeze(1721822150)]: ID:-617667331 你好
04:34:53 [App] saw message from 1721822150
04:34:53 [App] keyword hit: 你好 in group 1134381782 from 1721822150
04:34:53 [Event] [自身] 群 [大肥鱼测试群] | [Nekolux测试(3816829185)]:
[回复 寒拂晴ColdBreeze(1721822150): 你好] 收到~
完整链路:
群消息 → SnowLuma 事件管道 → PluginManager.dispatch
→ keyword-reply.plugin_onmessage
→ 关键词匹配
→ ctx.callAction('send_group_msg') ← 新增能力
→ OneBot action 分发
→ 群内回复(带引用)
PluginContext 缺少操作 QQ 的能力(设计缺口)现象:写完关键词插件才发现——ctx 只有 actions.register(注册),
没有调用 action 的能力。插件能收到消息但没法回。
修复:新增 ctx.callAction(action, params)。
设计取舍:
| 方案 | 问题 |
|---|---|
暴露 bridge / oneBot 实例 |
插件绕过 OneBot 抽象,直接操作协议 |
| 暴露原始封包发送 | 插件能构造任意 OIDB,安全边界消失 |
只暴露 callAction |
复用 214 个 action,能力完整且边界清晰 |
加了 3 个测试(tests/runtime.test.ts),覆盖:转发、无实现时报错、失败传播。
/root/.config/QQ挂载到 /app/.config/QQ 无效(那是 napcat 镜像的空 VOLUME)。
改挂 /root/.config/QQ 后登录态才能持久化。
docker cp 的副本docker cp 更新 /app/unified-bootstrap.mjs 后,docker rm -f 重建容器
又会用镜像里的旧版本。必须重建镜像,不能只 docker cp。
# 1. 压力测试(容器内,排除 SSH 开销)
docker cp tools/stress.cjs unified:/tmp/
docker exec unified node /tmp/stress.cjs 100 20
# 2. 边界测试
docker cp tools/edge.cjs unified:/tmp/
docker exec unified node /tmp/edge.cjs
# 3. 关键词插件
# - 把 examples/plugins/keyword-reply 复制到 data/plugins/
# - 加进 UNIFIED_PLUGIN_ALLOW
# - 重启容器
docker cp tools/keyword-test.cjs unified:/tmp/
docker exec unified node /tmp/keyword-test.cjs 1134381782
| 路径 | 说明 |
|---|---|
tools/stress.cjs |
并发压力测试(容器内运行) |
tools/edge.cjs |
边界用例测试 |
tools/keyword-test.cjs |
关键词插件端到端测试 |
tools/nas-onebot.py |
通过 SSH 调 OneBot(有 7.5s 开销,仅适合单次调用) |
unified/examples/plugins/keyword-reply/ |
示例插件源码 |
docs/PLUGIN-SYSTEM.md |
callAction 设计说明 |
docs/NAS-DEPLOYMENT.md |
部署与排障 |