api-server/docs/learning-info-api.md
wangdl 38a8629e42
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 11s
feat: M8 学习信息收集系统完整实现
Phase 1-2: 设计文档 + 数据库 (ReadingEvent/MaterialReadingProgress/TemporaryReadingMaterial/LearningSession扩展/DailyLearningActivity扩展/LearningRecord)
Phase 3: 批量上报 + 校验去重 + ReadingEventProcessorService
Phase 4: 4表聚合管线 (LearningSession/MaterialReadingProgress/DailyLearningActivity/LearningRecord)
Phase 5: 查询接口 (progress/continue/summary/trend/heatmap/history/reprocess)
Phase 6: 权限校验 + session中断清理 + API文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 21:09:13 +08:00

6.2 KiB
Raw Blame History

学习信息收集 API Contract

M8 | 版本 v1.0 | 2026-06-08

所有响应 shape、错误码以本文档为准。 设计逻辑参见 学习信息收集总设计。 上传协议详见 阅读事件上传协议


1. 基础信息

项目
Base Path /learning / /materials / /activity
Auth Bearer JWT (所有端点需要)
Content-Type (Request) application/json
Content-Type (Response) application/json
Batch Limit 100 条/次

2. 端点总览

方法 路径 说明
POST /learning/reading-events/batch 批量上报阅读事件
GET /materials/:id/reading-progress 查询资料阅读进度
GET /learning/continue 首页继续学习
GET /learning/summary 学习摘要
GET /learning/trend?days=7 阅读趋势
GET /activity/heatmap?days=365 学习热力图
GET /learning/records?cursor=&limit=20&type=reading 学习历史记录
POST /internal/learning/reading-events/:id/reprocess 重处理单事件
POST /internal/learning/reading-events/reprocess-failed 批量重处理失败事件

3. 上报阅读事件

POST /learning/reading-events/batch

// Request
{
  "events": [{
    "eventId": "550e8400-e29b-41d4-a716-446655440001",
    "clientSessionId": "550e8400-e29b-41d4-a716-446655440000",
    "materialId": "cuid_mat_001",
    "readingTargetType": "knowledge_source",
    "eventType": "material_opened",
    "position": { "type": "Markdown", "blockId": "intro", "scrollProgress": 0.25 },
    "activeSecondsDelta": 0,
    "clientTimestampMs": 1717800000000,
    "sequence": 1,
    "platform": "ios",
    "appVersion": "1.2.3",
    "clientTimezoneOffsetMinutes": -480
  }]
}

// Response
{
  "processed": 1,
  "duplicate": 0,
  "failed": 0,
  "warnings": []
}

校验规则

字段 规则 失败处理
eventId UUID v4, userId+eventId unique DUPLICATE_EVENT
activeSecondsDelta 0 , <0 , >300 截断+warning INVALID_ACTIVE_SECONDS
readingTargetType knowledge_source / temporary_file INVALID_TARGET_TYPE
eventType 5 种之一 INVALID_EVENT_TYPE
materialId knowledge_source: KnowledgeSource 存在+归属temporary_file: 存在+归属+未过期 MATERIAL_ACCESS_DENIED / SOURCE_DELETED

4. 查询资料阅读进度

GET /materials/:id/reading-progress?readingTargetType=knowledge_source

// Response (有记录)
{
  "status": "reading",
  "lastPosition": { "type": "Markdown", "blockId": "ch1", "scrollProgress": 0.5 },
  "lastProgress": 0.5,
  "totalActiveSeconds": 120,
  "isMarkedRead": false,
  "firstOpenedAt": "2026-06-01T00:00:00Z",
  "lastReadAt": "2026-06-08T12:00:00Z"
}

// Response (无记录)
{
  "status": "not_started",
  "lastPosition": null,
  "lastProgress": null,
  "totalActiveSeconds": 0,
  "isMarkedRead": false
}

// Response (权限拒绝)
{
  "status": "not_started",
  "reason": "MATERIAL_ACCESS_DENIED"
}

5. 首页继续学习

GET /learning/continue

// Response (有数据)
{
  "type": "knowledge_source",
  "materialId": "cuid_mat_001",
  "title": "Document Title",
  "lastPosition": { "type": "Pdf", "pageNumber": 3, "pageProgress": 0.5, "overallProgress": 0.32 },
  "lastProgress": 0.32,
  "totalActiveSeconds": 1200,
  "lastReadAt": "2026-06-08T12:00:00Z"
}

// Response (无数据)
{ "type": "none" }

6. 学习摘要

GET /learning/summary

{
  "todaySeconds": 300,
  "weekSeconds": 1800,
  "totalSeconds": 7200,
  "activeDays": 12,
  "sessionsCount": 20,
  "materialsReadCount": 5,
  "markedReadCount": 2,
  "dailyAverageSeconds": 600
}

7. 阅读趋势

GET /learning/trend?days=7

参数 默认 最大
days 7 90
{
  "days": 7,
  "series": [
    { "date": "2026-06-02", "value": 120 },
    { "date": "2026-06-03", "value": 0 },
    { "date": "2026-06-04", "value": 300 }
  ]
}

8. 学习热力图

GET /activity/heatmap?days=365

参数 默认 最大
days 365 365
{
  "2026-06-01": 120,
  "2026-06-02": 0,
  "2026-06-03": 300
}

9. 学习历史记录

GET /learning/records?cursor=&limit=20&type=reading

参数 默认 说明
cursor 分页游标(记录 id
limit 20 最大 50
type recordType 过滤
{
  "items": [{
    "id": "cuid_rec_001",
    "recordType": "reading",
    "title": "Reading started",
    "description": null,
    "durationSeconds": 120,
    "occurredAt": "2026-06-08T12:00:00Z",
    "metadata": {
      "materialId": "cuid_mat_001",
      "readingTargetType": "knowledge_source",
      "knowledgeBaseId": "kb_001",
      "totalActiveSeconds": 120,
      "lastPosition": { "type": "progress", "progress": 0.5 }
    },
    "createdAt": "2026-06-08T12:00:00Z"
  }],
  "nextCursor": "cuid_rec_021"
}

10. 重处理Internal

POST /internal/learning/reading-events/:id/reprocess?force=true

  • failed/pending 事件可重处理
  • processed 事件需 ?force=true
  • 返回 { id, result: { outcome, warnings } }

POST /internal/learning/reading-events/reprocess-failed?limit=50

  • 批量重处理 status=failed 事件
  • limit 默认 50最大 200
  • 返回 { reprocessed: N, results: [{ id, outcome }] }

11. 错误码

类型 含义
MATERIAL_NOT_FOUND error knowledge_source 不存在
TEMPORARY_MATERIAL_NOT_FOUND error temporary_file 不存在
MATERIAL_ACCESS_DENIED error 不属于当前用户
TEMPORARY_MATERIAL_EXPIRED error 临时文件已过期
INVALID_TARGET_TYPE error 未知 readingTargetType
INVALID_EVENT_TYPE error 未知 eventType
INVALID_ACTIVE_SECONDS error delta < 0
BATCH_LIMIT_EXCEEDED error 超过 100 条
ACTIVE_SECONDS_CAPPED warning delta > 300 截断
CLIENT_TIMESTAMP_SKEWED warning 时钟偏差 > 5min
POSITION_IGNORED warning position 无效
DUPLICATE_EVENT warning 幂等重放
SOURCE_DELETED warning 来源已删除