diff --git a/docs/reading/PRD.md b/docs/reading/PRD.md new file mode 100644 index 0000000..9b95eef --- /dev/null +++ b/docs/reading/PRD.md @@ -0,0 +1,1089 @@ +# 阅读打卡小程序 - 产品需求文档 (PRD) + +## 1. 产品概述 + +### 1.1 产品定位 +一款阅读打卡小程序,通过 AI 预生成每本书的分日阅读计划(含导读、预估阅读时长、理解问题),引导用户按计划阅读并每日打卡,培养持续阅读习惯。 + +### 1.2 核心价值 +- **AI 阅读计划**:AI 预先为每本书拆分每日阅读导读,预估阅读时长,后台可人工调整 +- **引导式阅读**:每天展示导读内容 + 预估阅读时长 + 理解问题,降低阅读门槛 +- **打卡激励**:读完当日内容后回答问题、写读书感悟,完成打卡 +- **分享传播**:生成精美分享宣传图,展示阅读成果 + +### 1.3 目标用户 +- 想养成阅读习惯但缺乏计划性的用户 +- 读书会/学习社群成员 +- 家长引导孩子阅读 + +--- + +## 2. 功能范围 + +### 2.1 核心功能(MVP) + +#### ✅ 书单与书籍 +- 浏览书单列表(按分类/主题组织) +- 查看书单下的书籍列表 +- 查看书籍详情和阅读计划概览 + +#### ✅ AI 阅读计划 +- AI 预生成每本书的分日阅读导读(后台触发) +- 每日导读包含:导读内容、阅读范围(页码/章节)、**AI 预估阅读时长**、理解问题 +- 后台管理员可编辑调整 AI 生成的计划内容 + +#### ✅ 每日打卡 +- 展示今日导读内容和 AI 预估阅读时长(用户无需手动填写时长) +- 用户阅读完成后回答理解问题 +- 提交打卡 + 选填读书感悟 +- 打卡日历展示连续打卡记录 + +#### ✅ 分享宣传图 +- 生成分享海报(连续打卡天数、已读书籍数、累计阅读时长等) +- 支持多种海报模板 + +#### ✅ 阅读统计 +- 连续打卡天数 / 最长连续天数 +- 已读完书籍数 +- 累计阅读时长(由 AI 预估时长按打卡天数累加) +- 阅读趋势图 + +### 2.2 暂不实现(后续迭代) +- ❌ 用户自建书单 +- ❌ 社区互动(评论/点赞感悟) +- ❌ 阅读排行榜 +- ❌ 订阅消息推送提醒 +- ❌ 书籍推荐算法 + +--- + +## 3. 页面结构 + +### 3.1 页面清单与 TabBar + +``` +TabBar: + ├── 今日阅读 (pages/home/index) - 首页,展示今日任务与打卡入口 + ├── 书单 (pages/book-lists/index) - 浏览书单和书籍 + ├── 统计 (pages/stats/index) - 阅读数据统计 + └── 我的 (pages/profile/index) - 个人中心 + +非 Tab 页: + ├── 书籍详情 (pages/book-detail/index) - 书籍信息 + 阅读计划预览 + ├── 阅读打卡 (pages/reading/index) - 今日导读 + 问题 + 打卡 + ├── 感悟列表 (pages/reflections/index) - 某本书的感悟记录 + └── 分享海报 (pages/poster/index) - 生成/预览分享图 +``` + +### 3.2 首页 — 今日阅读 (home) + +**核心目标**:一目了然展示今日阅读任务,快速进入打卡 + +| 元素 | 说明 | 数据来源 | +|------|------|----------| +| 打卡日历条 | 近 7 天打卡状态(圆点标记) | `GET /reading/my/stats` | +| 在读书籍卡片 | 书名、封面、进度(第 X/Y 天) | `GET /reading/my/books` | +| 今日任务预览 | 导读标题 + AI 预估阅读时长 | `GET /reading/my/books/:id/today` | +| 打卡按钮 | "开始今日阅读" / "今日已打卡 ✅" | 根据 daily_status 判断 | +| 空状态引导 | 未选书时展示 "去选一本书开始吧" | 跳转书单页 | + +**首页布局示意**: + +``` +┌─────────────────────────────┐ +│ 📖 今日阅读 │ +│ ───────────────────────── │ +│ [日 一 二 三 四 五 六] │ +│ ● ● ● ○ ○ ○ ○ │ +│ 连续打卡 3 天 │ +│ ───────────────────────── │ +│ ┌───────────────────────┐ │ +│ │ 📕 《人类简史》 │ │ +│ │ 第 5/30 天 │ │ +│ │ ████████░░░ 17% │ │ +│ └───────────────────────┘ │ +│ ───────────────────────── │ +│ 今日导读:第三章 农业革命 │ +│ ⏱ 预估阅读 25 分钟 │ +│ 📝 3 道理解问题 │ +│ ───────────────────────── │ +│ [ 🟢 开始今日阅读 ] │ +└─────────────────────────────┘ +``` + +### 3.3 书单页 (book-lists) + +**核心目标**:浏览和选择想读的书 + +| 元素 | 说明 | +|------|------| +| 书单卡片列表 | 书单封面、标题、描述、书籍数量 | +| 点击书单 | 展开书单下的书籍列表 | +| 书籍卡片 | 书名、作者、封面、计划天数、"开始阅读"按钮 | + +### 3.4 书籍详情页 (book-detail) + +**核心目标**:了解书籍信息和阅读计划,决定是否开始阅读 + +| 元素 | 说明 | +|------|------| +| 书籍封面大图 | 封面、书名、作者 | +| 书籍简介 | 内容简介 | +| 阅读计划概览 | 共 X 天、AI 预估总阅读时长 | +| 每日计划预览 | 可展开查看每天的导读标题和预估时长 | +| 操作按钮 | "开始阅读"(未开始)/ "继续阅读"(进行中)/ "已读完"(已完成) | + +### 3.5 阅读打卡页 (reading) + +**核心目标**:完成今日阅读任务并打卡 + +``` +┌─────────────────────────────┐ +│ ← 返回 第 5/30 天 │ +│ ───────────────────────── │ +│ 📖 今日导读 │ +│ 第三章 农业革命 (P45-P62) │ +│ ───────────────────────── │ +│ ⏱ 预估阅读时长: 25 分钟 │ +│ ───────────────────────── │ +│ [导读内容区域] │ +│ "农业革命是人类历史上最具 │ +│ 争议的转折点之一。作者认为 │ +│ 农业并非进步,而是..." │ +│ ───────────────────────── │ +│ 📝 阅读理解 (读完后作答) │ +│ ───────────────────────── │ +│ Q1: 作者为什么认为农业革命 │ +│ 是"史上最大的骗局"? │ +│ [文本输入框] │ +│ │ +│ Q2: 采集社会与农业社会相比 │ +│ 各有什么优劣? │ +│ [文本输入框] │ +│ │ +│ Q3: 你是否同意作者的观点? │ +│ 为什么? │ +│ [文本输入框] │ +│ ───────────────────────── │ +│ 💭 读书感悟 (选填) │ +│ [多行文本输入框] │ +│ ───────────────────────── │ +│ [ ✅ 完成今日打卡 ] │ +└─────────────────────────────┘ +``` + +**交互规则**: +- 理解问题至少回答 1 道才能提交打卡 +- 读书感悟为选填项 +- 打卡成功后展示鼓励动画 + 连续打卡天数 +- 当天最后一天打卡 → 弹出"恭喜读完全书" + 引导生成分享图 + +### 3.6 统计页 (stats) + +| 元素 | 说明 | +|------|------| +| 核心数据卡片 | 连续打卡天数、已读完书籍数、累计阅读时长 | +| 打卡日历 | 月度日历视图,标记已打卡日期 | +| 阅读趋势 | 周/月维度的每日阅读时长趋势图(来自 AI 预估时长) | +| 已读书籍列表 | 已完成的书籍及完成日期 | + +### 3.7 个人中心 (profile) + +| 元素 | 说明 | +|------|------| +| 用户信息 | 头像、昵称 | +| 在读书籍 | 当前正在阅读的书籍列表 | +| 已读书籍 | 已完成的书籍列表 | +| 我的感悟 | 所有读书感悟汇总 | +| 分享成就 | 进入海报生成页 | + +### 3.8 分享海报页 (poster) + +| 元素 | 说明 | +|------|------| +| 海报预览 | 实时预览生成的宣传图 | +| 模板选择 | 多种海报风格可选 | +| 展示字段 | 连续打卡天数、已读书籍数、累计阅读时长、当前在读书名 | +| 操作按钮 | "保存到相册" / "分享给好友" | + +--- + +## 4. 数据模型 + +### 4.1 书单表 (fa_reading_book_list) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| title | varchar(100) | ✅ | 书单标题 | +| description | varchar(500) | ❌ | 书单描述 | +| cover_image | varchar(500) | ❌ | 书单封面图 URL | +| sort_order | int | ❌ | 排序权重(越小越靠前,默认 0) | +| status | varchar(20) | ✅ | 状态:active / inactive | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +### 4.2 书籍表 (fa_reading_book) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| book_list_id | bigint | ✅ | 所属书单 ID | +| title | varchar(200) | ✅ | 书名 | +| author | varchar(100) | ❌ | 作者 | +| cover_image | varchar(500) | ❌ | 书籍封面图 URL | +| description | text | ❌ | 内容简介 | +| isbn | varchar(20) | ❌ | ISBN 编号 | +| total_pages | int | ❌ | 总页数(用于 AI 生成计划) | +| total_plan_days | int | ❌ | 计划总天数 | +| sort_order | int | ❌ | 排序权重(默认 0) | +| status | varchar(20) | ✅ | 状态:active / inactive | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `idx_book_list_id` (book_list_id) +- `idx_status` (status) + +### 4.3 阅读计划表 (fa_reading_plan) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| book_id | bigint | ✅ | 关联书籍 ID(唯一) | +| total_days | int | ✅ | 计划总天数 | +| total_estimated_minutes | int | ❌ | AI 预估总阅读时长(分钟,所有天数累加) | +| ai_generated | tinyint(1) | ✅ | 是否 AI 生成(1=是,0=否) | +| ai_model | varchar(50) | ❌ | 生成时使用的 AI 模型 | +| status | varchar(20) | ✅ | 状态:draft / published | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `uidx_book_id` (book_id) UNIQUE + +### 4.4 每日导读表 (fa_reading_plan_day) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| plan_id | bigint | ✅ | 关联阅读计划 ID | +| day_number | int | ✅ | 第几天(从 1 开始) | +| title | varchar(200) | ✅ | 导读标题(如"第三章 农业革命") | +| guide_content | text | ✅ | 导读正文(AI 生成的阅读引导) | +| reading_range | varchar(100) | ❌ | 阅读范围(如"P45-P62"或"第三章") | +| estimated_minutes | int | ✅ | AI 预估阅读时长(分钟) | +| questions_json | json | ❌ | 理解问题数组 `[{"q":"问题内容","hint":"提示"}]` | +| sort_order | int | ❌ | 排序(默认等于 day_number) | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `uidx_plan_day` (plan_id, day_number) UNIQUE +- `idx_plan_id` (plan_id) + +**questions_json 结构**: +```json +[ + { + "q": "作者为什么认为农业革命是'史上最大的骗局'?", + "hint": "可以从人类生活质量、劳动强度等角度思考" + }, + { + "q": "采集社会与农业社会相比各有什么优劣?", + "hint": "" + } +] +``` + +### 4.5 用户阅读进度表 (fa_reading_user_book) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| uid | bigint | ✅ | 用户 ID | +| book_id | bigint | ✅ | 书籍 ID | +| plan_id | bigint | ✅ | 阅读计划 ID | +| current_day | int | ✅ | 当前进度(已完成到第几天,默认 0) | +| total_days | int | ✅ | 计划总天数(冗余,方便查询) | +| status | varchar(20) | ✅ | 状态:reading / completed / dropped | +| started_at | timestamp | ✅ | 开始阅读时间 | +| completed_at | timestamp | ❌ | 读完时间 | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `uidx_uid_book` (uid, book_id) UNIQUE +- `idx_uid_status` (uid, status) + +### 4.6 每日打卡表 (fa_reading_daily_checkin) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| uid | bigint | ✅ | 用户 ID | +| user_book_id | bigint | ✅ | 关联用户阅读进度 ID | +| plan_day_id | bigint | ✅ | 关联每日导读 ID | +| day_number | int | ✅ | 第几天 | +| date | date | ✅ | 打卡自然日 | +| estimated_minutes | int | ✅ | AI 预估阅读时长(冗余自 plan_day) | +| answers_json | json | ❌ | 问题回答 `[{"q":"问题","a":"回答"}]` | +| reflection | text | ❌ | 读书感悟 | +| status | varchar(20) | ✅ | 状态:checked_in / missed | +| checked_in_at | timestamp | ❌ | 打卡时间 | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `uidx_uid_date` (uid, date) UNIQUE +- `idx_uid_user_book` (uid, user_book_id) +- `idx_user_book_day` (user_book_id, day_number) + +**answers_json 结构**: +```json +[ + { + "q": "作者为什么认为农业革命是'史上最大的骗局'?", + "a": "因为农业让人类的劳动量大幅增加,饮食更加单一,但总人口的增长又让人们无法回到采集社会..." + } +] +``` + +### 4.7 分享记录表 (fa_reading_share) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | bigint | PK | 主键 | +| uid | bigint | ✅ | 用户 ID | +| share_type | varchar(20) | ✅ | 分享类型:daily / book_complete / streak_milestone | +| template_code | varchar(50) | ✅ | 海报模板编码 | +| data_json | json | ❌ | 海报数据快照 | +| image_url | varchar(500) | ❌ | 生成的海报图片 URL | +| created_at | timestamp | 自动 | 创建时间 | +| updated_at | timestamp | 自动 | 更新时间 | +| deleted_at | timestamp | 自动 | 软删除时间 | + +**索引**: +- `idx_uid` (uid) + +--- + +## 5. API 设计 + +### 5.1 路由前缀与鉴权 + +| 前缀 | 鉴权 | 用途 | +|------|------|------| +| `/api/reading` | 部分公开 | 小程序主接口 | +| `/api/admin/reading` | Admin JWT | 管理后台 | + +### 5.2 书单与书籍(公开接口) + +#### 获取书单列表 +``` +GET /api/reading/book-lists + +Response: +{ + "code": 0, + "data": { + "items": [ + { + "id": 1, + "title": "经典必读书单", + "description": "精选 10 本改变思维方式的经典作品", + "cover_image": "https://...", + "book_count": 10 + } + ] + } +} +``` + +#### 获取书单下的书籍列表 +``` +GET /api/reading/book-lists/:id/books + +Response: +{ + "code": 0, + "data": { + "book_list": { "id": 1, "title": "经典必读书单" }, + "items": [ + { + "id": 1, + "title": "人类简史", + "author": "尤瓦尔·赫拉利", + "cover_image": "https://...", + "total_plan_days": 30, + "total_estimated_minutes": 750, + "description": "..." + } + ] + } +} +``` + +#### 获取书籍详情(含计划概览) +``` +GET /api/reading/books/:id + +Response: +{ + "code": 0, + "data": { + "book": { + "id": 1, + "title": "人类简史", + "author": "尤瓦尔·赫拉利", + "cover_image": "https://...", + "description": "...", + "total_pages": 440 + }, + "plan": { + "id": 1, + "total_days": 30, + "total_estimated_minutes": 750, + "status": "published", + "days_preview": [ + { + "day_number": 1, + "title": "序章 - 人类的三大革命", + "reading_range": "P1-P15", + "estimated_minutes": 20 + }, + { + "day_number": 2, + "title": "第一章 - 人类:一种也没什么特别的动物", + "reading_range": "P16-P32", + "estimated_minutes": 25 + } + ] + } + } +} +``` + +### 5.3 用户阅读(需登录) + +#### 开始阅读一本书 +``` +POST /api/reading/books/:id/start + +Response: +{ + "code": 0, + "data": { + "user_book_id": 1, + "book_id": 1, + "book_title": "人类简史", + "current_day": 0, + "total_days": 30, + "status": "reading", + "started_at": "2026-03-21T10:00:00+08:00" + } +} +``` + +**业务规则**: +- 同一本书不能重复开始(返回 409) +- 同时只能在读一本书(可配置,MVP 先限制 1 本) + +#### 获取我的阅读列表 +``` +GET /api/reading/my/books +Query: + - status: string (reading/completed/dropped/all,默认 all) + +Response: +{ + "code": 0, + "data": { + "items": [ + { + "user_book_id": 1, + "book_id": 1, + "book_title": "人类简史", + "book_cover": "https://...", + "author": "尤瓦尔·赫拉利", + "current_day": 5, + "total_days": 30, + "progress_percent": 17, + "status": "reading", + "started_at": "2026-03-15T10:00:00+08:00" + } + ] + } +} +``` + +#### 获取某本书的阅读进度详情 +``` +GET /api/reading/my/books/:user_book_id + +Response: +{ + "code": 0, + "data": { + "user_book": { + "user_book_id": 1, + "book_id": 1, + "book_title": "人类简史", + "author": "尤瓦尔·赫拉利", + "current_day": 5, + "total_days": 30, + "status": "reading" + }, + "checkin_history": [ + { + "day_number": 5, + "date": "2026-03-20", + "title": "第三章 农业革命", + "estimated_minutes": 25, + "has_reflection": true, + "checked_in_at": "2026-03-20T21:30:00+08:00" + } + ] + } +} +``` + +### 5.4 每日打卡(需登录) + +#### 获取今日阅读任务 +``` +GET /api/reading/my/books/:user_book_id/today + +Response: +{ + "code": 0, + "data": { + "daily_status": "pending", // pending / checked_in + "day_number": 6, + "total_days": 30, + "plan_day": { + "id": 6, + "title": "第三章 农业革命(续)", + "guide_content": "上一节我们了解了农业革命的起源...", + "reading_range": "P63-P80", + "estimated_minutes": 25, + "questions": [ + { + "q": "农业社会带来了哪些社会结构的变化?", + "hint": "可以从私有制、阶级分化等角度思考" + }, + { + "q": "作者认为小麦'驯化'了人类,你怎么理解?", + "hint": "" + } + ] + }, + "checkin": null // 已打卡时返回打卡详情 + } +} +``` + +#### 提交今日打卡 +``` +POST /api/reading/my/books/:user_book_id/checkin +Body: +{ + "answers": [ + { + "q": "农业社会带来了哪些社会结构的变化?", + "a": "农业带来了定居生活,随之产生了私有制和社会阶级分化..." + }, + { + "q": "作者认为小麦'驯化'了人类,你怎么理解?", + "a": "从小麦的角度看,它成功地让人类为它服务..." + } + ], + "reflection": "今天读到农业革命的部分很有启发,原来我们以为的进步..." +} + +Response: +{ + "code": 0, + "data": { + "checkin_id": 10, + "day_number": 6, + "date": "2026-03-21", + "estimated_minutes": 25, + "status": "checked_in", + "checked_in_at": "2026-03-21T22:00:00+08:00", + "summary": { + "current_streak_days": 6, + "max_streak_days": 6, + "total_reading_minutes": 145, + "total_books_completed": 0, + "book_progress_percent": 20, + "is_book_completed": false + } + } +} +``` + +**业务规则**: +- 同一天同一本书只能打卡一次(返回 409) +- `answers` 数组至少包含 1 条非空回答 +- `reflection` 选填 +- 打卡成功后 `user_book.current_day` 自增 1 +- 若 `current_day == total_days`,自动将 `user_book.status` 更新为 `completed` +- 当天未打卡自然过期后标记为 `missed`(可通过定时任务或下次请求时回填) + +#### 获取打卡记录列表 +``` +GET /api/reading/my/books/:user_book_id/checkins +Query: + - page: int (默认 1) + - page_size: int (默认 20) + +Response: +{ + "code": 0, + "data": { + "items": [ + { + "id": 10, + "day_number": 6, + "date": "2026-03-21", + "title": "第三章 农业革命(续)", + "estimated_minutes": 25, + "answers_count": 2, + "has_reflection": true, + "checked_in_at": "2026-03-21T22:00:00+08:00" + } + ], + "total": 6, + "page": 1, + "page_size": 20 + } +} +``` + +### 5.5 读书感悟(需登录) + +#### 获取某本书的感悟列表 +``` +GET /api/reading/my/books/:user_book_id/reflections +Query: + - page: int (默认 1) + - page_size: int (默认 20) + +Response: +{ + "code": 0, + "data": { + "items": [ + { + "day_number": 6, + "date": "2026-03-21", + "title": "第三章 农业革命(续)", + "reflection": "今天读到农业革命的部分很有启发..." + } + ], + "total": 3, + "page": 1, + "page_size": 20 + } +} +``` + +### 5.6 阅读统计(需登录) + +#### 获取统计概览 +``` +GET /api/reading/my/stats + +Response: +{ + "code": 0, + "data": { + "current_streak_days": 6, + "max_streak_days": 6, + "total_reading_minutes": 145, + "total_books_completed": 0, + "total_checkin_days": 6, + "current_book": { + "book_title": "人类简史", + "progress_percent": 20, + "current_day": 6, + "total_days": 30 + }, + "recent_checkins": [ + { "date": "2026-03-21", "status": "checked_in", "estimated_minutes": 25 }, + { "date": "2026-03-20", "status": "checked_in", "estimated_minutes": 20 }, + { "date": "2026-03-19", "status": "checked_in", "estimated_minutes": 30 } + ] + } +} +``` + +### 5.7 分享海报(需登录) + +#### 获取海报预览数据 +``` +GET /api/reading/poster/data +Query: + - template_code: string (默认 "reading_1") + +Response: +{ + "code": 0, + "data": { + "nickname": "读书人", + "streak_days": 6, + "total_books_completed": 2, + "total_reading_minutes": 1450, + "current_book_title": "人类简史", + "current_book_progress": "6/30", + "template_code": "reading_1", + "share_title": "我已连续阅读打卡 6 天" + } +} +``` + +#### 生成分享海报 +``` +POST /api/reading/poster/generate +Body: +{ + "template_code": "reading_1", + "show_fields": ["streak_days", "total_books_completed", "total_reading_minutes"] +} + +Response: +{ + "code": 0, + "data": { + "template_code": "reading_1", + "image_url": "https://static.nepiedg.top/reading/posters/...", + "share_title": "我已连续阅读打卡 6 天" + } +} +``` + +### 5.8 管理后台 API + +| 方法 | 路径 | 说明 | +|------|------|------| +| **书单管理** | | | +| GET | `/api/admin/reading/book-lists` | 书单列表(支持分页) | +| POST | `/api/admin/reading/book-lists` | 创建书单 | +| PUT | `/api/admin/reading/book-lists/:id` | 更新书单 | +| DELETE | `/api/admin/reading/book-lists/:id` | 删除书单 | +| **书籍管理** | | | +| GET | `/api/admin/reading/books` | 书籍列表(支持按书单筛选) | +| POST | `/api/admin/reading/books` | 创建书籍 | +| PUT | `/api/admin/reading/books/:id` | 更新书籍 | +| DELETE | `/api/admin/reading/books/:id` | 删除书籍 | +| **阅读计划管理** | | | +| GET | `/api/admin/reading/plans/:book_id` | 获取书籍的阅读计划 | +| POST | `/api/admin/reading/plans/:book_id/generate` | AI 生成阅读计划 | +| PUT | `/api/admin/reading/plans/:plan_id` | 更新阅读计划状态 | +| **导读内容管理** | | | +| GET | `/api/admin/reading/plan-days?plan_id=X` | 获取某计划的所有导读 | +| PUT | `/api/admin/reading/plan-days/:id` | 编辑单日导读内容 | + +--- + +## 6. 核心业务流程 + +### 6.1 用户打卡流程 + +``` +用户进入"今日阅读"首页 + │ + ├── 未选书 → 展示空状态 → 引导去"书单"页选书 + │ │ + │ ↓ + │ 点击书籍 → 书籍详情页 + │ │ + │ ↓ + │ 点击"开始阅读" + │ │ + │ POST /books/:id/start + │ │ + │ ↓ + │ 返回首页(展示在读书籍) + │ + └── 有在读书 → GET /my/books/:id/today + │ + ├── 今日已打卡 (daily_status = checked_in) + │ → 展示"今日已完成 ✅" + │ → 展示打卡记录(时长、回答、感悟) + │ + └── 今日未打卡 (daily_status = pending) + → 展示今日导读内容 + → 展示 AI 预估阅读时长(如"预估 25 分钟") + → 用户阅读完成后: + │ + ├── 1) 回答理解问题(至少 1 题) + ├── 2) 写读书感悟(选填) + └── 3) 点击"完成打卡" + │ + POST /my/books/:id/checkin + │ + ├── 普通天 → 打卡成功 + │ → current_day + 1 + │ → 展示鼓励动画 + 连续天数 + │ + └── 最后一天 (current_day == total_days) + → user_book.status = completed + → 弹出"🎉 恭喜读完全书!" + → 引导生成分享宣传图 +``` + +### 6.2 AI 阅读计划生成流程(后台) + +``` +管理员在后台创建书籍 + │ + ↓ +点击"AI 生成阅读计划" + │ + ↓ +POST /admin/reading/plans/:book_id/generate + │ + ├── 输入给 AI: + │ - 书名、作者、简介、总页数 + │ - 期望计划天数(如 30 天) + │ + ├── AI 返回(结构化 JSON): + │ - 每日导读标题 (title) + │ - 导读正文 (guide_content) + │ - 阅读范围 (reading_range) + │ - 预估阅读时长 (estimated_minutes) + │ - 理解问题 (questions) + │ + ├── 写入 fa_reading_plan 表 + │ - ai_generated = true + │ - status = draft + │ + └── 写入 fa_reading_plan_day 表(逐日) + │ + ↓ +管理员逐日审核/编辑导读内容、时长、问题 + │ + ↓ +将计划状态改为 published → 前端可见 +``` + +**AI Prompt 参考**: +``` +你是一位资深阅读导师,请为以下书籍制定一个 {total_days} 天的阅读计划: + +书名:{title} +作者:{author} +简介:{description} +总页数:{total_pages} + +要求: +1. 将全书合理拆分为 {total_days} 天的阅读任务 +2. 每天包含:导读标题、导读正文(200-300字,引导读者关注重点)、阅读范围(页码)、预估阅读时长(分钟)、2-3 道理解问题 +3. 预估阅读时长应考虑该部分的难度和信息密度 +4. 理解问题应引导深度思考,而非简单的信息检索 + +请以 JSON 数组格式返回,每个元素包含: +{ + "day_number": 1, + "title": "导读标题", + "guide_content": "导读正文", + "reading_range": "P1-P15", + "estimated_minutes": 20, + "questions": [ + {"q": "问题内容", "hint": "思考提示"} + ] +} +``` + +### 6.3 分享宣传图流程 + +``` +用户点击"分享成就" + │ + ↓ +GET /reading/poster/data → 获取海报数据 + │ + ↓ +选择海报模板(多种风格) + │ + ↓ +POST /reading/poster/generate → 生成海报图片 + │ + ↓ +预览海报 + │ + ├── "保存到相册" → wx.saveImageToPhotosAlbum + └── "分享给好友" → open-type="share" +``` + +--- + +## 7. 后端模块结构 + +遵循现有 `wx_service` 的 Handler → Service → Model 分层架构: + +``` +wx_service/internal/reading/ +├── handler/ +│ └── handler.go # HTTP 接口处理(入参校验、调用 Service、返回 JSON) +├── model/ +│ ├── book_list.go # 书单模型 (fa_reading_book_list) +│ ├── book.go # 书籍模型 (fa_reading_book) +│ ├── reading_plan.go # 阅读计划模型 (fa_reading_plan) +│ ├── plan_day.go # 每日导读模型 (fa_reading_plan_day) +│ ├── user_book.go # 用户阅读进度模型 (fa_reading_user_book) +│ ├── daily_checkin.go # 每日打卡模型 (fa_reading_daily_checkin) +│ └── share.go # 分享记录模型 (fa_reading_share) +└── service/ + └── service.go # 业务逻辑(打卡、统计、计划生成、海报) +``` + +### 7.1 接入 main.go 的步骤 + +```go +// 1. 在 AutoMigrate 中注册所有 Model +database.AutoMigrate( + // ... 现有模型 ... + &readingmodel.BookList{}, + &readingmodel.Book{}, + &readingmodel.ReadingPlan{}, + &readingmodel.PlanDay{}, + &readingmodel.UserBook{}, + &readingmodel.DailyCheckin{}, + &readingmodel.Share{}, +) + +// 2. 创建 Service +readingService := readingservice.NewService(database.DB, config.AppConfig.AI) + +// 3. 创建 Handler +readingHandler := readinghandler.NewHandler(readingService) + +// 4. 传入 routes.Register 并注册路由 +``` + +### 7.2 路由注册 + +```go +// routes.go 中新增 +readingAPI := router.Group("/api/reading") +{ + // 公开接口 + readingAPI.GET("/book-lists", readingHandler.ListBookLists) + readingAPI.GET("/book-lists/:id/books", readingHandler.ListBooksByList) + readingAPI.GET("/books/:id", readingHandler.GetBookDetail) + + // 需登录接口 + readingProtected := readingAPI.Group("") + readingProtected.Use(middleware.AuthMiddleware(db, sessionCache)) + readingProtected.Use(middleware.RequireUserMiddleware()) + { + readingProtected.POST("/books/:id/start", readingHandler.StartReading) + readingProtected.GET("/my/books", readingHandler.ListMyBooks) + readingProtected.GET("/my/books/:id", readingHandler.GetMyBookDetail) + readingProtected.GET("/my/books/:id/today", readingHandler.GetTodayTask) + readingProtected.POST("/my/books/:id/checkin", readingHandler.Checkin) + readingProtected.GET("/my/books/:id/checkins", readingHandler.ListCheckins) + readingProtected.GET("/my/books/:id/reflections", readingHandler.ListReflections) + readingProtected.GET("/my/stats", readingHandler.Stats) + readingProtected.GET("/poster/data", readingHandler.PosterData) + readingProtected.POST("/poster/generate", readingHandler.GeneratePoster) + } +} +``` + +--- + +## 8. 前端项目结构 + +``` +apps/reading-checkin/ +├── src/ +│ ├── api/ +│ │ ├── auth.js # 微信登录(复用现有模式) +│ │ ├── request.js # 请求封装(Bearer Token + 401 重试) +│ │ ├── book.js # 书单/书籍相关 API +│ │ ├── checkin.js # 打卡相关 API +│ │ └── poster.js # 海报相关 API +│ ├── components/ +│ │ ├── book-card/ # 书籍卡片组件 +│ │ ├── checkin-calendar/ # 打卡日历组件 +│ │ ├── progress-bar/ # 阅读进度条 +│ │ └── question-card/ # 问题回答卡片 +│ ├── pages/ +│ │ ├── home/index.vue # 今日阅读(首页) +│ │ ├── book-lists/index.vue # 书单列表 +│ │ ├── book-detail/index.vue# 书籍详情 +│ │ ├── reading/index.vue # 阅读打卡页 +│ │ ├── stats/index.vue # 统计 +│ │ ├── profile/index.vue # 我的 +│ │ └── poster/index.vue # 分享海报 +│ ├── stores/ +│ │ ├── index.js # Pinia store 入口 +│ │ └── user.js # 用户状态 +│ ├── utils/ +│ │ ├── format.js # 时长、日期格式化 +│ │ └── storage.js # reading_checkin_ 前缀存储 +│ ├── config/ +│ │ └── index.js # mini_program_id, baseUrl 配置 +│ ├── App.vue # 应用入口(onLaunch 登录) +│ ├── main.js +│ ├── manifest.json +│ ├── pages.json # 页面路由 + TabBar 配置 +│ └── uni.scss # 全局样式 +├── package.json +└── vite.config.js +``` + +### 8.1 关键配置 + +**config/index.js**: +```javascript +const config = { + mini_program_id: '', + baseUrl: { + dev: 'http://localhost:8080/api', + prod: 'https://wx.nepiedg.top/api' + } +} +``` + +**utils/storage.js** 存储前缀: `reading_checkin_` + +--- + +## 9. 实施计划 + +| 阶段 | 内容 | 优先级 | +|------|------|--------| +| **P0: 后端基础** | Model 定义 + AutoMigrate + Service 骨架 + Handler + Routes 接入 | 高 | +| **P0: 前端脚手架** | 基于 quit-checkin 创建项目骨架、登录、请求封装 | 高 | +| **P1: 书单与书籍** | 后台 CRUD + 前端展示 | 高 | +| **P1: AI 阅读计划** | 后台 AI 生成 + 导读编辑 | 高 | +| **P1: 打卡核心流程** | 今日任务获取 + 回答问题 + 提交打卡 + 进度更新 | 高 | +| **P2: 统计页面** | 连续天数、日历、趋势图 | 中 | +| **P2: 分享海报** | 海报数据 + 模板 + 生成 | 中 | +| **P3: 优化体验** | 打卡动画、空状态引导、错误处理 | 低 | +| **P3: 联调测试** | 前后端联调 + 微信真机测试 | 低 | + +--- + +## 10. 与现有系统的关系 + +| 复用项 | 说明 | +|--------|------| +| 用户表 (`users`) | 共用,通过 `mini_program_id` 区分小程序 | +| 微信登录 (`AuthService`) | 完全复用 | +| 鉴权中间件 (`AuthMiddleware`) | 完全复用 | +| 七牛上传 (`QiniuService`) | 复用(书籍封面、海报图片上传) | +| Redis 缓存 (`SessionUserCache`) | 复用 | +| 管理后台框架 (`admin`) | 复用 JWT 鉴权和管理员体系 | +| AI 配置 (`config.AI`) | 复用 AI 调用配置(参照 smoke 模块的 AI 集成) | diff --git a/docs/reading/README.md b/docs/reading/README.md new file mode 100644 index 0000000..75e1ea4 --- /dev/null +++ b/docs/reading/README.md @@ -0,0 +1,19 @@ +# 阅读打卡模块文档 + +## 文档清单 + +- `PRD.md`:产品需求文档(数据模型、API 设计、业务流程、前后端结构) + +## 说明 + +- 小程序端接口前缀:`/api/reading` +- 管理后台接口前缀:`/api/admin/reading` +- 鉴权方式:`Authorization: Bearer ` +- 业务语义:书单浏览、阅读计划、每日打卡、读书感悟、分享海报 + +## 核心设计要点 + +1. **阅读时长由 AI 预估**:每日导读中的 `estimated_minutes` 由 AI 在生成阅读计划时自动预估,前端仅展示,用户无需手动输入 +2. **模块命名**:后端 `internal/reading/`,前端 `apps/reading-checkin/` +3. **表前缀**:`fa_reading_`,共 7 张表 +4. **存储前缀**:前端 localStorage 使用 `reading_checkin_` 前缀