292 lines
7.6 KiB
Go
292 lines
7.6 KiB
Go
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
|
||
}
|