package logic import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" "sync" "time" ) type dailyCache struct { Date string `json:"date"` FetchedAt string `json:"fetched_at"` OK bool `json:"ok"` Data any `json:"data"` } var memCache sync.Map func getJSON(rawURL string, params url.Values, timeout time.Duration) (map[string]any, error) { if params != nil { if strings.Contains(rawURL, "?") { rawURL += "&" + params.Encode() } else { rawURL += "?" + params.Encode() } } req, err := http.NewRequest(http.MethodGet, rawURL, nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "big-qmt-go-zt/1") client := &http.Client{Timeout: timeout} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } out := map[string]any{} if err := json.Unmarshal(body, &out); err != nil { return nil, err } return out, nil } func daily(cfg Config, name, filename string, loader func() (any, error), now time.Time) *dailyCache { if int(parseHM(now)) < cfg.ReadyCacheStart { return nil } day := now.Format("20060102") path := filepath.Join(cfg.DataDir, fmt.Sprintf(filename, day)) if v, ok := memCache.Load(path); ok { if c, ok := v.(*dailyCache); ok && c.Date == day { return c } } cached := loadDailyFile(path) if cached != nil && cached.Date == day { memCache.Store(path, cached) return cached } data, err := loader() ok := err == nil if err != nil { logf("ERROR", "%s 当日请求失败: %v", name, err) data = map[string]any{} } cached = &dailyCache{ Date: day, FetchedAt: now.Format("2006-01-02 15:04:05"), OK: ok, Data: data, } raw, _ := json.MarshalIndent(map[string]any{"version": 1, "data": map[string]any{ "date": cached.Date, "fetched_at": cached.FetchedAt, "ok": cached.OK, "data": cached.Data, }}, "", " ") if err := os.WriteFile(path+".tmp", raw, 0o644); err == nil { _ = os.Rename(path+".tmp", path) } memCache.Store(path, cached) return cached } func loadDailyFile(path string) *dailyCache { raw, err := os.ReadFile(path) if err != nil { return nil } var payload struct { Version int `json:"version"` Data map[string]any `json:"data"` } if json.Unmarshal(raw, &payload) != nil || payload.Version != 1 || payload.Data == nil { return nil } c := &dailyCache{} b, _ := json.Marshal(payload.Data) if json.Unmarshal(b, c) != nil { return nil } return c } func fetchSignal(cfg Config, name string) map[string]map[string]any { cached := daily(cfg, name, "open_%s.json", func() (any, error) { var q url.Values if strings.TrimSpace(cfg.HostKey) != "" { q = url.Values{"host_key": {cfg.HostKey}} } payload, err := getJSON(cfg.APIHost+"/a/"+name, q, cfg.HTTPTimeout) if err != nil { return nil, err } return normalizeZT(payload), nil }, time.Now()) if cached == nil || !cached.OK { return map[string]map[string]any{} } return asSignalMap(cached.Data) } func asSignalMap(data any) map[string]map[string]any { out := map[string]map[string]any{} switch v := data.(type) { case map[string]map[string]any: return v case map[string]any: for code, val := range v { if m, ok := val.(map[string]any); ok { out[code] = m } else { out[code] = map[string]any{"code": code} } } } return out } func normalizeZT(payload map[string]any) map[string]map[string]any { data, _ := payload["data"] out := map[string]map[string]any{} switch v := data.(type) { case []any: for _, item := range v { m, ok := item.(map[string]any) if !ok { continue } code, _ := m["code"].(string) if code != "" { out[code] = m } } case map[string]any: if code, _ := v["code"].(string); code != "" { out[code] = v return out } for code, val := range v { if m, ok := val.(map[string]any); ok { if _, has := m["code"]; !has { m["code"] = code } out[code] = m } else { out[code] = map[string]any{"code": code} } } } return out }