feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具
落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,27 +1,192 @@
|
||||
// Package config loads process configuration from environment variables.
|
||||
// Package config loads process configuration from YAML + optional env overrides.
|
||||
package config
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config holds runtime settings for the API server.
|
||||
type Config struct {
|
||||
HTTPAddr string
|
||||
DatabaseURL string
|
||||
AppEnv string
|
||||
DeepSeek DeepSeekConfig
|
||||
}
|
||||
|
||||
// Load reads configuration from the environment with safe defaults for local dev.
|
||||
// DeepSeekConfig for Ask LLM.
|
||||
type DeepSeekConfig struct {
|
||||
APIKey string
|
||||
BaseURL string
|
||||
Model string
|
||||
TimeoutSec int
|
||||
}
|
||||
|
||||
type fileConfig struct {
|
||||
App struct {
|
||||
Env string `yaml:"env"`
|
||||
HTTPAddr string `yaml:"http_addr"`
|
||||
} `yaml:"app"`
|
||||
Database struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
Name string `yaml:"name"`
|
||||
SSLMode string `yaml:"sslmode"`
|
||||
} `yaml:"database"`
|
||||
DeepSeek struct {
|
||||
APIKey string `yaml:"api_key"`
|
||||
BaseURL string `yaml:"base_url"`
|
||||
Model string `yaml:"model"`
|
||||
TimeoutSec int `yaml:"timeout_sec"`
|
||||
} `yaml:"deepseek"`
|
||||
}
|
||||
|
||||
// Load reads config.local.yaml (or CONFIG_PATH), then applies env overrides.
|
||||
func Load() Config {
|
||||
return Config{
|
||||
HTTPAddr: getenv("HTTP_ADDR", ":8080"),
|
||||
DatabaseURL: getenv("DATABASE_URL", "postgres://yuxingu:yuxingu@127.0.0.1:5432/yuxingu?sslmode=disable"),
|
||||
AppEnv: getenv("APP_ENV", "dev"),
|
||||
cfg := Config{
|
||||
HTTPAddr: ":8080",
|
||||
DatabaseURL: "postgres://yuxingu:yuxingu@127.0.0.1:5432/yuxingu?sslmode=disable",
|
||||
AppEnv: "dev",
|
||||
DeepSeek: DeepSeekConfig{
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
Model: "deepseek-chat",
|
||||
TimeoutSec: 60,
|
||||
},
|
||||
}
|
||||
|
||||
path := resolveConfigPath()
|
||||
if path != "" {
|
||||
if err := mergeFile(&cfg, path); err != nil {
|
||||
log.Printf("config: load %s: %v (using defaults/env)", path, err)
|
||||
} else {
|
||||
log.Printf("config: loaded %s", path)
|
||||
}
|
||||
} else {
|
||||
log.Printf("config: no config.local.yaml found — copy config.example.yaml → config.local.yaml")
|
||||
}
|
||||
|
||||
applyEnv(&cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func resolveConfigPath() string {
|
||||
if p := os.Getenv("CONFIG_PATH"); p != "" {
|
||||
return p
|
||||
}
|
||||
candidates := []string{
|
||||
"config.local.yaml",
|
||||
"apps/api/config.local.yaml",
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if st, err := os.Stat(c); err == nil && !st.IsDir() {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mergeFile(cfg *Config, path string) error {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var f fileConfig
|
||||
if err := yaml.Unmarshal(raw, &f); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.App.Env != "" {
|
||||
cfg.AppEnv = f.App.Env
|
||||
}
|
||||
if f.App.HTTPAddr != "" {
|
||||
cfg.HTTPAddr = f.App.HTTPAddr
|
||||
}
|
||||
if f.Database.Host != "" || f.Database.User != "" || f.Database.Name != "" {
|
||||
cfg.DatabaseURL = buildDatabaseURL(f)
|
||||
}
|
||||
if f.DeepSeek.APIKey != "" {
|
||||
cfg.DeepSeek.APIKey = f.DeepSeek.APIKey
|
||||
}
|
||||
if f.DeepSeek.BaseURL != "" {
|
||||
cfg.DeepSeek.BaseURL = strings.TrimRight(f.DeepSeek.BaseURL, "/")
|
||||
}
|
||||
if f.DeepSeek.Model != "" {
|
||||
cfg.DeepSeek.Model = f.DeepSeek.Model
|
||||
}
|
||||
if f.DeepSeek.TimeoutSec > 0 {
|
||||
cfg.DeepSeek.TimeoutSec = f.DeepSeek.TimeoutSec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildDatabaseURL(f fileConfig) string {
|
||||
host := f.Database.Host
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := f.Database.Port
|
||||
if port == 0 {
|
||||
port = 5432
|
||||
}
|
||||
user := f.Database.User
|
||||
if user == "" {
|
||||
user = "yuxingu"
|
||||
}
|
||||
pass := f.Database.Password
|
||||
name := f.Database.Name
|
||||
if name == "" {
|
||||
name = "yuxingu"
|
||||
}
|
||||
ssl := f.Database.SSLMode
|
||||
if ssl == "" {
|
||||
ssl = "disable"
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: "postgres",
|
||||
User: url.UserPassword(user, pass),
|
||||
Host: fmt.Sprintf("%s:%d", host, port),
|
||||
Path: "/" + name,
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("sslmode", ssl)
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func applyEnv(cfg *Config) {
|
||||
if v := os.Getenv("HTTP_ADDR"); v != "" {
|
||||
cfg.HTTPAddr = v
|
||||
}
|
||||
if v := os.Getenv("DATABASE_URL"); v != "" {
|
||||
cfg.DatabaseURL = v
|
||||
}
|
||||
if v := os.Getenv("APP_ENV"); v != "" {
|
||||
cfg.AppEnv = v
|
||||
}
|
||||
if v := os.Getenv("DEEPSEEK_API_KEY"); v != "" {
|
||||
cfg.DeepSeek.APIKey = v
|
||||
}
|
||||
if v := os.Getenv("DEEPSEEK_BASE_URL"); v != "" {
|
||||
cfg.DeepSeek.BaseURL = strings.TrimRight(v, "/")
|
||||
}
|
||||
if v := os.Getenv("DEEPSEEK_MODEL"); v != "" {
|
||||
cfg.DeepSeek.Model = v
|
||||
}
|
||||
if v := os.Getenv("DEEPSEEK_TIMEOUT_SEC"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
||||
cfg.DeepSeek.TimeoutSec = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
// Enabled reports whether DeepSeek can be called.
|
||||
func (d DeepSeekConfig) Enabled() bool {
|
||||
return strings.TrimSpace(d.APIKey) != ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user