# 后台实时推送:后端部署 更新日期:2026-11-01。适用于 `upgrade/laravel-13` 分支及本次审查修正后的完整代码。 状态:待首次上线。以下按首次部署流程执行。 本文面向后端和运维,包含迁移、环境变量、常驻进程、Supervisor、Nginx、调度和验收步骤。前端的接口、频道、事件字段及 Echo 参考实现统一在[前端接入文档](总后台/后台实时推送-前端接入.md)。 ## 1. Reverb 与 relay 的职责 | 进程 | 命令 | 作用 | | --- | --- | --- | | Reverb | `php artisan reverb:start` | 维护 WebSocket 连接,把消息推给后台页面的 Laravel Echo 客户端 | | relay(中继) | `php artisan admin-realtime:relay` | 读取共享事件表,解析列表行和统计快照并发给 Reverb;失败重试,同时处理登录到期断连和在线状态扫描 | 两者各运行一个实例,并由 Supervisor 守护。此次推送链路同步发送,不需要额外的队列 worker;项目原有支付、Telegram 等队列任务及其 worker 继续保留。 ## 2. 发布准备 本文示例项目目录为 `/www/wwwroot/bot-28`,WebSocket 公网域名为 `admin.example.com`,运行用户为 `www`,PHP CLI 为 `/usr/bin/php`。请替换成服务器实际值;网站 PHP-FPM、命令行、Supervisor 和 cron 均应使用 PHP 8.3+。若是宝塔等面板,PHP 路径可能是 `/www/server/php/83/bin/php`,以实际安装为准。 发布前备份业务数据库及现有后端发布目录(包括 `composer.json`、`composer.lock`、`vendor` 和 `.env`),再按项目原有发布方式部署完整代码、新迁移和配套 `composer.lock`。 现有未跟踪的 `composer.lock` 如阻止 Git 更新,先移到备份目录,使用新代码配套的 lock 文件。 ## 3. 配置环境变量 编辑项目 `.env`,保留现有数据库、Redis 连接和前缀、`APP_KEY` 等配置。不要重新执行 `key:generate`。后台 JWT 密钥必须至少 32 字节,已有合格密钥可继续使用;更换后后台管理员需要重新登录。 先将推送设为关闭: ```dotenv ADMIN_WS_ENABLED=false ADMIN_WS_BROADCASTER=reverb ADMIN_WS_SUMMARY_SCAN_SECONDS=5 # 前端 Echo 连接的公网地址:这里的 host 不带协议或路径 ADMIN_WS_PUBLIC_HOST=admin.example.com ADMIN_WS_PUBLIC_PORT=443 ADMIN_WS_PUBLIC_SCHEME=https REVERB_APP_ID=bot28-admin REVERB_APP_KEY=填写第一条生成命令的结果 REVERB_APP_SECRET=填写第二条生成命令的结果 # Laravel、relay 调用 Reverb 的本机 HTTP 地址 REVERB_HOST=127.0.0.1 REVERB_PORT=8080 REVERB_SCHEME=http # Reverb 只监听本机,公网连接由 Nginx 转发 REVERB_SERVER_HOST=127.0.0.1 REVERB_SERVER_PORT=8080 REVERB_ALLOWED_ORIGINS=admin.example.com REVERB_SCALING_ENABLED=false ``` 运行两次,各生成一个不同值,分别填入 KEY 和 SECRET: ```bash openssl rand -hex 16 openssl rand -hex 16 ``` `REVERB_ALLOWED_ORIGINS` 填后台页面所在的域名,不带协议;若页面和 API 域名不同,填页面域名。`ADMIN_WS_PUBLIC_HOST` 则填承接 WebSocket 的域名。KEY 会提供给前端,SECRET 只留在服务器。 PHP-FPM 与 relay 必须使用同一缓存存储,本项目通常使用 `CACHE_DRIVER=redis`;保留原有 Redis 配置即可。无需为此清空 Redis,也不要更改共享键前缀。 ## 4. 安装依赖和执行迁移 以下命令在项目根目录执行,假定 `php` 已指向选定的 PHP 8.3+,Composer 也由该 PHP 运行: ```bash cd /www/wwwroot/bot-28 php -v composer validate --no-check-publish composer install --no-dev --optimize-autoloader composer check-platform-reqs --no-dev php artisan optimize:clear --except=cache php artisan migrate --force php artisan migrate:status ``` 迁移会自动跳过已经执行过的文件。本次推送相关迁移有三个: - `2026_10_31_160000_create_admin_realtime_events.php`:共享事件表。 - `2026_10_31_210000_add_seq_to_admin_realtime_events.php`:补发用的 `seq`。 - `2026_11_01_000000_add_delivery_state_to_admin_realtime_events.php`:发送确认 `published_at`、待发送索引及持久状态表 `admin_realtime_state`。 `migrate:status` 中这三个应为已执行。不要手动清空 `admin_realtime_state`;它保存不会随事件历史删除的序号和发现游标。迁移及生产安装失败时先解决错误,不继续启动进程。生产使用 lock 文件安装依赖,不运行 `composer update`。 `--except=cache` 必须保留,避免 `optimize:clear` 连带清空共享 Redis 业务数据。 ## 5. Nginx 配置 在已有 HTTPS 站点的 `server` 块内添加以下配置,保留原有 Laravel/PHP 配置: ```nginx location ^~ /app/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 120s; proxy_buffering off; } # 服务端发布消息、断开连接的接口只通过本机 8080 访问 location ^~ /apps/ { return 404; } ``` 这里必须使用 `/app/`,不要写成会同时匹配 `/apps/` 的 `/app` 前缀。公网只使用站点的 HTTPS 443,8080 不需要对外开放。`/admin/broadcasting/auth`、`/admin/realtime/config` 和 `/admin/realtime/replay` 继续由 Laravel 处理,不反代到 Reverb。匹配规则见 [Nginx 官方 location 说明](https://nginx.org/en/docs/http/ngx_http_core_module.html#location)。 确认 Nginx 检查通过后再重载: ```bash nginx -t nginx -s reload ``` ## 6. 开启推送并建立缓存 把 `.env` 的开关改成: ```dotenv ADMIN_WS_ENABLED=true ``` 然后执行: ```bash php artisan optimize ``` 该命令重建配置、事件、路由和视图缓存;事件缓存必须更新,新的 Reverb 订阅检查才会加载。后续每次改 `.env` 或发布代码后都需重建缓存并重启常驻进程。 确保 `storage`、`bootstrap/cache` 可由进程运行用户写入。以 `www` 为例,由有权限的账号执行: ```bash mkdir -p /www/wwwroot/bot-28/storage/logs/realtime chown -R www:www /www/wwwroot/bot-28/storage /www/wwwroot/bot-28/bootstrap/cache ``` ## 7. Supervisor 守护两个进程 在 Supervisor 实际使用的 include 目录创建配置,例如 `/etc/supervisor/conf.d/bot28-realtime.conf`。以下目录、PHP 绝对路径和用户都要与服务器一致: ```ini [program:bot28-reverb] directory=/www/wwwroot/bot-28 command=/usr/bin/php artisan reverb:start user=www numprocs=1 priority=10 autostart=true autorestart=true startsecs=3 startretries=10 stopsignal=TERM stopwaitsecs=20 redirect_stderr=true stdout_logfile=/www/wwwroot/bot-28/storage/logs/realtime/reverb.log stdout_logfile_maxbytes=20MB stdout_logfile_backups=5 [program:bot28-admin-relay] directory=/www/wwwroot/bot-28 command=/usr/bin/php artisan admin-realtime:relay user=www numprocs=1 priority=20 autostart=true autorestart=true startsecs=3 startretries=10 stopsignal=TERM stopwaitsecs=30 redirect_stderr=true stdout_logfile=/www/wwwroot/bot-28/storage/logs/realtime/relay.log stdout_logfile_maxbytes=20MB stdout_logfile_backups=5 ``` 保存配置后执行,`update` 会按 `autostart=true` 启动两个进程: ```bash supervisorctl reread supervisorctl update supervisorctl status bot28-reverb bot28-admin-relay ``` 两个状态都应为 `RUNNING`。不要在启动 Supervisor 后再手动运行第二份 relay。relay 默认每小时正常退出一次,`autorestart=true` 会重新拉起它。 若需提高连接数,`minfds=10000` 属于 Supervisor 主配置的 `[supervisord]` 段,不属于上述 `[program:...]` 段;按服务器进程管理方式调整。[Supervisor 官方配置说明](https://supervisord.org/configuration.html#supervisord-section-settings) ## 8. 保留调度和原有后台任务 若现有 cron 已经每分钟执行 `schedule:run`,保留它并确认使用 PHP 8.3+,不要重复新增。尚未配置时,以运行用户的 crontab 为例: ```cron * * * * * cd /www/wwwroot/bot-28 && /usr/bin/php artisan schedule:run >> /dev/null 2>&1 ``` 调度中已经包含每小时的 `admin-realtime:prune`,不需要再单独新增清理任务。项目原有队列 worker 如在运行,需按原守护配置重启,以加载新代码和 PHP;此次推送自身不依赖它们。PHP-FPM 也应在切换 PHP 或发布时按原有运维方式重载。 ## 9. 首次上线验收 先检查本机 Reverb 和进程: ```bash curl -fsS http://127.0.0.1:8080/up supervisorctl status bot28-reverb bot28-admin-relay tail -n 50 storage/logs/realtime/relay.log tail -n 50 storage/logs/realtime/reverb.log ``` `/up` 返回 200、两个进程 `RUNNING` 只说明进程和本机服务就绪。还需用后台页面完成以下联调: 可打开项目提供的 `https://API域名/realtime-test.html` 检查连接、授权、实时消息和补发;使用步骤见[前端接入文档的快速验证页面](总后台/后台实时推送-前端接入.md#快速验证页面)。测试页所在域名同样需要被 `REVERB_ALLOWED_ORIGINS` 允许。 1. 已登录管理员请求 `GET /admin/realtime/config` 成功,公网 host/443/https 正确。 2. 浏览器连接 `wss://域名/app/应用KEY?...` 获得 101,`POST /admin/broadcasting/auth` 授权 200。 3. 操作一条后台订单,列表在不手动刷新的情况下更新;断网重连后能补发或重新拉取列表。 4. 无权限频道返回 403,登录过期后断开;新登录可以重新接入。 业务侧错误记录在 `storage/logs/realtime/admin-日期.log`,Supervisor 标准输出在上面两个 `.log` 文件。广播失败会保留批次重试;连续报错时应检查 Reverb 地址、凭据、数据和请求体大小。 前端须同步发布与服务端匹配的版本,前端说明见[后台实时推送-前端接入](总后台/后台实时推送-前端接入.md)。 ## 10. 上线后暂停推送(按需) 先停止 relay,再把 `.env` 改为 `ADMIN_WS_ENABLED=false`,执行 `php artisan config:cache`;不再使用时也停止 Reverb,并禁用对应 Supervisor 配置。HTTP 列表继续可用。不要通过 `cache:clear`、`Cache::flush()` 或清空状态表来停用推送。 本节是统一的部署操作说明,未执行生产部署。Reverb 生产运行方式参见[Laravel 官方文档](https://laravel.com/docs/13.x/reverb#running-reverb-in-production),本项目验证范围见第 12 节。 ## 11. 后端事件写入 平台在线人数与待处理角标由 relay 每 `ADMIN_WS_SUMMARY_SCAN_SECONDS`(默认 5)秒复用原 HTTP 控制器的统计查询,仅在结果变化时登记 `online_platform_count` / `payment_unprocessed` 事件,再经现有的持久发送、重试和补发流程投递。无需新增迁移或在 melbet 写入点加钩子;同一用户切换平台、订单跨过 24 小时窗口、分享领取待审核数量变化都能检测。统计扫描失败只记录日志,恢复后重试,不把失败当作已推送。 ### 11.1 bot-28 已有写入覆盖 bot-28 内以下写入会产生事件: - **所有经 Eloquent 模型的新增、修改、删除**(`BaseModel` 钩子,覆盖 `sport_order`、`lhc_order`、`sport_game_order`、`jisu_game_order`、`third_game_orders`、`bets`、`recharges`、`payment_orders`、`withdraws`、`manual_audits`):后台审核、退款、人工上下分、Telegram 机器人下注 / 充值 / 提现、各支付回调中以模型保存的部分等。只改 `updated_at`、`is_send`(通知标记)这类列时不推送。 - **用户新增**:`User` 模型创建(含 Telegram 注册 `PublicService`)。用户资料的其它修改不推送。 - **不经模型的批量写入,已单独登记**: - 六合彩开奖结算、开奖失败退分(`BetService`) - 足球游戏批量判定未中奖(`SportGameOrderJob`) - 三方游戏订单每分钟同步(只推新订单或状态、结算金额、更新时间、会员归属有变化的订单)及旧账号回填 - USDT 充值链上确认、忽略无效充值、充值登记(`RechargeService`、`RechargeBonusService`) - 支付通道代付回调的状态更新(`PaymentOrderService`) - 后台批量设置打款信息、批量锁定 / 解锁提现与代付(`PaymentOrder`、`Withdraw` 控制器) - 会员合并时转移的投注与支付订单(`SecretService`) - **在线状态**:不依赖写入点,由 relay 定期比对在线集合,bot-28 与 melbet_sport-api 的登录、活跃都能覆盖。 以后新增不经模型的批量写入时,在写入后调用: ```php \App\Services\AdminRealtime\AdminRealtime::record('payment_orders', $ids); // 默认 updated \App\Services\AdminRealtime\AdminRealtime::record('withdraws', $id, 'created'); ``` 它在事务内会等提交后再写,回滚则丢弃,失败只记日志。 ### 11.2 melbet_sport-api 写入约定 会员前台 API(melbet_sport-api)也直接写这些表:体彩下单与结算(`SportOrder`)、六合彩 / 极速 / 足球游戏下注、投注、充值、代收代付、提现、**会员注册**。**这些写入目前不会产生事件**,在该仓库接入前,前台产生的新订单、新用户不会实时出现在后台列表(列表本身照常,刷新可见)。在线状态不受影响。 接入方式:在 melbet_sport-api 对应写入之后往同一张表插入一行即可,不需要新增依赖或配置,也不需要跟着换框架或接入 Reverb。`seq` 列由 relay 填写,写入方不要赋值。以 ThinkPHP 模型事件为例: ```php // app/common/service/AdminRealtimeEvent.php namespace app\common\service; use think\facade\Db; use think\facade\Log; class AdminRealtimeEvent { /** $resource 为不带前缀的表名,如 sport_order、payment_orders、users */ public static function push(string $resource, $id, string $action = 'updated'): void { try { Db::name('admin_realtime_events')->insert([ 'resource' => $resource, 'resource_id' => (string) $id, 'action' => $action, 'source' => 'melbet_sport-api', 'created_at' => date('Y-m-d H:i:s'), ]); } catch (\Throwable $e) { Log::error('admin_realtime_event_failed: ' . $e->getMessage()); } } } // app/common/model/SportOrder.php public static function onAfterInsert($model) { AdminRealtimeEvent::push('sport_order', $model->id, 'created'); } public static function onAfterUpdate($model) { AdminRealtimeEvent::push('sport_order', $model->id); } ``` 注意: - `resource` 只能是本节上方列出的表名(`users` 只处理 `created`);`action` 用 `created` / `updated` / `deleted`。时间使用 Asia/Shanghai。 - ThinkPHP 的模型事件只在模型 `save()` / `create()` 时触发,`Db::name()->update()` 这类批量写入需要在写入后手动调用 `push()`。 - 在事务内插入也可以(回滚即不推送)。relay 会在 60 秒(`ADMIN_WS_GAP_SECONDS`)内补发暂未提交的事件;事务持续超过该时长的事件可能漏推,长事务建议提交后再插入。 - 插入失败必须吞掉异常,不能影响下单、支付。 ## 12. 后端验证范围 - 2026-11-01 在隔离、关闭网络的本地 MySQL 9.6.0 上验证了新增迁移、旧 seq 高水位初始化、未确认批次重试、清空历史后序号继续递增,以及超期待发送批次不被清理。测试数据库已删除、临时服务已停止;这不代表生产 MySQL 或全部业务列表已完成联调。 - 2026-11-01 修正后,本机真实 Reverb 验证:同一有效会话订阅多个频道保持连接;带有效签名但已过期的授权会被断开;同一 socket 混用不同会话会被断开,独立的新登录连接保持有效。后台实时测试 15 项通过;完整 Integration 为 661 项,仍有原来的 1 个错误、2 个失败、10 个跳过;Unit 为 90 项,仍有原来的 2 个错误。 - `tests/Integration/AdminRealtimeTest.php`(隔离 SQLite):事务、模型和行格式、晚提交、补发与频道过滤、401/403、断开失败后重试且保留新登录、登记失败拒绝授权、到期后延迟使用旧频道授权、有效订阅保持连接、广播部分失败后新进程恢复并按序重试、清空历史后不重用序号、超期未发送批次保留。 - 本机端到端(SQLite + `reverb:start` + `admin-realtime:relay` + Node 中的 `laravel-echo@2.1.6`/`pusher-js@8.4.0`):授权订阅成功、relay 广播实时送达且未订阅频道收不到;token 40 秒到期时 Reverb 断开连接、自动重连授权 401;Reverb 停止期间写入的事件广播失败记日志,Reverb 恢复后 Echo 自动重连、补发接口取回缺失事件并继续实时接收;relay 收到 `SIGTERM` 正常退出并释放锁。 - 在线用户扫描与各订单列表的单行解析依赖 MySQL 语法(`COLLATE` 等),未在真实数据库、真实 Nginx / wss 环境下验证;上线后请按第 9 节的验收步骤实际下单核对。