package tail import ( "bytes" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "time" ) // 请求结构体 // PriceCheckRequest 价格检查请求体 // 功能:用于向价格检查服务提交待查询的 ISBN 列表 type PriceCheckRequest struct { ISBNList []string `json:"isbn_list"` } // 响应结构体 - 根据实际响应调整 // PriceCheckResponse 价格检查响应体 // 功能:接收价格检查服务返回的数据,并以 RawMessage 处理 data,兼容 [] 与 {} type PriceCheckResponse struct { Code int `json:"code"` Message string `json:"message"` DataRaw json.RawMessage `json:"data"` } // 更精确的响应结构体定义 // ISBNItem 每个条目为一个 ISBN 的信息映射 type ISBNItem map[string]ISBNInfo // ISBNInfo 单个 ISBN 的详细信息 type ISBNInfo struct { Data []interface{} `json:"data"` ISBN string `json:"isbn"` OnSaleCount FlexInt `json:"onSaleCount"` } // 检查价格并获取onSaleCount的方法 // CheckPriceAndGetOnSaleCount 批量查询在售数量 // 入参:ISBN 列表 // 返回:每个 ISBN 的在售数量映射;当远端返回 data:{} 时返回空 map func CheckPriceAndGetOnSaleCount(isbnList []string) (map[string]int, error) { result, err := CheckPrice(isbnList) log.Printf("Response: %+v", result) if err != nil { return nil, err } counts, _, err := parseOnSaleCounts(result.DataRaw) if err != nil { return nil, err } return counts, nil } // 原有的CheckPrice方法 // CheckPrice 调用远程价格检查服务 // 功能:请求在售信息,返回完整响应体以供进一步解析 func CheckPrice(isbnList []string) (*PriceCheckResponse, error) { url := "http://114.66.2.223:7842/api/check_price/get" token := "2cbdddfc747b437683cdb633b8c556e2" requestBody := PriceCheckRequest{ ISBNList: isbnList, } jsonData, err := json.Marshal(requestBody) if err != nil { return nil, fmt.Errorf("序列化请求参数失败: %v", err) } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("创建请求失败: %v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("token", token) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %v", err) } var response PriceCheckResponse if err = json.Unmarshal(body, &response); err != nil { return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body)) } return &response, nil } // 专门获取onSaleCount的简化方法 // GetOnSaleCount 查询单个 ISBN 的在售数量 // 入参:isbn(必填) // 返回:该 ISBN 的在售数量;当远端返回 data:{} 或未命中时返回 0 且不报错 func GetOnSaleCount(isbn string) (int, error) { isbnList := []string{isbn} result, err := CheckPrice(isbnList) log.Printf("Response: %+v", result) if err != nil { return 0, err } counts, empty, err := parseOnSaleCounts(result.DataRaw) if err != nil { return 0, err } if empty { return 0, nil } if v, ok := counts[isbn]; ok { return v, nil } // 未命中时返回 0,不视为错误 return 0, nil } // parseOnSaleCounts 解析在售数量 // 功能:兼容远端返回的 data 类型既可能是 [],也可能是 {} // 返回: // - counts: 每个 ISBN 的在售数量 // - empty: data 为空对象或空数组时为 true // - err: 解析失败错误 func parseOnSaleCounts(raw json.RawMessage) (map[string]int, bool, error) { counts := make(map[string]int) // 尝试按数组解析 var arr []ISBNItem if err := json.Unmarshal(raw, &arr); err == nil { if len(arr) == 0 { return counts, true, nil } for _, item := range arr { for isbn, info := range item { counts[isbn] = int(info.OnSaleCount) } } return counts, false, nil } // 尝试按对象解析(可能为空对象 {}) var obj map[string]ISBNInfo if err := json.Unmarshal(raw, &obj); err == nil { if len(obj) == 0 { return counts, true, nil } for isbn, info := range obj { counts[isbn] = int(info.OnSaleCount) } return counts, false, nil } // 两种形态都不匹配时返回解析错误 return nil, false, fmt.Errorf("无法解析在售数量数据") } // FlexInt 可兼容字符串或数字的整型解析 // 功能:用于解析远端返回的既可能是字符串又可能是数字的整型字段 type FlexInt int // UnmarshalJSON 自定义反序列化 // 兼容 "123" 或 123 两种形式,并忽略空字符串 func (fi *FlexInt) UnmarshalJSON(data []byte) error { // 尝试解析为数字 var asNum int if err := json.Unmarshal(data, &asNum); err == nil { *fi = FlexInt(asNum) return nil } // 尝试解析为字符串 var asStr string if err := json.Unmarshal(data, &asStr); err == nil { if asStr == "" { *fi = 0 return nil } // 去除可能的空格 // 注意:不引入 strconv 以保持依赖简单,改用 json.Number 方案 var num json.Number if err := json.Unmarshal([]byte("\""+asStr+"\""), &num); err == nil { n, err := num.Int64() if err == nil { *fi = FlexInt(int(n)) return nil } } } // 尝试使用通用 Number 解析 var num json.Number if err := json.Unmarshal(data, &num); err == nil { n, err := num.Int64() if err == nil { *fi = FlexInt(int(n)) return nil } } return fmt.Errorf("FlexInt 解析失败") } // 注意:此文件作为库使用,不包含可执行入口 // 请求结构 type SalesRequest struct { ISBNList []string `json:"isbn_list"` } // 响应结构(可根据需要扩展) type SalesResponse struct { Code int `json:"code"` Message string `json:"message"` Data map[string]BookSales `json:"data"` } type BookSales struct { ISBN string `json:"isbn"` DaySale7 string `json:"day_sale_7"` DaySale15 string `json:"day_sale_15"` DaySale30 string `json:"day_sale_30"` DaySale60 string `json:"day_sale_60"` DaySale90 string `json:"day_sale_90"` DaySale180 string `json:"day_sale_180"` DaySale365 string `json:"day_sale_365"` ThisYearSale string `json:"this_year_sale"` LastYearSale string `json:"last_year_sale"` Sale string `json:"sale"` SoldOut1 string `json:"sold_out_1"` SoldOut2 string `json:"sold_out_2"` SoldOut3 string `json:"sold_out_3"` SoldOut4 string `json:"sold_out_4"` SoldOut5 string `json:"sold_out_5"` SoldOut6 string `json:"sold_out_6"` SoldOut7 string `json:"sold_out_7"` SoldOut8 string `json:"sold_out_8"` ShipmentCycle string `json:"shipment_cycle"` } func CheckSales(isbnList []string) (*SalesResponse, error) { url := "http://114.66.2.223:7842/api/check_price/sales" // 请求体 reqBody := SalesRequest{ ISBNList: isbnList, } bodyBytes, _ := json.Marshal(reqBody) client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes)) if err != nil { return nil, err } // 设置头 req.Header.Set("Content-Type", "application/json") req.Header.Set("token", "2cbdddfc747b437683cdb633b8c556e2") // 发送请求 resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 读取响应 respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } // 解析 JSON var salesResp SalesResponse err = json.Unmarshal(respBytes, &salesResp) if err != nil { return nil, err } return &salesResp, nil }