128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package video
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
rediscache "feedsystem_video_go/internal/redis"
|
||
)
|
||
|
||
type VideoService struct {
|
||
repo *VideoRepository
|
||
cache *rediscache.Client
|
||
cacheTTL time.Duration
|
||
}
|
||
|
||
func NewVideoService(repo *VideoRepository, cache *rediscache.Client) *VideoService {
|
||
return &VideoService{repo: repo, cache: cache, cacheTTL: 5 * time.Minute}
|
||
}
|
||
|
||
func (vs *VideoService) Publish(ctx context.Context, video *Video) error {
|
||
if video.Title == "" {
|
||
return errors.New("title is required")
|
||
}
|
||
if video.PlayURL == "" {
|
||
return errors.New("play url is required")
|
||
}
|
||
if video.CoverURL == "" {
|
||
return errors.New("cover url is required")
|
||
}
|
||
if err := vs.repo.CreateVideo(ctx, video); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (vs *VideoService) Delete(ctx context.Context, id uint, authorID uint) error {
|
||
video, err := vs.repo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if video.AuthorID != authorID {
|
||
return errors.New("unauthorized")
|
||
}
|
||
if err := vs.repo.DeleteVideo(ctx, id); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (vs *VideoService) ListByAuthorID(ctx context.Context, authorID uint) ([]Video, error) {
|
||
videos, err := vs.repo.ListByAuthorID(ctx, int64(authorID))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return videos, nil
|
||
}
|
||
|
||
func (vs *VideoService) GetDetail(ctx context.Context, id uint) (*Video, error) {
|
||
if vs.cache != nil {
|
||
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
|
||
cacheCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
|
||
defer cancel()
|
||
|
||
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil {
|
||
var cached Video
|
||
if err := json.Unmarshal(b, &cached); err == nil {
|
||
return &cached, nil
|
||
}
|
||
} else if rediscache.IsMiss(err) {
|
||
lockKey := "lock:" + cacheKey
|
||
token, locked, _ := vs.cache.Lock(cacheCtx, lockKey, 500*time.Millisecond)
|
||
if locked {
|
||
defer func() { _ = vs.cache.Unlock(context.Background(), lockKey, token) }()
|
||
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil {
|
||
var cached Video
|
||
if err := json.Unmarshal(b, &cached); err == 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++ {
|
||
time.Sleep(20 * time.Millisecond)
|
||
if b, err := vs.cache.GetBytes(cacheCtx, cacheKey); err == nil {
|
||
var cached Video
|
||
if err := json.Unmarshal(b, &cached); err == nil {
|
||
return &cached, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
video, err := vs.repo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if vs.cache != nil {
|
||
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
|
||
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
|
||
}
|
||
|
||
func (vs *VideoService) UpdateLikesCount(ctx context.Context, id uint, likesCount int64) error {
|
||
if err := vs.repo.UpdateLikesCount(ctx, id, likesCount); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|