Files
cryptoHermes/internal/repo/webapi/coinglass/mapper.go
dela 7e31e9cbaf
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
fix: map decrypted CoinGlass liq heatmap payload
2026-05-25 17:12:41 +08:00

101 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package coinglass — CoinGlass V2 私有 API 客户端实现。
// specai/specs/coinglass.md。
package coinglass
import (
"encoding/json"
"fmt"
"math"
"strconv"
"cryptoHermes/internal/entity"
)
// CGEnvelope 是 CoinGlass V2 endpoint 的兼容外壳。
// HTTP body 解密前有 success/code/data解密后的 plaintext 通常已经是 data 本体。
// 测试和旧 dump 可能仍喂 envelopemapper 保留兼容。
type CGEnvelope struct {
Success *bool `json:"success"`
Code json.RawMessage `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
// MapLiqHeatMap 把解密后的 plaintext 映射成 entity.CGLiqHeatMap。
//
// 真实响应的 plaintext 是 data 本体(例如 {"instrument":...,"liq":[...]})。
// 为兼容早期 fixture如 plaintext 仍带 success/code/data envelope则先拆 data。
// PR-1 阶段做最薄映射data 整体进 RawGrid
// 若顶层有 minPrice/maxPrice/priceMin/priceMax 字段则抽出(容忍命名漂移)。
// 真实 schema 经 spike dump 后回填到 spec §5mapper 同步加显式字段)。
//
// 数值边界ADR-0004 D3解 JSON number 默认 float64 是 transient
// 通过 formatFloat 转 string 进 entityentity 字段全 stringG12.4 兜底)。
func MapLiqHeatMap(symbol string, interval string, ts int64, plaintext []byte) (*entity.CGLiqHeatMap, error) {
raw, err := liqHeatMapDataPayload(plaintext)
if err != nil {
return nil, err
}
out := &entity.CGLiqHeatMap{
Symbol: symbol,
Interval: interval,
Timestamp: ts,
RawGrid: append(json.RawMessage(nil), raw...),
}
var probe map[string]json.RawMessage
if err := json.Unmarshal(raw, &probe); err == nil {
out.PriceMin = pickFloatField(probe, "priceMin", "minPrice", "min")
out.PriceMax = pickFloatField(probe, "priceMax", "maxPrice", "max")
}
return out, nil
}
func liqHeatMapDataPayload(plaintext []byte) (json.RawMessage, error) {
if !json.Valid(plaintext) {
return nil, fmt.Errorf("liq_heatmap plaintext: invalid json")
}
var env CGEnvelope
if err := json.Unmarshal(plaintext, &env); err == nil && env.Success != nil {
if !*env.Success {
return nil, fmt.Errorf("%w: code=%s msg=%q", ErrAPIBusiness, envelopeCode(env.Code), env.Msg)
}
if len(env.Data) == 0 || string(env.Data) == "null" {
return nil, fmt.Errorf("%w: empty data code=%s msg=%q", ErrFakeSuccess, envelopeCode(env.Code), env.Msg)
}
return append(json.RawMessage(nil), env.Data...), nil
}
return append(json.RawMessage(nil), plaintext...), nil
}
// pickFloatField 在 probe map 里按候选 key 顺序找第一个能解成 float64 的字段,
// 用 formatFloat 转回 string。找不到返回 ""。
func pickFloatField(probe map[string]json.RawMessage, keys ...string) string {
for _, k := range keys {
raw, ok := probe[k]
if !ok {
continue
}
var f float64
if err := json.Unmarshal(raw, &f); err == nil {
return formatFloat(f)
}
var s string
if err := json.Unmarshal(raw, &s); err == nil {
return s
}
}
return ""
}
// formatFloat 是 mapper 出口的唯一数值格式化点ADR-0002 / ADR-0004 D3
// NaN / Inf 统一映射成 "",下游消费方自行判空,避免 entity 出现非法字面量。
func formatFloat(f float64) string {
if math.IsNaN(f) || math.IsInf(f, 0) {
return ""
}
return strconv.FormatFloat(f, 'f', -1, 64)
}