feat: 增加管理员 AI 模型配置 (#200)

This commit is contained in:
chengma
2026-08-14 09:49:11 +08:00
parent ad44f83ea6
commit 018da3f732
24 changed files with 1546 additions and 8 deletions
+47
View File
@@ -33,6 +33,8 @@ const (
databaseTLSCAEnv = "CMAUTOBUY_DB_TLS_CA"
catalogSourceEnv = "CMAUTOBUY_CATALOG_SOURCE"
catalogTokenEnv = "CMAUTOBUY_CATALOG_TOKEN"
aiSecretsPathEnv = "CMAUTOBUY_AI_SECRETS_PATH"
aiAllowedHostsEnv = "CMAUTOBUY_AI_ALLOWED_HOSTS"
)
const (
@@ -322,6 +324,51 @@ type Config struct {
Database DatabaseConfig `yaml:"database"`
Syb SybConfig `yaml:"syb"`
Integrations IntegrationsConfig `yaml:"integrations"`
AI AIConfig `yaml:"ai"`
}
// AIConfig 只保存部署级安全边界,不保存网页维护的服务商配置和 API Key。
type AIConfig struct {
SecretsPath string `yaml:"secrets_path"`
AllowedHosts []string `yaml:"allowed_hosts"`
}
// LoadAIConfig 读取可选的 AI 密钥路径和私有端点允许列表。
// 未配置时 Admin 仍能启动,但网页不能保存密钥或测试连接。
func LoadAIConfig() (AIConfig, error) {
path, err := ConfigPath()
if err != nil {
return AIConfig{}, fmt.Errorf("无法确定 AI 配置路径: %w", err)
}
var cfg AIConfig
raw, readErr := os.ReadFile(path)
if readErr == nil {
parsed, parseErr := parseConfig(raw, path)
if parseErr != nil {
return AIConfig{}, parseErr
}
cfg = parsed.AI
} else if !os.IsNotExist(readErr) {
return AIConfig{}, fmt.Errorf("读取配置文件 %s 失败: %w", path, readErr)
}
if value := strings.TrimSpace(os.Getenv(aiSecretsPathEnv)); value != "" {
cfg.SecretsPath = value
}
if value := strings.TrimSpace(os.Getenv(aiAllowedHostsEnv)); value != "" {
cfg.AllowedHosts = strings.Split(value, ",")
}
cfg.SecretsPath = strings.TrimSpace(cfg.SecretsPath)
seen := map[string]bool{}
hosts := make([]string, 0, len(cfg.AllowedHosts))
for _, host := range cfg.AllowedHosts {
host = strings.ToLower(strings.TrimSpace(host))
if host != "" && !seen[host] {
seen[host] = true
hosts = append(hosts, host)
}
}
cfg.AllowedHosts = hosts
return cfg, nil
}
// IntegrationsConfig 收拢所有非 Client 的第三方数据接入配置。
+15
View File
@@ -327,3 +327,18 @@ func TestCatalogIntegrationConfig_弱Token拒绝且留空可禁用(t *testing.T)
t.Fatalf("弱 Token 应被拒绝:%v", err)
}
}
func TestLoad_AI部署配置不包含APIKey(t *testing.T) {
cfg, err := loadFrom(t, `
ai:
secrets_path: "C:/secure/ai-secrets.yaml"
allowed_hosts:
- model.internal.example
`)
if err != nil {
t.Fatal(err)
}
if cfg.AI.SecretsPath != "C:/secure/ai-secrets.yaml" || len(cfg.AI.AllowedHosts) != 1 {
t.Fatalf("AI 部署配置解析错误: %+v", cfg.AI)
}
}