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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b0c7439c01
commit
d0e31b198f
@@ -0,0 +1,91 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user