fix: 提前检测video是否为nil

This commit is contained in:
Leon
2025-12-24 20:48:36 +08:00
parent a7c6525f57
commit bd3d26ddfe
2 changed files with 80 additions and 38 deletions

View File

@@ -2,6 +2,7 @@ package video
import ( import (
"context" "context"
"errors"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -43,7 +44,7 @@ func (vr *VideoRepository) ListByAuthorID(ctx context.Context, authorID int64) (
func (vr *VideoRepository) GetByID(ctx context.Context, id uint) (*Video, error) { func (vr *VideoRepository) GetByID(ctx context.Context, id uint) (*Video, error) {
var video Video var video Video
if err := vr.db.WithContext(ctx).First(&video, id).Error; err != nil { if err := vr.db.WithContext(ctx).First(&video, id).Error; err != nil {
return nil, err return (*Video)(nil), err
} }
return &video, nil return &video, nil
} }
@@ -60,7 +61,7 @@ func (vr *VideoRepository) UpdateLikesCount(ctx context.Context, id uint, likesC
func (vr *VideoRepository) IsExist(ctx context.Context, id uint) (bool, error) { func (vr *VideoRepository) IsExist(ctx context.Context, id uint) (bool, error) {
var video Video var video Video
if err := vr.db.WithContext(ctx).First(&video, id).Error; err != nil { if err := vr.db.WithContext(ctx).First(&video, id).Error; err != nil {
if err == gorm.ErrRecordNotFound { if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil return false, nil
} }
return false, err return false, err

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
rediscache "feedsystem_video_go/internal/redis" rediscache "feedsystem_video_go/internal/redis"
@@ -21,6 +22,13 @@ func NewVideoService(repo *VideoRepository, cache *rediscache.Client) *VideoServ
} }
func (vs *VideoService) Publish(ctx context.Context, video *Video) error { func (vs *VideoService) Publish(ctx context.Context, video *Video) error {
if video == nil {
return errors.New("video is nil")
}
video.Title = strings.TrimSpace(video.Title)
video.PlayURL = strings.TrimSpace(video.PlayURL)
video.CoverURL = strings.TrimSpace(video.CoverURL)
if video.Title == "" { if video.Title == "" {
return errors.New("title is required") return errors.New("title is required")
} }
@@ -41,12 +49,19 @@ func (vs *VideoService) Delete(ctx context.Context, id uint, authorID uint) erro
if err != nil { if err != nil {
return err return err
} }
if video == nil {
return errors.New("video not found")
}
if video.AuthorID != authorID { if video.AuthorID != authorID {
return errors.New("unauthorized") return errors.New("unauthorized")
} }
if err := vs.repo.DeleteVideo(ctx, id); err != nil { if err := vs.repo.DeleteVideo(ctx, id); err != nil {
return err return err
} }
if vs.cache != nil {
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
_ = vs.cache.Del(context.Background(), cacheKey)
}
return nil return nil
} }
@@ -59,45 +74,77 @@ func (vs *VideoService) ListByAuthorID(ctx context.Context, authorID uint) ([]Vi
} }
func (vs *VideoService) GetDetail(ctx context.Context, id uint) (*Video, error) { func (vs *VideoService) GetDetail(ctx context.Context, id uint) (*Video, error) {
if vs.cache != nil { cacheKey := fmt.Sprintf("video:detail:id=%d", id)
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
cacheCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) getCached := func() (*Video, bool) {
opCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel() defer cancel()
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil { b, err := vs.cache.GetBytes(opCtx, cacheKey)
if err != nil {
return nil, false
}
var cached Video
if err := json.Unmarshal(b, &cached); err != nil {
return nil, false
}
return &cached, true
}
setCached := func(video *Video) {
b, err := json.Marshal(video)
if err != nil {
return
}
opCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
_ = vs.cache.SetBytes(opCtx, cacheKey, b, vs.cacheTTL)
}
if vs.cache != nil {
if v, ok := getCached(); ok {
return v, nil
}
opCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
b, err := vs.cache.GetBytes(opCtx, cacheKey)
cancel()
if err == nil {
var cached Video var cached Video
if err := json.Unmarshal(b, &cached); err == nil { if err := json.Unmarshal(b, &cached); err == nil {
return &cached, nil return &cached, nil
} }
} else if rediscache.IsMiss(err) { } else if rediscache.IsMiss(err) {
lockKey := "lock:" + cacheKey lockKey := "lock:" + cacheKey
token, locked, _ := vs.cache.Lock(cacheCtx, lockKey, 500*time.Millisecond)
if locked { lockCtx, lockCancel := context.WithTimeout(ctx, 50*time.Millisecond)
token, locked, lockErr := vs.cache.Lock(lockCtx, lockKey, 2*time.Second)
lockCancel()
if lockErr == nil && locked {
defer func() { _ = vs.cache.Unlock(context.Background(), lockKey, token) }() defer func() { _ = vs.cache.Unlock(context.Background(), lockKey, token) }()
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil {
var cached Video if v, ok := getCached(); ok {
if err := json.Unmarshal(b, &cached); err == nil { return v, nil
return &cached, nil
}
} else { // 缓存未命中,从数据库中查询
video, err := vs.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
if b, err := json.Marshal(video); err == nil {
_ = vs.cache.SetBytes(cacheCtx, cacheKey, b, vs.cacheTTL)
}
return video, nil
} }
} else { // 缓存未命中其他goroutine正在查询等待
for i := 0; i < 5; i++ { video, err := vs.repo.GetByID(ctx, id)
time.Sleep(20 * time.Millisecond) if err != nil {
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil { return nil, err
var cached Video }
if err := json.Unmarshal(b, &cached); err == nil { setCached(video)
return &cached, nil return video, nil
} }
}
// 没拿到锁:等待别人回填缓存
for i := 0; i < 5; i++ {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(20 * time.Millisecond):
}
if v, ok := getCached(); ok {
return v, nil
} }
} }
} }
@@ -107,14 +154,8 @@ func (vs *VideoService) GetDetail(ctx context.Context, id uint) (*Video, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if vs.cache != nil { if vs.cache != nil {
cacheKey := fmt.Sprintf("video:detail:id=%d", id) setCached(video)
if b, err := json.Marshal(video); err == nil {
cacheCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
_ = vs.cache.SetBytes(cacheCtx, cacheKey, b, vs.cacheTTL)
}
} }
return video, nil return video, nil
} }