- Multi-account pool with round-robin load balancing - Auto token refresh for IAM IdC and Social auth - Streaming support (SSE) - Web admin panel with account management - Docker support with GitHub Actions CI/CD - Machine ID management per account - Usage tracking (requests, tokens, credits)
332 lines
7.9 KiB
Go
332 lines
7.9 KiB
Go
// Package config 配置管理模块
|
|
// 负责账号、设置、统计数据的持久化存储
|
|
package config
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// GenerateMachineId 生成 UUID v4 格式的机器码
|
|
func GenerateMachineId() string {
|
|
bytes := make([]byte, 16)
|
|
rand.Read(bytes)
|
|
bytes[6] = (bytes[6] & 0x0f) | 0x40 // 版本 4
|
|
bytes[8] = (bytes[8] & 0x3f) | 0x80 // 变体
|
|
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
|
bytes[0:4], bytes[4:6], bytes[6:8], bytes[8:10], bytes[10:16])
|
|
}
|
|
|
|
// Account 账号信息
|
|
type Account struct {
|
|
// 基本信息
|
|
ID string `json:"id"`
|
|
Email string `json:"email,omitempty"`
|
|
UserId string `json:"userId,omitempty"`
|
|
Nickname string `json:"nickname,omitempty"`
|
|
|
|
// 认证信息
|
|
AccessToken string `json:"accessToken"`
|
|
RefreshToken string `json:"refreshToken"`
|
|
ClientID string `json:"clientId,omitempty"`
|
|
ClientSecret string `json:"clientSecret,omitempty"`
|
|
AuthMethod string `json:"authMethod"` // idc | social
|
|
Provider string `json:"provider,omitempty"`
|
|
Region string `json:"region"`
|
|
StartUrl string `json:"startUrl,omitempty"`
|
|
ExpiresAt int64 `json:"expiresAt,omitempty"`
|
|
MachineId string `json:"machineId,omitempty"` // UUID 格式机器码
|
|
|
|
// 状态
|
|
Enabled bool `json:"enabled"`
|
|
|
|
// 订阅信息
|
|
SubscriptionType string `json:"subscriptionType,omitempty"` // FREE | PRO | PRO_PLUS | POWER
|
|
SubscriptionTitle string `json:"subscriptionTitle,omitempty"`
|
|
DaysRemaining int `json:"daysRemaining,omitempty"`
|
|
|
|
// 使用量
|
|
UsageCurrent float64 `json:"usageCurrent,omitempty"`
|
|
UsageLimit float64 `json:"usageLimit,omitempty"`
|
|
UsagePercent float64 `json:"usagePercent,omitempty"`
|
|
NextResetDate string `json:"nextResetDate,omitempty"`
|
|
LastRefresh int64 `json:"lastRefresh,omitempty"`
|
|
|
|
// 运行时统计
|
|
RequestCount int `json:"requestCount,omitempty"`
|
|
ErrorCount int `json:"errorCount,omitempty"`
|
|
LastUsed int64 `json:"lastUsed,omitempty"`
|
|
TotalTokens int `json:"totalTokens,omitempty"`
|
|
TotalCredits float64 `json:"totalCredits,omitempty"`
|
|
}
|
|
|
|
// Config 全局配置
|
|
type Config struct {
|
|
Password string `json:"password"`
|
|
Port int `json:"port"`
|
|
Host string `json:"host"`
|
|
ApiKey string `json:"apiKey,omitempty"`
|
|
RequireApiKey bool `json:"requireApiKey"`
|
|
Accounts []Account `json:"accounts"`
|
|
|
|
// 全局统计
|
|
TotalRequests int `json:"totalRequests,omitempty"`
|
|
SuccessRequests int `json:"successRequests,omitempty"`
|
|
FailedRequests int `json:"failedRequests,omitempty"`
|
|
TotalTokens int `json:"totalTokens,omitempty"`
|
|
TotalCredits float64 `json:"totalCredits,omitempty"`
|
|
}
|
|
|
|
// AccountInfo 账户信息更新结构
|
|
type AccountInfo struct {
|
|
Email string
|
|
UserId string
|
|
SubscriptionType string
|
|
SubscriptionTitle string
|
|
DaysRemaining int
|
|
UsageCurrent float64
|
|
UsageLimit float64
|
|
UsagePercent float64
|
|
NextResetDate string
|
|
LastRefresh int64
|
|
}
|
|
|
|
var (
|
|
cfg *Config
|
|
cfgLock sync.RWMutex
|
|
cfgPath string
|
|
)
|
|
|
|
// Init 初始化配置
|
|
func Init(path string) error {
|
|
cfgPath = path
|
|
return Load()
|
|
}
|
|
|
|
// Load 从文件加载配置
|
|
func Load() error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
|
|
data, err := os.ReadFile(cfgPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// 创建默认配置
|
|
cfg = &Config{
|
|
Password: "changeme",
|
|
Port: 8080,
|
|
Host: "127.0.0.1",
|
|
RequireApiKey: false,
|
|
Accounts: []Account{},
|
|
}
|
|
return Save()
|
|
}
|
|
return err
|
|
}
|
|
|
|
var c Config
|
|
if err := json.Unmarshal(data, &c); err != nil {
|
|
return err
|
|
}
|
|
cfg = &c
|
|
return nil
|
|
}
|
|
|
|
// Save 保存配置到文件
|
|
func Save() error {
|
|
data, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(cfgPath, data, 0600)
|
|
}
|
|
|
|
// SetPassword 设置密码(用于环境变量覆盖)
|
|
func SetPassword(password string) {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
cfg.Password = password
|
|
}
|
|
|
|
func Get() *Config {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
return cfg
|
|
}
|
|
|
|
func GetPassword() string {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
return cfg.Password
|
|
}
|
|
|
|
func GetPort() int {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
if cfg.Port == 0 {
|
|
return 8080
|
|
}
|
|
return cfg.Port
|
|
}
|
|
|
|
func GetHost() string {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
if cfg.Host == "" {
|
|
return "127.0.0.1"
|
|
}
|
|
return cfg.Host
|
|
}
|
|
|
|
func GetAccounts() []Account {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
accounts := make([]Account, len(cfg.Accounts))
|
|
copy(accounts, cfg.Accounts)
|
|
return accounts
|
|
}
|
|
|
|
func GetEnabledAccounts() []Account {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
var accounts []Account
|
|
for _, a := range cfg.Accounts {
|
|
if a.Enabled {
|
|
accounts = append(accounts, a)
|
|
}
|
|
}
|
|
return accounts
|
|
}
|
|
|
|
func AddAccount(account Account) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
cfg.Accounts = append(cfg.Accounts, account)
|
|
return Save()
|
|
}
|
|
|
|
func UpdateAccount(id string, account Account) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
for i, a := range cfg.Accounts {
|
|
if a.ID == id {
|
|
cfg.Accounts[i] = account
|
|
return Save()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeleteAccount(id string) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
for i, a := range cfg.Accounts {
|
|
if a.ID == id {
|
|
cfg.Accounts = append(cfg.Accounts[:i], cfg.Accounts[i+1:]...)
|
|
return Save()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func UpdateAccountToken(id, accessToken, refreshToken string, expiresAt int64) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
for i, a := range cfg.Accounts {
|
|
if a.ID == id {
|
|
cfg.Accounts[i].AccessToken = accessToken
|
|
if refreshToken != "" {
|
|
cfg.Accounts[i].RefreshToken = refreshToken
|
|
}
|
|
cfg.Accounts[i].ExpiresAt = expiresAt
|
|
return Save()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetApiKey() string {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
return cfg.ApiKey
|
|
}
|
|
|
|
func IsApiKeyRequired() bool {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
return cfg.RequireApiKey
|
|
}
|
|
|
|
func UpdateSettings(apiKey string, requireApiKey bool, password string) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
cfg.ApiKey = apiKey
|
|
cfg.RequireApiKey = requireApiKey
|
|
if password != "" {
|
|
cfg.Password = password
|
|
}
|
|
return Save()
|
|
}
|
|
|
|
func UpdateStats(totalReq, successReq, failedReq, totalTokens int, totalCredits float64) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
cfg.TotalRequests = totalReq
|
|
cfg.SuccessRequests = successReq
|
|
cfg.FailedRequests = failedReq
|
|
cfg.TotalTokens = totalTokens
|
|
cfg.TotalCredits = totalCredits
|
|
return Save()
|
|
}
|
|
|
|
func GetStats() (int, int, int, int, float64) {
|
|
cfgLock.RLock()
|
|
defer cfgLock.RUnlock()
|
|
return cfg.TotalRequests, cfg.SuccessRequests, cfg.FailedRequests, cfg.TotalTokens, cfg.TotalCredits
|
|
}
|
|
|
|
func UpdateAccountStats(id string, requestCount, errorCount, totalTokens int, totalCredits float64, lastUsed int64) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
for i, a := range cfg.Accounts {
|
|
if a.ID == id {
|
|
cfg.Accounts[i].RequestCount = requestCount
|
|
cfg.Accounts[i].ErrorCount = errorCount
|
|
cfg.Accounts[i].TotalTokens = totalTokens
|
|
cfg.Accounts[i].TotalCredits = totalCredits
|
|
cfg.Accounts[i].LastUsed = lastUsed
|
|
return Save()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateAccountInfo 更新账户的订阅和使用量信息
|
|
func UpdateAccountInfo(id string, info AccountInfo) error {
|
|
cfgLock.Lock()
|
|
defer cfgLock.Unlock()
|
|
for i, a := range cfg.Accounts {
|
|
if a.ID == id {
|
|
if info.Email != "" {
|
|
cfg.Accounts[i].Email = info.Email
|
|
}
|
|
if info.UserId != "" {
|
|
cfg.Accounts[i].UserId = info.UserId
|
|
}
|
|
cfg.Accounts[i].SubscriptionType = info.SubscriptionType
|
|
cfg.Accounts[i].SubscriptionTitle = info.SubscriptionTitle
|
|
cfg.Accounts[i].DaysRemaining = info.DaysRemaining
|
|
cfg.Accounts[i].UsageCurrent = info.UsageCurrent
|
|
cfg.Accounts[i].UsageLimit = info.UsageLimit
|
|
cfg.Accounts[i].UsagePercent = info.UsagePercent
|
|
cfg.Accounts[i].NextResetDate = info.NextResetDate
|
|
cfg.Accounts[i].LastRefresh = info.LastRefresh
|
|
return Save()
|
|
}
|
|
}
|
|
return nil
|
|
}
|