This commit is contained in:
zhengchiliu
2026-09-15 00:53:04 +08:00
parent 32a0b87e9e
commit 112c5f8e46
42 changed files with 1121 additions and 304 deletions
+29 -43
View File
@@ -82,37 +82,9 @@ func genUUID() string {
// recordDailyUsage 记录用户当日使用计数(best-effort,用于统计,不作为硬限制;失败仅告警)。
// kind 复用 QuotaType 语义:QuotaTypeAI / QuotaTypeOutfitShare / QuotaTypeClosetShare。
func recordDailyUsage(ctx context.Context, userID int64, kind int8) {
usageDao := mysqldao.NewUserDailyUsageDao()
statDate := todayStatDate()
// 确保当日计数行存在。
if _, err := usageDao.UserDailyUsageGetByUserIDAndDate(ctx, userID, statDate); err != nil {
if isNotFound(err) {
if cerr := usageDao.UserDailyUsageCreate(ctx, &mysqlmodel.UserDailyUsage{
UserID: userID,
StatDate: statDate,
}); cerr != nil {
slog.WarnContext(ctx, "create daily usage failed", "user_id", userID, "error", cerr)
return
}
} else {
slog.WarnContext(ctx, "get daily usage failed", "user_id", userID, "error", err)
return
}
}
var err error
switch kind {
case QuotaTypeAI:
err = usageDao.UserDailyUsageIncrementAI(ctx, userID, statDate)
case QuotaTypeOutfitShare:
err = usageDao.UserDailyUsageIncrementOutfitShare(ctx, userID, statDate)
case QuotaTypeClosetShare:
err = usageDao.UserDailyUsageIncrementClosetShare(ctx, userID, statDate)
}
if err != nil {
slog.WarnContext(ctx, "increment daily usage failed", "user_id", userID, "kind", kind, "error", err)
func recordDailyUsage(ctx context.Context, userID int64, kind int8, delta int64) {
if err := mysqldao.NewUserDailyUsageDao().UserDailyUsageAdjust(ctx, userID, todayStatDate(), kind, delta); err != nil {
slog.WarnContext(ctx, "adjust daily usage failed", "user_id", userID, "kind", kind, "delta", delta, "error", err)
}
}
@@ -216,16 +188,24 @@ func dailyQuotaKey(quotaType int8, userID int64, date int64) string {
}
}
// getDailyUsed 读取当日已用计数(Redis 优先,异常回落 MySQL 统计)。
// getDailyUsed 读取 MySQL 权威当日计数;数据库异常时只把 Redis 作为展示兜底。
func getDailyUsed(ctx context.Context, userID int64, quotaType int8) int64 {
key := dailyQuotaKey(quotaType, userID, todayStatDate())
if key == "" {
return 0
usage, err := mysqldao.NewUserDailyUsageDao().UserDailyUsageGetByUserIDAndDate(ctx, userID, todayStatDate())
if err == nil {
switch quotaType {
case QuotaTypeAI:
return usage.AIRecommendCount
case QuotaTypeOutfitShare:
return usage.OutfitShareCount
case QuotaTypeClosetShare:
return usage.ClosetShareCount
}
}
if val, err := redisdao.DailyQuotaValue(ctx, key); err == nil {
key := dailyQuotaKey(quotaType, userID, todayStatDate())
if val, rerr := redisdao.DailyQuotaValue(ctx, key); rerr == nil {
return val
}
return getDailyUsedFromMySQL(ctx, userID, quotaType)
return 0
}
// getDailyUsedFromMySQL 从 user_daily_usages 读取当日计数(Redis 故障兜底)。
@@ -257,15 +237,17 @@ func consumeDailyQuota(ctx context.Context, userID int64, quotaType int8, n int6
if key == "" {
return errcode.QuotaInsufficient
}
val, err := redisdao.DailyQuotaConsume(ctx, key, quota.Total, n)
consumed, err := mysqldao.NewUserDailyUsageDao().UserDailyUsageConsume(ctx, userID, todayStatDate(), quotaType, n, quota.Total)
if err != nil {
return errcode.CacheError
return errcode.DatabaseError
}
if val < 0 {
if !consumed {
return errcode.QuotaInsufficient
}
// 同步 MySQL 当日计数(统计/兜底)。
recordDailyUsage(ctx, userID, quotaType)
// Redis 只作缓存;删除后下次从 MySQL 权威计数恢复,不受丢键影响。
if err := redisdao.DailyQuotaDelete(ctx, key); err != nil {
slog.WarnContext(ctx, "delete daily quota cache failed", "user_id", userID, "quota_type", quotaType, "error", err)
}
return nil
}
@@ -275,8 +257,12 @@ func refundDailyQuota(ctx context.Context, userID int64, quotaType int8, n int64
if key == "" {
return
}
if err := redisdao.DailyQuotaRefund(ctx, key, n); err != nil {
if err := mysqldao.NewUserDailyUsageDao().UserDailyUsageAdjust(ctx, userID, todayStatDate(), quotaType, -n); err != nil {
slog.WarnContext(ctx, "refund daily quota failed", "user_id", userID, "quota_type", quotaType, "error", err)
return
}
if err := redisdao.DailyQuotaDelete(ctx, key); err != nil {
slog.WarnContext(ctx, "delete daily quota cache after refund failed", "user_id", userID, "quota_type", quotaType, "error", err)
}
}