@@ -0,0 +1,130 @@
|
||||
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[:])
|
||||
}
|
||||
Reference in New Issue
Block a user