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:
Sakurasan
2026-08-15 21:05:02 +08:00
co-authored by Claude Sonnet 5
parent b0c7439c01
commit d0e31b198f
45 changed files with 6222 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
package channel
import (
"time"
"go.uber.org/zap"
"openteam/server/internal/store"
)
// StartHealthCheck runs the periodic health-check loop in a goroutine.
// Only channels that are enabled and have a bound test model are checked.
func (s *Service) StartHealthCheck() {
go func() {
ticker := time.NewTicker(s.cfg.HealthCheck.Interval)
defer ticker.Stop()
s.runHealthCheck()
for range ticker.C {
s.runHealthCheck()
}
}()
}
func (s *Service) runHealthCheck() {
var channels []store.Channel
if err := s.db.Where("enabled = ?", true).Find(&channels).Error; err != nil {
s.log.Warn("health check query failed", zap.Error(err))
return
}
for i := range channels {
ch := &channels[i]
// Skip channels already in cooldown until the cooldown elapses.
if ch.HealthStatus == "cooldown" {
continue
}
ok, _, _, err := s.Test(ch)
if err != nil || !ok {
s.MarkFailure(ch.ID)
continue
}
s.MarkHealthy(ch.ID, "healthy")
}
}
// RecoverCooldown moves channels back from cooldown to degraded after the
// cooldown window, giving them another chance to pass the health check.
// Called periodically; keeps cooldown bounded.
func (s *Service) RecoverCooldown() {
cutoff := time.Now().Add(-s.cfg.HealthCheck.Cooldown)
s.db.Model(&store.Channel{}).
Where("health_status = ? AND updated_at < ?", "cooldown", cutoff).
Updates(map[string]any{"health_status": "degraded", "health_failures": 0})
}