7 Commits
Author SHA1 Message Date
dsp 86d0477aa1 chore: e2e报告JSON列入忽略(本地产物不进仓)
CI / backend (push) Canceled after 0s
CI / web (push) Canceled after 0s
CI / android (push) Canceled after 0s
2026-09-14 10:11:16 +08:00
dsp 30a57ca9df test: S-01统一E2E入口+缺口覆盖11例(visitors/logs/搜索边界/通知/反模式自查,61例全绿) 2026-09-14 10:09:39 +08:00
dsp ef4726d65f fix: S-01反模式清扫收尾(opt_id归一化+music分页钳制+visitors外键归一化) 2026-09-14 10:08:36 +08:00
dsp b5ac9b9ec8 test: Android mode回显契约+ranking漂移修正(mode回显/null-mode/非法回落/顺序衰减)
CI / backend (push) Failing after 52s
CI / web (push) Successful in 29m15s
CI / android (push) Failing after 3m52s
2026-09-12 13:43:52 +08:00
dsp 8693762281 feat: 推荐引擎(热度排序 + 关注流 + 冷启动曝光)
## 背景

hot_score 在此之前是一个死字段:全库唯一赋值点是上传时的 `v.hot_score = 0.0`,
没有任何代码给它算过值。`order_by("-hot_score", "-published_at", "-id")` 实际
退化为按发布时间排序 —— 平台没有推荐,feed 就是纯时间倒序:老而优质的内容永久
沉底,新上传但无人互动的霸屏。同时 VideoPlayStat(完播数据)只用来给创作者看板
出报表,最强的质量信号完全没参与排序。

## 评分公式

  engagement = 3×赞 + 4×评论 + 5×收藏 + 6×分享 + 0.2×播放
  quality    = 1 + 完播率                     (∈[1,2])
  score      = (engagement × quality + 8) / (发布小时数 + 2) ^ 1.2

- 互动权重按行为成本排序(分享>收藏>评论>点赞)
- view_count 只给 0.2:它衡量曝光而非质量,且曝光会被排序放大(得分高→曝光多
  →得分更高)。实测权重 1.0 时一条 9000 播放的视频有 64% 分数来自播放量本身,
  足以让"高赞低完播"的标题党和"高完播"的优质内容打平;调到 0.2 后分差从
  3.4% 拉大到 29%
- 完播率作乘数而非加数:区分"被划过去"和"被看完"
- +8 冷启动曝光预算:没有它新视频互动为 0 → 排最后 → 没人看 → 永远没互动,
  创作者发第一条就石沉大海。8 分约"几条赞"量级,且同样随时间衰减

## 复合游标(含一个真实数据丢失 bug 的修复)

推荐流按 (hot_score DESC, id DESC) 排序,游标必须含两个键。但游标里存的是
上一页最后一行的**分值快照**,而 hot_score 会随时间衰减、也会被后台重算改写:

  - 分值变小 → score < c_score 恒真 → 该行被重复返回
  - 分值变大 → 两个分支都不成立 → 该行及后续被永久跳过

这是真实可复现的丢数据,不是理论问题(翻页中途恰好触发一次重算就能命中)。
解法 resolve_boundary():用 id 查出锚点行的当前分值作为分界;锚点被删除时
退化为按 id 续页。

## 三种流

GET /videos/feed?mode=recommend|following|latest
- recommend 默认,热度排序,复合游标
- following 只看已关注作者,需登录(否则 401),未关注任何人时返回空**不回落全站**
- latest 纯时间倒序
非法 mode 回落到 recommend,响应体回显实际生效值

## 重算调度

feed 首次翻页时节流触发(TTL 300s,单 worker 线程池,不阻塞响应、失败不影响
返回)+ 管理命令 `manage.py recompute_hot_scores`(--dry-run/--top/--max-age-days)
+ 部署后初始化。翻页时不触发,避免游标锚点分值抖动。

## 客户端

- Web: feed 顶部悬浮 Tab(推荐/关注/最新),未登录隐藏关注 Tab;侧边栏「推荐」
  改为反映当前流;空态按流给不同提示
- Android: 对齐的三 Tab(含空态下的 Tab,避免关注流为空时用户被困)

## 验证

- 后端 49 passed,其中推荐专项 19 条锁"排序质量"而非"接口 200":
  权重方向、时间衰减、完播加成、冷启动预算、游标精度往返、分值漂移不丢行、
  锚点删除、同分场景、关注流隔离、非法 mode 回落
- 真实数据端到端(distinct 数据:爆款/标题党/零互动新号/沉底老内容):
  推荐首位=爆款;零互动新号进第 4 位(有曝光);标题党被高完播内容压住
- 浏览器 E2E(web/e2e/feed-tabs.mjs):三 Tab 渲染、推荐/最新首位确实不同、
  关注流只含已关注作者、切回恢复
- Android/Web 构建 + typecheck 全绿
- 文档 docs/ranking.md 说明公式依据、漂移问题与调参入口
2026-09-11 17:40:16 +08:00
dsp 20cd74edd3 fix: 三端缺陷修复(后端契约/Web 分页/auth 闭环/Android 会话态)
经三端只读审计 + 真实端到端验证后修复,每项都带回归用例。

后端
- 游标分页 off-by-one:next_cursor 指向被丢弃的哨兵行,严格不等式又排除它,
  13 个端点每页静默丢 1 条;统一收敛到 core.api.paginate_cursor
- Feed 排序键(-hot_score/-published_at)与游标键(id)不同序,翻页重复+跳过;
  hot_score 恒为 0,统一按 -id
- 无关/非法 page_size 曾 500 或一次拉全表;统一 parse_page_size 容错限幅
- 匿名写评论 500(AnonymousUser 写非空 FK)→ 读公开写登录;下架视频评论区可读写 → 404
- 取消点赞不回退作者获赞、收藏删除按固定 -1 → 按实际行数回退(可刷计数)
- view_count 去重失效(+1 在缓存判断之外)→ 计数与写历史同入去重分支
- WatchHistory 无唯一约束 + update_or_create → 并发重复行后永久 500;补约束
- is_active 未校验:封禁账号可继续用 access/refresh → 三处补校验
- WS user_<id> 组无订阅者,实时通知整条链是死的 → 补 UserConsumer + 广播
- 广播系统消息已读状态跨用户共享、可改他人定向消息 → 加 per-user 已读表
- 评论回复路由双前缀(comments/comments/…)导致 Web 与 Android 均 404
- 通知 type="all" 落到 filter(type="all") 匹配 0 行 → 全部已读
- 播放上报无鉴权可刷量、无视频存在性校验 → IsAuthenticated + 404
- 生产默认密钥是公开串,可离线伪造任意用户 JWT(含管理员)→ DEBUG=0 时拒启
- 搜索热词读-改-写丢更新 → F() 原子自增

Web
- 3 处把分页 envelope 当数组读(历史/访客/系统消息)→ 页面不可用/抛 TypeError
- CommentPanel 复用不跟随视频切换,显示并写入别的视频的评论
- 上传受全局 15s 超时,500MB 文件必然失败 → 单请求放宽到 15min
- auth 闭环:新 refresh 从不落盘、刷新失败不登出、restore 是死代码、深链接被
  App.vue 的 replace("/") 拽回首页
- 私信被前后端双重反转,时序颠倒;点赞/收藏/评论点赞只有 POST 取消不掉
- 通知全读、消息中心两处 filter 崩溃、WS 4401 后永久不重连

Android
- 播放统计返回类型 Map<String,Boolean> 解不了 {"status":"recorded"} → 每次上报
  都抛异常;且每 2 秒插一行播放记录,完播率被灌水 10 倍以上 → 改为离开时结算一次
- 上传走通用客户端(20s readTimeout)对同步转码必然超时 → 专用长超时客户端
- BODY 级 HTTP 日志(含密码/令牌/私信)打到 release 包 → 仅 debug BASIC
- allowBackup=true 可导出含 30 天 refresh 的 prefs → 关闭并加 dataExtractionRules
- 全局明文放行 → networkSecurityConfig 白名单
- 服务器地址只改内存、重启即丢,且 remember 旧 service 导致改地址不生效
- 登录态只算一次:登录后不轮询、登出后仍轮询、401 不跳登录 → SessionState 可观察
- 昵称含 "/" 会崩导航(路由未编码);观看历史/访客页无入口不可达

测试与 CI
- backend 30 passed(新增 11 条回归:分页/计数/鉴权/越权/契约)
- web 5 passed(vitest:401 单飞刷新、滑动续期回写、失败登出)+ typecheck 绿
- android 4 passed(JVM 契约测试,无需模拟器)
- e2e smoke 补取消点赞与 4 个分页页断言;CI 增加 web/android job

验证:API 端到端 19/19、浏览器 E2E 全绿、三端构建通过
2026-09-11 17:25:23 +08:00
dsp ac962a91fe DSP 双端短视频平台:Android + Web(Django/DRF + Vue3 + Channels)
- 后端 12 模块:用户/视频/评论/消息/搜索/历史/访客/日志/音乐/通知/审核/创作者
- JWT 双 token + /accounts/refresh 无感续期(双端共用)
- Web 端 Vue3+TS+Vite:沉浸 feed/评论/搜索/私信 WS/上传/通知中心/个人主页
- Android Kotlin+Compose:平台标识头 + 401 单飞刷新
- 部署:docker compose(nginx 网关 SPA+API+WS+媒体),已上线 192.168.5.7:19000
- 测试:pytest 19 用例(含 WS 回归)+ Playwright 冒烟脚本
2026-09-10 20:42:49 +08:00