dev 2
This commit is contained in:
184
go-client/apps/zt/logic/remote.go
Normal file
184
go-client/apps/zt/logic/remote.go
Normal file
@@ -0,0 +1,184 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user