mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-03 16:41:20 +00:00
user_platform_quotas 只保存至少配置了一档限额的记录;不存在的行等价于不限额。 - 注册播种只为配置了限额的平台建行,一个都没有时不写入 - IncrementUsageWithReset 无行时不再 fail-open 建行 - BatchSnapshotUsage 改为只更新既有活跃行,不再插入无限额的新行 - 仓储 BulkInsertInitial/UpsertForUser 把三档全空的记录视为未配置; 管理员全量替换接口同样剔除,清空限额即软删并放弃累计用量 - 迁移 238 删除三档全 NULL 的历史行 - 用户仪表盘平台卡片 = 有用量的平台 ∪ 配置了限额的平台,计数改为卡片数 - 管理员限额弹窗对未保存限额的平台禁用"重置窗口"
77 lines
4.1 KiB
Go
77 lines
4.1 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
)
|
||
|
||
// ErrUserPlatformQuotaNotFound service 层 sentinel:quota 记录不存在。
|
||
// adapter 将 repository.ErrUserPlatformQuotaNotFound 包装为此错误,
|
||
// handler 只需引用 service 包,无需直接依赖 repository 包。
|
||
var ErrUserPlatformQuotaNotFound = errors.New("user platform quota not found")
|
||
|
||
// ErrUserPlatformQuotaFKViolation service 层 sentinel:批量快照写入命中了 user_id 不在 users 表的记录。
|
||
// adapter 负责将 repository 层同名 sentinel 包装为此错误。
|
||
var ErrUserPlatformQuotaFKViolation = errors.New("user platform quota snapshot FK violation")
|
||
|
||
// UserPlatformQuotaSnapshot 是 service 层 flusher 向 DB 写入快照时使用的传输结构。
|
||
// 字段语义与 repository.UserPlatformQuotaSnapshot 完全对应,由 adapter 负责转换。
|
||
type UserPlatformQuotaSnapshot struct {
|
||
UserID int64
|
||
Platform string
|
||
DailyUsageUSD float64
|
||
WeeklyUsageUSD float64
|
||
MonthlyUsageUSD float64
|
||
DailyWindowStart time.Time
|
||
WeeklyWindowStart time.Time
|
||
MonthlyWindowStart time.Time
|
||
}
|
||
|
||
// UserPlatformQuotaRecord service 层传输结构体(与 repository 层解耦)。
|
||
type UserPlatformQuotaRecord struct {
|
||
UserID int64
|
||
Platform string
|
||
DailyLimitUSD *float64
|
||
WeeklyLimitUSD *float64
|
||
MonthlyLimitUSD *float64
|
||
DailyUsageUSD float64
|
||
WeeklyUsageUSD float64
|
||
MonthlyUsageUSD float64
|
||
// 窗口起始时间(可选,用于未来 reset 校验)
|
||
DailyWindowStart *time.Time
|
||
WeeklyWindowStart *time.Time
|
||
MonthlyWindowStart *time.Time
|
||
}
|
||
|
||
// HasAnyLimit 报告该记录是否至少配置了一档限额(0 也算配置)。
|
||
// 三档全空的记录视为未配置:不写入 user_platform_quotas,等价于不限额。
|
||
func (r UserPlatformQuotaRecord) HasAnyLimit() bool {
|
||
return r.DailyLimitUSD != nil || r.WeeklyLimitUSD != nil || r.MonthlyLimitUSD != nil
|
||
}
|
||
|
||
// UserPlatformQuotaRepository 定义 service 层所需的 user × platform quota 数据访问端口。
|
||
// repository 包的 userPlatformQuotaRepository 实现此接口。
|
||
type UserPlatformQuotaRepository interface {
|
||
// GetByUserPlatform 查询单条配额记录,未找到时返回 (nil, nil)。
|
||
GetByUserPlatform(ctx context.Context, userID int64, platform string) (*UserPlatformQuotaRecord, error)
|
||
// BulkInsertInitial 幂等批量插入初始配额记录:冲突时只补既有行为 NULL 的档位;三档全空的记录跳过。
|
||
BulkInsertInitial(ctx context.Context, records []UserPlatformQuotaRecord) error
|
||
// IncrementUsageWithReset 原子地累加用量,若窗口已过期则先重置再累加;记录不存在时不建行、直接返回 nil。
|
||
IncrementUsageWithReset(ctx context.Context, userID int64, platform string, cost float64, now time.Time) error
|
||
// ListByUser 查询用户的所有平台配额记录。
|
||
ListByUser(ctx context.Context, userID int64) ([]UserPlatformQuotaRecord, error)
|
||
// UpsertForUser 全量替换该用户所有平台限额配置(事务内):
|
||
// 1. 软删除未在 records 中出现、或在 records 中但三档全空的所有 active 行
|
||
// 2. 对每条至少配置了一档限额的 record:UPDATE 已存在的 active 行;未命中时 INSERT
|
||
// 仅改 *_limit_usd + updated_at,保留 *_usage_usd / *_window_start。
|
||
// 清空某平台的限额即软删该行并放弃其累计用量;之后再次配置限额会新建行、从新窗口起算。
|
||
// records 为空时仅执行步骤 1。
|
||
UpsertForUser(ctx context.Context, userID int64, records []UserPlatformQuotaRecord) error
|
||
// ResetExpiredWindow 重置指定窗口("daily"|"weekly"|"monthly")的用量与起始时间。
|
||
// 未命中活跃记录时返回(service-side wrapper of repository.ErrUserPlatformQuotaNotFound)。
|
||
ResetExpiredWindow(ctx context.Context, userID int64, platform string, window string, newStart time.Time) error
|
||
// BatchSnapshotUsage 绝对值覆盖写入整批 usage 快照到既有活跃行;行不存在或已软删的 (user,platform) 跳过。
|
||
BatchSnapshotUsage(ctx context.Context, snapshots []UserPlatformQuotaSnapshot, now time.Time) error
|
||
}
|