Files
huangguo_server/app/service/shareser/recommend_share.go
T
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

131 lines
3.3 KiB
Go

package shareser
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
const (
recommendShareEventIDMaxLength = 128
recommendShareEventDedupTTL = 7 * 24 * time.Hour
recommendShareDailyDedupTTL = 48 * time.Hour
recommendShareCleanupTimeout = 5 * time.Second
)
type recommendShareDeduper interface {
SetNXContext(
ctx context.Context,
key string,
value interface{},
expiration time.Duration,
) (bool, error)
DelContext(ctx context.Context, keys ...string) (int64, error)
}
type recommendShareIncrement func(context.Context, primitive.ObjectID) error
func incrementRecommendShareOnce(
ctx context.Context,
deduper recommendShareDeduper,
uid uint64,
videoID primitive.ObjectID,
eventID string,
now time.Time,
) error {
return incrementRecommendShareOnceWith(
ctx,
deduper,
uid,
videoID,
eventID,
now,
func(ctx context.Context, id primitive.ObjectID) error {
return vidmod.IncrementRecommendInteractionContext(
ctx,
id,
vidmod.RecommendInteractionShare,
)
},
)
}
func incrementRecommendShareOnceWith(
ctx context.Context,
deduper recommendShareDeduper,
uid uint64,
videoID primitive.ObjectID,
eventID string,
now time.Time,
increment recommendShareIncrement,
) error {
if err := ctx.Err(); err != nil {
return err
}
if deduper == nil {
return fmt.Errorf("recommend share deduper is nil")
}
if uid == 0 || videoID.IsZero() {
return nil
}
eventID = strings.TrimSpace(eventID)
if len(eventID) > recommendShareEventIDMaxLength {
return fmt.Errorf("share eventId exceeds %d characters", recommendShareEventIDMaxLength)
}
// 无论客户端是否提供 eventId,同一用户、视频、自然日只累计一次推荐分享分,
// 避免客户端通过不断生成新 eventId 刷高权重。eventId 仍用于跨日重试幂等。
day := now.In(time.FixedZone("CST", 8*60*60)).Format("20060102")
dailyKey := recommendShareDedupKey(uid, videoID, "day:"+day)
dailyAcquired, err := deduper.SetNXContext(
ctx, dailyKey, "1", recommendShareDailyDedupTTL,
)
if err != nil || !dailyAcquired {
return err
}
keysToRelease := []string{dailyKey}
if eventID != "" {
eventKey := recommendShareDedupKey(uid, videoID, "event:"+eventID)
eventAcquired, eventErr := deduper.SetNXContext(
ctx, eventKey, "1", recommendShareEventDedupTTL,
)
if eventErr != nil || !eventAcquired {
releaseRecommendShareKeys(deduper, keysToRelease...)
return eventErr
}
keysToRelease = append(keysToRelease, eventKey)
}
err = increment(ctx, videoID)
if err != nil {
// 推荐累计失败时释放幂等标记,使同一事件后续重试仍有补偿机会。
releaseRecommendShareKeys(deduper, keysToRelease...)
return err
}
return nil
}
func releaseRecommendShareKeys(deduper recommendShareDeduper, keys ...string) {
if deduper == nil || len(keys) == 0 {
return
}
cleanupCtx, cancel := context.WithTimeout(
context.Background(), recommendShareCleanupTimeout,
)
defer cancel()
_, _ = deduper.DelContext(cleanupCtx, keys...)
}
func recommendShareDedupKey(uid uint64, videoID primitive.ObjectID, scope string) string {
sum := sha256.Sum256([]byte(fmt.Sprintf("%d:%s:%s", uid, videoID.Hex(), scope)))
return "recommend:short:share-dedup:" + hex.EncodeToString(sum[:])
}