简介QQ云端免挂智能机器人云端发信API是一套基于服务器/虚拟主机的自动化发信接口面向需要长期在线自动发QQ消息、又不愿意本地挂机的个人开发者或站长。将压缩包上传至空间并完成简单配置即可让机器人24小时运行适用于自动回复、定时通知等场景。包体共11个文件42KB核心为6个PHP脚本承担接口监听、请求解析与发信逻辑另有2个txt分别提供使用教程和必看说明辅以1个CSS样式文件及菜单、指定回复等辅助文件结构精简。该API目前已有205人学习因为功能相对基础更适合初次尝试云端机器人搭建的读者。通过阅读文档并修改PHP参数可以掌握API路由、消息构造与服务器部署的基本思路虽然现阶段仅支持文本类消息但完整代码开放便于在此基础上扩展关键词触发、多类型消息或集成其他服务是学习HTTP接口与云函数开发不错的起步样例。1. 云端免挂QQ智能机器人与发信API一次调用把消息发进QQ做过QQ机器人的人应该都体会过本地电脑关机、休眠、宽带闪断一次机器人就失联一整夜第二天早上起来群里全是怎么不回复了。“QQ云端免挂智能机器人云端发信API”这个标题本质上把喊了三年的“挂机方案”换了个思路登录态和运行环境搬到云服务器本地电脑可以彻底关机对外只暴露一个发信API业务系统一条HTTP请求就能让机器人把话发到指定QQ号或群里。拆开看是三层免挂的云端运行时、接上大模型API的智能对话、统一鉴权的发信接口。适合做群通知、定时推送、内部告警也适合拿QQ当消息出口的自动化脚本。这篇文章直接讲怎么在一台2核4G云服务器上把它跑起来并把账号风控、消息丢失这些绕不过去的坑说清楚。2. 免挂架构与协议选型客户端协议容器化是当下最省心的路径“免挂”两个字容易让人误解成免风控、免掉线实际上它只解决一件事不要再依赖本地电脑保持开机。QQ机器人能收发消息本质上还是得有一个“QQ客户端”活着只是这个客户端现在跑在云端的容器里本地可以关机。选哪条路把客户端跑起来决定了这个项目能活多久、能不能稳定接收消息。2.1 三条落地路线的取舍网页协议、NT客户端协议与官方开放平台先说一个很多人还在用的老路子基于QQ网页版/旧版本的协议模拟登录。早期一批机器人框架走的是这条路优点是内存小、不依赖图形界面但腾讯这几年对这类模拟登录的校验越来越重新号扫码后常常几分钟就掉线很多老项目已经进入半废弃状态。我的判断是新项目不要往这条路上走除非你手里有养了很久的老号并且愿意接受频繁抢救。第二条路是NT客户端协议代表实现就是NapCat这类框架。它不再模拟协议而是直接复用QQNT桌面客户端的内核和登录态用浏览器扫码后把登录态持久化在容器里再对外暴露一套标准的机器人接口。这套路的好处是登录成功率明显高掉线后重连逻辑也比较成熟是目前个人开发者和中小团队落地“云端免挂”的首选。第三条路是QQ官方开放平台的机器人。合规、接口稳定但申请门槛高需要企业主体、应用审核、回调配置个人想第二天就上线一哥定时提醒工具流程上不太现实。三条路放在一起很好选路线登录成本开发成本风控风险适合场景旧网页协议模拟难易被限制低高不推荐新起项目NT客户端协议NapCat扫码即可低中可控个人/中小团队快速落地官方开放平台机器人申请加审核高低正式商业应用我一般会直接选第二条路用Docker跑一个NapCat容器把QQ登录态留在容器数据卷里本地扫码一次之后机器人的“身体”就在云端常住了。2.2 OneBot 11协议下发信API的核心动作与报文NapCat对外的接口遵循OneBot 11标准这套标准把QQ机器人能力抽象成一组固定动作。做云端发信API你真正要关心的动作只有三个send_private_msg发私聊、send_group_msg发群聊、send_msg自动判断发私聊还是群聊。它们的请求体结构非常接近核心字段就四个字段含义备注message_typeprivate 或 group决定打到哪个动作user_id / group_id接收方私聊传user_id群聊传group_idmessage内容纯文本或CQ码auto_escape是否转义CQ码默认false发普通文本建议设true一个发私聊的报文长这样{ action: send_private_msg, params: { user_id: 10001, message: 这是一条来自云端的消息, auto_escape: true } }这段报文可以走NapCat的HTTP接口也可以走WebSocket差别在于调用方式HTTP是一次请求一次响应适合业务系统主动调WebSocket适合机器人被动接收消息事件。做统一发信API时我建议把HTTP作为主链路消息事件的监听走WebSocket两条通道互不干扰出问题时排查边界也清晰。2.3 智能从哪来消息事件、大模型API和发信动作的分工很多人第一次搭的时候会有一个错觉NapCat本身就是“智能机器人框架”装完应该就能聊天。恰恰相反这类协议框架对你大脑负责的部分一无所知它只干两件事把收到的QQ消息转成事件推给你把你要求发送的内容投递到QQ。所谓“智能”完全靠你自己在中间接一个大模型API。完整的链路是QQ群里的消息通过WebSocket事件推给业务服务业务服务把消息拼上上下文调用DeepSeek、智谱这类大模型API拿到回复文本再通过send_private_msg或send_group_msg把回复发出去。“免挂”免的是本地开机不是免你写服务。这一段想清楚后面封装的API才是真正有用的东西而不是一堆脚本散落在服务器上。3. 把QQ机器人搬上云端的部署从Docker容器到第一条消息部署这件事坑不在Docker命令本身而在“登录环境的干净程度”。我见过太多人在本地反复扫码成功一上云服务器就掉线原因基本都是IP、设备和账号三者的组合太“可疑”。所以这一章的步骤不只是跑通还要让你跑起来之后不被腾讯的风控盯上。3.1 云服务器与Docker基础先准备好一个干净的登录环境服务器配置不需要高2核4G跑这个场景绰绰有余系统的Ubuntu 22.04即可。登录环境有几个注意点地域离你常用IP不要跨度太大不要用机房大网段里明显被大量机器人用过的IP账号尽量用养了一段时间的小号别把主号拿来试错。这些条件决定了后续“免挂”能免多久。装好系统后先装Docker国内服务器直接用官方安装脚本即可# 安装 Docker 并设为开机自启 curl -fsSL https://get.docker.com | bash systemctl enable --now docker装完之后用一个简单的容器验证一下Docker是否正常再继续下一步。这一步如果网络受限可以换镜像源但要确认你的Docker版本与源匹配不然拉取时会报manifest错误。3.2 用Docker跑起NapCat容器端口映射与登录态持久化NapCat镜像的发行名在不同作者那里不完全一样这里用占位符表示你手上用的镜像名自己替换即可。关键是端口和数据卷一定不要省docker run -d \ --name qq-cloud-robot \ --restart unless-stopped \ -p 3000:3000 \ -p 3001:3001 \ -p 6099:6099 \ -v napcat-data:/app/napcat \ napcat镜像名:标签参数说明-p 3000:3000暴露OneBot HTTP服务-p 3001:3001暴露WebSocket服务-p 6099:6099是登录用的Web管理页面-v napcat-data:/app/napcat把数据目录挂载到命名卷里这个卷至关重要里面存着扫码后的登录态容器销毁重建都不用重新扫码。--restart unless-stopped让服务器重启后容器自动拉起。启动后浏览器打开http://服务器IP:6099用手机QQ扫码完成登录。看到管理页面显示在线状态再进配置页确认HTTP端口和WebSocket端口跟容器映射一致。注意这里服务器防火墙要放行这三个端口但后面我会强烈建议你不要让业务随便直连3000端口。3.3 冒烟测试发信API用curl给指定QQ发一条私聊容器跑起来登录态也建好了先用一条curl验证发信链路。这里以OneBot HTTP服务为例curl -X POST http://127.0.0.1:3000/send_private_msg \ -H Content-Type: application/json \ -H Authorization: Bearer 你的token \ -d { user_id: 你的QQ号, message: hello from cloud, auto_escape: true }正常情况下返回体里的status为okretcode为0手机QQ能立刻收到这条消息。如果提示鉴权失败去NapCat配置页把token检查打开并填入上面的Authorization头如果返回超时先确认容器是否真的在线再看端口映射有没有写反。这里建议用一个小号加自己测试不要拿主号反复试。3.4 先验证大模型API能通DeepSeek/智谱的快速调用发信通了接下来验证“智能”的部分。以DeepSeek为例申请API Key后先手动调一次确认网络、Key、模型名都对curl https://api.deepseek.com/chat/completions \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -H Content-Type: application/json \ -d { model: deepseek-chat, messages: [{role: user, content: 用一句话介绍你自己}], max_tokens: 200, temperature: 0.7 }这里temperature控制回复的随机性聊天场景0.7到0.9比较自然做告警文案可以降到0.3让输出更规范max_tokens限制单次回复长度避免长文把对话上下文撑爆。智谱的接口路径不同但参数结构类似换一下域名和模型名即可。这一步只验证连通性真正接入要在业务层统一管理Key和计费否则一个Key散落在脚本里月底看账单会很心疼。4. 封装统一云端发信API鉴权、限流、重试与错误码机器人能发消息之后自然就会有人想把“发消息”做成一个正式接口给业务系统用。这里最大的坑不是写接口而是“直接暴露”。很多人图省事把NapCat的3000端口直接开给内网结果一个同事误调、一个脚本失控机器人短时间内发出去几百条消息然后账号就被盯上了。4.1 为什么不能直接把OneBot端口暴露到公网OneBot 11这套接口本身是给本地或可信网络用的它没有细粒度的权限控制也没有调用量限制。把3000端口暴露给业务方等于让任何人拿到地址都能替你的QQ发消息。我见过最严重的案例是测试环境被扫描到端口机器人成了发广告的肉鸡账号直接冻结。所以要做一层转发层统一对外提供一个RESTful风格接口内部帮你做鉴权、限流、日志、错误码映射。业务方不需要知道NapCat存在也不需要关心OneBot协议细节只需要记住一个POST /v1/send接口。4.2 用FastAPI写一个POST /v1/send完整代码与参数说明下面这个例子可以直接跑它做了三件事校验API Key、把统一请求转成OneBot动作、调用NapCat HTTP端口并返回统一结构import httpx from fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel app FastAPI(titleQQ Cloud Send API) # 上游是跑在容器里的 NapCat HTTP 服务 UPSTREAM http://127.0.0.1:3000 # 实际项目里从环境变量读取不要硬编码 API_KEYS {sk-your-key: app-a} class SendRequest(BaseModel): message_type: str private # private / group target: int # 私聊传 user_id群聊传 group_id content: str auto_escape: bool True msg_id: str # 调用方生成的幂等键防止重复发送 app.post(/v1/send) async def send(req: SendRequest, authorization: str Header(default)): # 1. 鉴权 key authorization.removeprefix(Bearer ).strip() if key not in API_KEYS: raise HTTPException(status_code401, detailinvalid api key) # 2. 统一请求转成 OneBot 11 动作 action send_private_msg if req.message_type private else send_group_msg target_field user_id if action send_private_msg else group_id payload { target_field: req.target, message: req.content, auto_escape: req.auto_escape, } # 3. 调用上游超时和异常都映射成统一错误码 try: async with httpx.AsyncClient(timeout10.0) as client: resp await client.post(f{UPSTREAM}/{action}, jsonpayload) data resp.json() except httpx.TimeoutException: return {code: 1004, msg: upstream timeout, data: None} if data.get(status) ! ok: return {code: 1003, msg: upstream rejected, detail: data} return {code: 0, msg: ok, data: {msg_id: req.msg_id}}代码逻辑不复杂重点是两个设计。一是auto_escapetrue作为默认值业务方传过来的内容往往包含特殊符号如果不转义OneBot可能把里面的一段文本错误解析成CQ码。二是msg_id字段由调用方生成配合第5章的幂等逻辑可以从源头避免“上游超时重试导致消息发两遍”这个老大难问题。参数上content建议限制长度我一般禁止超过3000字符超长就截断target必须大于10000防止传错ID。4.3 限流与队列单账号每分钟该控制在什么水位QQ对非官方机器人的风控重点看两个特征发送频率和内容重复度。我踩过最狠的一次是每分钟连发40条公告前20条没事后面开始被吞再往后账号直接接不到消息。后来的经验是单账号全局限速每分钟不超过30条单群连续发送不超过10条同样内容的消息前后至少间隔20秒。限流实现不需要上Redis进程内一个带时间戳的计数器就够import time class TokenBucket: def __init__(self, rate: float, capacity: int): self.rate rate # 每秒补充多少令牌 self.capacity capacity self.tokens capacity self.updated time.time() def acquire(self) - bool: now time.time() self.tokens min(self.capacity, self.tokens (now - self.updated) * self.rate) self.updated now if self.tokens 1: self.tokens - 1 return True return False bucket TokenBucket(rate0.5, capacity30) # 每秒补0.5即一分钟30条这个桶的rate0.5对应每分钟30条的硬上限capacity30允许短时间突发到30条但长期看会被压制在限速以内。实际项目中我还会把同一个target单独限一次比如群聊里连续人的动作要更苛刻。另外大模型API的“api调用量”也要在这里设每日预算机器人被刷屏时真正烧钱的是模型调用而不是发消息本身。4.4 错误码设计让上游业务方只记住一套返回没有统一错误码的接口会在联调时变成灾难业务方要同时看懂NapCat错误、模型返回错误、网络错误。转发层存在的意义就是把这些全部折叠成一张表code含义上游发生了什么0成功消息已投递给QQ1001鉴权失败API Key不对或缺失1002参数非法target不存在、content超长1003上游拒绝账号被风控或消息内容被拦1004上游超时NapCat无响应或网络断裂1005触发限流请求过快建议退避重试业务方看到1004会自己做退避重试看到1003则不应该重试因为重试只会加重风控。这个语义区分在接口文档里要写清楚否则上游脚本会把风控拒绝当成超时反复轰炸账号死得更快。5. 云端QQ机器人避坑记录掉线、吞消息、上下文超限这一章全部来自真实运行中会反复踩的坑。每一条都不是“可能遇到”而是“大概率遇到”尤其是账号风控和消息丢失。5.1 现象登录几分钟就掉线提示异地登录或需要设备锁验证现象是扫码成功后一切正常半小时内收到“账号在异地登录”的提示机器人掉线要求重新验证。原因是你的账号第一次在云服务器的IP和设备组合上登录腾讯的安全模型把这判为异常如果这个IP段还被大量机器人号用过判定会更重。解决方法是不要急着否认这个环境而是先让账号在“同一IP同一容器”下稳定挂够几天第一天上线只发一两句测试消息第二天开始加小流量对话第三天再上正式业务。同时把QQ自带的设备锁验证走完能绑定就绑定。主号不要拿来实验花几块钱养一个小号专门跑机器人是值得的。5.2 现象接口返回成功但对方一直收不到消息这是最诡异的一类status是okretcode是0消息却像丢进黑洞。原因基本是发送频率触发了风控的“静默吞消息”腾讯不报错、不拒绝只是让你的消息不被投递。另一个常见原因是内容特征比如连续多条完全相同的文本、文本里带链接和营销词都会提高被吞概率。解决方法是先看NapCat日志里有没有消息丢弃记录没有的话基本就是风控把发送间隔拉长内容做一点“人味”加工比如在公告末尾拼上时间戳或用随机序号微调文本。还有一个容易被忽略的细节auto_escape没有置真时消息里的特殊字符会被当作CQ码解析显示出来的内容跟你发的不一样看起来也像是发错了。5.3 现象发信API偶发超时重试后消息发了两遍超时重试是业务系统的应激反应但在消息投递这里重试可能制造重复。NapCat把消息交给QQ后网络抖动了一下你的接口等不到响应就报超时调用方重试于是对方收到两条一模一样的内容。解决关键在幂等键。调用方每次请求生成一个唯一的msg_id转发层在把消息转给NapCat前记录这个ID重试时发现同样的ID已经在处理中就忽略也可以在发消息内容里附带请求序号让接收方自然去重。我在生产里最值钱的一条教训宁可让日志里出现重复请求也不要让人收到重复消息。5.4 现象多轮对话报400超限模型直接拒绝回复日志里会看到类似“api error: 400 this models maximum context length is...”之类的报错。现在的模型窗口普遍不小但QQ群里的多轮对话会不停把历史消息拼进上下文每轮都带全量历史窗口再大也会被撑爆。解决思路是做上下文裁剪def build_messages(history: list[dict], max_chars: int 12000) - list[dict]: # 从最新一条往旧裁剪优先保留最近的对话 budget max_chars picked [] for msg in reversed(history): cost len(msg.get(content, )) if budget - cost 0: break budget - cost picked.append(msg) return list(reversed(picked))这个函数的作用是固定一个总字符预算从最新的消息开始往前保留超出预算的直接丢掉。配置上单轮对话上限不要超过模型最大窗口的一半多轮历史总字符数控制在窗口的三分之一以内剩下的留给系统提示词和回复生成。如果模型API连续三次返回超限错误就熔断成单轮模式只拿当前消息请求不再带历史。5.5 现象Docker重启或升级后机器人在线但回不了消息排查到最后会发现登录态丢了需要重新扫码。原因几乎都是创建容器时没挂数据卷或者挂成了匿名卷容器一删登录态跟着没了。检查方法是执行docker volume ls看有没有命名的数据卷再看docker inspect里的Mounts指向。解决就是回到第3章的启动命令确保-v napcat-data:/app/napcat这个参数在场。升级前先docker cp备份容器内的数据目录到宿主机升级失败还能还原。这条我栽过两次现在每次动容器之前第一件事就是确认数据卷。6. 进阶用消息回执与分级推送把发信链路做成可审计的最后一个技巧是把“发了就完事”变成“发了能证明、能追踪”。做法不复杂但运行一段时间之后你会感激这个设计。第一个手段是回执对账。转发层在返回code:0时同时把NapCat实际返回的message_id记录进日志表字段包括msg_id、target、content_hash、message_id、timestamp。每天跑一个定时任务统计哪些msg_id在发出后没有对应的message_id这些就是被静默吞掉的消息。有了这张表再遇到“用户说没收到”的反馈你可以直接查日志而不是靠猜。第二个手段是分级推送。不是所有消息都值得通过QQ发出去推得越多账号风控面越大。我现在的做法是分三级级别发送目标典型场景高私聊 群内线上故障、服务不可用中群聊日报、定时提醒低只落日志不发QQ调试日志、低频统计高优先级消息可以放宽一点频率限制低优先级消息宁可不发也不要打扰。这个分级还能配合限流桶用低级别请求直接走本地日志中级别走限流队列高级别才允许占用突发容量。最后一个复用技巧把文本预处理做成插件的思路在/v1/send入口统一做三件事——去空白、截断超长内容、把、[、]这类会被CQ码解析的字符转义掉。不要把这些逻辑散落在每个调用方统一收口才改得动。我自己的使用习惯是任何要发出去的消息都强制过一道预处理宁可在接口里多耗几毫秒也不要让一条格式错乱的消息出现在群里。这套链路我已经跑了大半年每周换一次小号登录配合回执对账再也没出现过“发了但没人收到”还找不到原因的情况。希望这一套思路能帮你把QQ云端机器人做成一个真正省心的消息出口。本文还有配套的精品资源点击获取