131 lines
3.3 KiB
Go
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[:])
|
|
}
|