Merge remote-tracking branch 'origin/dev' into feature/70-sense-windows-delivery

# Conflicts:
#	wiki-docs.json
This commit is contained in:
QiuSW
2026-08-16 19:33:09 +08:00
3 changed files with 104 additions and 2 deletions
@@ -0,0 +1,80 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-95-Sense旧媒体路由唯一约束兼容迁移
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-95-Sense%E6%97%A7%E5%AA%92%E4%BD%93%E8%B7%AF%E7%94%B1%E5%94%AF%E4%B8%80%E7%BA%A6%E6%9D%9F%E5%85%BC%E5%AE%B9%E8%BF%81%E7%A7%BB.-
wiki_revision: 5f3bdf3786f2a72dac5d29236ff466743e2912b5
synchronized_at: 2026-08-16T11:31:47Z
<!-- gitea-wiki-mirror:end -->
# 95 Sense旧媒体路由唯一约束兼容迁移
- 类型:缺陷
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:已完成
- 日期:2026-08-15
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/95
- Wiki 页面:Task-95-Sense旧媒体路由唯一约束兼容迁移
- Wiki revision:见本地镜像头
## 背景与目标
#92 修复进入真实旧库后,设备能力字段已成功转换为 JSONB;下一条媒体迁移因 GORM 尝试删除不存在的推导约束名 `uni_sense_media_routes_path` 而报 SQLSTATE 42704。只读核对确认旧 `sense_media_routes.path` 实际由 PostgreSQL 自动命名约束 `sense_media_routes_path_key` 保证唯一,且表中已有 2 条路由。
隔离回归越过约束错误后进一步确认,旧非空表缺少当前模型要求的运行态列,直接新增 `source_ready NOT NULL` 会报 SQLSTATE 23502。目标是在不删除路由、不削弱 path 唯一性、不伪造媒体已就绪的前提下完成旧表迁移。
## 最终方案
在现有 `2026081419000` 媒体迁移事务开头执行 PostgreSQL 专用兼容步骤。仅当旧表存在时取得 ACCESS EXCLUSIVE 锁,从 pg_catalog 读取包含 path 的唯一约束;只接受唯一的单列 `UNIQUE(path)`,复合、多重或冲突结构拒绝迁移并整体回滚。
确认结构后,把数据库实际约束名规范为 GORM 能识别和移除的名称,让 AutoMigrate 转换为模型的 `idx_sense_media_routes_path` 唯一索引。锁在整个迁移事务提交前持续有效,因此约束切换期间没有并发写入窗口。
兼容步骤同时为旧路由添加并回填当前模型要求的运行态列:`source_ready=false`、`failure_count=0`、`last_error_code=''`,随后设为 NOT NULL。保守初值表示服务启动后必须重新对账,不把旧路由冒充为已经就绪;`next_retry_at` 保持可空并由 AutoMigrate 建立。
## 修改文件
- `Sense/server/cmd/migrate/migration/version/2026081419000_media.go`:旧约束识别、规范化、锁表和运行态列兼容。
- `Sense/server/cmd/migrate/migration/version/2026081419000_media_test.go`:隔离 PostgreSQL 旧表、非空路由、空库、无表、重复执行、唯一性和不安全结构回滚测试。
- Wiki `Troubleshooting`、`docs/06-troubleshooting.md`:错误含义、备份、迁移、验证和回退步骤。
- `wiki-docs.json`、`docs/task/95-Sense旧媒体路由唯一约束兼容迁移.md`:任务归档登记和镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 旧约束名不再触发 SQLSTATE 42704 | 通过;隔离和真实 PostgreSQL 均完成媒体迁移 |
| 既有媒体路由完整保留 | 通过;真实库迁移前后均为 2 条 |
| path 始终具有唯一性保护 | 通过;迁移后 `idx_sense_media_routes_path` 唯一索引有效,重复 path 写入被拒绝 |
| 无表、新库、已迁移库和重复兼容 | 通过 |
| 不安全结构拒绝并回滚 | 通过;复合 path 约束夹具未发生部分变更 |
| Go 全量与隔离 PostgreSQL 回归 | 通过 |
| #70 Windows 包真实迁移与启动 smoke | 通过;Web/SPA/health/MediaMTX 200,停止后端口清空 |
| Wiki 镜像与任务归档一致 | 通过 |
## 测试
- 隔离 PostgreSQL 17 `TestMediaMigrationOnPostgres`:旧 2 路由、旧约束、运行态回填、空库、无表、重复兼容、唯一性冲突和复合约束回滚全部通过;独立测试数据库每次运行后删除。
- `go test ./...`、`go vet ./...`、`go build ./...`:通过。
- `go test -race ./cmd/migrate/migration/version -run TestMediaMigrationOnPostgres -count=1`:使用隔离 PostgreSQL 通过。
- #70 固定 Go 1.26.5、Node 22.22.1、pnpm 9.15.1 production build 与包审计通过。
- 迁移前 PostgreSQL custom-format 备份通过 `pg_restore --list`;真实迁移完成,2 条旧路由保留、运行态列无空值、媒体迁移版本登记、唯一索引有效。
- Web 首页、SPA、`/healthz` 与 MediaMTX Control API 均返回 200;`stop-sense.bat` 后相关端口无监听。
- `git diff --check`:通过。
- **未验证部分**:尚未在客户全新 Windows 主机、客户生产 PostgreSQL 账号和真实获准摄像机上验收;当前回归使用本机 PostgreSQL、脱敏业务计数和已配置测试摄像机环境。Harness strict 仍只受既存 #66/#67 归档格式影响。
## 遗留问题
- PowerShell `Invoke-WebRequest` 在本机受代理环境影响,访问 loopback 时失败;明确绕过代理的本机 HTTP 请求验证服务正常,不属于 Sense 服务端故障。
- #95 需先经用户验收并合入 `dev`,随后 #70 才能按依赖顺序完成合并。
## 相关提交
- `3dd4890` 兼容旧媒体路由约束和运行态列迁移。
- `6257859` 记录旧媒体路由迁移排错。
- `c088caf` #70 集成 #95 后用于 Windows 包真实回归。
## 人工验收
- 2026-08-16:用户明确验收通过 #95。
- 按依赖顺序先将 PR #96 合入 `dev`;`main` 保持不变。