Files
openteam/server/internal/usage/service.go
T
SakurasanandClaude Sonnet 5 d0e31b198f feat(server): API relay gateway backend M0-M4
Gin + GORM + pure-Go SQLite. Users/auth (JWT), API key management with
quotas, proxy gateway with weighted channel failover and health checks,
usage/billing ledger, cross-protocol conversion (Anthropic Messages /
OpenAI Chat Completions / OpenAI Responses), and channel/model admin API.
Channels declare native API formats and auto-convert the rest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:05:02 +08:00

92 lines
2.4 KiB
Go

package usage
import (
"time"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"openteam/server/internal/store"
)
type Record struct {
RequestID string
UserID int64
KeyID int64
ChannelID int64
ModelID int64
ModelName string
InputTokens int64
OutputTokens int64
CacheReadTokens int64
CacheCreationTokens int64
InputPrice decimal.Decimal
OutputPrice decimal.Decimal
CacheReadPrice decimal.Decimal
Cost decimal.Decimal
LatencyMs int
Status string
ErrorCode string
}
type Service struct {
db *gorm.DB
log *zap.Logger
}
func NewService(db *gorm.DB, log *zap.Logger) *Service {
return &Service{db: db, log: log}
}
// Record inserts a request-level usage log and upserts the daily aggregate.
func (s *Service) Record(r Record) error {
date := time.Now().Format("2006-01-02")
err := s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&store.UsageLog{
RequestID: r.RequestID,
UserID: r.UserID,
KeyID: r.KeyID,
ChannelID: r.ChannelID,
ModelID: r.ModelID,
ModelName: r.ModelName,
InputTokens: r.InputTokens,
OutputTokens: r.OutputTokens,
CacheReadTokens: r.CacheReadTokens,
CacheCreationTokens: r.CacheCreationTokens,
InputPrice: r.InputPrice,
OutputPrice: r.OutputPrice,
CacheReadPrice: r.CacheReadPrice,
Cost: r.Cost,
LatencyMs: r.LatencyMs,
Status: r.Status,
ErrorCode: r.ErrorCode,
CreatedAt: time.Now(),
}).Error; err != nil {
return err
}
var daily store.UsageDaily
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("user_id = ? AND model_id = ? AND date = ?", r.UserID, r.ModelID, date).
FirstOrCreate(&daily, store.UsageDaily{
UserID: r.UserID, ModelID: r.ModelID, Date: date,
}).Error
if err != nil {
return err
}
daily.Requests++
daily.InputTokens += r.InputTokens
daily.OutputTokens += r.OutputTokens
daily.CacheReadTokens += r.CacheReadTokens
daily.CacheCreationTokens += r.CacheCreationTokens
daily.Cost = daily.Cost.Add(r.Cost)
return tx.Save(&daily).Error
})
if err != nil {
s.log.Warn("record usage failed", zap.Error(err))
}
return err
}