Files
VLoop/feedsystem_video_go项目设计.md
2025-12-30 01:21:25 +08:00

186 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# feedsystem_video_go
> 项目介绍本项目是一款由Go开发的高性能短视频Feed流系统提供账号、视频、点赞、评论、关注Social与 Feed 等接口并用Redis和RabbitMQ优化同时编写了docker文件使其方便部署。
# 技术栈
| 维度 | 组件/工具 | 说明 |
| --------------- | ---------------------------- | ------------------------------------------------------------ |
| 开发语言 | Go (Golang) | 后端核心业务逻辑实现API + Worker。 |
| Web 框架 | Gin | HTTP 路由注册、参数绑定、统一返回与中间件链JWTAuth / SoftJWTAuth。 |
| ORM 框架 | GORM | 模型定义、CRUD、启动时 AutoMigrate 自动迁移表结构。 |
| 持久化 | MySQL | 存储 `Account / Video / Like / Comment / Social` 五张核心表及相关统计字段(如 likes_count、popularity。 |
| 缓存/排行榜 | Redis可选 | Token 校验缓存(`account:<id>`、Feed 匿名流缓存、视频详情缓存、热榜 ZSET滑动窗口聚合/快照分页)。 |
| 消息队列 | RabbitMQ可选 | Topic 事件总线:`like.events``comment.events``social.events``video.popularity.events`;由 Worker 异步消费写 MySQL/Redis支持异常降级直写。 |
| 异步执行 | WorkerGo | `cmd/worker`LikeWorker/CommentWorker/SocialWorker/PopularityWorker异步落库、更新计数/热度、更新热榜与缓存失效。 |
| 文件存储 | Local Disk可替换对象存储 | 视频与封面文件存放本地目录(生产可替换为 OSS/S3/MinIO。 |
| 容器化/依赖编排 | Docker / Docker Compose | 一键拉起 RabbitMQ 等依赖(可配合 `./start.sh`),便于本地联调与部署环境一致性。 |
| 接口调试 | Postman Collection | `test/postman.json`:预置变量、批量跑接口与部分断言脚本。 |
| 前端 | Vue + Vite | 前端工程 `frontend/`,通过 Vite 代理 `/api` 对接后端。 |
## 模块设计
### 用户系统
#### 表设计
![image-20251226232237606](picture/用户表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/Redis) | 核心说明 |
| ----------------- | ------------------------------------------------------------ | ---------------------------------------------- | ----------------- | ------------------------------------------------------------ |
| Handler | POST `/account/register` | `{username,password}` -> `{account}` | MySQL ✅ | 注册账号;密码 bcrypt 哈希入库。 |
| Handler | POST `/account/login` | `{username,password}` -> `{token}` | MySQL ✅ / Redis ✅ | 登录成功写 `account.token`Redis 写 `account:<id>` TTL 24h可选。 |
| Handler | POST `/account/changePassword` | `{username,old_password,new_password}` -> `{}` | MySQL ✅ / Redis ✅ | 修改密码成功后清空 token强制下线删除 Redis token 缓存。 |
| Handler | POST `/account/findByID` | `{id}` -> `{account}` | MySQL ✅ | 按 ID 查用户。 |
| Handler | POST `/account/findByUsername` | `{username}` -> `{account}` | MySQL ✅ | 按用户名查用户(前端常用保存 accountId/vloggerId。 |
| Handler | POST `/account/rename` | `{new_username}` -> `{token}` | MySQL ✅ / Redis ✅ | 改名并**生成新 JWT**;旧 token 立即失效;更新 DB/Redis。 |
| Handler | POST `/account/logout` | `{}` -> `{}` | MySQL ✅ / Redis ✅ | 清空 DB token 并删除 Redis token旧 token 立即失效。 |
| Service(建议命名) | `Register/Login/ChangePassword/FindByID/FindByUsername/Rename/Logout` | - | - | 对应 Handler 的业务实现。 |
### 视频系统
#### 表设计
![image-20251226232342038](picture/视频表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/Redis) | 核心说明 |
| ----------------- | ---------------------------------- | ----------------------------------------------------- | ----------------- | -------------------------------------------------- |
| Handler | POST `/video/publish` | `{title,description,play_url,cover_url}` -> `{video}` | MySQL ✅ | JWT 保护;写视频记录;热度字段初始化。 |
| Handler | POST `/video/listByAuthorID` | `{author_id}` -> `{videos[]}` | MySQL ✅ | 作者主页视频列表。 |
| Handler | POST `/video/getDetail` | `{id}` -> `{video_detail}` | MySQL ✅ / Redis ✅ | 视频详情可走缓存Redis 可选);变更时需失效。 |
| Service(建议命名) | `Publish/ListByAuthorID/GetDetail` | - | - | `GetDetail`:优先 Redis未命中回源 MySQL 后回填。 |
### 点赞系统
#### 表设计
![image-20251226232421969](picture/点赞表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/Redis/MQ) | 核心说明 |
| ----------------- | --------------------- | ---------------------------- | ------------------------------ | ------------------------------------------------------------ |
| Handler | POST `/like/isLiked` | `{video_id}` -> `{is_liked}` | MySQL ✅ | JWT 保护;判断当前用户是否点赞该视频。 |
| Handler | POST `/like/like` | `{video_id}` -> `{}` | MQ ✅(可选) / MySQL ✅ / Redis ✅ | 优先发布 `like.events``like.like`)与热度增量事件;发布失败降级直写。 |
| Handler | POST `/like/unlike` | `{video_id}` -> `{}` | MQ ✅(可选) / MySQL ✅ / Redis ✅ | 同上(`like.unlike`);更新 likes_count 与 popularity。 |
| Service(建议命名) | `IsLiked/Like/Unlike` | - | - | 与 MQ 降级策略绑定:任一发布失败则对失败目标直写。 |
### 评论系统
#### 表设计
![image-20251226232506945](picture/评论表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/Redis/MQ) | 核心说明 |
| ----------------- | ------------------------ | ----------------------------------- | ------------------------------ | ------------------------------------------------------------ |
| Handler | POST `/comment/listAll` | `{video_id}` -> `{comments[]}` | MySQL ✅ | 列出某视频全部评论。 |
| Handler | POST `/comment/publish` | `{video_id,content}` -> `{comment}` | MQ ✅(可选) / MySQL ✅ / Redis ✅ | 发布 `comment.events``comment.publish`)并触发 `popularity + 1`;发布失败降级直写。 |
| Handler | POST `/comment/delete` | `{comment_id}` -> `{}` | MQ ✅(可选) / MySQL ✅ / Redis ✅ | 仅作者可删;发布 `comment.delete`;必要时失效缓存/更新热度。 |
| Service(建议命名) | `ListAll/Publish/Delete` | - | - | 评论写入与热度增量解耦到 MQ/Worker。 |
### 关注系统
#### 表设计
![image-20251226232538787](picture/关注表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/MQ) | 核心说明 |
| ----------------- | ------------------------------------------------ | ---------------------------------- | -------------------- | -------------------------------------------- |
| Handler | POST `/social/follow` | `{vlogger_id}` -> `{}` | MQ ✅(可选) / MySQL ✅ | JWT 保护;关注事件可异步写入。 |
| Handler | POST `/social/unfollow` | `{vlogger_id}` -> `{}` | MQ ✅(可选) / MySQL ✅ | 取关事件可异步写入。 |
| Handler | POST `/social/getAllFollowers` | `{vlogger_id?}` -> `{followers[]}` | MySQL ✅ | vlogger_id 可空:默认当前登录账号。 |
| Handler | POST `/social/getAllVloggers` | `{follower_id?}` -> `{vloggers[]}` | MySQL ✅ | follower_id 可空:默认当前登录账号。 |
| Service(建议命名) | `Follow/Unfollow/GetAllFollowers/GetAllVloggers` | - | - | follow/unfollow 可走 MQ 异步,异常降级直写。 |
### Feed系统
#### 返回结构体设计,方便前端渲染。
![image-20251226232110997](picture/Feed返回表.png)
#### 相关方法
| 层级 | 方法/路由 | 输入 -> 输出 | 存储(MySQL/Redis) | 核心说明 |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------- | ------------------------------------------------------------ |
| Handler | POST `/feed/listLatest` | `{limit,latest_time}` -> `{videos[], next_time}` | MySQL ✅ / Redis ✅ | 匿名流可缓存(短 TTL`latest_time` 游标分页。 |
| Handler | POST `/feed/listLikesCount` | `{limit,likes_count_before,id_before}` -> `{videos[], next_likes_count_before,next_id_before}` | MySQL ✅ | 复合游标分页:`likes_count + id` 保证稳定不重不漏。 |
| Handler | POST `/feed/listByPopularity` | `{limit,as_of,offset}` -> `{videos[], as_of,next_offset}` | Redis ✅ / MySQL ✅ | 热榜优先 Redis ZSET快照+offsetRedis 不可用回退 MySQL/简化逻辑。 |
| Handler | POST `/feed/listByFollowing` | `{limit}` -> `{videos[]}` | MySQL ✅ | 需要登录(关注流);按关注关系聚合视频。 |
| Service(建议命名) | `ListLatest/ListLikesCount/ListByPopularity/ListByFollowing` | - | - | `ListByPopularity`:滑动窗口聚合 + 快照分页;`ListLatest`:匿名缓存。 |
### 各个模块的关系
![image-20251226232632102](picture/表关系.png)
## Redis优化部分
| 业务模块 | 数据类型 | Key 模式 | Value 内容 | TTL有效期 | 备注 / 高可用策略 |
| ----------------------- | -------- | ------------------------------------------------- | --------------------------------- | ------------- | ------------------------------------------------------------ |
| 鉴权 Token | STRING | `account:<accountID>` | `jwt_token` | 24h | **自愈机制**:鉴权优先查 Redis未命中/失败回退 MySQL 校验 `account.token`;通过后回填 Redis。 |
| Feed 匿名流缓存 | STRING | `feed:listLatest:limit=<n>:before=<u>` | `ListLatestResponse`JSON | 5s | **防击穿**:缓存未命中时用 `lock:<cacheKey>``SETNX`)互斥回源(如 500ms/短等待),避免并发打爆 DB。 |
| Feed 关注流缓存(可选) | STRING | `feed:listByFollow:limit=<n>:uid=<id>:before=<u>` | `ListByFollowResponse`JSON | 5s | **防击穿**:同样使用 `lock:<cacheKey>` 互斥回源(短等待/快速失败兜底)。 |
| 视频详情缓存 | STRING | `video:detail:id=<videoID>` | `Video`JSON | 5m | **一致性**:视频删除/更新时主动 `DEL`**防击穿**:详情回源可加互斥锁(如 2s 锁 TTL。 |
| 实时热榜窗 | ZSET | `hot:video:1m:<yyyyMMddHHmm>` | member=`videoID` score=`热度增量` | 2h | **滚动窗口**:按分钟分桶写入;用 `ZINCRBY` 更新热度,减少单 Key 竞争。 |
| 热榜快照 | ZSET | `hot:video:merge:1m:<as_of>` | `ZUNIONSTORE` 合并结果 | 2m | **聚合查询**:合并最近 60 个分钟窗生成快照;快照分页读取,保证分页一致性与稳定性。 |
## RabbitMQ优化部分
| 业务模块 | Exchange / RoutingKey | 事件类型 | Payload示例字段 | 消费者Worker | 失败/降级策略 |
| -------- | ----------------------------------------------------- | ------------- | --------------------------------------------------- | ------------------ | ------------------------------------------------------------ |
| 点赞 | `like.events` / `like.like` `like.unlike` | 点赞/取消点赞 | `{account_id, video_id, ts}` | `LikeWorker` | 发布失败对失败目标降级直写MySQL 或 Redis确保 `likes` 与计数可落地。 |
| 评论 | `comment.events` / `comment.publish` `comment.delete` | 发布/删除评论 | `{account_id, video_id, comment_id?, content?, ts}` | `CommentWorker` | 发布失败:降级直写 MySQL热度增量事件失败则直接更新 Redis或同步更新 popularity。 |
| 关注 | `social.events` / `social.follow` `social.unfollow` | 关注/取关 | `{follower_id, vlogger_id, ts}` | `SocialWorker` | 发布失败:降级直写 MySQL保证关注关系即时生效。 |
| 热度增量 | `video.popularity.events` / `video.popularity.update` | 热度更新 | `{video_id, delta, reason, ts}` | `PopularityWorker` | `UpdatePopularity` 发布失败:直接更新 Redis 热榜;并触发详情缓存失效(如需要)。 |
# 整体架构
![image-20251230003301451](picture/整体架构.png)
# 流程图
## 整体流程图
![整体流程](picture/整体流程.png)
## 核心子流程图
### 子流程 A登录/鉴权/撤销 token
![登录_鉴权_撤销token](picture/登录_鉴权_撤销token.png)
### 子流程 B点赞/评论 + 异步落库 + 热度更新 + 降级
![点赞评论_异步落库_热度更新_降级](picture/点赞评论_异步落库_热度更新_降级.png)
### 子流程 CFeed 软鉴权 + 缓存/热榜 + 分页游标
![Feed 软鉴权_缓存热榜_分页游标](picture/Feed%20软鉴权_缓存热榜_分页游标.png)
# 亮点解析
| 维度 | 亮点名称 | 技术实现与设计细节 | 业务价值与优势 |
| ---------- | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 缓存架构 | 鉴权缓存自愈机制 | 鉴权中间件优先查 Redis`account:<accountID>`);若失效/不可用则回退 MySQL 校验 `account.token`;通过后自动回填 Redis自愈。 | 兼顾高性能与鲁棒性Redis 宕机不影响鉴权;恢复后可自动“热启动”缓存,降低 DB 压力。 |
| 缓存架构 | 分布式锁防击穿 | Feed 匿名流/视频详情等缓存未命中时,用 Redis `SETNX` 做互斥锁控制,仅允许一个请求回源构建缓存,其余等待/返回兜底结果。 | 避免热点 Key 过期瞬间大量并发回源,保护 MySQL提升高峰期稳定性。 |
| 缓存架构 | 滑动窗口热榜快照 | 互动/热度按分钟写入 ZSET查询时用 `ZUNIONSTORE` 聚合最近 N 个时间窗(如 60 分钟)生成“短期快照”并分页读取。 | 降低高频写 Key 竞争;利用快照保证分页一致性,减少“榜单抖动”。 |
| 缓存架构 | 主动失效一致性 | 视频删除/改名/点赞/评论导致数据变化时,主动 `DEL` 相关详情缓存、Feed 缓存或热榜相关缓存。 | 提升数据一致性与用户体验:避免看到已删除/过期/状态错误的旧数据。 |
| 分页设计 | 双字段复合游标分页 | `/feed/listLikesCount` 使用 `likes_count_before + id_before` 作为复合游标(两者一起定位下一页)。 | 解决“点赞数相同”排序不稳定问题,确保不重复、不漏数据,分页稳定可复现。 |
| 分页设计 | 快照式稳定分页 | `/feed/listByPopularity` 首次请求生成 `as_of`(分钟级快照版本),后续分页携带相同 `as_of + offset`。 | 规避热度实时变化导致的“跳页/重复/缺失”,滚动浏览更稳定。 |
| 安全鉴权 | 软硬鉴权兼容模式 | 提供 `JWTAuth`(强制拦截)与 `SoftJWTAuth`(可不带 token带了必须合法否则 401。 | 既支持匿名浏览 Feed又支持登录态个性化如点赞/关注状态),体验与安全兼顾。 |
| 系统稳定性 | 多级存储降级设计 | Redis 为可选依赖:连接失败自动降级走 MySQLRedis 恢复后通过请求自愈回填缓存。 | 提升环境适应性与容灾能力,基础设施异常时核心业务仍可用。 |
| 异步架构 | RabbitMQ 事件驱动解耦 | 使用 RabbitMQ topic exchanges`like.events``comment.events``social.events``video.popularity.events`;后端接口仅负责发布事件,`cmd/worker` 内的 Like/Comment/Social/Popularity Worker 异步消费并更新 MySQL/Redis。 | 削峰填谷、降低接口响应时延;写扩散与热度计算解耦,提升吞吐与可维护性,便于后续扩展更多消费者(统计、风控等)。 |
| 异步架构 | MQ 异常降级直写 | 点赞/评论:尝试同时发布“写 MySQL 的队列”+“写 Redis 热度队列”任一发布失败则对失败目标降级为直写MySQL 或 Redis`UpdatePopularity` 发布失败则直接更新 Redis。 | MQ 不可用时仍能保证核心数据正确落库/可见,避免“请求成功但数据不落地”的一致性风险。 |
| 工程交付 | Docker Compose 一键依赖拉起 | 通过 `docker compose up -d rabbitmq`(或 `./start.sh` 自动拉起)快速启动 RabbitMQ 等依赖;本地环境以容器化方式对齐。 | 降低环境搭建成本,减少“在我机器上没问题”;便于 CI/本地联调/演示,提升交付效率。 |
| 工程交付 | 脚本化一键启动与可拆分运行 | `./start.sh` 默认启动后端+前端,并可用 `START_FRONTEND=0` 仅启后端Worker 可单独运行 `go run ./cmd/worker`。 | 提升开发体验与部署灵活性既能一键体验全链路也能按需拆分进程满足生产部署API/Worker 独立伸缩)。 |
| 工程质量 | 自动化基础设施 | 服务启动时执行 GORM `AutoMigrate` 自动同步 `Account/Video/Like/Comment/Social` 等表结构。 | 简化部署与迭代成本,“开箱即用”,保证 Schema 与模型一致性。 |