XZCLOUD · 开发对接文档
版本 v1.0.1 · 配合 schema_version 1.0.0 · 每次功能更新必须同步更新本文档1. 系统总览
XZCLOUD 是一套 PHP 8.2 + MySQL 8.0 的多平台机器人云平台,对齐 AstrBot 的功能模型,并强兼容 AstrBot 原生 Python 插件。
┌────────────────────────────────────────────┐
│ 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 根说明:Nginxroot必须指向public/,且location /必须带回退try_files $uri $uri/ /index.php?$query_string;—— 否则/docs、/doc/xxx、/profile等非文件路由将全部 404(线上故障案例)。样例见deploy/nginx.xzqq.conf。
进程角色
| 进程 | 入口 | 职责 |
|---|---|---|
| Web | public/index.php | 前台页面、REST API、QQ官方 webhook |
| Daemon | php bin/xzqq start | WebSocket 服务、平台适配器长连接、消息中枢、抢红包 |
| Bridge | python3 bridge/astrbot_bridge.py | 加载运行 AstrBot 原生 Python 插件 |
三个进程独立,可分别重启;Daemon 与 Bridge 断链自动重连(指数退避 1→30s)。
2. 目录结构
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_tokens | WebSocket 长连接令牌 |
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_signals | WebRTC 通话 |
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/webhook | QQ 官方机器人事件回调(Ed25519 验签) |
| GET | /api/qqbot/webhook | URL 校验(op=13 在 POST 内处理) |
5. WebSocket 协议
5.1 前端聊天(ws://host:9527/ws?token=<user_token>)
客户端 → 服务端:
{"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"}服务端 → 客户端:
{"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→daemon | hello | 插件清单:{dir,name,author,desc,version,enabled,commands:[{name,alias,description,usage}]} |
| daemon→bridge | ready | 各插件启用态 + admin_ids |
| daemon→bridge | message | {id, umo, message_str, chain:[组件], platform, sender_id, group_id, self_id, is_group} |
| bridge→daemon | result | {id, chain:[组件], plugin}(带 id 时回复到对应事件) |
| bridge→daemon | log / error / stop | 日志与停止传播通知 |
| daemon→bridge | enabled | 单个插件启用/停用(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. 消息流转(端到端)
平台消息 ──► 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_whitelist | config/app.php 或 settings cfg.admin.ip_whitelist | 后台白名单,默认仅 120.225.18.96 |
| 后台密码 | admins.password_hash(bcrypt) | 主来源。安装向导写入;后台「安全」页可改 |
admin.password_plain | config/app.php | 兜底明文:哈希为空/非法时自动重建;输入与它一致时直接放行(详见下方「后台登录机制」) |
| SMTP | config/mail.php | QQ 邮箱授权码(敏感,勿入库/勿入公开仓库) |
| 平台密钥 | bot_instances.config_json | 仅存数据库,后台不回显明文(secret/token 需重新粘贴) |
后台登录机制(public/admin/includes/guard.php)
两层判定,顺序为 IP 白名单 → 密码:
- IP 白名单:
admin.ip_whitelist内的 IP 才放行,其余一律 403(127.0.0.1/::1亦放行)。403 与「密码不正确」是两种不同提示,排障时先区分 - 密码判定,满足其一即通过:
- 与
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)。
配置优先级(易踩坑)
数据库 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. 新增一个平台适配器的步骤
src/Platform/<Name>/<Name>Adapter.php实现PlatformInterface+PlatformMetadataAdapterRegistry注册 runtime keyDaemon按bot_instances.platform实例化并start(EventLoop)- 收到平台消息 → 调
ConversationRouter::onPlatformMessage()(字段见第 7 节) - 发消息 → 实现
send(int $conversationId, MessageChain $chain) - 后台
platforms.php平台下拉自动出现(读取Config::get('platforms')模板) - 更新本文档与《更新日志》
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 节「文档可见性分级」) - 每次发布更新:本文档、《插件开发文档》、《部署文档》、《更新日志》