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:
jackyu66git
2026-08-03 11:37:53 +08:00
co-authored by Cursor
parent 15a9db374a
commit bd22d9dddd
248 changed files with 26309 additions and 842 deletions
+177 -12
View File
@@ -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) != ""
}