package handlers import ( "encoding/json" "net/http" "strings" "trade/web/internal/store" ) type llmConfigReq struct { BaseURL string `json:"base_url"` APIKey string `json:"api_key"` Model string `json:"model"` } type llmConfigResp struct { BaseURL string `json:"base_url"` APIKey string `json:"api_key"` // 脱敏:仅显示首尾 Model string `json:"model"` HasAPIKey bool `json:"has_api_key"` // 前端据此判断是否已配置 } // maskKey 将 sk-abc123xyz 脱敏为 sk-a...xyz。 func maskKey(key string) string { if len(key) <= 8 { return strings.Repeat("*", len(key)) } return key[:4] + "..." + key[len(key)-4:] } // GetLLMConfig 返回当前 LLM 配置(API Key 脱敏)。 func (d *Deps) GetLLMConfig(w http.ResponseWriter, r *http.Request) { cfg, err := d.Futures.GetLLMConfig() if err != nil { writeErr(w, http.StatusInternalServerError, err.Error()) return } resp := llmConfigResp{ BaseURL: cfg.BaseURL, Model: cfg.Model, HasAPIKey: cfg.APIKey != "", } if cfg.APIKey != "" { resp.APIKey = maskKey(cfg.APIKey) } writeJSON(w, http.StatusOK, resp) } // SaveLLMConfig 保存 LLM 配置。api_key 为脱敏占位时保留原值。 func (d *Deps) SaveLLMConfig(w http.ResponseWriter, r *http.Request) { var req llmConfigReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeErr(w, http.StatusBadRequest, "invalid json") return } req.BaseURL = strings.TrimSpace(req.BaseURL) req.Model = strings.TrimSpace(req.Model) if req.BaseURL == "" { req.BaseURL = "https://api.deepseek.com/v1" } if req.Model == "" { req.Model = "deepseek-chat" } // 如果前端传的是脱敏值(含 ...),说明未修改 Key,保留旧值 if strings.Contains(req.APIKey, "...") { old, err := d.Futures.GetLLMConfig() if err != nil { writeErr(w, http.StatusInternalServerError, err.Error()) return } req.APIKey = old.APIKey } cfg := &store.LLMConfig{ BaseURL: req.BaseURL, APIKey: req.APIKey, Model: req.Model, } if err := d.Futures.SaveLLMConfig(cfg); err != nil { writeErr(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) }