sub2api/backend/internal/service/user_platform_quota_port.go
DaydreamCoding 6b39b344d8 feat(quota): 用户 × 平台 USD 配额
为用户在 anthropic/openai/gemini/antigravity 四个平台上提供日/周/月
三个窗口的 USD 配额管控。配额语义:未设置=不限制,0=禁用,>0=美元上限。

两层模型:
- 配置层:系统默认配额,以及 email/linuxdo/oidc/wechat/github/google/
  dingtalk 七个鉴权来源的默认配额,存于 settings,以嵌套 JSON 整体读写
  (系统 1 个 key + 每个来源 1 个 key),整体替换语义。
- 运行时层:user_platform_quota 表按用户记录实际配额,与配置层解耦。

后端:新增 ent schema 与 140_user_platform_quotas.sql 迁移、repository
与 service 端口、计费链路集成、管理端与用户端读写接口。
前端:管理端设置页配额编辑、用户配额管理 Modal、用户 Dashboard 展示、
中英文案。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:49:20 +08:00

51 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"errors"
"time"
)
// ErrUserPlatformQuotaNotFound service 层 sentinelquota 记录不存在。
// adapter 将 repository.ErrUserPlatformQuotaNotFound 包装为此错误,
// handler 只需引用 service 包,无需直接依赖 repository 包。
var ErrUserPlatformQuotaNotFound = errors.New("user platform quota not found")
// 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
}
// UserPlatformQuotaRepository 定义 service 层所需的 user × platform quota 数据访问端口。
// repository 包的 userPlatformQuotaRepository 实现此接口。
type UserPlatformQuotaRepository interface {
// GetByUserPlatform 查询单条配额记录,未找到时返回 (nil, nil)。
GetByUserPlatform(ctx context.Context, userID int64, platform string) (*UserPlatformQuotaRecord, error)
// BulkInsertInitial 幂等批量插入初始配额记录ON CONFLICT DO NOTHING
BulkInsertInitial(ctx context.Context, records []UserPlatformQuotaRecord) error
// IncrementUsageWithReset 原子地累加用量,若窗口已过期则先重置再累加。
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 中出现的所有 active 行
// 2. 对 records 中每条UPDATE 已存在的含重新激活软删行UPDATE 未命中时 INSERT
// 仅改 *_limit_usd + deleted_at + 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
}