move: 后端

This commit is contained in:
Leon
2025-12-25 22:40:20 +08:00
parent 99951ec3dc
commit 9ba916643e
31 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
package video
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"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 == 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 == "" {
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 == nil {
return errors.New("video not found")
}
if video.AuthorID != authorID {
return errors.New("unauthorized")
}
if err := vs.repo.DeleteVideo(ctx, id); err != nil {
return err
}
if vs.cache != nil {
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
_ = vs.cache.Del(context.Background(), cacheKey)
}
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) {
cacheKey := fmt.Sprintf("video:detail:id=%d", id)
getCached := func() (*Video, bool) {
opCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
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
if err := json.Unmarshal(b, &cached); err == nil {
return &cached, nil
}
} else if rediscache.IsMiss(err) {
lockKey := "lock:" + cacheKey
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) }()
if v, ok := getCached(); ok {
return v, nil
}
video, err := vs.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
setCached(video)
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
}
}
}
}
video, err := vs.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
if vs.cache != nil {
setCached(video)
}
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
}