# laic-backend 低空智控平台 后端 Go 服务。 ## 生产部署 部署脚本参考 `D:/GoProject/roller-service/roller-manage/publish-prod.sh`,使用 Supervisor 管理服务。 ### 服务器准备 在生产服务器安装 Supervisor。生产配置保存在仓库根目录的 `config-prod.yaml`,部署脚本会将它上传为服务器部署目录中的 `config.yaml`: ```bash sudo apt update sudo apt install supervisor -y sudo systemctl enable --now supervisor ``` 生产配置至少包含: ```yaml oss: endpoint: https://obs.cn-east-2.myhuaweicloud.com access-key: 实际的华为云 OBS AK secret-key: 实际的华为云 OBS SK bucket: gcs-edu region: cn-east-2 use-ssl: true ``` 后端启动时直接读取部署目录下的 `config.yaml`。本地开发不设置 `LAIC_CONFIG_FILE` 时读取本地 `config.yaml`;Supervisor 显式设置 `LAIC_CONFIG_FILE=config.yaml`。 ### 执行部署 修改 `publish-prod.sh` 顶部的 `REMOTE`、`REMOTE_DIR` 和服务参数,然后执行: ```bash chmod +x publish-prod.sh ./publish-prod.sh ``` 脚本会完成: 1. 交叉编译 Linux amd64 二进制。 2. 创建远程部署目录、日志目录和备份目录。 3. 备份旧二进制和 `config.yaml`(首次部署没有旧文件时跳过)。 4. 将本地 `config-prod.yaml` 上传为远程 `config.yaml`。 5. 更新 Supervisor 配置、重启服务并检查状态。 6. 启动失败时自动恢复最近的旧二进制。 首次部署前确认远程服务器已安装并运行 Supervisor: ```bash sudo apt update sudo apt install supervisor -y sudo systemctl enable --now supervisor ``` 查看状态和日志: ```bash sudo supervisorctl status laic-backend-prod sudo tail -f /usr/local/laic-backend-prod/log/info.log sudo tail -f /usr/local/laic-backend-prod/log/err.log ``` 部署目录和服务名可以在 `publish-prod.sh` 顶部调整。回滚时,脚本会使用远程 `backup/` 目录中最近的二进制备份;也可以手工恢复后执行: ```bash sudo supervisorctl restart laic-backend-prod ``` ## Payment and SIM schema reconciliation If a production database was upgraded from an older partial release, back it up first and then run `sql/019_reconcile_payment_schema.sql`. The migration is repeatable and fills missing `traffic_order.updated_at`, payment fields, pending order guards, and normalizes legacy empty payment idempotency keys. It also creates the payment/SIM tables. It does not create orders, charge users, or create SIM cards. Verify the schema and platform resource pool after it finishes, then restart the backend before retrying payment. AGPay returns the WeChat QR code as binary `image/png`. The backend converts it to a JSON-safe `providerPayload` data URI (`data:image/png;base64,...`); clients should use that value directly as the image `src` and must not UTF-8 decode the original response body. ## Live session device event uniqueness `live_session.uk_live_session_device_event` was originally built directly on `device_event_id`. MySQL unique indexes treat multiple `NULL` values as distinct but multiple empty strings as duplicates, so only one live session without a reported device event could exist at a time. Creating a second live session then failed with: ``` Error 1062 (23000): Duplicate entry '' for key 'live_session.uk_live_session_device_event' ``` Run `sql/023_live_session_device_event_nullable.sql` on any database created before this fix. It adds a `device_event_key` generated column (`NULLIF(device_event_id, '')`) and moves the unique index onto it. The migration is repeatable. `sql/001_schema.sql` already contains the fixed schema for new databases. ## Live stop convergence `stopping` is never moved back to an active phase (`starting`/`streaming`/ `reconnecting`). Any transition that did so would make the reconciler see an active session again and publish another `video.stop_stream`, producing an unbounded stop loop when the edge no longer knows the session. If the edge restarts `dock-video-relay`, it returns to `idle` and rejects the stop with `VIDEO_SESSION_MISMATCH` (protocol section 5.2.4; the mock uses `SESSION_NOT_FOUND`). Both codes mark the session `stopped` immediately. Otherwise the session stays `stopping` and is forced to `stopped` after 120 seconds (`stopForceAfter`), including when the stop command times out because the edge is offline. That bound also keeps `uk_live_session_active_dock` released so the dock can start a new session. ## Live session lifetime A live session's absolute deadline comes from `live.max-session-seconds` (falling back to `live.auth-expire-seconds` when unset). It is sent to the edge as `video.start_stream.expiresAt` and also bounds the signed push/play URLs, so it cannot be extended mid-session: re-sending `video.start_stream` with a different `expiresAt` changes the params digest and the edge rejects it with `COMMAND_ID_CONFLICT` (protocol section 5.1/6). Idle shutdown is therefore handled by viewer leases, not by that deadline: with no active lease the session is stopped after `live.stop-grace-seconds`, while a viewer that keeps calling the heartbeat endpoint keeps the stream alive until the absolute deadline. The production value is 2 hours (`max-session-seconds: 7200`); raise it if users need longer continuous viewing. A `stopping` session is forced to `stopped` after 120 seconds even if the edge keeps reporting `streaming=true`, so a stuck publisher cannot block the dock from starting a new session. ## Live billing bitrate Live billing now charges the average bitrate the edge reports in `state/video.bitrateBps` (the protocol's `expectedBitrateBps`, 1 Mbps by default) instead of the session peak `maxBitrateBps` (1.5 Mbps). Run `sql/024_live_session_billing_bitrate.sql` before deploying; it adds `live_session.billing_bitrate_bps`. The migration is repeatable and `sql/001_schema.sql` already contains the column for new databases. Until the edge reports a bitrate the charged value falls back to `max_bitrate_bps`. ## Live start params rehearsal `video.start_stream` retries must resend the exact same `params` because the edge compares a params digest for `commandId` idempotency. The push URL carries a short-lived credential and is therefore stored in Redis under `laic:live:startparams:{commandId}` (never in MySQL, per the live backend design, section 11) and replayed verbatim on retry. ## Test traffic grant For QA environments, set the following configuration explicitly (it defaults to disabled): ```yaml testing: allow-traffic-grant: true ``` An authenticated administrator can then credit a regular user without creating a payment order: ```http POST /v1/admin/traffic/test-grants Content-Type: application/json {"userId":8243938201600,"amountGb":10,"reason":"live test"} ``` The operation updates the user snapshot, appends a `test_grant` traffic ledger entry, invalidates the Redis balance cache, and writes an operation log. Keep the switch disabled in production after testing.