daShangDao_centerBook/tail/tail.go
2026-02-28 14:27:33 +08:00

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