From 0275ad7ea36cae4074698f1d0ad3ca59aeb1a679 Mon Sep 17 00:00:00 2001 From: leonincs Date: Sun, 26 Apr 2026 15:04:16 +0800 Subject: [PATCH] chore: Remove unrelated files and ignored local artifacts --- .gitignore | 4 +- .sisyphus/plans/optimization-plan.md | 1014 ----------------- data/.erlang.cookie | 1 - data/mnesia/rabbit@1ae715dbc287-feature_flags | 23 - .../rabbit@1ae715dbc287/DECISION_TAB.LOG | Bin 155 -> 0 bytes data/mnesia/rabbit@1ae715dbc287/LATEST.LOG | Bin 1695 -> 0 bytes .../rabbit@1ae715dbc287/cluster_nodes.config | 1 - .../rabbit@1ae715dbc287/00000003.wal | Bin 13940 -> 0 bytes .../RABBITDRASO8J6QGNT/00000001.segment | Bin 159605 -> 0 bytes .../RABBITDRASO8J6QGNT/config | 11 - .../rabbit@1ae715dbc287/meta.dets | Bin 5925 -> 0 bytes .../rabbit@1ae715dbc287/names.dets | Bin 5905 -> 0 bytes .../vhosts/628WB79CIFDYO9LJI6DKMI09L/.config | 2 - .../vhosts/628WB79CIFDYO9LJI6DKMI09L/.vhost | 1 - .../msg_store_persistent/0.rdq | 0 .../msg_store_persistent/clean.dot | 2 - .../msg_store_persistent/file_summary.ets | Bin 591 -> 0 bytes .../msg_store_persistent/msg_store_index.ets | Bin 512 -> 0 bytes .../msg_store_transient/0.rdq | 0 .../msg_store_transient/clean.dot | 2 - .../msg_store_transient/file_summary.ets | Bin 591 -> 0 bytes .../msg_store_transient/msg_store_index.ets | Bin 512 -> 0 bytes .../DIH3SR5T3GFJ86CWM34SYIQ8P/.queue_name | 2 - .../DIH3SR5T3GFJ86CWM34SYIQ8P/journal.jif | 0 .../628WB79CIFDYO9LJI6DKMI09L/recovery.dets | Bin 6003 -> 0 bytes data/mnesia/rabbit@1ae715dbc287/node-type.txt | 1 - .../nodes_running_at_shutdown | 1 - .../quorum/rabbit@1ae715dbc287/00000003.wal | 1 - .../quorum/rabbit@1ae715dbc287/meta.dets | Bin 5464 -> 0 bytes .../quorum/rabbit@1ae715dbc287/names.dets | Bin 5464 -> 0 bytes .../rabbit_durable_exchange.DCD | Bin 1398 -> 0 bytes .../rabbit_durable_queue.DCD | Bin 310 -> 0 bytes .../rabbit_durable_route.DCD | 1 - .../rabbit_runtime_parameters.DCD | Bin 194 -> 0 bytes data/mnesia/rabbit@1ae715dbc287/rabbit_serial | 1 - .../rabbit_topic_permission.DCD | 1 - .../rabbit@1ae715dbc287/rabbit_user.DCD | Bin 230 -> 0 bytes .../rabbit_user_permission.DCD | Bin 188 -> 0 bytes .../rabbit@1ae715dbc287/rabbit_vhost.DCD | Bin 170 -> 0 bytes data/mnesia/rabbit@1ae715dbc287/schema.DAT | Bin 22105 -> 0 bytes docs/plans/2025-04-25-features-design.md | 90 -- docs/plans/2025-04-25-optimization-design.md | 149 --- docs/plans/2025-04-25-p1-implementation.md | 411 ------- frontend/index.html | 1 - frontend/public/vite.svg | 1 - frontend/src/assets/vue.svg | 1 - frontend/src/components/HelloWorld.vue | 41 - 47 files changed, 3 insertions(+), 1760 deletions(-) delete mode 100644 .sisyphus/plans/optimization-plan.md delete mode 100644 data/.erlang.cookie delete mode 100644 data/mnesia/rabbit@1ae715dbc287-feature_flags delete mode 100644 data/mnesia/rabbit@1ae715dbc287/DECISION_TAB.LOG delete mode 100644 data/mnesia/rabbit@1ae715dbc287/LATEST.LOG delete mode 100644 data/mnesia/rabbit@1ae715dbc287/cluster_nodes.config delete mode 100644 data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/00000003.wal delete mode 100644 data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/00000001.segment delete mode 100644 data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/config delete mode 100644 data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/meta.dets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/names.dets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/.config delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/.vhost delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/0.rdq delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/clean.dot delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/file_summary.ets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/msg_store_index.ets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/0.rdq delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/clean.dot delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/file_summary.ets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/msg_store_index.ets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/queues/DIH3SR5T3GFJ86CWM34SYIQ8P/.queue_name delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/queues/DIH3SR5T3GFJ86CWM34SYIQ8P/journal.jif delete mode 100644 data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/recovery.dets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/node-type.txt delete mode 100644 data/mnesia/rabbit@1ae715dbc287/nodes_running_at_shutdown delete mode 100644 data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/00000003.wal delete mode 100644 data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/meta.dets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/names.dets delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_durable_exchange.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_durable_queue.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_durable_route.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_runtime_parameters.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_serial delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_topic_permission.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_user.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_user_permission.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/rabbit_vhost.DCD delete mode 100644 data/mnesia/rabbit@1ae715dbc287/schema.DAT delete mode 100644 docs/plans/2025-04-25-features-design.md delete mode 100644 docs/plans/2025-04-25-optimization-design.md delete mode 100644 docs/plans/2025-04-25-p1-implementation.md delete mode 100644 frontend/public/vite.svg delete mode 100644 frontend/src/assets/vue.svg delete mode 100644 frontend/src/components/HelloWorld.vue diff --git a/.gitignore b/.gitignore index aa1440b..4e39804 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .run/ backend/.run/ +data/ +.sisyphus/ +docs/ # # Binaries for programs and plugins @@ -31,4 +34,3 @@ go.work.sum # Editor/IDE .idea/ .vscode/ - diff --git a/.sisyphus/plans/optimization-plan.md b/.sisyphus/plans/optimization-plan.md deleted file mode 100644 index fe36818..0000000 --- a/.sisyphus/plans/optimization-plan.md +++ /dev/null @@ -1,1014 +0,0 @@ -# feedsystem_video_go 全项目优化实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. - -**Goal:** 修复 17 项代码审查发现的问题,涵盖安全性、数据库性能、MQ 可靠性、代码质量、前端架构,按三批渐进交付。 - -**Architecture:** 风险驱动分批 — P1 修复直接威胁稳定性的 Bug,P2 加固安全面和规范化,P3 前端拆分和服务瘦身。每批独立验证可发布。 - -**Tech Stack:** Go 1.24.5 + Gin + GORM + MySQL + Redis + RabbitMQ + Vue 3 + TypeScript + Pinia - -**参考设计文档:** `docs/plans/2025-04-25-optimization-design.md` - ---- - -## P1 止血(4项) - ---- - -### Task 1: Router 变量赋值 Bug - -**Files:** -- Modify: `backend/internal/http/router.go:145-148` - -**Step 1: 修复** - -```go -// 定位到 router.go 第 147 行 -timelineMQ, err := rabbitmq.NewTimelineMQ(rmq) -if err != nil { - log.Printf("timelineMQ init failed (mq disabled): %v", err) - socialMQ = nil // ❌ 当前 -``` - -改为: -```go - timelineMQ = nil // ✅ -``` - -**Step 2: 编译验证** - -```bash -cd backend && go build ./... -``` -Expected: 编译成功 (exit code 0) - -**Step 3: Commit** - -```bash -git add backend/internal/http/router.go -git commit -m "fix: router timelineMQ 初始化失败时误将 socialMQ 置空" -``` - ---- - -### Task 2: 数据库复合索引 - -**Files:** -- Modify: `backend/internal/video/video_entity.go:5-16` - -**Step 1: 修改 Video 模型** - -```go -type Video struct { - ID uint `gorm:"primaryKey" json:"id"` - AuthorID uint `gorm:"index;not null" json:"author_id"` - Username string `gorm:"type:varchar(255);not null" json:"username"` - Title string `gorm:"type:varchar(255);not null" json:"title"` - Description string `gorm:"type:varchar(255);" json:"description,omitempty"` - PlayURL string `gorm:"type:varchar(255);not null" json:"play_url"` - CoverURL string `gorm:"type:varchar(255);not null" json:"cover_url"` - CreateTime time.Time `gorm:"autoCreateTime;index:idx_videos_create_time,sort:desc" json:"create_time"` - LikesCount int64 `gorm:"column:likes_count;not null;default:0;index:idx_videos_likes_count_id,priority:1,sort:desc" json:"likes_count"` - Popularity int64 `gorm:"column:popularity;not null;default:0;index:idx_videos_popularity_time_id,priority:1,sort:desc" json:"popularity"` -} -``` - -**Step 2: 编译验证** - -```bash -cd backend && go build ./... -``` -Expected: 编译成功 - -**Step 3: 验证索引创建(启动时 AutoMigrate 自动执行)** - -启动后端,观察日志中无 MySQL 错误: -```bash -cd backend && CONFIG_PATH=configs/config.compose-local.yaml go run ./cmd 2>&1 | head -20 -``` -Expected: 启动成功,GORM AutoMigrate 完成无报错 - -**Step 4: Commit** - -```bash -git add backend/internal/video/video_entity.go -git commit -m "perf: Video 表增加 Feed 流排序查询复合索引(create_time/likes_count/popularity)" -``` - ---- - -### Task 3: ListByAuthorID 加 LIMIT - -**Files:** -- Modify: `backend/internal/video/video_repo.go:39-48` - -**Step 1: 修改查询** - -```go -func (vr *VideoRepository) ListByAuthorID(ctx context.Context, authorID uint) ([]Video, error) { - var videos []Video - if err := vr.db.WithContext(ctx). - Where("author_id = ?", authorID). - Order("create_time desc"). - Limit(200). - Find(&videos).Error; err != nil { - return nil, err - } - return videos, nil -} -``` - -**Step 2: 编译验证** - -```bash -cd backend && go build ./... -``` -Expected: 编译成功 - -**Step 3: Commit** - -```bash -git add backend/internal/video/video_repo.go -git commit -m "fix: ListByAuthorID 加 Limit(200) 防止海量数据内存溢出" -``` - ---- - -### Task 4: MQ Worker 死信队列 + 退避重试 - -**Files:** -- Modify: `backend/internal/middleware/rabbitmq/` — 队列声明增加 DLX 参数 -- Modify: `backend/internal/worker/likeworker.go` — handleDelivery 增加重试计数 -- Modify: `backend/internal/worker/commentworker.go` — 同上 -- Modify: `backend/internal/worker/socialworker.go` — 同上 -- Modify: `backend/internal/worker/popularityworker.go` — 同上 - -**Step 1: 在 rabbitmq 包中声明 DLX** - -在 `backend/internal/middleware/rabbitmq/` 中新增 `dlx.go`: - -```go -package rabbitmq - -import ( - "log" - amqp "github.com/rabbitmq/amqp091-go" -) - -const ( - DLXExchange = "dlx.events" - MaxRetryCount = 3 -) - -// DeclareDLX 声明死信交换和死信队列 -func DeclareDLX(ch *amqp.Channel, queueName string) error { - if err := ch.ExchangeDeclare( - DLXExchange, "topic", true, false, false, false, nil, - ); err != nil { - return err - } - dlxQueue := queueName + ".dlx" - _, err := ch.QueueDeclare( - dlxQueue, true, false, false, false, nil, - ) - if err != nil { - return err - } - if err := ch.QueueBind(dlxQueue, "#", DLXExchange, false, nil); err != nil { - return err - } - log.Printf("DLX declared: exchange=%s, queue=%s", DLXExchange, dlxQueue) - return nil -} - -// QueueArgsWithDLX 返回带 DLX 配置的队列参数 -func QueueArgsWithDLX() amqp.Table { - return amqp.Table{ - "x-dead-letter-exchange": DLXExchange, - "x-message-ttl": int32(60000), // 死信消息 60s 后移入 DLX 队列 - } -} - -// GetRetryCount 从 x-death header 中提取重试次数 -func GetRetryCount(d amqp.Delivery) int { - deaths, ok := d.Headers["x-death"].([]interface{}) - if !ok || len(deaths) == 0 { - return 0 - } - death, ok := deaths[0].(amqp.Table) - if !ok { - return 0 - } - count, ok := death["count"].(int64) - if !ok { - return 0 - } - return int(count) -} -``` - -**Step 2: 修改 Worker 的队列声明,传入 DLX 参数** - -以 LikeWorker 为例(其他 Worker 同理),修改 `likeworker.go` 中声明队列的地方。需要在每个 Worker 初始化时调用 `DeclareDLX`,并在 `QueueDeclare` 时传入 args。 - -在 `middleware/rabbitmq/` 中找到各 MQ 初始化函数(如 `NewLikeMQ`),修改队列声明加上 `QueueArgsWithDLX()`。 - -**Step 3: 修改 handleDelivery 增加重试上限** - -```go -func (w *LikeWorker) handleDelivery(ctx context.Context, d amqp.Delivery) { - if err := w.process(ctx, d.Body); err != nil { - retryCount := rabbitmq.GetRetryCount(d) - if retryCount >= rabbitmq.MaxRetryCount { - log.Printf("like worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err) - _ = d.Ack(false) // Ack 触发 DLX - return - } - log.Printf("like worker: failed to process message (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err) - _ = d.Nack(false, true) - return - } - _ = d.Ack(false) -} -``` - -**Step 4: 编译验证** - -```bash -cd backend && go build ./... -``` -Expected: 编译成功 - -**Step 5: 功能验证** - -启动 Worker,观察日志: -- 处理成功 → Ack -- 处理失败 < 3 次 → Nack 重试 -- 处理失败 ≥ 3 次 → Ack(进入 DLX)+ 日志告警 - -**Step 6: Commit** - -```bash -git add backend/internal/middleware/rabbitmq/dlx.go backend/internal/worker/ -git commit -m "feat: MQ Worker 增加死信队列 — 重试上限 3 次后移入 DLX 并告警" -``` - ---- - -## P2 加固(6项) - ---- - -### Task 5: rand.Read 错误处理 - -**Files:** -- Modify: `backend/internal/video/video_handler.go:164-168` -- Modify: 调用 `randHex()` 的 `UploadVideo` 和 `UploadCover` 方法 - -**Step 1: 修改 randHex 返回 error** - -```go -func randHex(n int) (string, error) { - b := make([]byte, n) - if _, err := rand.Read(b); err != nil { - return "", fmt.Errorf("rand.Read: %w", err) - } - return hex.EncodeToString(b), nil -} -``` - -**Step 2: 修改调用方** - -在 `UploadVideo` (line 96) 和 `UploadCover` (line 148) 中: - -```go -filename, err := randHex(16) -if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate filename"}) - return -} -filename = filename + ext -``` - -**Step 3: 编译验证** - -```bash -cd backend && go build ./... -``` -Expected: 编译成功 - -**Step 4: Commit** - -```bash -git add backend/internal/video/video_handler.go -git commit -m "fix: randHex 不再忽略 rand.Read 错误,防止弱随机文件名冲突" -``` - ---- - -### Task 6: Handler 错误码精确化 - -**Files:** -- Create: `backend/internal/http/errors.go` — 哨兵错误 + 分类函数 -- Modify: `backend/internal/video/video_service.go` — Service 返回哨兵错误 -- Modify: `backend/internal/video/like_service.go` — 同上 -- Modify: `backend/internal/video/video_handler.go` — Handler 使用 classifyHTTPStatus -- Modify: 其他 handler 文件同理 - -**Step 1: 创建哨兵错误和分类函数** - -```go -// backend/internal/http/errors.go -package http - -import ( - "errors" - "net/http" - - "gorm.io/gorm" -) - -var ( - ErrUnauthorized = errors.New("unauthorized") - ErrValidation = errors.New("validation error") -) - -func ClassifyHTTPStatus(err error) int { - switch { - case err == nil: - return http.StatusOK - case errors.Is(err, ErrUnauthorized): - return http.StatusUnauthorized - case errors.Is(err, ErrValidation): - return http.StatusBadRequest - case errors.Is(err, gorm.ErrRecordNotFound): - return http.StatusNotFound - default: - return http.StatusInternalServerError - } -} -``` - -**Step 2: Service 层返回哨兵错误** - -示例 — `video_service.go` 中 `Delete` 方法: - -```go -if video.AuthorID != authorID { - return http.ErrUnauthorized -} -``` - -`Publish` 方法中的参数校验: - -```go -if video.Title == "" || video.PlayURL == "" || video.CoverURL == "" { - return http.ErrValidation -} -``` - -**Step 3: Handler 层使用** - -```go -// video_handler.go PublishVideo -if err := vh.service.Publish(c.Request.Context(), video); err != nil { - c.JSON(httputil.ClassifyHTTPStatus(err), gin.H{"error": err.Error()}) - return -} -``` - -注意 package 命名冲突:handler 文件在 `package video`,需要 import `httputil "feedsystem_video_go/internal/http"`。 - -**Step 4: 编译验证 + 测试** - -```bash -cd backend && go build ./... && go vet ./... -``` -Expected: 编译通过 - -**Step 5: Commit** - -```bash -git add backend/internal/http/errors.go backend/internal/video/video_handler.go backend/internal/video/video_service.go -git commit -m "refactor: Handler 错误码精确化 — 区分 400/401/404/500" -``` - ---- - -### Task 7: JWT Secret 弱默认值加固 - -**Files:** -- Modify: `backend/internal/auth/jwt.go:12-18` - -**Step 1: 修改 jwtSecret** - -```go -func jwtSecret() []byte { - secret := os.Getenv("JWT_SECRET") - if secret == "" { - b := make([]byte, 32) - if _, err := rand.Read(b); err != nil { - log.Printf("FATAL: cannot generate JWT secret: %v", err) - return []byte("fallback-unsafe-key-change-me") - } - secret = hex.EncodeToString(b) - log.Printf("WARNING: JWT_SECRET not set, generated random key. All tokens invalid on restart.") - } - return []byte(secret) -} -``` - -需要增加 import: `"crypto/rand"`, `"encoding/hex"`, `"log"` - -**Step 2: 编译验证** - -```bash -cd backend && go build ./... -``` - -**Step 3: Commit** - -```bash -git add backend/internal/auth/jwt.go -git commit -m "security: JWT Secret 未设环境变量时生成随机密钥而非使用弱默认值" -``` - ---- - -### Task 8: 配置密码集中管理 - -**Files:** -- Create: `.env.example` -- Modify: `docker-compose.yml` -- Modify: `.gitignore` — 确保 `.env` 被忽略 - -**Step 1: 创建 .env.example** - -```bash -# .env.example — 复制为 .env 后修改实际值 -MYSQL_ROOT_PASSWORD=123456 -MYSQL_DATABASE=feedsystem -REDIS_PASSWORD=123456 -RABBITMQ_USER=admin -RABBITMQ_PASS=password123 -JWT_SECRET=change-me-in-production -``` - -**Step 2: 修改 docker-compose.yml** - -```yaml -mysql: - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456} - MYSQL_DATABASE: ${MYSQL_DATABASE:-feedsystem} - -redis: - command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:-123456}"] - -rabbitmq: - environment: - RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-admin} - RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS:-password123} -``` - -**Step 3: 确认 .gitignore 包含 .env** - -```bash -grep '\.env' .gitignore || echo '.env' >> .gitignore -``` - -**Step 4: Commit** - -```bash -git add .env.example docker-compose.yml .gitignore -git commit -m "security: 敏感配置迁移至 .env,docker-compose 引用环境变量" -``` - ---- - -### Task 9: 前端路由鉴权守卫 - -**Files:** -- Modify: `frontend/src/router/index.ts` - -**Step 1: 添加 beforeEach 守卫** - -```typescript -import { useAuthStore } from '../stores/auth' - -const router = createRouter({ - // ... 现有配置不变 -}) - -router.beforeEach((to, _from, next) => { - const auth = useAuthStore() - const authRequired = ['/settings', '/video'] - - if (authRequired.some(p => to.path.startsWith(p)) && !auth.isLoggedIn) { - next({ path: '/account', query: { redirect: to.fullPath } }) - return - } - next() -}) - -export default router -``` - -**Step 2: 构建验证** - -```bash -cd frontend && npm run build -``` -Expected: 构建成功 - -**Step 3: Commit** - -```bash -git add frontend/src/router/index.ts -git commit -m "feat: 前端路由鉴权守卫 — /settings 和 /video 需登录" -``` - ---- - -### Task 10: pprof 生产保护(确认) - -**Files:** 无需改动 - -**Step 1: 确认已有配置安全** - -```bash -grep -A5 'pprof' backend/configs/config.docker.yaml -``` -Expected: `enabled: true`(不对,需要确认是 `false`) - -确认 `config.docker.yaml` 中 pprof 已禁用或仅监听 `127.0.0.1`。 - -**Step 2: 如果未禁用则修改** - -```yaml -observability: - pprof: - enabled: false -``` - -**Step 3: Commit(如有改动)** - -```bash -git add backend/configs/config.docker.yaml -git commit -m "security: 确认 pprof 容器内部署时禁用" -``` - ---- - -## P3 优化(7项) - ---- - -### Task 11: HomeView.vue 拆分为 composable + 子组件 - -**Files:** -- Create: `frontend/src/composables/useVideoFeed.ts` -- Create: `frontend/src/composables/useVideoPlayer.ts` -- Create: `frontend/src/composables/useLikeFollow.ts` -- Create: `frontend/src/components/CommentDrawer.vue` -- Modify: `frontend/src/views/HomeView.vue`(精简至 ~350 行) - -**Step 1: 提取 useVideoFeed composable** - -```typescript -// composables/useVideoFeed.ts -import { reactive, ref } from 'vue' -import { ApiError } from '../api/client' -import * as feedApi from '../api/feed' -import type { FeedVideoItem } from '../api/types' - -export type TabKey = 'recommend' | 'hot' | 'following' - -export function useVideoFeed() { - const tab = ref('recommend') - - const recommend = reactive({ - items: [] as FeedVideoItem[], - loading: false, error: '', - hasMore: false, nextTime: 0, - }) - - const hot = reactive({ - items: [] as FeedVideoItem[], - loading: false, error: '', - hasMore: false, - nextLikesCountBefore: undefined as number | undefined, - nextIdBefore: undefined as number | undefined, - }) - - const following = reactive({ - items: [] as FeedVideoItem[], - loading: false, error: '', - hasMore: false, nextTime: 0, - }) - - // ... 复制原有 loadRecommend / loadHot / loadFollowing 逻辑 - // 各 load 函数保持原样 - - return { tab, recommend, hot, following, loadRecommend, loadHot, loadFollowing } -} -``` - -**Step 2: 提取 useVideoPlayer composable** - -```typescript -// composables/useVideoPlayer.ts -import { nextTick, ref } from 'vue' - -export function useVideoPlayer() { - const muted = ref(true) - const activeIndex = ref(0) - const videoMap = new Map() - - function setVideoRef(id: number, el: HTMLVideoElement | null) { /* ... */ } - function playActive() { /* ... */ } - function toggleMute() { /* ... */ } - function togglePlayPause() { /* ... */ } - - return { muted, activeIndex, videoMap, setVideoRef, playActive, toggleMute, togglePlayPause } -} -``` - -**Step 3: 提取 useLikeFollow composable** - -```typescript -// composables/useLikeFollow.ts -import { reactive } from 'vue' -import { ApiError } from '../api/client' -import * as likeApi from '../api/like' -import { useAuthStore } from '../stores/auth' -import { useSocialStore } from '../stores/social' -import { useToastStore } from '../stores/toast' -import type { FeedVideoItem } from '../api/types' - -export function useLikeFollow() { - const likeBusy = reactive>({}) - const followBusy = reactive>({}) - - async function toggleLike(item: FeedVideoItem) { /* ... */ } - async function toggleFollow(authorId: number) { /* ... */ } - function share(item: FeedVideoItem) { /* ... */ } - - return { likeBusy, followBusy, toggleLike, toggleFollow, share } -} -``` - -**Step 4: 提取 CommentDrawer.vue 组件** - -将原 HomeView.vue 中 drawer 相关的 state + 模板 + 样式提取为独立组件。 - -**Step 5: 精简 HomeView.vue** - -```vue - -``` - -**Step 6: 构建验证** - -```bash -cd frontend && npm run build -``` -Expected: 构建成功,类型检查通过 - -**Step 7: Commit** - -```bash -git add frontend/src/composables/ frontend/src/components/CommentDrawer.vue frontend/src/views/HomeView.vue -git commit -m "refactor: HomeView 拆分为 3 个 composable + CommentDrawer 组件" -``` - ---- - -### Task 12: Feed Service 策略拆分 - -**Files:** -- Create: `backend/internal/feed/strategy_latest.go` -- Create: `backend/internal/feed/strategy_follow.go` -- Create: `backend/internal/feed/strategy_hot.go` -- Create: `backend/internal/feed/build_feed.go` -- Modify: `backend/internal/feed/service.go`(精简入口) - -**Step 1: 拆分 strategy_latest.go** - -将原 `service.go` 中 `ListLatest` 方法完整移入,包含 ZSET 热冷分离逻辑。 - -**Step 2: 拆分 strategy_follow.go** - -将 `ListByFollowing` 方法完整移入,包含 Redis 缓存穿透防护逻辑。 - -**Step 3: 拆分 strategy_hot.go** - -将 `ListByPopularity` 方法完整移入,包含快照合并 + 降级逻辑。 - -**Step 4: 拆分 build_feed.go** - -将 `buildFeedVideos` 和 `buildOrderedResult` 移入。 - -**Step 5: 精简 service.go** - -```go -type FeedService struct { - repo *FeedRepository - likeRepo *video.LikeRepository - rediscache *rediscache.Client - localcache *cache.Cache - cacheTTL time.Duration - requestGroup singleflight.Group -} - -func (f *FeedService) ListLatest(ctx context.Context, limit int, latestBefore time.Time, viewerAccountID uint) (ListLatestResponse, error) { - return listLatestStrategy(ctx, f, limit, latestBefore, viewerAccountID) -} -``` - -**Step 6: 编译验证 + 运行测试** - -```bash -cd backend && go build ./... && go test ./... -``` - -**Step 7: Commit** - -```bash -git add backend/internal/feed/ -git commit -m "refactor: Feed Service 按查询策略拆分为 4 个文件" -``` - ---- - -### Task 13: 视频列表虚拟滚动 - -**Files:** -- Modify: `frontend/src/views/HomeView.vue` - -**Step 1: 替换 v-for 为虚拟化渲染** - -在模板中,将: -```html -
-``` -改为只渲染 `visibleRange` 内的 item,其余用占位 div。使用 `v-show` 控制显隐而非 `v-if`(保留 video 实例)。 - -```typescript -const visibleRange = computed(() => { - const idx = activeIndex.value - const len = filteredItems.value.length - return { - start: Math.max(0, idx - 1), - end: Math.min(len - 1, idx + 1), - } -}) -``` - -模板中: -```html -
-``` - -**Step 2: 离屏视频 pause** - -在 `playActive` 中,pause 所有不在 visibleRange 内的视频。 - -**Step 3: 构建验证** - -```bash -cd frontend && npm run build -``` - -**Step 4: Commit** - -```bash -git add frontend/src/views/HomeView.vue -git commit -m "perf: Feed 流虚拟滚动 — 仅渲染当前±1条视频 DOM" -``` - ---- - -### Task 14: 缓存键版本化 - -**Files:** -- Modify: `backend/internal/middleware/redis/redis.go` -- Modify: 所有使用 Redis 键的 service 文件(account, video, feed, social) - -**Step 1: 在 Client 增加 keyPrefix** - -```go -type Client struct { - rdb *redis.Client - keyPrefix string -} - -func (c *Client) Key(format string, args ...any) string { - return c.keyPrefix + fmt.Sprintf(format, args...) -} -``` - -在 `NewFromEnv` 中从 config 读入 `keyPrefix`(默认 `"v1:"`)。 - -**Step 2: 替换所有硬编码键** - -- `"feed:global_timeline"` → `c.Key("feed:global_timeline")` -- `"video:detail:id=%d"` → `c.Key("video:detail:id=%d", id)` (注意:Key 内部做 Sprintf) -- 等等... - -**Step 3: 编译验证 + 测试** - -```bash -cd backend && go build ./... && go test ./... -``` - -**Step 4: Commit** - -```bash -git add backend/internal/middleware/redis/redis.go backend/internal/ -git commit -m "refactor: Redis 缓存键增加版本前缀支持(默认 v1:)" -``` - ---- - -### Task 15: Docker 健康检查 - -**Files:** -- Modify: `docker-compose.yml` - -**Step 1: 增加 backend healthcheck** - -```yaml -backend: - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8080/account/findByID -d '{}' --header='Content-Type: application/json' || exit 1"] - interval: 10s - timeout: 5s - retries: 3 -``` - -**Step 2: worker healthcheck** - -```yaml -worker: - healthcheck: - test: ["CMD-SHELL", "pgrep worker || exit 1"] - interval: 15s - timeout: 5s - retries: 3 -``` - -**Step 3: frontend healthcheck** - -```yaml -frontend: - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:80/ || exit 1"] - interval: 10s - timeout: 5s - retries: 3 -``` - -**Step 4: Commit** - -```bash -git add docker-compose.yml -git commit -m "feat: docker-compose 增加 backend/worker/frontend 健康检查" -``` - ---- - -### Task 16: 前端错误监控 - -**Files:** -- Create: `frontend/src/utils/error-reporter.ts` -- Modify: `frontend/src/api/client.ts` -- Modify: `frontend/src/main.ts` - -**Step 1: 创建 error-reporter** - -```typescript -// utils/error-reporter.ts -export function reportError(error: Error, context?: Record) { - if (import.meta.env.DEV) { - console.error('[ErrorReporter]', error.message, context) - return - } - // 生产环境发送到日志服务 - fetch('/api/error-report', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - message: error.message, - stack: error.stack, - context, - timestamp: new Date().toISOString(), - }), - }).catch(() => { /* 静默失败 */ }) -} -``` - -**Step 2: 在 client.ts 中集成** - -在 `ApiError` 抛出前调用 `reportError`。 - -**Step 3: 在 main.ts 中注册全局错误处理器** - -```typescript -app.config.errorHandler = (err, _instance, info) => { - reportError(err instanceof Error ? err : new Error(String(err)), { info }) -} -``` - -**Step 4: 构建验证** - -```bash -cd frontend && npm run build -``` - -**Step 5: Commit** - -```bash -git add frontend/src/utils/error-reporter.ts frontend/src/api/client.ts frontend/src/main.ts -git commit -m "feat: 前端全局错误监控 — 开发 console,生产上报 API" -``` - ---- - -### Task 17: Worker 优雅重启 - -**Files:** -- Modify: `backend/cmd/worker/main.go` - -**Step 1: 增加连接重试** - -```go -func connectWithRetry(name string, fn func() error, maxRetries int) { - for i := 0; i < maxRetries; i++ { - if err := fn(); err == nil { - return - } - wait := time.Duration(math.Min(float64(1<@a1M;U6mmoEL6hPG)gQ4p53QgSniuq$n}3IKCt^rJSuewJ0+&Cowt;01!|uQ2+n{ diff --git a/data/mnesia/rabbit@1ae715dbc287/LATEST.LOG b/data/mnesia/rabbit@1ae715dbc287/LATEST.LOG deleted file mode 100644 index 16c6350c061f7079efb6b8cbd66cf67bde76b85b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1695 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 S8#o!lHK#+1@OiFJ%KHB@yj~js diff --git a/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/00000001.segment b/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/00000001.segment deleted file mode 100644 index 0f66e5aff3fe8f14563c46d43c1038e11a38208b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159605 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 diff --git a/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/config b/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/config deleted file mode 100644 index e191582..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/RABBITDRASO8J6QGNT/config +++ /dev/null @@ -1,11 +0,0 @@ -#{id => {rabbitmq_metadata,rabbit@1ae715dbc287}, - machine => - {module,khepri_machine, - #{member => {rabbitmq_metadata,rabbit@1ae715dbc287}, - store_id => rabbitmq_metadata}}, - friendly_name => "RabbitMQ metadata store", - cluster_name => rabbitmq_metadata,uid => <<"RABBITDRASO8J6QGNT">>, - initial_members => [], - log_init_args => #{uid => <<"RABBITDRASO8J6QGNT">>}, - tick_timeout => 1000,broadcast_time => 100, - install_snap_rpc_timeout => 120000,await_condition_timeout => 30000}. \ No newline at end of file diff --git a/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/meta.dets b/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/meta.dets deleted file mode 100644 index d846f5115c539d08cdfcaab57ecd3c8eecd05128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5925 zcmZQz5S_}f`s{lK1_nkb=0q^zVn8`Y1}2~as2T_hL4x$s6f}7T?0vK;UMo0n)zpun zXRg!KGNKG;wIE8_sG`vj7!8487y`Vb`F|M3>Zq4SLtr!nMnhmU1V%$(Gz3ONfC?cH z!oVOp+E%1OK#m$Q8UmvsFd71bBm_c)Ou{OFZt-c(V95p2LP3sBPM#qyL5{)x7G7q7 z?tUSO%o$AOf<=i*Ntq?Nh4HzmC5b7CC5h$25FrP{#8h)b)0CuSBMb9H`O>_U)U?dJ z)D)m$0?d#>Sq@<3u>*+@9Gn#cRVFZbE(Qj1WIi_ogCsJahk-#Bna|6>po+}rV_+~v z=JPW!gd+0=7#OA^^930g!Ic@zejx@X6uvM63kqL^fdhpv${>Kk7h_OB;fpgkpztLa u8c_I>jF8F{X1^2@Dqorfl`q4A%9j;D<;y9c^5q>Md>%;n8(0FvR}}zC$8!?^ diff --git a/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/names.dets b/data/mnesia/rabbit@1ae715dbc287/coordination/rabbit@1ae715dbc287/names.dets deleted file mode 100644 index 27d30cb8445357878b577ec656c9f1d87ad7d11f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5905 zcmZQz5G`a_efB*A0|O%zb0U~iNAo{T!*|s1(GVC7fzc2c4S~@R7}OzP zIq-dvK^;D$jvEbu(GVC7fzc2c4T0el0+vE1VHH3R88&Ayl?xUnCM9K-K>>v?&ftK;mtbf>;Y%_?26>]}. -{index_module,rabbit_msg_store_ets_index}. diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/file_summary.ets b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/file_summary.ets deleted file mode 100644 index d9f4b072342959bab998fb551b4e324c35b9714b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmZuuJ5Izf5OqR!1uTpBNOW{a)Qg0c4(Op?gpeY~p0EzF9gQ7KXgLKp-~`+O(Q^VC zZowoevc1SRxhf%bwbmS)mgXoBkGcjcVXY4pC#{-HJN$X?R;sPQFn446~#YK Cw#beE diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/msg_store_index.ets b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent/msg_store_index.ets deleted file mode 100644 index 32d76326d74bfc2cac40d794cc79348b0f4850a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmZvZzfQw25XPM}?a)%hABlk(i8)B9jC6;&6+*K7=RzG4J370#VdZ6b0(NGefsKW? z;F1nys3+_DzB~W!oTP(HE$=6cC7*I}H-B55tkReo{lEtC6or%~+~xvi`L&kn>SksZ zzh5l*_%04vn!nEjq@U<`?x_DKn3G$ z95riM8Aa-}7NtY}LW?6qB)w$pKdIxG!X^OGX<{r~MZ+cWz{!%@ZTDW1blHDoXHvlh T#kniCbbSG}8TNl9SXur7LkX)y diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/0.rdq b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/0.rdq deleted file mode 100644 index e69de29..0000000 diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/clean.dot b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/clean.dot deleted file mode 100644 index 537b343..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/clean.dot +++ /dev/null @@ -1,2 +0,0 @@ -{client_refs,[]}. -{index_module,rabbit_msg_store_ets_index}. diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/file_summary.ets b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/file_summary.ets deleted file mode 100644 index 79e4eae9af23271cd3982aedd8fc6d03afeb3c2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmZuuJ5Izf5OqR!1uTpBNOW{a)Qg0c4(Op?gpeY~p0EzF9gQ7KXgLKJK%9U(ATB}6 zEtn)lwpdrzn>X*xn~CC$MBLp@7>mBM#m)8G@?aT@qjLja$Y(h!X5%|aU*I(Frpbs`vH}Q z^|ftbb%o#@$f04ZL0fG2JL!&WFo1V!=cGWtj9xo0QIjPo42iL(9VjteLpbi^rP@8F z&Rc{uS0$vb)|z9}(i{ciQP*H4to5Pdq*art=UEBEpowoP2osAEkg5(mQO8fvRMzpB z{@WE=55g;J!nmrlQVBw*nim$+XIdZ?I`gSkW(8s`3tl`JNxI79wxpa<*s)nba{c}b zf-d`mZib!JT%2(hwrXn5m7&cJU&8y7tzaZ`+R3yYQ2t1qWA~p Ci^y64 diff --git a/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/msg_store_index.ets b/data/mnesia/rabbit@1ae715dbc287/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_transient/msg_store_index.ets deleted file mode 100644 index cd659e2ecb8c74fe522be6baf6b9946b16c1eff0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmZvZzfQw25XPM}?a)%hABlk(i8)B9jC6;&6+*K7=RzG4J370#VdZ7|1nkVb1S@aB zB^}C8PuBN+cmCZuNe7u)K28`*zU1Oz{;@h+r7<=7i4Ed83Moyv&jrl#TP@Yi-OMb0 zzghCREsj~5Z|BdHzR;k+1{9Tf2hEjR+W_d}un?683Y;K_PPb}}2v)Uw>olSXaM&9w zS2qaWgWea6YN#Eyl7Mq+J#^6mE)Swo&2zrXj}eZeA8<&f&hs zCc%-OfffNdKs9U`OywNKsp+|?c_qbJKn{0aYDGzWacW_FW=bLhP(TunQ2DX~sC+pERKC0egwF#BZv#tU_!a{I*nX3? diff --git a/data/mnesia/rabbit@1ae715dbc287/node-type.txt b/data/mnesia/rabbit@1ae715dbc287/node-type.txt deleted file mode 100644 index 342cbf2..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/node-type.txt +++ /dev/null @@ -1 +0,0 @@ -disc. diff --git a/data/mnesia/rabbit@1ae715dbc287/nodes_running_at_shutdown b/data/mnesia/rabbit@1ae715dbc287/nodes_running_at_shutdown deleted file mode 100644 index 217b036..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/nodes_running_at_shutdown +++ /dev/null @@ -1 +0,0 @@ -[rabbit@1ae715dbc287]. diff --git a/data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/00000003.wal b/data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/00000003.wal deleted file mode 100644 index 698b19c..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/00000003.wal +++ /dev/null @@ -1 +0,0 @@ -RAWA \ No newline at end of file diff --git a/data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/meta.dets b/data/mnesia/rabbit@1ae715dbc287/quorum/rabbit@1ae715dbc287/meta.dets deleted file mode 100644 index c1ae47e3b05f8128913ddbd12be673bebf7cc413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5464 zcmZQz5Vc@fefB*A0|O%zb0U~vF`zIAFfuT~7!aDSiJR2M?+wQhXC(r{vY8fVAT7gAut*OqaiRF0;3@?8UmvsFd71*Aut*O dqaiRF0wW>>1ehV?rV7xpNFE5^z!KiJR2M?+wQhXC(r{vY8fVAT7gAut*OqaiRF0;3@?8UmvsFd71*Aut*O dqaiRF0wW>>1ehV?rV7xpNFE5^z!KS6oseer|Aza^f`NiohBHlz z8$lwbxVm$h;qWo%oX(cx@|%kRA>@)>pPxM>KK8R>I!=HJP}mh*jLkkHP81%@NN^O! zhhY@PxT`2*sXmP;jG|+~cziNS97c%$!HD;^%}8+dY-FEm*&zmS&l4)AkhmBqD2qyQ z$jyt+{@0z|3Hl;cz%{yBRe;`vX61U^Vy}_{CaDDR<5636bgL4S6&|X5zL!c3O47{i zFOAXqG@pkPDvL^^znU#WE*eAjETk^(QjT6dT1I?tjJRVF>i}6+MY-|!8`dTGUt0dy w7}YiDFWWNPqash`ucEkgIj5Q@*saT2{?op!%DkaczczMwV8gin!j1gpCoTfZ-v9sr diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_durable_queue.DCD b/data/mnesia/rabbit@1ae715dbc287/rabbit_durable_queue.DCD deleted file mode 100644 index 213522f1c0b2113792230e2480026f0e06e74ffa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 310 zcmZusyH3ME5ZvIiV}e8j{D#2>6iVocP%6?O>mD0?XZx(&`Pd%5j4$9X_yc*c92Y|O z_c_O9eyfBm?v_;~0FrM3r}!cu`FtO2zb4xYN_o561q@g*N*($xIEn3`m|ar5UyNYU zb6=NxNUV1ODc@a1M;VDL{0_i=2_U@Pa!$xn~ZNKH&hEh=YENlu9e^0OJ34fPDN88}Sz zjEwaR^^D4eixQKPGD{o`6I0C%O;eJRjV#PFn3EV7*q$XZ@I{p;F>vlb0W>xmVk~pH zP*G`KNoH=A(7V1L9bu&|R agM$;zvplmcigF4Hg3603vw|{HUD5$?>OGVI diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_serial b/data/mnesia/rabbit@1ae715dbc287/rabbit_serial deleted file mode 100644 index 358e77b..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/rabbit_serial +++ /dev/null @@ -1 +0,0 @@ -3. diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_topic_permission.DCD b/data/mnesia/rabbit@1ae715dbc287/rabbit_topic_permission.DCD deleted file mode 100644 index f8dd237..0000000 --- a/data/mnesia/rabbit@1ae715dbc287/rabbit_topic_permission.DCD +++ /dev/null @@ -1 +0,0 @@ -cXM \ No newline at end of file diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_user.DCD b/data/mnesia/rabbit@1ae715dbc287/rabbit_user.DCD deleted file mode 100644 index 94cf3099cc62af31fb7388a64031d16c18852448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmZQ%VrEH>@a1M;VDL{0_i=2_U@Pa!$xn~ZNKH&hEh=YENlu9e^0OJ34fPDN88}Sz zjEwaR^^D4eixQKPGD{o`6I0C%O;eJRjV#PFn3EV7*q$XZ@I{p;F>pP)0yMTBW-M=J zUP)?EUSdvsX>n>%E>Ma!F(o%M55!U_d|;RqaU^lub4~@h&Ccfi$5_MFo?ngskzH_P z-|VQa8Iot@#dClP8OwRWnlg(^iV{omi?Yh)Anu4SNGvWc&o4@e&qyrJ$jnQRFV09b LGBqm!YGMEY8O2I> diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_user_permission.DCD b/data/mnesia/rabbit@1ae715dbc287/rabbit_user_permission.DCD deleted file mode 100644 index 4959afd67f0dc0a983015b7db2b58308cb02ade9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 zcmZQ%VrEH>@a1M;VDL{0_i=2_U@Pa!$xn~ZNKH&hEh=YENlu9e^0OJ34fPDN88}Sz zjEwaR^^D4eixQKPGD{o`6I0C%O;eJRjV#PFn3EV7*q$XZ@I{p;F>pP(3^X1m-- E0AOJ=e*gdg diff --git a/data/mnesia/rabbit@1ae715dbc287/rabbit_vhost.DCD b/data/mnesia/rabbit@1ae715dbc287/rabbit_vhost.DCD deleted file mode 100644 index 1cb0ec10b9fb8680100b99801faa72fdd743e25f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170 zcmZQ%VrEH>@a1M;VDL{0_i=2_U@Pa!$xn~ZNKH&hEh=YENlu9e^0OJ34fPDN88}Sz zjEwaR^^D4eixQKPGD{o`6I0C%O;eJRjV#PFn3EV7*q$XZ@I{p;F>qI`1{&)OF_xvA zwJamQxFi=S#HgQD0;HJAxl>Y$lZ!G7N;32FKoTM@scDI&IVB2ZnMEa~i8%@&9px+~ IiRr~z03szYKL7v# diff --git a/data/mnesia/rabbit@1ae715dbc287/schema.DAT b/data/mnesia/rabbit@1ae715dbc287/schema.DAT deleted file mode 100644 index 1078c919f19e604ce418882adb4a532fd870086d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22105 zcmeHPU5F&b9k1@5-I<-)b>`;IJvZl_2FZhn;`y~olH~~IkPshyPzVH@n(lI@*Z!F6 z>fXKu5kd2Qc?dBt_n4RnAqGN1AO?KU1U&HNKrW~dAty>a4?Wb=JN;L8^-Oh7&$>I; zvpY6DFiiiutE;;Hzu*6(K6(&(^j7rZnF|OZiGLS{he(EV%h6W~1zrYzGCvJCI}cxW zU#_1QesJ#D7mt7V>BZlzH2(D0ztCJOpQy6A`zN}sys6YcseuA$V5zkK0-#uSq0~UB zfl>pd21*U&qXFe2LXQZ!fqWdHTLn3vR`$LY=?#RwKLh=eo=52VEuKSyvF*KET%Wck z#sb7&=2>p{rEY`-$(;8%=3J2ig?H{fAlca z&vICgc=Mg$O!F_3*E9VgUaSrvQ>)o!*^l|$hwl7^m=z$#CXH$BIbEs## zdYiR!(#Hs`2>q9yZ{PFSN#OUXXM0L8PqiMgaQth+)3zZC3uai?Q=$BtsMdh}ksquq7?Dl6FvOFFi>DcoK+9jvR4 zs}mO^Rvp$fLaAywI)SDLTnt>pBXDWXuyt~h)l{Lj)n^AwrlVmKsxX`uE+b>e8R=u)VX=h zah^1wv7Tbr(23`MdEvxAp1Z-WNo!w)pDNDQ1T<0IATBi=8}5~{jM4h~?y*L?!tuW( z;@^lMQ(51cX4>$S5edXA?Bq-^p=RNA2UxK=M0`?mEEzx@*9H=?zC#aF9GMGh>_SMS zr9P%KV7%&K+5=tSE)DjvW5e#M*zB@r>RG|OX;_9ATE}+T)TW*T93xwj?4R11%ba8{ zU!9O-(#IVC4@CTz1^iVYzGYBG&?FVc6e=PsIBWw&jPe4A5;}^Fo)gzIL~dm{cT9DmohJ;MSPVsP>V3?uZDLvJv?ToGB&9#uyc z5tJthIHC<~`XqPo-+cYIUvcoSUYXG3hv37JjJW-OCgOjP({u*r9j_uc$}>(+#0Tjuj{jK^|3!{}=*^N|KXIvuNY90x9)(v47B>vn z^C2||eLRoqK@A)u*0G0kH};qQ@T$bYU7HBpgr#Q-@+%Sk^4~@L4{-c-9}Y~Sp74oJ zl03*PQbaz~uyrEr|0pF6@T9oP^#r38-*Z%*m=G(2CON5@J~cLo>I61%g9Yq%eG3LA zG*J469@MFc;UXN;ph-7vO%+bG*^mVW2BH4L^E8<6JEoy+22C~?!H^-U{6w{DMb*@G zHqc?0XfQ`T43BYLR7T~4jNs6)5%BD`&T>`S^F19N62exTOyH;cwn~s>RLc@y~W{KD%%4 z-Ohb;{DGe??LFHlck!%zoX7t)(f;GiK92to$$HrCrc!`$r3SXY*E57U+I%fm!KkG- zcoq=Ng=OagwRl=dOad0D2B8{eT(^kqVnC0ha_%ws;XlsLbDKO;+T<=;-`+PRFL3*p zMEj4Y_JlxZ!vuUX_>UJx@{$@%cqs~AH7Q(R4)D_f;4pX?A1G8ws5>KEaF?SRG z+aoVs7n|_sJI@1Vbai6y?W5>VAf4y=|2tXs9|t{1-dzKm+4i3*G_e2C=>M(F_}NfC zOn|r|M(sXcAFJ5p9T~=J10HahCtZ&aHb35@*l2i8cF=w}w$0{irs zZfh>g&}eTi?0gpDRqoQKHMU#zl{!cL>g8Nf9}+^k!0rFMX#erJo)XJY<3T_M3C5Qj zFrk21z}_*U@!Eq8&WO|bFhV-Z@joNtA5ZdUK%Tf%qCAa7zWdx;7CFjm`Jg>QWJb~)uEl^}czWGO1ow%D=_zY7CR=I$>4yLxAvyUWwNGI2CJ z6fU3S8Z@9>dI{_s3J?52f#!;rvlTO%E3uhl zwi9TcDbATS@uI%}M#DW0AZV>F3S|5PIxqUjw~$SY-eJ diff --git a/docs/plans/2025-04-25-features-design.md b/docs/plans/2025-04-25-features-design.md deleted file mode 100644 index 78d9eb8..0000000 --- a/docs/plans/2025-04-25-features-design.md +++ /dev/null @@ -1,90 +0,0 @@ -# 用户体系 + 社交深化 设计文档 - -> **日期**: 2025-04-25 -> **状态**: 待实施 -> **方案**: 依赖驱动分批(3 阶段) - -## 概述 - -在现有短视频 Feed 系统基础上,扩展用户 profile 体系(头像、简介、Refresh Token、主页统计)和社交互动能力(通知、私信、话题、@提及)。 - ---- - -## P1 — 用户基石(4 项) - -### 1. 头像上传 + 个人简介 - -**Account 模型扩展**: -```go -AvatarURL string `gorm:"type:varchar(512)" json:"avatar_url,omitempty"` -Bio string `gorm:"type:varchar(255)" json:"bio,omitempty"` -``` - -**新增接口**: -| 方法 | 路径 | 说明 | -|------|------|------| -| POST | `/account/uploadAvatar` | multipart 上传头像,校验类型/大小,存 `.run/uploads/avatars/{id}/` | -| POST | `/account/updateProfile` | JSON `{ avatar_url?, bio? }` 更新当前用户 | - -**前端**: UserAvatar 组件支持 `src`,AccountView 加头像上传 + bio 编辑,Feed 卡片显示头像。 - -### 2. 登录态优化(Refresh Token) - -**双 Token**: -- Access Token: 15min 过期 -- Refresh Token: 7天过期,落库 + Redis 缓存 - -**新增接口**: `POST /account/refresh` — 接收 refresh token 返回新 access token - -**前端**: auth store 存双 token,client.ts 401 自动刷新,登录页"记住我"。 - ---- - -## P2 — Feed 可见 + 通知(3 项) - -### 3. 粉丝数 / 关注数展示 - -**后端**: 社交接口返回中加 `follower_count` / `vlogger_count`,通过聚合查询计数。 - -**前端**: UserProfileView 和 Feed 卡片显示粉丝数。 - -### 4. 用户主页增强 - -**后端**: `POST /account/getProfile` — 返回用户信息 + 视频列表 + 获赞总数。 - -**前端**: UserProfileView 加视频列表网格 + 统计卡片。 - -### 5. 实时消息通知 - -**架构**: 复用 MQ 事件(like.events / comment.events / social.events)→ NotificationWorker 消费 → 写 `Notification` 表 + WebSocket 推送。 - -**新增表**: `Notification` — id, recipient_id, sender_id, type, target_id, is_read, created_at。 - -**新增接口**: -| 方法 | 路径 | 说明 | -|------|------|------| -| POST | `/notification/list` | 返回当前用户未读通知列表 | -| POST | `/notification/markRead` | 标记单条/全部已读 | -| GET | `/ws/notifications` | WebSocket 升级,实时推送 | - -**前端**: AppShell 右上角通知铃铛 + 未读红点。 - ---- - -## P3 — 互动深化(3 项) - -### 6. 私信 / 即时通讯 - -**新增表**: `Message` — id, from_id, to_id, content, is_read, created_at - -**后端**: WebSocket 双向通道,`POST /message/send` + `POST /message/list` - -### 7. #话题标签 - -**新增表**: `Tag` — id, name (unique);`VideoTag` — video_id, tag_id - -**改动**: 视频发布时从 title/description 中提取 `#xxx`,写入 `VideoTag` 关联表;`POST /feed/listByTag` 按话题浏览。 - -### 8. @提及 - -**改动**: 评论发布时解析 `@username`,创建 Notification 并推送。 diff --git a/docs/plans/2025-04-25-optimization-design.md b/docs/plans/2025-04-25-optimization-design.md deleted file mode 100644 index 0bb7e10..0000000 --- a/docs/plans/2025-04-25-optimization-design.md +++ /dev/null @@ -1,149 +0,0 @@ -# feedsystem_video_go 全项目优化设计文档 - -> **日期**: 2025-04-25 -> **状态**: 待实施 -> **方案**: 风险驱动分批(方案 A) - -## 概述 - -基于全项目代码审查,识别出 17 项优化点,覆盖安全、数据库、MQ 可靠性、代码质量、架构、前端六大维度。按风险优先级分为三批实施。 - ---- - -## P1 止血(4项)— 消除生产风险 - -### 1. Router 变量赋值 Bug - -- **文件**: `backend/internal/http/router.go:147` -- **问题**: `timelineMQ` 初始化失败时错误地将 `socialMQ` 设为 nil -- **修复**: `socialMQ = nil` → `timelineMQ = nil` -- **影响**: 1 行 - -### 2. 数据库复合索引 - -- **文件**: `backend/internal/video/video_entity.go` -- **问题**: Feed 流排序查询缺少索引,可能全表扫描 -- **修复**: 在 Video 模型 GORM tag 中添加 3 个复合索引 - - `idx_videos_create_time` — `ListLatest` - - `idx_videos_likes_count_id` — `ListLikesCountWithCursor` - - `idx_videos_popularity_time_id` — `ListByPopularity` -- **实施**: 修改 model tag + AutoMigrate 自动创建 - -### 3. ListByAuthorID 加 LIMIT - -- **文件**: `backend/internal/video/video_repo.go:39-48` -- **问题**: 查询无上限,单作者海量视频可导致内存溢出 -- **修复**: 加 `Limit(200)` 硬上限 - -### 4. MQ Worker 死信队列 + 退避重试 - -- **文件**: `middleware/rabbitmq/` + 4 个 Worker 文件 -- **问题**: 所有 Worker 使用 `Nack(false, true)` 无限重试 -- **修复**: - - 声明死信交换 + 死信队列 - - 利用 `x-death` header 判断重试次数,≥3 次 Ack 并告警 - ---- - -## P2 加固(6项)— 安全隐患 + 规范化 - -### 5. rand.Read 错误处理 - -- **文件**: `backend/internal/video/video_handler.go:164-168` -- **问题**: 忽略 `rand.Read` 错误,失败时文件名全零可能覆盖 -- **修复**: `randHex()` 返回 error,调用方处理 - -### 6. Handler 错误码精确化 - -- **文件**: 所有 handler 文件 -- **问题**: DB/内部错误统一返回 400 -- **修复**: 新增 `classifyHTTPStatus()` 辅助函数,Service 层返回哨兵错误区分 400/401/404/500 - -### 7. JWT Secret 弱默认值 - -- **文件**: `backend/internal/auth/jwt.go` -- **问题**: 默认值 `"change-me-in-env"` 过于明显 -- **修复**: 未设环境变量时生成随机密钥并警告 - -### 8. 配置密码集中管理 - -- **文件**: `docker-compose.yml` + 3 个 config YAML -- **问题**: 多处重复硬编码密码 -- **修复**: docker-compose 引用 `.env`,创建 `.env.example`,config YAML 保持现状 - -### 9. 前端路由鉴权守卫 - -- **文件**: `frontend/src/router/index.ts` -- **问题**: Settings/Video 页面无登录拦截 -- **修复**: 添加 `router.beforeEach` 守卫 - -### 10. pprof 生产保护 - -- **现状**: 已监听 `127.0.0.1`,`config.docker.yaml` 已禁用 -- **动作**: 确认安全,仅需注释说明 - ---- - -## P3 优化(7项)— 架构 + 可维护性 - -### 11. HomeView.vue 拆分 - -- **文件**: `frontend/src/views/HomeView.vue` (918 行) -- **拆分目标**: - - `composables/useVideoFeed.ts` - - `composables/useVideoPlayer.ts` - - `composables/useLikeFollow.ts` - - `components/CommentDrawer.vue` - - `views/HomeView.vue`(精简至 ~350 行) - -### 12. Feed Service 策略拆分 - -- **文件**: `backend/internal/feed/service.go` (547 行) -- **拆分目标**: 按查询策略拆为 4 个文件 - - `strategy_latest.go` — 热冷分离 + ZSET - - `strategy_follow.go` — 缓存穿透防护 - - `strategy_hot.go` — 快照合并 + 降级 - - `build_feed.go` — 公共方法 - -### 13. 视频列表虚拟滚动 - -- **问题**: 所有视频渲染 DOM,内存压力大 -- **修复**: 仅保留当前 ±1 条 DOM,离屏 `display:none` + `pause()` - -### 14. 缓存键版本化 - -- **文件**: `middleware/redis/redis.go` + 所有 service -- **修复**: `Client` 增加 `keyPrefix`,所有键通过 `c.Key()` 生成 - -### 15. Docker 健康检查 - -- **文件**: `docker-compose.yml` -- **修复**: 为 backend/worker/frontend 增加 healthcheck - -### 16. 前端错误监控 - -- **文件**: `frontend/src/api/client.ts` + 新增 `utils/error-reporter.ts` -- **修复**: 增加全局错误上报钩子 - -### 17. Worker 优雅重启 - -- **文件**: `backend/cmd/worker/main.go` -- **修复**: 替换 `log.Fatal` 为指数退避重试 - ---- - -## 实施顺序 - -``` -P1 (第1周) P2 (第2周) P3 (第3-4周) -──────────────────────────────────────────── -#1 Router Bug #5 rand.Read #11 HomeView 拆分 -#2 DB 索引 #6 错误码 #12 Feed 拆分 -#3 LIMIT #7 JWT #13 虚拟滚动 -#4 MQ 死信 #8 密码管理 #14 缓存版本化 - #9 路由守卫 #15 健康检查 - #10 pprof #16 错误监控 - #17 Worker 重启 -``` - -每批独立验证:`go test ./...` + `npm run build` + 冒烟测试。 diff --git a/docs/plans/2025-04-25-p1-implementation.md b/docs/plans/2025-04-25-p1-implementation.md deleted file mode 100644 index 2774179..0000000 --- a/docs/plans/2025-04-25-p1-implementation.md +++ /dev/null @@ -1,411 +0,0 @@ -# P1 用户基石 实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. - -**Goal:** 扩展 Account 模型(头像+简介)、实现双 Token 登录态优化(Access + Refresh Token) - -**Architecture:** Account 模型加 avatar_url/bio/refresh_token 字段;复用 UploadCover 的文件上传逻辑做头像;JWT 双 Token 机制 — Access 15min / Refresh 7天 - -**Tech Stack:** Go + Gin + GORM + JWT + Vue 3 + Pinia - ---- - -### Task 1: Account 模型扩展 - -**Files:** -- Modify: `backend/internal/account/entity.go:3-8` - -**Step 1: 修改 Account struct** - -```go -type Account struct { - ID uint `gorm:"primaryKey" json:"id"` - Username string `gorm:"unique" json:"username"` - Password string `json:"-"` - Token string `json:"-"` - RefreshToken string `json:"-"` - AvatarURL string `gorm:"type:varchar(512)" json:"avatar_url,omitempty"` - Bio string `gorm:"type:varchar(255)" json:"bio,omitempty"` -} -``` - -**Step 2: 编译验证** - -Run: `go build ./...` -Expected: 编译通过(AutoMigrate 自动加列) - -**Step 3: Commit** - -```bash -git add backend/internal/account/entity.go -git commit -m "feat: Account 模型加 avatar_url/bio/refresh_token 字段" -``` - ---- - -### Task 2: 头像上传 Handler - -**Files:** -- Modify: `backend/internal/account/handler.go` — 新增 UploadAvatar 方法 -- Modify: `backend/internal/http/router.go` — 注册路由 - -**Step 1: 添加 UploadAvatar handler** - -参考 `video/video_handler.go` 的 `UploadCover`,在 `account/handler.go` 中新增: - -```go -func (ah *AccountHandler) UploadAvatar(c *gin.Context) { - accountID, err := jwt.GetAccountID(c) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) - return - } - f, err := c.FormFile("file") - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "missing file"}) - return - } - const maxSize = 10 << 20 - if f.Size <= 0 || f.Size > maxSize { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file size"}) - return - } - ext := strings.ToLower(filepath.Ext(f.Filename)) - switch ext { - case ".jpg", ".jpeg", ".png", ".webp": - default: - c.JSON(http.StatusBadRequest, gin.H{"error": "only .jpg/.jpeg/.png/.webp allowed"}) - return - } - dir := filepath.Join(".run", "uploads", "avatars", strconv.FormatUint(uint64(accountID), 10)) - if err := os.MkdirAll(dir, 0o755); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - filename, err := randHex(16) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - filename = filename + ext - absPath := filepath.Join(dir, filename) - if err := c.SaveUploadedFile(f, absPath); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - urlPath := path.Join("/static", "avatars", strconv.FormatUint(uint64(accountID), 10), filename) - avatarURL := buildAbsoluteURL(c, urlPath) - - // 更新数据库 - if err := ah.accountService.UpdateAvatar(c.Request.Context(), accountID, avatarURL); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"avatar_url": avatarURL}) -} -``` - -需要新增 import: `"os"`, `"path"`, `"path/filepath"`, `"crypto/rand"`, `"encoding/hex"`, `"strconv"`, `"strings"`, `"net/http"` — 但 account/handler.go 已有部分,按需补。 - -同时需要从 `video_handler.go` 复制 `randHex` 和 `buildAbsoluteURL` 函数(或提取到公共 util)。 - -**Step 2: 在 router.go 注册路由** - -```go -protectedAccountGroup.POST("/uploadAvatar", accountHandler.UploadAvatar) -``` - -**Step 3: 添加 AccountService.UpdateAvatar 方法** - -```go -func (as *AccountService) UpdateAvatar(ctx context.Context, accountID uint, avatarURL string) error { - return as.accountRepo.UpdateAvatar(ctx, accountID, avatarURL) -} -``` - -**Step 4: 添加 AccountRepository.UpdateAvatar 方法** - -```go -func (ar *AccountRepository) UpdateAvatar(ctx context.Context, accountID uint, avatarURL string) error { - return ar.db.WithContext(ctx).Model(&Account{}).Where("id = ?", accountID).Update("avatar_url", avatarURL).Error -} -``` - -**Step 5: 编译验证** - -Run: `go build ./...` -Expected: 通过 - -**Step 6: Commit** - -```bash -git add backend/internal/account/handler.go backend/internal/account/service.go backend/internal/account/repo.go backend/internal/http/router.go -git commit -m "feat: 头像上传接口 /account/uploadAvatar" -``` - ---- - -### Task 3: 更新个人简介接口 - -**Files:** -- Modify: `backend/internal/account/handler.go` — 新增 UpdateProfile -- Modify: `backend/internal/http/router.go` — 注册路由 - -**Step 1: 新增 request struct + handler** - -在 `entity.go` 加: -```go -type UpdateProfileRequest struct { - AvatarURL string `json:"avatar_url"` - Bio string `json:"bio"` -} -``` - -Handler: -```go -func (ah *AccountHandler) UpdateProfile(c *gin.Context) { - accountID, err := jwt.GetAccountID(c) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) - return - } - var req UpdateProfileRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - if err := ah.accountService.UpdateProfile(c.Request.Context(), accountID, &req); err != nil { - c.JSON(apierror.ClassifyHTTPStatus(err), gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"message": "profile updated"}) -} -``` - -**Step 2: Service + Repo 层** - -```go -func (as *AccountService) UpdateProfile(ctx context.Context, accountID uint, req *UpdateProfileRequest) error { - updates := map[string]interface{}{} - if req.Bio != "" { - updates["bio"] = strings.TrimSpace(req.Bio) - } - if req.AvatarURL != "" { - updates["avatar_url"] = strings.TrimSpace(req.AvatarURL) - } - if len(updates) == 0 { - return errors.New("nothing to update") - } - return as.accountRepo.UpdateFields(ctx, accountID, updates) -} -``` - -**Step 3: 注册路由** - -```go -protectedAccountGroup.POST("/updateProfile", accountHandler.UpdateProfile) -``` - -**Step 4: 编译 + 提交** - -Run: `go build ./...` -Expected: 通过 - -```bash -git add backend/internal/account/ && git add backend/internal/http/router.go -git commit -m "feat: 个人简介更新接口 /account/updateProfile" -``` - ---- - -### Task 4: Refresh Token 机制 - -**Files:** -- Modify: `backend/internal/auth/jwt.go` — 新增 GenerateRefreshToken + ValidateRefreshToken -- Modify: `backend/internal/account/handler.go` — 新增 Refresh handler -- Modify: `backend/internal/account/service.go` — Login 返回双 token -- Modify: `backend/internal/http/router.go` — 注册 refresh 路由 - -**Step 1: auth/jwt.go 增加 Refresh Token** - -```go -const ( - AccessTokenTTL = 15 * time.Minute - RefreshTokenTTL = 7 * 24 * time.Hour -) - -func GenerateAccessToken(accountID uint, username string) (string, error) { - // 原 GenerateToken 逻辑,TTL 改为 15min -} - -func GenerateRefreshToken(accountID uint) (string, error) { - b := make([]byte, 32) - if _, err := rand.Read(b); err != nil { - return "", err - } - return hex.EncodeToString(b), nil -} -``` - -**Step 2: Login 返回双 token** - -修改 `account/service.go` 的 `Login` 方法,返回值从 `(string, error)` 改为 `(accessToken, refreshToken string, err error)`,并更新 `entity.go` 中的 `LoginResponse`。 - -```go -type LoginResponse struct { - Token string `json:"token"` // access token - RefreshToken string `json:"refresh_token"` // refresh token - AccountID uint `json:"account_id"` - Username string `json:"username"` -} -``` - -Login 时生成两个 token,access token 落库 `account.token`,refresh token 落库 `account.refresh_token`,两者都缓存到 Redis。 - -**Step 3: Refresh handler** - -新增 `POST /account/refresh`: - -```go -type RefreshRequest struct { - RefreshToken string `json:"refresh_token"` -} - -func (ah *AccountHandler) Refresh(c *gin.Context) { - var req RefreshRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - newAccessToken, err := ah.accountService.RefreshAccessToken(c.Request.Context(), req.RefreshToken) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"}) - return - } - c.JSON(http.StatusOK, gin.H{"token": newAccessToken}) -} -``` - -`AccountService.RefreshAccessToken`:查 Redis `account:{id}:refresh` → 匹配 → 生成新 access token → 更新 token 字段。 - -**Step 4: 登出/改密时同时清空 refresh_token** - -在 `Logout` 和 `ChangePassword` 中增加 `Del("account:{id}:refresh")`。 - -**Step 5: 编译 + 提交** - -```bash -git add backend/internal/auth/jwt.go backend/internal/account/ -git commit -m "feat: Refresh Token 机制 — Access 15min + Refresh 7天" -``` - ---- - -### Task 5: 前端 auth store + client.ts 适配双 Token - -**Files:** -- Modify: `frontend/src/stores/auth.ts` -- Modify: `frontend/src/api/client.ts` -- Modify: `frontend/src/api/account.ts` - -**Step 1: auth store 存储双 token** - -```typescript -const ACCESS_KEY = 'access_token' -const REFRESH_KEY = 'refresh_token' - -// 新增字段 -const refreshToken = ref(readToken(REFRESH_KEY)) - -function setTokens(access: string, refresh: string) { - token.value = access; refreshToken.value = refresh - writeToken(ACCESS_KEY, access); writeToken(REFRESH_KEY, refresh) -} - -function clearTokens() { - token.value = null; refreshToken.value = null - removeToken(ACCESS_KEY); removeToken(REFRESH_KEY) -} -``` - -**Step 2: client.ts 401 自动刷新** - -```typescript -async function tryRefresh(): Promise { - const auth = useAuthStore() - if (!auth.refreshToken) return null - try { - const res = await postJson<{ token: string }>('/account/refresh', { refresh_token: auth.refreshToken }) - auth.setToken(res.token) - return res.token - } catch { - auth.clearTokens() - return null - } -} -``` - -在 `postJson` 和 `postForm` 的 `!res.ok` 分支中,401 时先尝试刷新,成功则重试原请求。 - -**Step 3: 编译验证** - -Run: `npm run build` -Expected: 通过 - -**Step 4: Commit** - -```bash -git add frontend/src/stores/auth.ts frontend/src/api/client.ts frontend/src/api/account.ts -git commit -m "feat: 前端双 Token 适配 — 401 自动刷新 + Refresh Token 存储" -``` - ---- - -### Task 6: 前端用户 Profile UI - -**Files:** -- Modify: `frontend/src/views/AccountView.vue` -- Modify: `frontend/src/components/UserAvatar.vue` -- Modify: `frontend/src/views/HomeView.vue` — Feed 卡片中 UserAvatar 传递头像 URL -- Modify: `frontend/src/api/account.ts` — 新增 API 调用 - -**Step 1: UserAvatar 支持 src** - -```vue - - -``` - -**Step 2: AccountView 加头像上传 + bio 编辑** - -在登录后的 AccountView 中增加:头像上传按钮(调用 `/account/uploadAvatar`)、bio 编辑输入框(调用 `/account/updateProfile`)。 - -**Step 3: 编译验证** - -Run: `npm run build` -Expected: 通过 - -**Step 4: Commit** - -```bash -git add frontend/src/components/UserAvatar.vue frontend/src/views/AccountView.vue frontend/src/views/HomeView.vue frontend/src/api/account.ts -git commit -m "feat: 前端用户 Profile UI — 头像上传 + bio 编辑 + 登录记住我" -``` - ---- - -## 验证清单 - -完成所有 Task 后: - -```bash -cd backend && go build ./... && go vet ./... && go test ./... -cd frontend && npm run build -``` - -Expected: 全部通过 diff --git a/frontend/index.html b/frontend/index.html index f0c77aa..fe569d4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,6 @@ - ShortVideo diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/vue.svg b/frontend/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/frontend/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue deleted file mode 100644 index a3281aa..0000000 --- a/frontend/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - -