M0+M1: 基建 + 用户/密钥/核心代理
后端 (Go/Gin/GORM): - 配置(viper+env)、SQLite/Postgres 迁移、argon2id、AES-GCM 渠道密钥、JWT+refresh cookie - 用户注册/登录/刷新/登出、API Key CRUD(仅存哈希、明文一次展示) - 代理网关: /v1/chat/completions、/v1/responses、/v1/models 直通 OpenAI 渠道 非流式+流式(SSE 零缓冲转发), 用量捕获(chat 末块/responses completed 嵌套), OpenAI 错误格式(401/402/404/502), 余额检查 - 异步批量记账 + 余额流水 + 日聚合, admin 用户/余额/配置 API - 单测: crypto/jwt/apikey/流式 usage 提取 前端 (Vue3+TS+Vite+Tailwind v4): - taste-skill 设计 tokens: 深色仪表盘, 石墨+信号铜色, Outfit+JetBrains Mono - Landing/登录/注册, 控制台(仪表盘图表/密钥管理/用量明细) - 基础组件 Button/Input/Badge/Modal, ECharts 用量图 部署: docker-compose(nginx+api+postgres), 双 Dockerfile, nginx SSE 反代 联调: scripts/mockupstream 本地 mock 上游, 端到端验证通过
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// Package config 加载服务配置:.env / 环境变量 / 默认值(viper)。
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Env string // development | production
|
||||
Port int
|
||||
DB DBConfig
|
||||
JWT JWTConfig
|
||||
Auth AuthConfig
|
||||
Proxy ProxyConfig
|
||||
Master string // 渠道密钥 AES-GCM 主密钥(来自环境变量)
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
Driver string // sqlite | postgres
|
||||
DSN string
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string
|
||||
AccessTTL time.Duration
|
||||
RefreshTTL time.Duration
|
||||
Issuer string
|
||||
CookieName string
|
||||
CookieSecure bool
|
||||
CookieDomain string
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
RegistrationMode string // open | invite
|
||||
Argon2Time uint32
|
||||
Argon2Memory uint32
|
||||
Argon2Threads uint8
|
||||
Argon2KeyLen uint32
|
||||
SaltLen int
|
||||
}
|
||||
|
||||
type ProxyConfig struct {
|
||||
DefaultChannelName string // 首次启动自动创建的渠道名(如 openai)
|
||||
UpstreamBaseURL string // 渠道 base_url 默认值
|
||||
UpstreamKey string // 渠道上游 key 默认值
|
||||
DefaultModel string // 渠道模型导入时使用的模型名
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
v := viper.New()
|
||||
v.SetEnvPrefix("OT")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// 默认值(与 .env.example 对应)
|
||||
v.SetDefault("env", "development")
|
||||
v.SetDefault("port", 8080)
|
||||
|
||||
v.SetDefault("db.driver", "sqlite")
|
||||
v.SetDefault("db.dsn", "data/openteam.db")
|
||||
|
||||
v.SetDefault("jwt.secret", "dev-only-secret-change-me")
|
||||
v.SetDefault("jwt.access_ttl", "2h")
|
||||
v.SetDefault("jwt.refresh_ttl", "168h")
|
||||
v.SetDefault("jwt.issuer", "openteam")
|
||||
v.SetDefault("jwt.cookie_name", "ot_refresh")
|
||||
v.SetDefault("jwt.cookie_secure", false)
|
||||
v.SetDefault("jwt.cookie_domain", "")
|
||||
|
||||
v.SetDefault("auth.registration_mode", "open")
|
||||
v.SetDefault("auth.argon2_time", 3)
|
||||
v.SetDefault("auth.argon2_memory", 64*1024) // 64 MiB
|
||||
v.SetDefault("auth.argon2_threads", 2)
|
||||
v.SetDefault("auth.argon2_keylen", 32)
|
||||
v.SetDefault("auth.salt_len", 16)
|
||||
|
||||
v.SetDefault("proxy.default_channel_name", "openai")
|
||||
v.SetDefault("proxy.upstream_base_url", "https://api.openai.com")
|
||||
v.SetDefault("proxy.upstream_key", "")
|
||||
v.SetDefault("proxy.default_model", "gpt-4o-mini")
|
||||
v.SetDefault("proxy.timeout", "120s")
|
||||
|
||||
// 支持读取 .env 文件(可选,不强制)
|
||||
v.SetConfigFile(".env")
|
||||
_ = v.ReadInConfig()
|
||||
|
||||
return &Config{
|
||||
Env: v.GetString("env"),
|
||||
Port: v.GetInt("port"),
|
||||
DB: DBConfig{
|
||||
Driver: v.GetString("db.driver"),
|
||||
DSN: v.GetString("db.dsn"),
|
||||
},
|
||||
JWT: JWTConfig{
|
||||
Secret: v.GetString("jwt.secret"),
|
||||
AccessTTL: v.GetDuration("jwt.access_ttl"),
|
||||
RefreshTTL: v.GetDuration("jwt.refresh_ttl"),
|
||||
Issuer: v.GetString("jwt.issuer"),
|
||||
CookieName: v.GetString("jwt.cookie_name"),
|
||||
CookieSecure: v.GetBool("jwt.cookie_secure"),
|
||||
CookieDomain: v.GetString("jwt.cookie_domain"),
|
||||
},
|
||||
Auth: AuthConfig{
|
||||
RegistrationMode: v.GetString("auth.registration_mode"),
|
||||
Argon2Time: v.GetUint32("auth.argon2_time"),
|
||||
Argon2Memory: v.GetUint32("auth.argon2_memory"),
|
||||
Argon2Threads: v.GetUint8("auth.argon2_threads"),
|
||||
Argon2KeyLen: v.GetUint32("auth.argon2_keylen"),
|
||||
SaltLen: v.GetInt("auth.salt_len"),
|
||||
},
|
||||
Proxy: ProxyConfig{
|
||||
DefaultChannelName: v.GetString("proxy.default_channel_name"),
|
||||
UpstreamBaseURL: v.GetString("proxy.upstream_base_url"),
|
||||
UpstreamKey: v.GetString("proxy.upstream_key"),
|
||||
DefaultModel: v.GetString("proxy.default_model"),
|
||||
Timeout: v.GetDuration("proxy.timeout"),
|
||||
},
|
||||
Master: v.GetString("master_key"),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user