Files
openteam/server/internal/cli/serve.go
T
Sakurasan b2e500ff51 feat: 金额 decimal 精确计费、failover 按失败类型封顶、渠道小时桶预聚合、分层守护测试
- 计费:19 个金额字段 float64→decimal.Decimal,DB 列保持 numeric(20,8) 零迁移;
  API 金额仍为 JSON number(MarshalJSONWithoutQuotes),前端零改动
- failover:保留时间预算,新增 OT_PROXY_FAILOVER_CAPS_NET/SERVER/RATE/QUOTA
  按失败类型封顶换渠道次数(429/配额类瞬时失败时间预算形同虚设)
- 预聚合:新增 usage_channel_hourlies(schema v10),记账事务内同步 upsert,
  长窗口渠道统计走桶不再扫明细,spent_last_30d 改扫 usage_daily,v10 一次性回填幂等
- 守护:internal/arch_test.go 用 go/parser 强制分层依赖(fail-closed)
2026-11-09 00:41:48 +08:00

80 lines
2.1 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 cli 命令行入口。
package cli
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/openteam/server/internal/api"
"github.com/openteam/server/internal/app"
"github.com/openteam/server/internal/config"
"github.com/openteam/server/internal/proxy"
"github.com/shopspring/decimal"
)
// serve 启动 HTTP 服务(默认子命令)。
func serve() error {
// 金额序列化为 JSON number 而非字符串:前端 types(balance/cost 等均为 number)零改动
decimal.MarshalJSONWithoutQuotes = true
cfg, err := config.Load()
if err != nil {
return err
}
a, err := app.New(cfg)
if err != nil {
return err
}
defer a.Shutdown(context.Background())
gw := proxy.NewGateway(a.DB, a.Enc, a.Usage, a.Limit, proxy.Options{
UserRPS: cfg.RateLimit.UserRPS,
LogRaw: cfg.Proxy.LogRaw,
GWTimeout: cfg.Proxy.Timeout,
MaxBodyMB: cfg.Proxy.MaxBodyMB,
FailoverBudget: cfg.Proxy.FailoverBudget,
FailoverSwitchCaps: cfg.Proxy.FailoverSwitchCaps,
StreamHeartbeat: cfg.Proxy.StreamHeartbeat,
FirstTokenWait: cfg.Proxy.FirstTokenWait,
AffinityTTL: cfg.Proxy.AffinityTTL,
ChannelAcquireWait: cfg.Proxy.ChannelAcquireWait,
AuthCacheTTL: cfg.Proxy.AuthCacheTTL,
})
// 渠道健康状态变化后让目录缓存立刻失效,而不是等 TTL
a.Health.OnChange = gw.InvalidateCatalog
router := api.NewRouter(a, gw)
srv := &http.Server{
Addr: ":" + strconv.Itoa(cfg.Port),
Handler: router,
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
log.Printf("openteam listening on %s (env=%s)", srv.Addr, cfg.Env)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("server: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("server shutdown: %v", err)
}
a.Usage.Close()
return nil
}