# 用户使用流程图 — 内容与媒体 > **端口说明**: 📱 小程序 | 🖥 管理后台 | 📋 规划师端 | ⚙️ 系统自动 > > **分层说明**: 🏠 页面 → 🔗 API → ⚙️ Service → 📦 数据实体 ```mermaid flowchart TD %% ============ 颜色定义 ============ classDef page fill:#e3f2fd,stroke:#1565c0,stroke-width:2px classDef api fill:#fff3e0,stroke:#f57c00,stroke-width:1px classDef service fill:#e8f5e9,stroke:#388e3c,stroke-width:1px classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px classDef external fill:#fce4ec,stroke:#d32f2f,stroke-width:1px,stroke-dasharray:3 2 classDef actor fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,stroke-dasharray:5 3 %% ============ 角色 ============ ROLE_USER(("👤 用户")):::actor ROLE_ADMIN(("🖥 管理员")):::actor %% ================================================================ %% 阶段一:文章浏览 %% ================================================================ subgraph 阶段一[阶段一:文章浏览] direction TB P1["📱 小程序:文章列表页"]:::page P1 -->|"查看分类"| A1["🔗 POST /api/articles/categories"]:::api A1 -->|"无参数"| S1["⚙️ ArticleCategoryService.getActiveCategories()"]:::service S1 -->|"启用分类列表(按sortOrder排序)"| D1["📦 article_categories 表"]:::data P1 -->|"按分类/维度/关键词过滤"| A2["🔗 POST /api/articles/list"]:::api A2 -->|"categoryId, keyword, dimensionCode, page, size"| S2["⚙️ ArticleService.getPublicList()"]:::service S2 -->|"已发布文章(含可见性过滤)"| D2["📦 articles 表(查询)"]:::data P1 -->|"查看精选"| A3["🔗 POST /api/articles/featured"]:::api A3 -->|"size, dimensionCode"| S3["⚙️ ArticleService.getFeatured()"]:::service S3 -->|"isFeatured=1的已发布文章"| D2 P1 -->|"点击查看详情"| P1a["📱 小程序:文章详情页"]:::page P1a -->|"文章ID"| A4["🔗 POST /api/articles/detail"]:::api A4 -->|"id"| S4["⚙️ ArticleService.getDetail()"]:::service S4 -->|"文章对象(含可见性校验)"| D2 end %% ================================================================ %% 阶段二:阅读记录与积分 %% ================================================================ subgraph 阶段二[阶段二:阅读记录与积分] P1a -->|"记录阅读(进入页面时)"| A5["🔗 POST /api/articles/record-read"]:::api A5 -->|"id(文章ID), durationSeconds, childId"| S5["⚙️ ArticleService.recordRead()"]:::service S5 -->|"viewCount+1, 写入阅读记录, 检查时长阈值, 创建Task(自动完成), 发放积分, PointsLog, 发放五维能量"| D3["📦 article_reading_records + tasks + points_logs + energy_records"]:::data P1a -->|"上报阅读时长(定时上报)"| A6["🔗 POST /api/articles/report-reading-time"]:::api A6 -->|"articleId, durationSeconds, childId"| S6["⚙️ ArticleService.reportReadingTime()"]:::service S6 -->|"当日记录累加/新建"| D4["📦 article_reading_records(累加durationSeconds)"]:::data P1 -->|"查看阅读统计"| P1b["📱 小程序:阅读统计页"]:::page P1b -->|"孩子ID"| A7["🔗 POST /api/articles/reading-stats"]:::api A7 -->|"childId"| S7["⚙️ ArticleService.getReadingStats()"]:::service S7 -->|"月阅读数, 总时长, 进度百分比"| D4 end %% ================================================================ %% 阶段三:每日贴士与AI互动 %% ================================================================ subgraph 阶段三[阶段三:每日贴士与AI互动] P1 -->|"查看今日贴士"| P1c["📱 小程序:每日贴士页"]:::page P1c -->|"无参数"| A8["🔗 POST /api/articles/daily-tip"]:::api A8 -->|"无"| S8["⚙️ ArticleService.getDailyTip()"]:::service S8 -->|"根据日期从心理类文章中轮选一篇"| D2 P1a -->|"获取AI问题"| A9["🔗 POST /api/articles/ai-questions"]:::api A9 -->|"articleId, childId"| S9["⚙️ ArticleService.getAiQuestions()"]:::service S9 -->|"调用Dify AI生成互动题目"| D5["📦 Dify AI(外部)"]:::data P1a -->|"提交AI答案"| A10["🔗 POST /api/articles/submit-answers"]:::api A10 -->|"recordId, answers(JSON), childId"| S10["⚙️ ArticleService.submitAnswers()"]:::service S10 -->|"AI评分和反馈"| D5 end %% ================================================================ %% 阶段四:用户发布内容 %% ================================================================ subgraph 阶段四[阶段四:用户发布内容] P1 -->|"发布文章"| P1d["📱 小程序:发布文章页"]:::page P1d -->|"标题, 正文, 封面图, 可见范围, 关联维度"| A11["🔗 POST /api/articles/publish"]:::api A11 -->|"title, content, coverImage, visibility, relatedDimensions"| S11["⚙️ ArticleService.publishByUser()"]:::service S11 -->|"公开→draft/待审核, 私密→published(家庭可见)"| D2 P1 -->|"我的帖子"| P1e["📱 小程序:我的帖子页"]:::page P1e -->|"分页参数"| A12["🔗 POST /api/articles/my-posts"]:::api A12 -->|"page, size"| S12["⚙️ ArticleService.getMyPosts()"]:::service S12 -->|"用户发布的文章列表"| D2 end %% ================================================================ %% 阶段五:分享 %% ================================================================ subgraph 阶段五[阶段五:内容分享] P1a -->|"分享给好友"| P1f["📱 小程序:分享弹窗"]:::page P1f -->|"目标类型, 目标ID, 分享渠道"| A13["🔗 POST /api/share/create"]:::api A13 -->|"targetType, targetId, shareChannel, title"| S13["⚙️ ShareService.createShareEvent()"]:::service S13 -->|"ShareEvent记录(含分享URL和有效期)"| D6["📦 share_events 表"]:::data P1f -->|"好友点击分享链接"| A14["🔗 POST /api/share/click"]:::api A14 -->|"shareEventId"| S14["⚙️ ShareService.recordClick()"]:::service S14 -->|"clickCount+1"| D6 P1f -->|"好友阅读分享内容"| A15["🔗 POST /api/share/read"]:::api A15 -->|"shareEventId, duration, readerIp"| S15["⚙️ ShareService.recordRead() + rewardReadPoints()"]:::service S15 -->|"readCount+1, 达标则发放阅读者积分"| D6 P1 -->|"我的分享"| A16["🔗 POST /api/share/my"]:::api A16 -->|"无参数"| S16["⚙️ ShareService.getUserShareEvents()"]:::service S16 -->|"用户分享记录列表"| D6 P1 -->|"分享统计"| A17["🔗 POST /api/share/stats"]:::api A17 -->|"无参数"| S17["⚙️ ShareService.getShareStats()"]:::service S17 -->|"分享统计信息"| D6 P1 -->|"家庭足迹"| A18["🔗 POST /api/share/family-footprint"]:::api A18 -->|"familyId"| S18["⚙️ ShareService.getFamilyFootprint()"]:::service S18 -->|"家庭成员分享汇总"| D6 end %% ================================================================ %% 阶段六:媒体管理 %% ================================================================ subgraph 阶段六[阶段六:媒体管理] P2["📱 小程序:任务执行页"]:::page P2 -->|"上传媒体文件"| A19["🔗 POST /api/media/upload"]:::api A19 -->|"file(多部件), taskId, creatorId, description"| S19["⚙️ MediaRecordService.uploadMedia()"]:::service S19 -->|"MediaRecord记录(含文件大小/类型校验)"| D7["📦 media_records 表"]:::data P2 -->|"保存文字记录"| A20["🔗 POST /api/media/upload-text"]:::api A20 -->|"taskId, creatorId, content"| S20["⚙️ MediaRecordService.saveTextRecord()"]:::service S20 -->|"MediaRecord(类型=text)"| D7 P2 -->|"查看任务媒体"| A21["🔗 POST /api/media/task/{taskId}"]:::api A21 -->|"路径参数: taskId"| S21["⚙️ MediaRecordService.getMediaByTaskId()"]:::service S21 -->|"任务的媒体文件列表"| D7 P2 -->|"删除媒体"| A22["🔗 POST /api/media/{mediaId}"]:::api A22 -->|"路径参数: mediaId, userId"| S22["⚙️ MediaRecordService.deleteMedia()"]:::service S22 -->|"删除记录"| D7 P2 -->|"批量关联媒体"| A23["🔗 POST /api/media/attach"]:::api A23 -->|"taskId, mediaIds列表"| S23["⚙️ MediaRecordService.attachMediaToTask()"]:::service S23 -->|"更新media关联的taskId"| D7 end %% ================================================================ %% 阶段七:管理后台文章管理 %% ================================================================ subgraph 阶段七[阶段七:管理后台文章管理] P3["🖥 管理后台:文章管理页"]:::page P3 -->|"查看列表(过滤)"| A24["🔗 POST /api/admin/articles/list"]:::api A24 -->|"status, categoryId, keyword, contentType, difficultyLevel, dimensionCode, tagId, auditStatus, page, size"| S24["⚙️ ArticleService.getAdminList()"]:::service S24 -->|"文章列表(含草稿)"| D2 P3 -->|"创建文章"| P3a["🖥 管理后台:创建文章表单"]:::page P3a -->|"标题, 正文, 分类, 摘要, 封面, 标签, 作者, 阅读时间, 内容类型, 难度等级, 关联维度, 可见性, 文章类型, 状态"| A25["🔗 POST /api/admin/articles/create"]:::api A25 -->|"全部文章字段"| S25["⚙️ ArticleService.create()"]:::service S25 -->|"计算wordCount, AI自动打标签"| D2 P3 -->|"编辑文章"| P3b["🖥 管理后台:编辑文章表单"]:::page P3b -->|"文章ID+修改字段"| A26["🔗 POST /api/admin/articles/update"]:::api A26 -->|"id, 修改字段"| S26["⚙️ ArticleService.update()"]:::service S26 -->|"更新字段, 内容变更则AI重新打标签"| D2 P3 -->|"查看详情"| A27["🔗 POST /api/admin/articles/detail"]:::api A27 -->|"id"| S27["⚙️ ArticleService.getById()"]:::service S27 -->|"Article对象"| D2 P3 -->|"删除"| A28["🔗 POST /api/admin/articles/delete"]:::api A28 -->|"id"| S28["⚙️ ArticleService.delete()"]:::service S28 -->|"删除记录"| D2 P3 -->|"发布/下架"| A29["🔗 POST /api/admin/articles/publish"]:::api A29 -->|"id, status"| S29["⚙️ ArticleService.toggleStatus()"]:::service S29 -->|"切换published/draft等状态"| D2 P3 -->|"设置精选"| A30["🔗 POST /api/admin/articles/toggle-featured"]:::api A30 -->|"id, isFeatured"| S30["⚙️ ArticleService.toggleFeatured()"]:::service S30 -->|"更新isFeatured字段"| D2 P3 -->|"提交审核"| A31["🔗 POST /api/admin/articles/submit-review"]:::api A31 -->|"id"| S31["⚙️ ArticleService.submitForReview()"]:::service S31 -->|"状态=pending, auditStatus=pending"| D2 P3 -->|"审核通过/驳回"| A32["🔗 POST /api/admin/articles/audit"]:::api A32 -->|"id, auditStatus(approved/rejected), auditReason"| S32["⚙️ ArticleService.auditArticle()"]:::service S32 -->|"approved→published / rejected→rejected(记录驳回原因)"| D2 P3 -->|"撤回"| A33["🔗 POST /api/admin/articles/withdraw"]:::api A33 -->|"id"| S33["⚙️ ArticleService.withdraw()"]:::service S33 -->|"状态=withdrawn"| D2 P3 -->|"驳回后转草稿"| A34["🔗 POST /api/admin/articles/re-draft"]:::api A34 -->|"id"| S34["⚙️ ArticleService.reDraft()"]:::service S34 -->|"状态=draft, 清空审核信息"| D2 P3 -->|"上传封面图"| A35["🔗 POST /api/admin/articles/upload/image"]:::api A35 -->|"file(多部件)"| S35["⚙️ FileStorageService.store()"]:::service S35 -->|"文件访问URL"| D8["📦 文件存储系统"]:::data end %% ================================================================ %% 阶段八:文章分类管理 %% ================================================================ subgraph 阶段八[阶段八:文章分类管理] P3 -->|"查看分类列表"| A36["🔗 POST /api/admin/articles/categories/list"]:::api A36 -->|"无参数"| S36["⚙️ ArticleCategoryService.listAll()"]:::service S36 -->|"全部分类(含禁用)"| D1 P3 -->|"创建分类"| A37["🔗 POST /api/admin/articles/categories/create"]:::api A37 -->|"name, icon, color, sortOrder"| S37["⚙️ ArticleCategoryService.create()"]:::service S37 -->|"新分类记录"| D1 P3 -->|"更新分类"| A38["🔗 POST /api/admin/articles/categories/update"]:::api A38 -->|"ArticleCategory对象(含id)"| S38["⚙️ ArticleCategoryService.update()"]:::service S38 -->|"更新分类字段"| D1 P3 -->|"删除分类"| A39["🔗 POST /api/admin/articles/categories/delete"]:::api A39 -->|"id"| S39["⚙️ ArticleCategoryService.delete()"]:::service S39 -->|"删除分类记录"| D1 end %% ================================================================ %% 角色关联 %% ================================================================ ROLE_USER -.- P1 ROLE_USER -.- P2 ROLE_ADMIN -.- P3 ``` ## 端点明细 ### 文章浏览 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/articles/list` | 文章列表 | 📱小程序 | `{categoryId, keyword, dimensionCode, page, size}` | `Page
` | | `POST /api/articles/categories` | 文章分类 | 📱小程序 | — | `[ArticleCategory]` | | `POST /api/articles/detail` | 文章详情 | 📱小程序 | `{id}` | `Article(含可见性校验)` | | `POST /api/articles/featured` | 精选文章 | 📱小程序 | `{size, dimensionCode}` | `[Article]` | | `POST /api/articles/record-read` | 记录阅读(进页面) | 📱小程序 | `{id, durationSeconds, childId}` | `{taskId, pointsEarned, alreadyCompleted, belowThreshold, minSeconds}` | | `POST /api/articles/report-reading-time` | 上报阅读时长 | 📱小程序 | `{articleId, durationSeconds, childId}` | `null` | | `POST /api/articles/reading-stats` | 阅读统计 | 📱小程序 | `{childId}` | `{totalArticles, totalDurationSeconds, targetDurationSeconds, progress}` | ### 每日贴士与AI互动 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/articles/daily-tip` | 每日贴士 | 📱小程序 | — | `{id, title, summary, ...}` | | `POST /api/articles/ai-questions` | AI问题列表 | 📱小程序 | `{articleId, childId}` | `{recordId, questions}` | | `POST /api/articles/submit-answers` | 提交AI答案 | 📱小程序 | `{recordId, answers(JSON), childId}` | `{score, feedback}` | ### 用户发布 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/articles/publish` | 发布文章(用户端) | 📱小程序 | `{title, content, coverImage, visibility, relatedDimensions}` | `{id, status, auditStatus}` | | `POST /api/articles/my-posts` | 我的帖子列表 | 📱小程序 | `{page, size}` | `Page
` | ### 分享 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/share/create` | 创建分享记录 | 📱小程序 | `{targetType, targetId, shareChannel, title}` | `ShareEvent` | | `POST /api/share/click` | 记录分享点击 | 📱小程序 | `{shareEventId}` | `null` | | `POST /api/share/read` | 记录分享阅读 | 📱小程序 | `{shareEventId, duration}` | `{rewarded}` | | `POST /api/share/my` | 我的分享记录 | 📱小程序 | — | `[ShareEvent]` | | `POST /api/share/stats` | 分享统计 | 📱小程序 | — | `Map` | | `POST /api/share/family-footprint` | 家庭足迹 | 📱小程序 | `{familyId}` | `Map` | ### 媒体管理 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/media/upload` | 上传媒体文件 | 📱小程序 | `file(多部件), taskId, creatorId, description` | `MediaRecord` | | `POST /api/media/upload-text` | 保存文字记录 | 📱小程序 | `{taskId, creatorId, content}` | `MediaRecord` | | `POST /api/media/task/{taskId}` | 获取任务媒体 | 📱小程序 | 路径参数: `taskId` | `[MediaRecord]` | | `POST /api/media/{mediaId}` | 删除媒体 | 📱小程序 | 路径参数: `mediaId` | `true` | | `POST /api/media/attach` | 批量关联媒体到任务 | 📱小程序 | `{taskId, mediaIds}` | `true` | ### 管理后台文章 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/admin/articles/list` | 文章列表(含草稿) | 🖥管理后台 | `{status, categoryId, keyword, contentType, difficultyLevel, dimensionCode, tagId, auditStatus, page, size}` | `Page
` | | `POST /api/admin/articles/create` | 创建文章 | 🖥管理后台 | 文章全部字段 | `{id}` | | `POST /api/admin/articles/update` | 更新文章 | 🖥管理后台 | `{id, title, content, categoryId, ...}` | `"更新成功"` | | `POST /api/admin/articles/detail` | 文章详情 | 🖥管理后台 | `{id}` | `Article` | | `POST /api/admin/articles/delete` | 删除文章 | 🖥管理后台 | `{id}` | `"删除成功"` | | `POST /api/admin/articles/publish` | 发布/下架文章 | 🖥管理后台 | `{id, status}` | `"操作成功"` | | `POST /api/admin/articles/toggle-featured` | 设置精选 | 🖥管理后台 | `{id, isFeatured}` | `"操作成功"` | | `POST /api/admin/articles/submit-review` | 提交审核 | 🖥管理后台 | `{id}` | `"已提交审核"` | | `POST /api/admin/articles/audit` | 审核文章 | 🖥管理后台 | `{id, auditStatus, auditReason}` | `"审核完成"` | | `POST /api/admin/articles/withdraw` | 撤回已发布文章 | 🖥管理后台 | `{id}` | `"已撤回"` | | `POST /api/admin/articles/re-draft` | 驳回后转草稿 | 🖥管理后台 | `{id}` | `"已保存到草稿箱"` | | `POST /api/admin/articles/upload/image` | 上传文章图片 | 🖥管理后台 | `file(多部件)` | `图片URL` | ### 管理后台分类 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/admin/articles/categories/list` | 分类列表 | 🖥管理后台 | — | `[ArticleCategory]` | | `POST /api/admin/articles/categories/create` | 创建分类 | 🖥管理后台 | `ArticleCategory对象` | `"创建成功"` | | `POST /api/admin/articles/categories/update` | 更新分类 | 🖥管理后台 | `ArticleCategory对象(含id)` | `"更新成功"` | | `POST /api/admin/articles/categories/delete` | 删除分类 | 🖥管理后台 | `{id}` | `"删除成功"` | ## 完整性分析 | # | 维度 | 评估 | 说明 | |---|------|------|------| | 1 | **流程完整性** | ✅ 完整 | 覆盖文章浏览→阅读记录→积分发放、每日贴士→AI互动、用户发布、分享全链路、媒体管理、管理后台文章CRUD+审核+分类管理共8个阶段 | | 2 | **异常路径** | ⚠️ 部分覆盖 | 阅读时长不足阈值不发放积分(返回belowThreshold标记)已覆盖;重复阅读去重(同文章+同孩子)在Service层校验;文章可见性权限在ArticlePermissionService处理 | | 3 | **端点覆盖** | ✅ 完整 | 39个端点全部映射到流程图中,与代码实际暴露的 `/api/articles/*`、`/api/share/*`、`/api/media/*`、`/api/admin/articles/*` 一致 | | 4 | **角色覆盖** | ✅ 完整 | 普通用户(家长/孩子)浏览阅读发布、管理员后台管理、系统自动(积分发放/去重)均已覆盖 | | 5 | **数据实体** | ✅ 完整 | articles表、article_categories表、article_reading_records表、share_events表、media_records表、article_tags表、tasks表、points_logs表、energy_records表均在图中映射 | | 6 | **一致性** | ✅ 与代码一致 | 所有端点路径与实际Controller(ArticleController、ArticleCategoryController、ShareController、MediaController、AdminArticleController)中的 `@PostMapping` 匹配 | ## 数据实体关系 ```mermaid erDiagram Article ||--o{ ArticleReadingRecord : "阅读记录" Article ||--o{ ShareEvent : "分享事件" Article }o--|| ArticleCategory : "所属分类" Article ||--o{ ArticleTag : "文章标签" Task ||--o{ MediaRecord : "任务媒体" ShareEvent ||--o{ ShareClick : "点击事件" ShareEvent ||--o{ ShareRead : "阅读事件" Article { Long id PK Long categoryId FK string title string summary string coverImage string content string tags string author string articleType "normal/premium/original" string contentType "article/knowledge/course/tip" int difficultyLevel "1-5" int wordCount string relatedDimensions string dimensionWeights "JSON五维权重" string visibility "public/private/restricted" string status "draft/pending/rejected/published/withdrawn" string auditStatus "approved/pending/rejected" string auditReason int isFeatured int viewCount Long createdBy string authorType "admin/parent/child" } ArticleCategory { Long id PK string name string icon string color int sortOrder int status "1=启用" } ArticleReadingRecord { Long id PK Long articleId FK Long userId FK Long childId FK int durationSeconds Long taskId "关联的积分任务" date readAt } ShareEvent { Long id PK Long userId FK string targetType "article/product" Long targetId string shareChannel "wechat/timeline" string shareTitle int clickCount int readCount string status string shareUrl date expiresAt } MediaRecord { Long id PK Long taskId FK Long creatorId FK string fileType "image/video/audio/text" string fileUrl string description long fileSize } ```