XZCLOUD/开发文档
下载 .md 文档中心 返回首页
文档中心

开发对接文档

docs/开发对接文档.md · 26.3 KB · 更新于 2026-10-14 20:59

XZCLOUD · 开发对接文档

版本 v1.0.1 · 配合 schema_version 1.0.0 · 每次功能更新必须同步更新本文档

1. 系统总览

XZCLOUD 是一套 PHP 8.2 + MySQL 8.0 的多平台机器人云平台,对齐 AstrBot 的功能模型,并强兼容 AstrBot 原生 Python 插件。

text
                 ┌────────────────────────────────────────────┐
                 │                Nginx (443/80)              │
                 │  /          → 前端聊天页(QQ桌面风白色主题) │
                 │  /docs      → 文档中心(/doc/{name} 阅读)  │
                 │  /admin     → 后台管理(IP白名单+密码)      │
                 │  /install   → 安装向导(lock 自锁)         │
                 │  /api       → PHP REST API                 │
                 │  /ws        → WebSocket 9527               │
                 └───────┬──────────────┬─────────────────────┘
                         │              │
        ┌────────────────▼───┐      ┌───▼────────────────────┐
        │ PHP-FPM / 内置服务  │      │  PHP 守护进程 (Daemon)  │
        │  HTTP 路由/控制器   │      │  - WebSocket 服务端     │
        └────────┬───────────┘      │  - ConversationRouter   │
                 │                  │  - PluginManager        │
                 │                  │  - 平台适配器 pod       │
                 │                  │  - RedPacketService     │
                 │                  └───────┬────────────────┘
                 │                          │ ws?role=bridge
                 │                  ┌───────▼────────────────┐
                 │                  │ Python 桥接 (bridge/)  │
                 │                  │  - astrbot shim 兼容层 │
                 │                  │  - plugins_py/ 原生插件│
                 │                  └────────────────────────┘
                 ▼
        MySQL 8.0(ai)
Web 根说明:Nginx root 必须指向 public/,且 location / 必须带回退 try_files $uri $uri/ /index.php?$query_string; —— 否则 /docs、/doc/xxx、/profile 等非文件路由将全部 404(线上故障案例)。样例见 deploy/nginx.xzqq.conf。

进程角色

进程入口职责
Webpublic/index.php前台页面、REST API、QQ官方 webhook
Daemonphp bin/xzqq startWebSocket 服务、平台适配器长连接、消息中枢、抢红包
Bridgepython3 bridge/astrbot_bridge.py加载运行 AstrBot 原生 Python 插件

三个进程独立,可分别重启;Daemon 与 Bridge 断链自动重连(指数退避 1→30s)。

2. 目录结构

text
XZQQ-Cloud/
├── public/                 # Web 根目录(Nginx root)
│   ├── index.php           # 前端入口 + REST 路由注册
│   ├── api/                # API 静态资源
│   ├── assets/             # css/js/img(含表情包 stickers)
│   ├── call/               # WebRTC 通话页面资源
│   ├── avatar/ uploads/    # 头像/上传文件
│   ├── admin/              # 后台管理(文件夹架构,非单文件)
│   │   ├── includes/       # guard.php(白名单+登录) header.php footer.php
│   │   ├── assets/         # admin.css
│   │   └── *.php           # 14 个管理页
│   └── install/            # install.php 安装向导 + schema.sql
├── src/
│   ├── Core/               # App/Config/Database/Auth/Mailer/Logger/helpers
│   ├── Event/              # MessageChain/MessageEvent/EventDispatcher
│   ├── Http/Controllers/   # Auth/Chat/Call/Home/Profile/Webhook
│   ├── Platform/           # Telegram/Discord/QQOfficial/NapCat 适配器
│   ├── Plugin/             # PluginManager/PluginBase/BridgeGateway
│   ├── Server/             # Daemon/ChatServer/WebSocketServer/EventLoop
│   └── Service/            # ConversationRouter/CallService/FileService
├── bridge/                 # Python 桥接(AstrBot 强兼容)
│   ├── shim/astrbot/       # AstrBot API 兼容层(同名包)
│   ├── bridge_core.py      # 插件加载/分发核心
│   ├── astrbot_bridge.py   # WS 传输 + 入口
│   └── config.json
├── plugins/                # PHP 插件(插件管理页上传/扫描)
├── plugins_py/             # Python(AstrBot 原生)插件
├── plugins_available/      # 插件市场本地安装源
├── config/                 # app/database/mail/platforms 配置
├── deploy/                 # systemd / nginx 样例
├── docs/                   # 公开:开发对接 / 插件开发;内部:部署文档 / 更新日志
├── bin/xzqq                # CLI:start/stop/restart/status/plugin/admin
└── tests/                  # 测试与校验脚本

3. 数据库(MySQL 8.0)

完整 DDL 见 public/install/schema.sql,核心表关系:

表说明
users前台用户(qq_email 唯一,仅允许 @qq.com)
email_codes邮箱验证码(register/reset)
login_logs登录审计
user_tokensWebSocket 长连接令牌
bot_instances平台适配器实例(config_json 存平台私有配置)
conversations统一会话(uk_instance_chatkey 唯一)
messages统一消息(in/out 双向,media_file_id 关联文件)
files文件(sha1 去重,kind: image/video/audio/emoji/file)
plugins插件注册表(runtime=php/python,dir_name 唯一)
commands指令表(QQ官方等可管理指令)
call_rooms / call_logs / call_messages / call_signalsWebRTC 通话
redpacket_logs自动抢红包日志
settings系统设置;key 以 cfg. 开头时在线覆盖 config 文件
admins后台管理员(用户名 + 密码哈希)
stats_daily仪表盘日统计

配置覆盖约定:后台「安全」页保存白名单时写 settings.k = 'cfg.admin.ip_whitelist',App::boot() 启动时以 cfg. 前缀的 settings 值覆盖 config/*.php,因此网页端改配置即时生效,无需改文件。

4. HTTP API

统一约定:JSON 请求/响应;{code:0, msg:"", data:{}} 包装(code=0 成功);鉴权走 Authorization: Bearer <token>(登录颁发)或会话 Cookie。

页面路由

方法路径说明
GET/登录后为 QQ 桌面主页,未登录为欢迎页(HomeController::index)
GET/profile个人主页(需登录)
GET/docs文档中心(索引卡片,只列出公开文档)
GET/doc/{slug}单篇文档在线阅读;?download=1 下载源文件。slug 为纯 ASCII(develop / plugin / deploy / changelog),同时兼容旧中文直链 /doc/开发对接文档。内部文档一律 404(见下)
GET/call/enter/{code}通话落地页(QQ 官方按钮跳转入口)
GET/install/install.php安装向导(runtime/installed.lock 自锁)
GET/admin/后台(IP 白名单 + 密码)

文档可见性分级

HomeController::DOCS 是文档的单一数据源,每项为 slug => [文件名, 说明, 图标, 主题色, 是否公开]:

slug文档公开
develop开发对接文档✅ 前台可读 / 可下载
plugin插件开发文档✅ 前台可读 / 可下载
deploy部署文档❌ 内部(含服务器路径、端口、systemd 配置)
changelog更新日志❌ 内部(版本演进细节)

约定:

  • 内部文档不出现在 /docs 列表,直接访问 /doc/{slug} 或其旧中文直链一律返回 404,与「文档不存在」共用同一出口(docNotFound()),不暴露该文档是否存在
  • 需要再公开一份文档:把对应 DOCS 项第 5 个字段改为 true 即可,前台列表与访问控制自动生效
  • 内部文档仅供后台(/admin/docs.php,IP 白名单内)通过 ?view=<文件名> 内联查看;该入口含 realpath 前缀校验防路径穿越
  • 新增文档时,只需在 docs/ 放 .md 并在 DOCS 登记,后台会自动发现(docMeta 缺失时按 📄 展示)

认证

方法路径说明
POST/api/auth/send-code发送注册/重置验证码(仅 QQ 邮箱)
POST/api/auth/register注册(email + code + password + nickname)
POST/api/auth/login登录,返回 token
POST/api/auth/logout登出
POST/api/auth/reset重置密码
GET/api/auth/me当前用户信息
GET/api/auth/ws-token换取 WebSocket 令牌(user_tokens)

聊天 / 文件

方法路径说明
GET/api/conversations会话列表(平台聚合收件箱)
GET/api/messages?conversation_id=&before_id=历史消息(游标分页)
GET/api/messages/latest各会话最新消息(轮询兜底)
POST/api/messages/send网页端发消息(自动路由到平台)
POST/api/conversations/read清除未读
POST/api/upload上传(图片/视频/语音/文件,sha1 去重)
GET/file/{id}文件下载(支持 Range,可在线播放视频)

个人资料

方法路径说明
POST/api/profile/update昵称/简介
POST/api/profile/avatar头像上传

通话(WebRTC)

方法路径说明
POST/api/call/create创建房间(返回 room_code + landing_url)
POST/api/call/signal上报 offer/answer/ice
GET/api/call/signal/poll拉取信令(HTTP 中转,可换 WS)
POST/api/call/message通话内文字聊天
POST/api/call/close关闭房间
GET/call/enter/{code}通话落地页(QQ markdown 按钮跳转)

平台回调

方法路径说明
POST/api/qqbot/webhookQQ 官方机器人事件回调(Ed25519 验签)
GET/api/qqbot/webhookURL 校验(op=13 在 POST 内处理)

5. WebSocket 协议

5.1 前端聊天(ws://host:9527/ws?token=<user_token>)

客户端 → 服务端:

json
{"t":"send",  "conversation_id":12, "msg_type":"text", "content":"你好"}
{"t":"read",  "conversation_id":12}
{"t":"call_signal", "room_id":3, "signal_type":"offer", "payload":"<sdp>"}
{"t":"call_msg",    "room_id":3, "content":"大家好"}
{"t":"ping"}

服务端 → 客户端:

json
{"t":"message", "data":{ ...消息对象... }}
{"t":"ack",     "client_msg_id":"...", "message_id":123}
{"t":"presence","conversation_id":12,"unread":0}
{"t":"call_signal","signal_type":"answer","payload":"<sdp>"}
{"t":"call_msg","sender_name":"...","content":"..."}
{"t":"pong"}

5.2 NapCat 反向 WS(OneBot 11,ws://host:6199/ws)

NapCat 作为客户端连入;action 调用走 echo(带 echo 字段响应)。平台消息(私聊/群聊/通知)由 NapCatAdapter 转换为统一 MessageChain 后进入 ConversationRouter。

5.3 桥接协议(PHP ↔ Python,ws://host:9527/ws?role=bridge&token=<bridge_token>)

帧格式与消息组件与 PHP 侧完全一致(组件 {"type":"Plain","data":{"text":"..."}})。

方向类型载荷要点
bridge→daemonhello插件清单:{dir,name,author,desc,version,enabled,commands:[{name,alias,description,usage}]}
daemon→bridgeready各插件启用态 + admin_ids
daemon→bridgemessage{id, umo, message_str, chain:[组件], platform, sender_id, group_id, self_id, is_group}
bridge→daemonresult{id, chain:[组件], plugin}(带 id 时回复到对应事件)
bridge→daemonlog / error / stop日志与停止传播通知
daemon→bridgeenabled单个插件启用/停用(3 秒 diff,热加载)
双向ping/pong保活

BridgeGateway 为每个 message 保存 60s TTL 的回复闭包;result 到达时重建 MessageChain::fromArray() 并回调。插件停发 stopPropagation 后续插件不再收到该事件。

6. 插件体系(PHP + Python 桥接)

6.1 PHP 插件

  • 位置:plugins/<作者>/<插件名>/main.php + metadata.json + _conf_schema.json
  • 加载:PluginManager::scan() 递归找 main.php,返回匿名类或 PluginBase 子类实例
  • 启用态:plugins.enabled;热加载:下次 loadAll() 生效(后台切换即时)
  • 指令注册:反射收集方法上的 #[Filter\Command] 等属性 → self::$handlers
  • 分发:PluginManager::dispatchMessage(MessageEvent $event),顺序匹配 handler,命中即执行,stopPropagation 可阻断后续插件

6.2 Python 插件(AstrBot 原生,强兼容)

  • 位置:plugins_py/<作者>/<插件名>/main.py + metadata.json
  • bridge/shim/astrbot/ 提供与 AstrBot 同名包的 API 兼容层,插件源码几乎原样可跑
  • 桥接进程以包方式注册模块(xzqq_plugins_py.<作者>.<插件>.main),支持插件内相对导入
  • hello 时用 AST 静态扫描 @filter.command 上报指令元数据(不 import,无副作用)
  • 启用/停用:Daemon 每 3s diff plugins 表,通过 enabled 帧推送,桥接热加载/卸载
  • 兼容矩阵:指令/别名/正则/事件类型/平台类型/权限/GreedyStr/消息链 send/生命周期 ✅;LLM Provider ❌(本平台不内置 AI 调用);自定义 filter ⚠️(可用但无法静态上报指令)

6.3 事件对象(MessageEvent)关键能力

能力说明
get_platform_name()napcat/qqofficial/telegram/discord
get_sender_id()/get_group_id()/get_message_id()平台内 ID
is_group()/is_private_chat()会话类型
get_umo()统一消息对象标识
get_extra()/set_extra()插件间传参(同一事件内)
stop_event()停止事件继续传播
send(chain)/plain_result()/chain_result()/make_result()回复(Python 侧经桥接 result 帧回传)

7. 消息流转(端到端)

text
平台消息 ──► Adapter(NapCat/QQOfficial/TG/DC)
   │  解析为 MessageChain + umo
   ▼
ConversationRouter::onPlatformMessage()
   ├─ 落库 conversations/messages(in)
   ├─ WS 广播给前端在线用户
   ▼
EventDispatcher::dispatch()
   ├─ PluginManager::dispatchMessage()      # PHP 插件
   ├─ BridgeGateway::dispatch()             # Python 插件(未 stopPropagation 时)
   ▼
回复(MessageChain)
   ├─ PHP:直接回调
   ├─ Python:桥接 result 帧 → 重建链 → 回调原事件闭包
   ▼
ConversationRouter::recordOutbound()        # 落库(out)+ 回推平台

网页端发消息:POST /api/messages/send → ConversationRouter::sendFromWeb() → Adapter 发到平台 → 平台回执。

8. 平台配置速查(bot_instances.config_json)

通用字段:appid/secret/token/access_token/enabled,以下按平台:

  • napcat:ws_reverse_host/ws_reverse_port/access_token,redpacket{enabled,whitelist_groups,blacklist_groups,cooldown,daily_limit,max_per_minute,action,action_params,retry}
  • qqofficial:appid/secret/sandbox/api_base/token_api/verify_signature/call_landing;回调地址 https://<域名>/api/qqbot/webhook
  • telegram:bot_token/webhook_url/polling
  • discord:bot_token/application_id/public_key/guild_id

适配器统一实现 PlatformInterface:meta()/defaultConfig()/validate()/start()/stop()/callAction()/send()/sendMarkdown()。

9. 环境变量与安全

项位置说明
XZQQ_BRIDGE_TOKEN环境变量桥接令牌,默认 xzqq-bridge-default-token(生产必须改)
admin.ip_whitelistconfig/app.php 或 settings cfg.admin.ip_whitelist后台白名单,默认仅 120.225.18.96
后台密码admins.password_hash(bcrypt)主来源。安装向导写入;后台「安全」页可改
admin.password_plainconfig/app.php兜底明文:哈希为空/非法时自动重建;输入与它一致时直接放行(详见下方「后台登录机制」)
SMTPconfig/mail.phpQQ 邮箱授权码(敏感,勿入库/勿入公开仓库)
平台密钥bot_instances.config_json仅存数据库,后台不回显明文(secret/token 需重新粘贴)

后台登录机制(public/admin/includes/guard.php)

两层判定,顺序为 IP 白名单 → 密码:

  1. IP 白名单:admin.ip_whitelist 内的 IP 才放行,其余一律 403(127.0.0.1 / ::1 亦放行)。403 与「密码不正确」是两种不同提示,排障时先区分
  2. 密码判定,满足其一即通过:
    • 与 config/app.php 的 admin.password_plain 完全一致(hash_equals 防时序侧信道)—— 明文兜底通道
    • 或 password_verify() 通过数据库 bcrypt 哈希
不启用登录失败频率限制 / 账号锁定(v1.0.1 起按需求移除)。admins 表的 login_fails / locked_until 列保留但不再写入与判断,失败只返回「密码不正确」。 后台防线为 IP 白名单 + 密码,白名单是主要防线。
设计要点:schema.sql 中 admins 初始 password_hash 为空串,需安装向导回填。若直接导入 SQL 未走向导,登录时会触发自愈 —— 无 admin 记录则新建,哈希为空/非法则用 password_plain 重建,避免「任何密码都验不过」的死锁。 忘记密码时用 php bin/xzqq admin reset ['新密码'] 重置(缺省取 password_plain)。

配置优先级(易踩坑)

text
数据库 settings 表的 cfg.* 覆盖项   >   config/app.php 文件值

App::boot() 启动时会遍历 settings 表,把所有 cfg. 前缀的键(值为 JSON)覆盖到内存配置上。因此:

  • 后台「安全」页保存白名单 → 写入 settings.cfg.admin.ip_whitelist → 之后改 config/app.php 不再生效
  • 想让文件配置重新生效:后台点「恢复 config/app.php 文件配置」,或 DELETE FROM settings WHERE k='cfg.admin.ip_whitelist';
  • 修改 config/app.php 后建议 systemctl restart php8.2-fpm(OPcache 可能缓存旧文件)

改密码时的同步约定

admin.password_plain 是可用登录通道(hash_equals 直接放行),因此任何改密码的入口都必须同步它,否则旧密码仍可登录:

入口是否自动同步 password_plain
后台「安全」页「修改密码」✅ 自动改写 config/app.php(文件不可写时提示手动)
php bin/xzqq admin reset '新密码'❌ 只改数据库哈希,需手动同步配置文件
php bin/reset-admin-password.php❌ 同上

10. 新增一个平台适配器的步骤

  1. src/Platform/<Name>/<Name>Adapter.php 实现 PlatformInterface + PlatformMetadata
  2. AdapterRegistry 注册 runtime key
  3. Daemon 按 bot_instances.platform 实例化并 start(EventLoop)
  4. 收到平台消息 → 调 ConversationRouter::onPlatformMessage()(字段见第 7 节)
  5. 发消息 → 实现 send(int $conversationId, MessageChain $chain)
  6. 后台 platforms.php 平台下拉自动出现(读取 Config::get('platforms') 模板)
  7. 更新本文档与《更新日志》

11. 前端设计规范(动态玻璃)

全站前端遵循统一的玻璃拟态(Dynamic Glass)语言,新增/修改页面请复用下列资产,不要自造样式体系。

11.1 依赖

资源引入方式用途
assets/css/base.css本地玻璃 token、.glass 系列、动画工具箱。所有前台页面必引
assets/vendor/fontawesome/css/all.min.css本地全站矢量图标(禁止再用 emoji 当图标)。仅含 solid + regular 字体
零外部依赖约定:全站不引入任何第三方 CDN(Tailwind / Font Awesome / 字体 / JS 库一律本地化)。 原因:① 浏览器隐私拦截(Tracking Prevention)会阻断第三方存储导致样式失效;② 商业站点不应把访客 IP 交给第三方; ③ 国内访问不稳、断网即白屏。历史上曾用 cdn.tailwindcss.com 与 cdnjs.cloudflare.com,已于 v1.0.1 移除。 本项目不使用 Tailwind。历史上 Tailwind 全站只用到 3 个类,已改为自定义类(.brand-link)。新增样式请写进各页面的 CSS 文件。 Font Awesome 升级:替换 public/assets/vendor/fontawesome/ 整目录;若用到 brands 图标需补下 fa-brands-400.woff2。

11.2 玻璃类(base.css)

类效果典型场景
.glass白 72% + blur(18px),浅边框 + 软阴影侧栏、卡片、面板默认层
.glass-strong白 86% + blur(24px),更强阴影弹窗、浮层、控制条
.glass-soft白 55% + blur(14px)局部小标签、深色背景上的胶囊
.glass-hi附加顶部 1px 高光与上面三个叠加使用

CSS 变量:--glass-bg(72% 白) / --glass-bg-strong / --glass-bg-soft / --glass-brd / --glass-blur(18px) / --ease。

11.3 动画工具箱

fadeUp / fadeIn / popIn / slideDown / floatY / drift1~3 / shine / pulseRing, 对应类 .fade-up .fade-in .pop-in .slide-down .float,错峰用 .d1~.d8(每档 +60ms)。 base.css 已带 prefers-reduced-motion 降级,勿在业务页重复处理。

11.4 图标替换对照(本次改版)

原 emoji现 Font Awesome
😊 表情fa-regular fa-face-smile
📎 文件fa-solid fa-paperclip
🖼️ 图片fa-regular fa-image
🎬 视频fa-solid fa-video
📞 通话fa-solid fa-phone / fa-solid fa-phone-slash(挂断)
👤 个人主页fa-regular fa-circle-user
📖 文档fa-solid fa-book / fa-book-open-reader
🚪 退出fa-solid fa-arrow-right-from-bracket
📦 文件气泡fa-solid fa-file-arrow-down

11.5 响应式与手机端

各页面在 base.css 之外自带断点,改动时对照下表,不要重复定义:

断点chat 桌面(app.css)通话页(call.css)欢迎页(welcome.css)个人主页(profile.css)
<= 1180px隐藏右侧会话信息栏———
<= 960px——主推卡片转单列—
<= 900px侧栏收成 76px 图标条聊天列移到底部(220px)——
<= 720px单屏切换(见下)视频铺满 + 底部聊天抽屉——
<= 640px——导航/hero/卡片/模拟图压缩,弹窗改底部抽屉间距压缩
<= 480px———单列 + 全宽按钮
<= 400px超窄屏压缩超窄屏压缩超窄屏压缩—

手机端单屏切换(app.css <= 720px):侧栏与聊天区不再并排,.app 无 .show-chat 时只显示会话列表,有 .show-chat 时只显示聊天区。

  • 进入会话:selectConversation() 内调用 showChat(),并在手机端 history.pushState 压一条历史
  • 返回列表:#btnBack 点击 → showList();浏览器物理返回键 → popstate 监听 → showList()
  • .btn-back 桌面端基线为 display:none,仅在 <= 720px 内改为 inline-flex;<button class="btn-back" id="btnBack"> 必须在 home.php 保留
  • 软键盘适配:visualViewport 的 resize/scroll 把键盘高度写入 CSS 变量 --kb,表情面板与输入区据此吸附;输入框聚焦时自动收起表情面板
  • 移动端输入框字号统一 >= 14px(home / profile / call 三处),避免 iOS 聚焦自动放大页面
  • 四个前台视图 viewport 统一为 width=device-width, initial-scale=1, viewport-fit=cover(后者供 env(safe-area-inset-*) 生效)

11.6 约束

  • DOM 契约优先:views/welcome.php、home.php、call.php 的元素 id 与 app.js / welcome.js / call.js 强耦合,

改版只允许改结构与样式,id 必须原样保留(含 #btnGotoLogin2、#sendCodeBtn、#msgInput、#stageEmpty、#btnBack 等)。

  • 一个页面只引一次 base.css;页面专属样式放各自的 welcome.css / app.css / profile.css / call.css。
  • 后台另用 admin/assets/admin.css(已在其中内置等价玻璃样式),不要再引 base.css。
  • 字重只用 400 / 500 两档,禁用 600 / 700(与全站统一视觉一致)。

12. 二次开发检查清单

  • 数据库变更 → 同步 public/install/schema.sql 并在安装向导加迁移
  • 新 API → public/index.php 注册路由 + Controller + 本文档第 4 节
  • 新 WS 帧 → src/Server/ChatServer.php + 前端 public/assets/js + 本文档第 5 节
  • 新插件过滤器 → src/Plugin/Filter.php + PluginManager 匹配逻辑 + 《插件开发文档》
  • 桥接协议变更 → src/Plugin/BridgeGateway.php + bridge/bridge_core.py 成对修改
  • 前端页面改动 → 遵循第 11 节设计规范(玻璃类 + FA 图标 + 动画工具箱),保持 DOM id 不变;改动布局时同步核对第 11.5 节各断点
  • 新增对外路由 → 确认 Nginx try_files 回退可用,并在《部署文档》备案
  • 新增/调整文档 → 在 HomeController::DOCS 登记,并明确标注公开或内部(第 2 节「文档可见性分级」)
  • 每次发布更新:本文档、《插件开发文档》、《部署文档》、《更新日志》