API-INFO-013 P0 | 聚合到 MaterialReadingProgress 【status:todo】 #112

Closed
opened 2026-06-07 11:03:39 +08:00 by wangdl · 2 comments
Owner

目标

按 userId+readingTargetType+materialId 更新阅读进度。

规则

  • MaterialOpened → 不存在则创建 progress
  • PositionChanged → 更新 lastPosition/lastProgress
  • Heartbeat → totalActiveSeconds += delta,含 position 则更新 lastPosition
  • MaterialClosed → delta 累加,有 position 才更新,无则不覆盖
  • MarkedAsRead → isMarkedRead=true, status=read, lastProgress=1.0
  • 非法 position 不崩溃

详见设计文档 API-INFO-000。

## 目标 按 userId+readingTargetType+materialId 更新阅读进度。 ### 规则 - MaterialOpened → 不存在则创建 progress - PositionChanged → 更新 lastPosition/lastProgress - Heartbeat → totalActiveSeconds += delta,含 position 则更新 lastPosition - MaterialClosed → delta 累加,有 position 才更新,无则不覆盖 - MarkedAsRead → isMarkedRead=true, status=read, lastProgress=1.0 - 非法 position 不崩溃 详见设计文档 API-INFO-000。
wangdl added this to the M8:学习信息收集与基础分析闭环 milestone 2026-06-07 11:03:39 +08:00
wangdl changed title from API-INFO-007 P0 | 聚合 ReadingEvent 到 MaterialReadingProgress to API-INFO-013 P0 | 聚合到 MaterialReadingProgress 2026-06-07 11:22:16 +08:00
wangdl changed title from API-INFO-013 P0 | 聚合到 MaterialReadingProgress to API-INFO-013 P0 | 聚合到 MaterialReadingProgress 【status:todo】 2026-06-07 19:04:14 +08:00
Author
Owner

审查结论:当前 API 项目学习信息收集体系基本为全新建设。可复用:JWT Guard、LearningSession 基础表/CRUD、DailyLearningActivity 基础表、ActivityController 部分接口、LearningRecord schema。其余 ReadingEvent/TemporaryMaterial/Progress/批量上报/Processor/聚合/查询接口/错误码/去重/权限/测试/文档均不存在或仅部分存在。

本 Issue: 聚合逻辑不存在。MaterialClosed无position时不覆盖lastPosition。blocked-by:processor

标签: audit:reviewed audit:api-info status:todo work:aggregation

## 审查结论:当前 API 项目学习信息收集体系基本为全新建设。可复用:JWT Guard、LearningSession 基础表/CRUD、DailyLearningActivity 基础表、ActivityController 部分接口、LearningRecord schema。其余 ReadingEvent/TemporaryMaterial/Progress/批量上报/Processor/聚合/查询接口/错误码/去重/权限/测试/文档均不存在或仅部分存在。 **本 Issue**: 聚合逻辑不存在。MaterialClosed无position时不覆盖lastPosition。blocked-by:processor **标签**: audit:reviewed audit:api-info status:todo work:aggregation
Author
Owner

完成报告

交付

1. MaterialReadingProgressService.upsertFromReadingEvent(tx, data) — 事务内聚合方法:

// MaterialOpened → 创建或更新,sessionCount++
// PositionChanged → 更新 lastPosition + lastProgress
// Heartbeat → totalActiveSeconds += delta
// MaterialClosed → delta 累加,有 position 才更新,无则不覆盖
// MarkedAsRead → isMarkedRead=true, status=read, lastProgress=1.0

2. extractProgress() 辅助 — 从 ReadingPosition 提取归一化进度值:

private extractProgress(position): number | null {
  if (typeof position.overallProgress === "number") return position.overallProgress;
  if (typeof position.scrollProgress === "number") return position.scrollProgress;
  if (typeof position.chapterProgress === "number") return position.chapterProgress;
  return null;
}

3. ProcessorService 事务扩展 — Step 5b 加入事务:

await this.prisma.$transaction(async (tx) => {
  await insertReadingEvent(tx, ...);
  await sessionRepo.upsertFromReadingEvent(tx, ...);  // 5a
  await progressSvc.upsertFromReadingEvent(tx, ...);  // 5b
  await tx.readingEvent.update(...);                   // 6
});
## 完成报告 ### 交付 **1. `MaterialReadingProgressService.upsertFromReadingEvent(tx, data)`** — 事务内聚合方法: ```typescript // MaterialOpened → 创建或更新,sessionCount++ // PositionChanged → 更新 lastPosition + lastProgress // Heartbeat → totalActiveSeconds += delta // MaterialClosed → delta 累加,有 position 才更新,无则不覆盖 // MarkedAsRead → isMarkedRead=true, status=read, lastProgress=1.0 ``` **2. `extractProgress()` 辅助** — 从 ReadingPosition 提取归一化进度值: ```typescript private extractProgress(position): number | null { if (typeof position.overallProgress === "number") return position.overallProgress; if (typeof position.scrollProgress === "number") return position.scrollProgress; if (typeof position.chapterProgress === "number") return position.chapterProgress; return null; } ``` **3. ProcessorService 事务扩展** — Step 5b 加入事务: ```typescript await this.prisma.$transaction(async (tx) => { await insertReadingEvent(tx, ...); await sessionRepo.upsertFromReadingEvent(tx, ...); // 5a await progressSvc.upsertFromReadingEvent(tx, ...); // 5b await tx.readingEvent.update(...); // 6 }); ```
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#112
No description provided.