fix: 提前检测video是否为nil
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user