- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
导读
多直播平台聚合客户端在"收藏分类(分类页关注)"这一看似简单的功能上,藏着一个极易被忽视的领域陷阱:不同平台的分类 ID 完全可能相同(例如虎牙与斗鱼都存在areaId=1),而旧实现只比较 ID,导致收藏判断、取消收藏与数据迁移时跨平台误伤。本文以 PureLive 仓库的docs/FAVORITE_AREA_IDENTITY_AUDIT_2026_09_08.md审计记录为核心,结合源码讲解分类收藏"领域身份(identity)"契约的设计动机、身份生成规则、持久化兼容策略与完整验证流程,读者可据此理解聚合类应用中"平台 + 分类"复合主键的落地方法,并掌握一套可复现的回归验证与收尾方法。
一、审计背景:为什么"收藏虎牙 areaId=1"会误删斗鱼记录
PureLive 在 3.1.8+4121 版本上对分类收藏功能做了一次专项审计,基线与结论均限定在冻结的本地上游引用(c6c9bd70...与 merge base527fea1b...)范围内,判为upstream-existing,不是对当前联网最新版的结论。
审计发现四个真实缺陷:
- 跨平台 ID 碰撞误判:收藏虎牙
areaId=1后打开斗鱼areaId=1,设置层isFavoriteArea与分类页关注按钮只比较 ID,会提前将该分类认作"已收藏";走"确认取消"UI 路径时按 ID 删除,会把其他平台的同 ID 记录一并删掉。 - 对象引用删除失效:设置层
removeArea使用对象引用删除,从 JSON 备份重建同一分类后引用不同,删除静默失败。这与 UI 按 ID 批量删除是两条独立调用路径,共同缺少统一的领域身份合同。 - 主键字段过宽:旧平台把
areaType(父分类元数据)一律纳入主键,但旧备份可能省略父分类字段,导致合并后身份漂移;而 IPTV 使用全局唯一频道 ID,getCategoryRooms仅按频道 ID 查找,provider 不参与频道身份,不应强行加上命名空间。 - 猫耳命名空间缺失:猫耳(Missevan)目录请求明确区分
catalog_id/tag_id两套 API 命名空间,未知/缺失type不应被当作通配符匹配二者,否则目录与标签收藏会互相污染。
二、领域身份契约:身份生成规则的源码落地
审计的设计结论是:收藏判断、添加、删除与旧 Hive 备份合并共用同一套身份生成规则,其实现位于 lib/core/models/live_area.dart:
static String? identityKeyFor({String? platform, String? areaId, String? areaType}) { final site = platform?.trim().toLowerCase() ?? ''; final id = areaId?.trim() ?? ''; if (site.isEmpty || id.isEmpty) return null; final namespace = site == 'missevan' ? areaType?.trim().toLowerCase() ?? '' : ''; return jsonEncode([site, namespace, id]); }规则要点:
- platform:去首尾空白并转小写,消除
HUYA/huya这类大小写与空格差异; - areaId:去首尾空白但保留大小写——部分平台频道 ID 大小写敏感,不做小写折叠;
- namespace:仅猫耳(
missevan)的areaType参与命名空间,区分catalog与tag两套 API;其余平台一律不引入父分类元数据,保证旧备份省略父分类时身份稳定; - 结构化编码:使用
jsonEncode([site, namespace, id])而非字符串拼接,避免platform|areaId这类分隔符碰撞(例如平台名自身含分隔符的情形)。
LiveArea保持可变对象、不重载==/hashCode、不改变既有序列化字段,身份判断通过显式方法完成:
String? get identityKey => identityKeyFor(platform: platform, areaId: areaId, areaType: areaType); bool hasSameIdentity(LiveArea other) { final key = identityKey; return key != null && key == other.identityKey; }hasSameIdentity是identityKey的语义比较,既不依赖对象引用,也不依赖==重载。
三、控制器三层操作:判断、添加、删除全部基于身份
设置层核心控制器 lib/domains/live/data/favorite_room_controller.dart 中,分类收藏的三类操作全部收敛到身份契约上:
- 判断(
isFavoriteArea,第 264-266 行):遍历favoriteAreas,用candidate.hasSameIdentity(area)判定,天然避免跨平台同 ID 误判; - 添加(
addArea,第 366-374 行):identityKey == null(平台或 ID 缺失)或已存在同身份记录时拒绝添加; - 删除(
removeArea,第 385-394 行):removeWhere((candidate) => candidate.hasSameIdentity(area)),只移除同一身份,保留其他平台、其他命名空间以及确认对话框期间新增的记录。
数据保留策略与审计要求一致:
- 没有平台或 ID 的旧记录原样保留,不自动清理用户数据,也不作为可添加/匹配的正常收藏;
- 取消收藏只删同身份记录,不触碰其他平台的同名分类;
- 旧备份合并保留当前顺序与非空展示字段,并允许补齐旧父分类字段。
持久化层使用"写失败回滚"的双写策略(_persistAreasOrRollback):先把内存列表写入 Hive(favoriteAreas键),失败时回滚内存并尽力重写旧快照,保证 UI 状态与磁盘一致。审计明确"没有为此修改应用持久化逻辑",即身份修订不动数据表结构、不影响播放、录制、弹窗与窗口模块。
四、分类页按钮:从"一次性快照"改为响应式读取
审计还暴露了一个 UI 生命周期缺陷:收藏分类页控制器只在onInit复制一次设置列表引用,设置层替换列表后,已挂载页面仍观察旧快照,导致关注状态不刷新。冻结上游同样存在此路径。
修复方案是不在控制器中复制快照,也不新增 Worker 或订阅,而是在页面Obx中实时读取实际收藏 observable。对应实现见 lib/domains/live/presentation/area_rooms/area_rooms_page.dart 的FavoriteAreaFloatingButton:
return Obx(() { final isFavorite = FavoriteRoomController.to.isFavoriteArea(area); // ...根据 isFavorite 渲染圆形"已关注"或"关注 + 名称"状态 });Obx直接订阅FavoriteRoomController.to.favoriteAreas(Rx<List<LiveArea>>),设置层任何替换都会立即驱动按钮重绘。交互侧还做了防抖:_busy标志阻止并发点击;取消收藏弹出确认对话框(unfollow/unfollow_message),确认后才调用removeAreaDurably,异常时通过ToastUtil.show提示favorite_changes_save_failed。分类列表控制器(area_rooms_controller.dart)按站点与分类生成areaRoomsControllerTag,仅作为页面/数据控制器标识,不参与收藏身份判定。
五、身份契约的连带应用:迁移去重与备份合并
同一身份规则被_itemIdentity复用于设置升级迁移的去重场景(lib/core/config/migrations/settings_upgrade_migration.dart):
if (key == 'favoriteAreas') { return LiveArea.identityKeyFor( platform: item['platform']?.toString(), areaId: item['areaId']?.toString(), areaType: item['areaType']?.toString(), ) ?? jsonEncode(item); }身份键为 null(缺平台或 ID)时回退为整条 JSON 编码,保证不可匹配的记录仍保留唯一性、不误合并。与此配套的还有房间收藏的身份归一化_normalizeFavoriteRoomIdentities:onInit与restoreFavoriteLists时对favoriteRooms做 trim、小写与去重,非法 ID(0/null/undefined/nan/none)直接剔除。
六、验证与交付:红测 → 修订 → 终测的完整闭环
审计批次严格执行"先证明缺陷、再验证修复"的流程,共三轮记录:
第一轮红测20260907T173752870Z-favorite-area-red.json:8 项业务断言稳定失败,覆盖跨平台判断、重建删除、旧父分类删除、猫耳命名空间、ID 规范化、IPTV 删除、无身份误匹配与旧备份合并;另 2 项是测试夹具问题(原模型已有的 null→空字符串反序列化约定、GetMaterialApp 首路由未挂载即点击),不算新应用缺陷。
第二轮修订验证20260907T174610432Z-favorite-area-identity.json:38 项测试主体通过,但文件 Hive 收尾停滞导致整轮标失败。排查发现:本地锁定依赖 Hive CE 2.19.3,其hive_impl.dart的openBox(bytes: ...)使用正式内存后端;最终 Widget 测试改用该内存后端与真实设置控制器(而非替换业务方法),普通异步测试继续验证真实文件 Hive 往返,收尾加入 15 秒超时避免夹具无限挂起。没有为此修改应用持久化逻辑。
最终通过20260907T174927592Z-favorite-area-identity.json:
- 38/38 通过(本批 12 项分类收藏测试 + 旧 Hive 合并、备份集合校验、房间有效性及原生目录控制器回归);
- 6 个文件
analyze无问题,49.2 秒; - 整轮 178.363 秒,峰值 CPU 11.72%,重型进程工作集约 13.4 GB,结束活跃重型进程数 0;
- 证据保存在
local-artifacts/favorite-area-20260908/,validation-source.json绑定 6 个 Dart 文件的 SHA-256,资源记录在local-artifacts/build-records/。
审计过程还记录了进程收尾纪律:初始 Widget 失败后 Hive 收尾停滞,核对父子进程与本项目测试命令后仅终止该轮flutter_tester子进程,守卫finally正常记录失败并释放,不误杀其他项目的 Java/Gradle 进程。测试结论明确不外推为 Android/Windows 原生验收。
七、回滚与边界:兼容性设计
本批修订为局部领域身份修订,独立提交、可按批回滚:
- 不改播放、录制、弹幕、窗口或数据表结构;
- 持久化 JSON 格式保持兼容,旧备份可正常合并;
- 回滚后恢复旧的 ID 碰撞行为(即回到"跨平台同 ID 误判/误删"状态),属于预期行为;
- 未使用 ADB、MT、Root 或 LSP,无安装包、手机变更或发布动作;版本仍
3.1.8+4121,不递增、不发布。
审计同时提示了相邻集成风险:猫耳catalog/tag同号是本分支新增适配的相邻风险,旧备份合并键只有platform/areaId,开放新入口前需补全身份合同;本批没有新增平台注册。后续计划为猫耳注册、设置迁移、链接/请求头与播放录制接入,最后执行原生验收与发布门禁。
八、实践要点速查
- 聚合收藏类数据必须建立复合身份:
(platform 归一化, 命名空间, areaId 归一化)三者构成身份,任何单一字段都不可作为主键; - 归一化规则要按字段差异化:platform 小写折叠、areaId 保留大小写、仅特殊平台引入命名空间,避免一刀切破坏既有数据;
- 判断、添加、删除共用同一身份函数:三条调用路径(UI 批量、设置层引用、备份合并)必须收敛,杜绝"判断用 A 规则、删除用 B 规则"的割裂;
- 用结构化编码代替字符串拼接:
jsonEncode([...])天然规避分隔符碰撞,且编码结果稳定可持久化; - 响应式 UI 直接订阅 Observable:不要在控制器里复制列表快照,
Obx中实时读取即可让关注状态始终同步; - 验证先红后绿:先用红测固化 8 项失败断言,再修订实现,最后以 38/38 全绿、analyze 零告警作为交付门禁,并保留 SHA-256 证据链;
- 兼容优先:不清理用户旧数据、不改变 JSON 格式,缺失身份字段的记录原样保留,回滚成本可控。
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
飞书 IM 领域建模与 lark-cli 实战指南:核心概念、身份映射与消息增强契约
飞书 IM 领域建模与 lark cli 实战指南:核心概念、身份映射与消息增强契约 本文以 lark cli(Lark/飞书官方 CLI 工具,由 larks
CLIAI 技能lark-cli 共享规则实战指南:身份、认证、输出契约与高风险操作审批协议
lark cli 共享规则实战指南:身份、认证、输出契约与高风险操作审批协议 lark cli 是飞书官方维护的命令行工具,面向人与 AI Agent 设计,覆
CLIAI 技能直播分区分类列表所有权与分页契约修复实战:以 PureLive 猫耳平台只读列表异常为例
直播分区分类列表所有权与分页契约修复实战:以 PureLive 猫耳平台只读列表异常为例 导读 本文以 PureLive(纯粹直播)开源仓库中的一份技术审计文档
音视频直播移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考