dsh-qqbot 是我的一个小项目:一个跑在 QQ 群里的 AI 助手。它能自动回答群友的重复提问、定时发提醒、统计群活跃度,甚至能帮忙处理简单的群管理操作。整个服务最终部署在国内某云厂商的一台轻量云主机上,从单体脚本一步步改成容器化,前后折腾了大概两周。这篇复盘不是官方文档,也不是纯代码贴,而是一个踩完坑之后的手记。如果你正想给自己的群做个类似的机器人,或者单纯好奇“一个 7×24 小时挂在群里的 AI 服务到底要处理哪些麻烦”,这篇文章应该能给你一些参考。
整个项目的主线很简单:群成员发消息,机器人接收,按命令分发给不同模块,需要智能回答的部分调用大模型 API,再把结果回写到群里。听起来不复杂,但真正落地时,登录态、消息频率、并发限制、数据存储、密钥安全、内容审核这些问题会一个个冒出来,任何一个处理不好,机器人都会在某个深夜悄悄掉线。下面按照我实际推进的顺序来复盘。
1. 项目背景与核心需求
1.1 为什么会在 QQ 群里跑一个 AI 助手
先说场景。我所在的工作交流群每天消息量有几百条,很多问题是重复的:新人问接入文档怎么看,老成员问某个工具的参数怎么调,还有一些例行通知需要固定时间推送。靠人肉回复效率太低,靠群公告又容易被消息流冲走。dsh-qqbot 最初的定位就是一个“群里的值班机器人”:能回答高频问题、能定时提醒、能统计群活跃度,后续再加入更多自定义能力。
做这个项目并不需要多前沿的技术,但“AI”这个点让它的价值提升了不少。传统关键词机器人只能命中固定规则,遇到说法一变就哑火。接入大模型之后,群友用自然语言提问也能得到像样的回答,哪怕问题没被预先录入,它也能根据上下文给一个合理的回复。这种体验上的提升,正是我决定自己做而不是直接用一个现成机器人平台的核心理由。
适合看这篇文章的人大概有三类:第一类是想在 QQ 群里跑自定义机器人的爱好者;第二类是做了团队内部工具、需要把群聊和 AI 服务打通的人;第三类是被群消息淹没、想要自动化值班的学生组织或小团队管理员。如果你只是想要一个“@ 一下就能回句话”的玩具,那不需要关心我后面聊的细节;但如果你希望它稳定跑几个月不崩,那接下来的内容会有帮助。
1.2 需求拆解与功能清单
动手之前我先把需求列成一张表,避免写着写着就失控。最终定下来的功能清单是这样的:
- 智能问答:群内 @机器人 或触发特定前缀提问,机器人调用大模型 API 生成回答,同时保留最近几轮对话上下文。
- 群管理:关键词提醒、广告识别、重复消息抑制,以及管理员可用的禁言、踢人等命令接口。
- 定时任务:每天固定时间发日报提醒,每周五发周报模板,支持 cron 表达式配置。
- 信息聚合:把 RSS 订阅更新、天气情况、待办事项等第三方接口的数据定时转发到群。
- 数据看板:统计每日消息量、活跃用户、机器人平均响应时长,方便后续优化。
功能看着不少,核心主线其实只有一条:消息进来后走一套插件化的事件分发。每个模块只负责自己关心的事件类型,主程序不写一堆互相纠缠的分支逻辑。这个设计也是我踩过坑之后的反思,最早版本把“所有功能写进一个大函数”,分支一多就完全没法维护。后来改成插件机制,每个模块独立声明自己关心的事件,主程序只负责分发,新增功能就是新增一个独立模块的事。如果你也要自己写机器人,我建议一开始就按插件化来设计,别走我那条弯路。
2. 方案选型:框架、云平台与部署方式的取舍
2.1 机器人侧:协议与实现方式
QQ 机器人的实现路线有好几条,国内社区里目前主流的是基于逆向协议抽象出来的标准化方案。我没有从零去解析消息协议,而是直接使用了遵循 OneBot 协议标准的事件服务作为消息中转层,业务侧通过 WebSocket 和它通信。选这套路线的考虑有三点:一是社区成熟,遇到问题不会孤立无援;二是协议层和业务层可以分开部署,登录态和业务代码解耦,业务模块就算挂了,协议层还活着;三是它支持多个业务端同时连接,本地调试和生产环境可以各挂一个实例。
这个选择并不是没有代价。逆向协议最大的风险是登录态可能失效,被平台风控拦截时没办法自动恢复,必须人工扫码或验证。这是所有非官方方案的共同特点,只能说通过运维手段尽量降低影响,没有彻底根治的办法。
作为对比,官方开放平台的机器人接口稳定性更好,但能力覆盖面有限,很多群管理操作做不了。如果你只需要小范围内的基础自动回复,官方接口就够了,完全不用碰逆向方案。
2.2 云服务器选型:规格与资源估算
部署位置我选了国内某云厂商的轻量云主机。不用旧电脑或者小型 VPS 的原因很现实:目标群成员都在国内,需要低延迟的固定公网出口;轻量云主机对个人项目来说性价比很高,短期试用成本也低。
云主机规格怎么定?我建议用最少需求估算,别一上来就买 8 核 16G。以我当时的经验,核心进程预计占内存 1.5G 到 2G,AI 请求全部走外部 API,不占本地计算资源;带宽按每秒钟最多 5 条消息、每条响应 4KB 计算,下行只需要 0.16Mbps,实际上 1Mbps 都用不满。最终我选了 2 核 4G 内存、3Mbps 带宽、60GB SSD 的配置,跑一个上限约 500 人、日均三千条消息的群绰绰有余。如果你群里的活跃度是日均五万条消息,就要按后面第 4 章的公式重新算一遍再决定配置。
地域选择也有讲究,我建议选目标用户集中的区域,而不是离自己最近的区域。群友主要在华东,我就选华东的机房。全国分散的话,选一个国内 BGP 线路口碑稳定的区域即可,不用过度纠结。
2.3 部署形态:容器化还是裸机
两种部署方式我都试过。第一版是裸机部署,Python 虚拟环境直接起业务进程,用系统服务管理工具守护,优点是资源占用少、调试直接,缺点是环境迁移很麻烦,换台机器基本等于再配一遍。
第二版切到了容器化部署:协议层一个容器,业务层一个容器,Redis 单独一个容器,数据库目录用 Docker 卷挂载到宿主机。写一个 Compose 文件,升级时改一下镜像版本再执行拉起命令就行。容器化带来的可重复性对多组件项目来说价值非常大,尤其当第二天发现服务器重启过后,能不能一键恢复服务是决定运维体验的关键。
容器多占的内存大约只有一两百兆,对比运维效率的提升完全值得。以下实操过程就按容器化路线来写。
3. 落地实操:从零到上线
3.1 云主机初始化与网络配置
拿到一台全新的云主机后,第一件事不是急着装机器人,而是把系统基础和网络链路理清楚。我选了 Ubuntu 22.04 LTS,理由很简单:LTS 支持周期长,软件源里依赖齐全,容器和 Python 生态的兼容性都很好。
初始化步骤分四步:
- 更新系统包,安装基础工具:curl、git、docker、docker compose 插件、vim。
- 修改 SSH 配置,禁用 root 密码登录,改用密钥登录。这是最基本的加固门槛。
- 配置防火墙,只放行 SSH 端口的指定来源 IP,其他入站端口一律关闭。QQ 机器人是主动外连长连接,云主机不需要开放任何入站端口给外部访问。
- 创建一个独立系统用户来跑 Docker 服务,并且不让这个用户拥有交互式 shell。
这里要特别注意云厂商安全组和系统防火墙是两层,两边都要保持一致。我遇到过安全组放行了但系统防火墙没改导致端口不通的情况,也遇到过反过来连不上的情况。后来我干脆只依赖一层,另一层改成相同规则,避免两边配置不一致的玄学问题。
3.2 核心服务部署步骤
整个服务分成四块:事件接入层、业务处理层、存储层、缓存层。我用一个 Compose 文件把它们串起来:
version: "3.8" services: protocol: image: dsh/qq-protocol:latest container_name: dsh-protocol restart: always volumes: - ./proto-data:/app/data networks: - dsh-net bot: image: dsh/qq-bot:latest container_name: dsh-qqbot restart: always environment: - ONE_BOT_WS_URL=ws://protocol:6700 - DB_DSN=sqlite:///data/dsh.db - REDIS_URL=redis://redis:6379/0 - LLM_API_BASE=${LLM_API_BASE} - LLM_API_KEY=${LLM_API_KEY} volumes: - ./bot-data:/app/data networks: - dsh-net depends_on: - protocol - redis redis: image: redis:7-alpine container_name: dsh-redis restart: always command: redis-server --appendonly yes volumes: - ./redis-data:/data networks: - dsh-net networks: dsh-net: driver: bridge这里有几个细节容易踩坑。restart: always保证了容器退出后能自动拉起,但depends_on只保证容器启动顺序,不保证服务已经真正可用,所以业务层代码里必须写连接重试逻辑。数据库一开始用 SQLite 文件存业务数据,零运维、够用,等数据量大了再考虑迁移到独立数据库。
协议层第一次启动会提示扫码登录,二维码会输出到日志里。我用手机扫码后,协议层把登录态缓存到 volume 目录,这一步只需做一次。部署完成后的自检清单如下:
- 容器状态是否全部
Up; - 业务层日志里是否出现正常的 WebSocket 握手记录;
- 用测试群发一条消息,确认机器人能正常回复;
- 手动
docker restart业务容器,观察是否能自动重连协议层。
这份清单我几乎每次变更后都会跑一遍,能省掉很多后续联调时间。
3.3 数据存储与缓存设计
数据层我一开始用了 SQLite,理由是省事。实际跑下来,在“定时任务表 + 消息日志表 + 用户行为表”这类并发写入场景下,CPU 占用确实会比 MySQL 高一些,但也没到不可用的程度。如果你的群人数超过五十、每天消息上万条,建议直接上独立数据库,连接池和锁机制会省心很多。
Redis 在这个项目里承担了三个关键职责。第一是会话上下文,每个群每轮 AI 对话的上下文片段放在 Redis 里,设置十分钟过期,防止内存无限增长。第二是频率限制,同一个用户三十秒内最多触发三次 AI 请求,用 INCR 加 EXPIRE 来实现。第三是定时任务锁,确保多实例或者容器重启的极端场景下,同一个定时任务不会被重复执行。
缓存策略上,我还对高频问题做了两层缓存:本地内存缓存五秒,Redis 缓存三十分钟。热词条的问答几乎不会重复触发大模型 API,一个月下来 API 费用降了差不多三成。如果你用的是自己的付费密钥,这一步非常值得做。
3.4 上线联调与拨测
部署完成之后,我建了一个只有三个人的测试群,把机器人拉进去,按功能清单逐项验收。重点测这几项:
- @机器人 发起提问,看五秒内能否收到回复;
- 设置一条定时任务,等第二天早上验证是否准时触发;
- 在群里发一条带链接的消息,验证广告识别是否会误判;
- 手动杀掉容器进程,观察自动拉起是否生效;
- 拔掉宿主机网络三十秒,观察协议层断线重连是否自动恢复。
联调过程中最容易被忽略的是日志管理。我把业务日志、协议层日志、系统日志分开落盘,并且配置了按天滚动,保留七天。上线前还对所有日志做了脱敏过滤,密钥、手机号、token 这些敏感信息统一打码,既是安全习惯,也是防止日志文件泄露后的连锁风险。
4. 深度拆解:关键设计为什么这么定
4.1 并发和带宽的估算过程
很多人第一次做这类项目容易高估并发,买了一台很贵的机器结果根本用不满。我提供一个简化模型,拿我当时的数据来算。
设群内消息峰值速率为 R(条/秒),平均每条处理需要调一次外部 API,外部 API 延迟为 L(秒),业务层单实例的最大并发处理数是 N,那么单实例能承受的峰值吞吐大约为 N / L 条/秒。我当时的群峰值 R 约等于三,API 延迟 L 约两秒,业务层用线程池控制并发 N 等于 8,那么 8 / 2 = 4 条/秒,大于实际峰值三,留了约三成余量。带宽按 3Mbps 算,已经足够支撑每秒五十条文本消息,纯文本场景下带宽几乎不会成为瓶颈。
不同规模群可以参考这个估算表:
| 群规模 | 日均消息量 | 峰值要求 | 推荐配置 |
|---|---|---|---|
| 小群(<100人) | 数百条 | 1条/秒 | 1核2G |
| 中群(100~500人) | 三千条 | 3条/秒 | 2核4G |
| 大群(500人+) | 上万条 | 10条/秒 | 4核8G + 独立数据库 |
这个模型的关键是先找出瓶颈在哪里,再决定是加机器还是限流。我的结论是 2 核 4G 足够,真正影响用户体验的是外部 API 的延迟和稳定性,本地资源反而不是短板。
4.2 消息可靠性:重试、幂等与背压
IM 机器人最让用户恼火的情况是“消息明明发出去了,机器人却毫无反应”。我在设计上做了三层防护。
首先是协议层断线自动重连,WebSocket 断开后按指数退避重连,最大间隔六十秒。其次在业务层对用户请求做了应答机制,收到事件后立刻返回一个“处理中”的提示,避免用户以为机器人挂了反复刷屏。最后是第三方 API 失败时的重试,在 Redis 里维护一个任务队列,按固定间隔重试,最多三次,超过三次就转人工告警。
同时还要处理幂等。同一条群消息被协议层重复投递时,业务层必须能去重。我的做法是用 Redis 的 SETNX 对消息 ID 做唯一标记,TTL 设为三十秒。这个细节能避免极端情况下的重复回复。
还有一层叫背压处理。如果外部 API 大面积超时,队列里的任务越积越多,我不会无限积压,而是丢弃超过队列长度上限的任务,同时记录告警日志。这个“丢消息”的策略听起来不完美,但在 IM 群场景里,宁可少回一条,也不能让机器人因为积压而彻底失去响应。
4.3 成本控制心法
一台轻量云主机包年费用不高,这项可以忽略不计,真正的大头是大模型 API 调用费。我的成本控制经验有三条。
第一,高频问题固化。群里问得最多的二十个问题,直接命中缓存,不调 API。第二,上下文裁剪。每次 AI 调用只保留最近五轮消息,超过部分用摘要替代,省 token 也降延迟。第三,定时任务避开高峰。每天的日报和周报都放在低峰时段执行,大模型 API 在高峰时段的计费和延迟都会更不友好。
存储成本也要注意。日志和容器镜像都占空间,60GB 的盘如果不清理,几轮镜像换代就满了。我写了一个定时清理脚本,保留最近三个镜像版本,每周自动执行一次 Docker 资源清理。
提示:做这种主动外连的长连接服务,云主机需要稳定的公网出口。选择云厂商时重点关注线路质量,不要只看带宽数字。
5. 常见问题与排障实录
5.1 登录态失效与风控
这是逆向协议方案最头疼的问题,没有之一。我上线第一周就遇到两次协议层掉线,掉线后必须手动扫码恢复。后来我做了三件事降低影响。
第一,协议层单独跑一个容器,和业务层隔离,掉线时业务进程不受影响。第二,写了一个健康检查脚本,定时探测 WebSocket 心跳状态,连续三次失败就在群里告警。第三,在管理面板里预留了重新扫码的入口,运维人员可以远程触发二维码输出。
风控层面我总结了不少触发条件:频繁加群、短时间内发送大量相同消息、异地 IP 登录后立刻高频操作,都容易被盯上。规避方式主要是上线初期控制发言频率,新账号先“养”一段时间,别上来就群发。这不是玄学,而是让账号行为曲线尽量贴近正常用户。
5.2 消息延迟与队列积压
有一次群友反馈机器人回复明显变慢,从原来的两秒涨到了三十秒。排查路径是这样的:先看协议层日志,发现消息是正常接收的;再看业务层日志,发现大量 API 调用响应时间超过十秒;接着查 Redis 队列长度,发现已经积压了上千条。根因是上游大模型 API 当时正在限流,错误率上升。我没有立刻扩容,因为瓶颈根本不在本地。
处理方式是优先停掉不紧急的定时任务,只保留实时问答,同时把 AI 请求的并发数从 8 降到 4,避免继续触发上游限流。等上游恢复后,再手动触发补发队列。这个案例说明,IM 机器人的“快”不只看自己,还看整条依赖链路的稳定性,线上一定要给外部依赖设置超时和降级策略。
5.3 内存占用与 OOM
跑了两周后我收到内存告警,业务容器频繁重启。用容器监控工具看内存占用,发现是日志组件把整个文件读进了内存。原因是日志库默认把所有日志缓存在内存里再异步写入,而我的消息量超过了默认阈值。
解决方案是启用日志文件的流式写入模式,限制单条日志长度,超过五百一十二字符直接截断,同时把日志切割周期从一天改成三小时。调整之后,业务容器内存稳定在了五百兆以内。
5.4 问题排查速查表
| 症状 | 常见原因 | 快速排查方式 |
|---|---|---|
| 机器人不回消息 | 协议层掉线、WebSocket 断连 | 查看协议容器日志,检查心跳 |
| 回复延迟高 | 外部 API 慢、队列积压 | 检查 Redis 队列长度、API 超时统计 |
| 容器反复重启 | 内存 OOM、配置语法错误 | 查看容器日志退出码 |
| 定时任务没执行 | cron 时区错误、任务锁冲突 | 检查任务时间和 Redis 锁状态 |
| 关键词识别误判 | 规则顺序冲突 | 开启规则调试开关 |
这张表覆盖了我日常遇到的大部分告警场景。遇到新问题,我的建议永远是先看日志再下结论,别靠猜。
6. 安全加固与合规自查
6.1 密钥与敏感信息管理
AI 助手的 API 密钥是最高价值的目标,绝对不能在代码里明文写。我的做法是用一个.env文件存放所有敏感配置,这个文件不进版本库,权限设为 600。在 Compose 文件里,密钥通过环境变量注入到容器:
# .env 示例,注意不要提交到版本库 LLM_API_BASE=https://api.example.com/v1 LLM_API_KEY=sk-xxxxx云厂商自带的密钥管理服务我也用过,功能很完善,但对我来说.env加密加权限控制已经足够。更重要的是一处容易被忽略的泄露点:聊天日志。QQ 群里哪怕只是闲聊,也可能包含手机号、地址等个人信息。我在日志模块里加了正则脱敏,手机号、身份证、密钥模式统一替换成脱敏符号。这个习惯对长期运营很重要。
6.2 权限模型与滥用防护
机器人不能把所有命令对所有人开放。我做了四级权限:黑名单用户、普通用户、群管理员、超级管理员。群管理员可以操作禁言和关键词提醒,超级管理员才能管理定时任务和查看系统状态。权限判断结果走 Redis 缓存,群成员列表变化时自动更新缓存。
滥用防护主要是限流和封禁。AI 类命令每个用户三十秒最多三次,超过就提示冷却。封禁名单存放在本地数据库里,同时提供解封命令。这个机制上线之后,群里恶意刷屏的情况基本消失了。
还有一个设计细节值得提:所有敏感操作命令都要求二次确认。比如“禁言”命令必须附带明确的目标和时长,避免用户手滑触发错误操作。这类保护看似繁琐,但在生产环境里很实用。
6.3 内容安全
AI 生成的回答如果直接发到群里,必须经过一道过滤。我在 AI 模块和大模型之间插入了一个轻量级的内容审核层,内置敏感词库和链接白名单机制。敏感词命中时不会直接报错,而是返回“该内容暂时无法显示”的兜底文案。
这部分没有复杂算法,但它决定了这个机器人能不能长期稳定地跑下去。群聊场景下,一段失控的回答可能直接导致账号被投诉、协议层被封。宁可功能窄一点,也不能让审核缺失。我目前的策略是宁可多拦截一些边缘内容,也要守住底线性安全。
7. 最后分享一点我的体会
dsh-qqbot 从想法到落地,我最大的感受是:这类“群聊 + AI”的项目,难的不是 AI 部分,而是稳定性和运营细节。协议层会掉线、账号会被风控、外部 API 会变慢,这些才是真正消耗时间的地方。如果你也打算做一个类似的群机器人,我建议把七成的精力放在监控、重试、限流和日志上,而不是急着堆功能。
还想提醒一点:机器人不是越智能越好。在群聊这种公共空间里,克制比炫技重要。保持简单的命令前缀、明确的反馈、稳定的响应,比任何花哨功能都更能留住用户。连续跑了一个月之后,我甚至觉得它的“可预期性”才是最有价值的部分——群友知道它几点提醒、怎么触发、多久回复,信任感就是这样一点点建立起来的。后续我打算加一个简易的 Web 管理面板,把定时任务和词库可视化,让非开发者也能够自行配置。等做完了再继续更新。