2025-12-18 13:50:39 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-01-03 17:10:32 -08:00
|
|
|
|
"errors"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
"fmt"
|
2025-12-25 17:15:01 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-01-03 17:10:32 -08:00
|
|
|
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
2025-12-31 23:42:01 +08:00
|
|
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
2025-12-24 21:07:21 +08:00
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
|
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
2025-12-25 20:52:47 +08:00
|
|
|
|
ErrUsageLogNotFound = infraerrors.NotFound("USAGE_LOG_NOT_FOUND", "usage log not found")
|
2025-12-18 13:50:39 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// CreateUsageLogRequest 创建使用日志请求
|
|
|
|
|
|
type CreateUsageLogRequest struct {
|
|
|
|
|
|
UserID int64 `json:"user_id"`
|
2026-01-04 19:27:53 +08:00
|
|
|
|
APIKeyID int64 `json:"api_key_id"`
|
2025-12-18 13:50:39 +08:00
|
|
|
|
AccountID int64 `json:"account_id"`
|
|
|
|
|
|
RequestID string `json:"request_id"`
|
|
|
|
|
|
Model string `json:"model"`
|
|
|
|
|
|
InputTokens int `json:"input_tokens"`
|
|
|
|
|
|
OutputTokens int `json:"output_tokens"`
|
|
|
|
|
|
CacheCreationTokens int `json:"cache_creation_tokens"`
|
|
|
|
|
|
CacheReadTokens int `json:"cache_read_tokens"`
|
|
|
|
|
|
CacheCreation5mTokens int `json:"cache_creation_5m_tokens"`
|
|
|
|
|
|
CacheCreation1hTokens int `json:"cache_creation_1h_tokens"`
|
|
|
|
|
|
InputCost float64 `json:"input_cost"`
|
|
|
|
|
|
OutputCost float64 `json:"output_cost"`
|
|
|
|
|
|
CacheCreationCost float64 `json:"cache_creation_cost"`
|
|
|
|
|
|
CacheReadCost float64 `json:"cache_read_cost"`
|
|
|
|
|
|
TotalCost float64 `json:"total_cost"`
|
|
|
|
|
|
ActualCost float64 `json:"actual_cost"`
|
|
|
|
|
|
RateMultiplier float64 `json:"rate_multiplier"`
|
|
|
|
|
|
Stream bool `json:"stream"`
|
|
|
|
|
|
DurationMs *int `json:"duration_ms"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UsageStats 使用统计
|
|
|
|
|
|
type UsageStats struct {
|
2025-12-19 21:26:19 +08:00
|
|
|
|
TotalRequests int64 `json:"total_requests"`
|
|
|
|
|
|
TotalInputTokens int64 `json:"total_input_tokens"`
|
|
|
|
|
|
TotalOutputTokens int64 `json:"total_output_tokens"`
|
|
|
|
|
|
TotalCacheTokens int64 `json:"total_cache_tokens"`
|
|
|
|
|
|
TotalTokens int64 `json:"total_tokens"`
|
|
|
|
|
|
TotalCost float64 `json:"total_cost"`
|
|
|
|
|
|
TotalActualCost float64 `json:"total_actual_cost"`
|
|
|
|
|
|
AverageDurationMs float64 `json:"average_duration_ms"`
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UsageService 使用统计服务
|
|
|
|
|
|
type UsageService struct {
|
2026-01-10 22:52:13 +08:00
|
|
|
|
usageRepo UsageLogRepository
|
|
|
|
|
|
userRepo UserRepository
|
|
|
|
|
|
entClient *dbent.Client
|
|
|
|
|
|
authCacheInvalidator APIKeyAuthCacheInvalidator
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewUsageService 创建使用统计服务实例
|
2026-01-10 22:52:13 +08:00
|
|
|
|
func NewUsageService(usageRepo UsageLogRepository, userRepo UserRepository, entClient *dbent.Client, authCacheInvalidator APIKeyAuthCacheInvalidator) *UsageService {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
return &UsageService{
|
2026-01-10 22:52:13 +08:00
|
|
|
|
usageRepo: usageRepo,
|
|
|
|
|
|
userRepo: userRepo,
|
|
|
|
|
|
entClient: entClient,
|
|
|
|
|
|
authCacheInvalidator: authCacheInvalidator,
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create 创建使用日志
|
2025-12-26 15:40:24 +08:00
|
|
|
|
func (s *UsageService) Create(ctx context.Context, req CreateUsageLogRequest) (*UsageLog, error) {
|
2026-01-03 17:10:32 -08:00
|
|
|
|
// 使用数据库事务保证「使用日志插入」与「扣费」的原子性,避免重复扣费或漏扣风险。
|
|
|
|
|
|
tx, err := s.entClient.Tx(ctx)
|
|
|
|
|
|
if err != nil && !errors.Is(err, dbent.ErrTxStarted) {
|
|
|
|
|
|
return nil, fmt.Errorf("begin transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
txCtx := ctx
|
|
|
|
|
|
if err == nil {
|
2026-01-04 10:14:47 +08:00
|
|
|
|
defer func() { _ = tx.Rollback() }()
|
2026-01-03 17:10:32 -08:00
|
|
|
|
txCtx = dbent.NewTxContext(ctx, tx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
|
// 验证用户存在
|
2026-01-03 17:10:32 -08:00
|
|
|
|
_, err = s.userRepo.GetByID(txCtx, req.UserID)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get user: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建使用日志
|
2025-12-26 15:40:24 +08:00
|
|
|
|
usageLog := &UsageLog{
|
2025-12-18 13:50:39 +08:00
|
|
|
|
UserID: req.UserID,
|
2026-01-04 19:27:53 +08:00
|
|
|
|
APIKeyID: req.APIKeyID,
|
2025-12-18 13:50:39 +08:00
|
|
|
|
AccountID: req.AccountID,
|
|
|
|
|
|
RequestID: req.RequestID,
|
|
|
|
|
|
Model: req.Model,
|
|
|
|
|
|
InputTokens: req.InputTokens,
|
|
|
|
|
|
OutputTokens: req.OutputTokens,
|
|
|
|
|
|
CacheCreationTokens: req.CacheCreationTokens,
|
|
|
|
|
|
CacheReadTokens: req.CacheReadTokens,
|
|
|
|
|
|
CacheCreation5mTokens: req.CacheCreation5mTokens,
|
|
|
|
|
|
CacheCreation1hTokens: req.CacheCreation1hTokens,
|
|
|
|
|
|
InputCost: req.InputCost,
|
|
|
|
|
|
OutputCost: req.OutputCost,
|
|
|
|
|
|
CacheCreationCost: req.CacheCreationCost,
|
|
|
|
|
|
CacheReadCost: req.CacheReadCost,
|
|
|
|
|
|
TotalCost: req.TotalCost,
|
|
|
|
|
|
ActualCost: req.ActualCost,
|
|
|
|
|
|
RateMultiplier: req.RateMultiplier,
|
|
|
|
|
|
Stream: req.Stream,
|
|
|
|
|
|
DurationMs: req.DurationMs,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-03 17:10:32 -08:00
|
|
|
|
inserted, err := s.usageRepo.Create(txCtx, usageLog)
|
|
|
|
|
|
if err != nil {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
return nil, fmt.Errorf("create usage log: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 扣除用户余额
|
2026-01-10 22:52:13 +08:00
|
|
|
|
balanceUpdated := false
|
2026-01-03 17:10:32 -08:00
|
|
|
|
if inserted && req.ActualCost > 0 {
|
|
|
|
|
|
if err := s.userRepo.UpdateBalance(txCtx, req.UserID, -req.ActualCost); err != nil {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
return nil, fmt.Errorf("update user balance: %w", err)
|
|
|
|
|
|
}
|
2026-01-10 22:52:13 +08:00
|
|
|
|
balanceUpdated = true
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-03 17:10:32 -08:00
|
|
|
|
if tx != nil {
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("commit transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 22:52:13 +08:00
|
|
|
|
s.invalidateUsageCaches(ctx, req.UserID, balanceUpdated)
|
|
|
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
|
return usageLog, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 22:52:13 +08:00
|
|
|
|
func (s *UsageService) invalidateUsageCaches(ctx context.Context, userID int64, balanceUpdated bool) {
|
|
|
|
|
|
if !balanceUpdated || s.authCacheInvalidator == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
s.authCacheInvalidator.InvalidateAuthCacheByUserID(ctx, userID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
|
// GetByID 根据ID获取使用日志
|
2025-12-26 15:40:24 +08:00
|
|
|
|
func (s *UsageService) GetByID(ctx context.Context, id int64) (*UsageLog, error) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
log, err := s.usageRepo.GetByID(ctx, id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get usage log: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return log, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListByUser 获取用户的使用日志列表
|
2025-12-26 15:40:24 +08:00
|
|
|
|
func (s *UsageService) ListByUser(ctx context.Context, userID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
logs, pagination, err := s.usageRepo.ListByUser(ctx, userID, params)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, fmt.Errorf("list usage logs: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return logs, pagination, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 19:27:53 +08:00
|
|
|
|
// ListByAPIKey 获取API Key的使用日志列表
|
|
|
|
|
|
func (s *UsageService) ListByAPIKey(ctx context.Context, apiKeyID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
|
|
|
|
|
|
logs, pagination, err := s.usageRepo.ListByAPIKey(ctx, apiKeyID, params)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, fmt.Errorf("list usage logs: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return logs, pagination, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListByAccount 获取账号的使用日志列表
|
2025-12-26 15:40:24 +08:00
|
|
|
|
func (s *UsageService) ListByAccount(ctx context.Context, accountID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
logs, pagination, err := s.usageRepo.ListByAccount(ctx, accountID, params)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, fmt.Errorf("list usage logs: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return logs, pagination, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStatsByUser 获取用户的使用统计
|
|
|
|
|
|
func (s *UsageService) GetStatsByUser(ctx context.Context, userID int64, startTime, endTime time.Time) (*UsageStats, error) {
|
2025-12-27 16:03:57 +08:00
|
|
|
|
stats, err := s.usageRepo.GetUserStatsAggregated(ctx, userID, startTime, endTime)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-27 16:03:57 +08:00
|
|
|
|
return nil, fmt.Errorf("get user stats: %w", err)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-27 16:03:57 +08:00
|
|
|
|
return &UsageStats{
|
|
|
|
|
|
TotalRequests: stats.TotalRequests,
|
|
|
|
|
|
TotalInputTokens: stats.TotalInputTokens,
|
|
|
|
|
|
TotalOutputTokens: stats.TotalOutputTokens,
|
|
|
|
|
|
TotalCacheTokens: stats.TotalCacheTokens,
|
|
|
|
|
|
TotalTokens: stats.TotalTokens,
|
|
|
|
|
|
TotalCost: stats.TotalCost,
|
|
|
|
|
|
TotalActualCost: stats.TotalActualCost,
|
|
|
|
|
|
AverageDurationMs: stats.AverageDurationMs,
|
|
|
|
|
|
}, nil
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 19:27:53 +08:00
|
|
|
|
// GetStatsByAPIKey 获取API Key的使用统计
|
|
|
|
|
|
func (s *UsageService) GetStatsByAPIKey(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) (*UsageStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetAPIKeyStatsAggregated(ctx, apiKeyID, startTime, endTime)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-27 16:03:57 +08:00
|
|
|
|
return nil, fmt.Errorf("get api key stats: %w", err)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-27 16:03:57 +08:00
|
|
|
|
return &UsageStats{
|
|
|
|
|
|
TotalRequests: stats.TotalRequests,
|
|
|
|
|
|
TotalInputTokens: stats.TotalInputTokens,
|
|
|
|
|
|
TotalOutputTokens: stats.TotalOutputTokens,
|
|
|
|
|
|
TotalCacheTokens: stats.TotalCacheTokens,
|
|
|
|
|
|
TotalTokens: stats.TotalTokens,
|
|
|
|
|
|
TotalCost: stats.TotalCost,
|
|
|
|
|
|
TotalActualCost: stats.TotalActualCost,
|
|
|
|
|
|
AverageDurationMs: stats.AverageDurationMs,
|
|
|
|
|
|
}, nil
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStatsByAccount 获取账号的使用统计
|
|
|
|
|
|
func (s *UsageService) GetStatsByAccount(ctx context.Context, accountID int64, startTime, endTime time.Time) (*UsageStats, error) {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
stats, err := s.usageRepo.GetAccountStatsAggregated(ctx, accountID, startTime, endTime)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return nil, fmt.Errorf("get account stats: %w", err)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return &UsageStats{
|
|
|
|
|
|
TotalRequests: stats.TotalRequests,
|
|
|
|
|
|
TotalInputTokens: stats.TotalInputTokens,
|
|
|
|
|
|
TotalOutputTokens: stats.TotalOutputTokens,
|
|
|
|
|
|
TotalCacheTokens: stats.TotalCacheTokens,
|
|
|
|
|
|
TotalTokens: stats.TotalTokens,
|
|
|
|
|
|
TotalCost: stats.TotalCost,
|
|
|
|
|
|
TotalActualCost: stats.TotalActualCost,
|
|
|
|
|
|
AverageDurationMs: stats.AverageDurationMs,
|
|
|
|
|
|
}, nil
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStatsByModel 获取模型的使用统计
|
|
|
|
|
|
func (s *UsageService) GetStatsByModel(ctx context.Context, modelName string, startTime, endTime time.Time) (*UsageStats, error) {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
stats, err := s.usageRepo.GetModelStatsAggregated(ctx, modelName, startTime, endTime)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return nil, fmt.Errorf("get model stats: %w", err)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return &UsageStats{
|
|
|
|
|
|
TotalRequests: stats.TotalRequests,
|
|
|
|
|
|
TotalInputTokens: stats.TotalInputTokens,
|
|
|
|
|
|
TotalOutputTokens: stats.TotalOutputTokens,
|
|
|
|
|
|
TotalCacheTokens: stats.TotalCacheTokens,
|
|
|
|
|
|
TotalTokens: stats.TotalTokens,
|
|
|
|
|
|
TotalCost: stats.TotalCost,
|
|
|
|
|
|
TotalActualCost: stats.TotalActualCost,
|
|
|
|
|
|
AverageDurationMs: stats.AverageDurationMs,
|
|
|
|
|
|
}, nil
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetDailyStats 获取每日使用统计(最近N天)
|
2025-12-20 16:19:40 +08:00
|
|
|
|
func (s *UsageService) GetDailyStats(ctx context.Context, userID int64, days int) ([]map[string]any, error) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
endTime := time.Now()
|
|
|
|
|
|
startTime := endTime.AddDate(0, 0, -days)
|
|
|
|
|
|
|
2025-12-31 08:50:12 +08:00
|
|
|
|
stats, err := s.usageRepo.GetDailyStatsAggregated(ctx, userID, startTime, endTime)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return nil, fmt.Errorf("get daily stats: %w", err)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 08:50:12 +08:00
|
|
|
|
return stats, nil
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Delete 删除使用日志(管理员功能,谨慎使用)
|
|
|
|
|
|
func (s *UsageService) Delete(ctx context.Context, id int64) error {
|
|
|
|
|
|
if err := s.usageRepo.Delete(ctx, id); err != nil {
|
|
|
|
|
|
return fmt.Errorf("delete usage log: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-12-24 08:41:31 +08:00
|
|
|
|
|
|
|
|
|
|
// GetUserDashboardStats returns per-user dashboard summary stats.
|
|
|
|
|
|
func (s *UsageService) GetUserDashboardStats(ctx context.Context, userID int64) (*usagestats.UserDashboardStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetUserDashboardStats(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get user dashboard stats: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 11:41:25 +08:00
|
|
|
|
// GetAPIKeyDashboardStats returns dashboard summary stats filtered by API Key.
|
|
|
|
|
|
func (s *UsageService) GetAPIKeyDashboardStats(ctx context.Context, apiKeyID int64) (*usagestats.UserDashboardStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetAPIKeyDashboardStats(ctx, apiKeyID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get api key dashboard stats: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 08:41:31 +08:00
|
|
|
|
// GetUserUsageTrendByUserID returns per-user usage trend.
|
|
|
|
|
|
func (s *UsageService) GetUserUsageTrendByUserID(ctx context.Context, userID int64, startTime, endTime time.Time, granularity string) ([]usagestats.TrendDataPoint, error) {
|
|
|
|
|
|
trend, err := s.usageRepo.GetUserUsageTrendByUserID(ctx, userID, startTime, endTime, granularity)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get user usage trend: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return trend, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserModelStats returns per-user model usage stats.
|
|
|
|
|
|
func (s *UsageService) GetUserModelStats(ctx context.Context, userID int64, startTime, endTime time.Time) ([]usagestats.ModelStat, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetUserModelStats(ctx, userID, startTime, endTime)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get user model stats: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 19:27:53 +08:00
|
|
|
|
// GetBatchAPIKeyUsageStats returns today/total actual_cost for given api keys.
|
|
|
|
|
|
func (s *UsageService) GetBatchAPIKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchAPIKeyUsageStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetBatchAPIKeyUsageStats(ctx, apiKeyIDs)
|
2025-12-24 08:41:31 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get batch api key usage stats: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListWithFilters lists usage logs with admin filters.
|
2025-12-26 15:40:24 +08:00
|
|
|
|
func (s *UsageService) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]UsageLog, *pagination.PaginationResult, error) {
|
2025-12-24 08:41:31 +08:00
|
|
|
|
logs, result, err := s.usageRepo.ListWithFilters(ctx, params, filters)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, fmt.Errorf("list usage logs with filters: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return logs, result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetGlobalStats returns global usage stats for a time range.
|
|
|
|
|
|
func (s *UsageService) GetGlobalStats(ctx context.Context, startTime, endTime time.Time) (*usagestats.UsageStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetGlobalStats(ctx, startTime, endTime)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get global usage stats: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|
2026-01-06 22:19:07 +08:00
|
|
|
|
|
|
|
|
|
|
// GetStatsWithFilters returns usage stats with optional filters.
|
|
|
|
|
|
func (s *UsageService) GetStatsWithFilters(ctx context.Context, filters usagestats.UsageLogFilters) (*usagestats.UsageStats, error) {
|
|
|
|
|
|
stats, err := s.usageRepo.GetStatsWithFilters(ctx, filters)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("get usage stats with filters: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
|
}
|