// services/hangguan.go package services import ( "context" "encoding/json" "fmt" "go-policy-service/config" "go-policy-service/models" "go-policy-service/structs" "go-policy-service/utils" "math" "strconv" "strings" ) type HangguanService struct { cfg *config.Config httpClient utils.HTTPClient tokenMgr *TokenManager } // 更新后的New方法 func NewHangguanService( cfg *config.Config, httpClient utils.HTTPClient, tokenMgr *TokenManager, ) *HangguanService { return &HangguanService{ cfg: cfg, httpClient: httpClient, tokenMgr: tokenMgr, } } func (s *HangguanService) requestSFDataConvert(task models.HgFlightSearchTask, startDate string) ([]byte, error) { // 将 uint 日期转换为 YYYY-MM-DD 格式字符串) req := structs.ReqSearchFlightData{ DepCode: task.Dep, ArrCode: task.Arr, Date: startDate, FlyNo: task.FlightNo, } // 将结构体转换为 JSON jsonData, err := utils.StringJsonConvert(req) if err != nil { return []byte{}, fmt.Errorf("SF 请求数据转化失败: %w", err) } return jsonData, nil } func (h *HangguanService) RequestSFlightData(ctx context.Context, task models.HgFlightSearchTask) { token, err := h.tokenMgr.GetAccessToken() if err != nil { utils.Logger.WithField("task_id", task.ID).Error("Failed to get access token: ", err) } //获取起飞时间列表 startDateList, err := utils.ParseDateRange(task.DateRange) if err != nil { utils.Logger.WithField("task_id", task.ID).Error("获取起飞日期时间列表错误: ", err) return } for _, startDate := range startDateList { // 构建请求数据 reqData, _ := h.requestSFDataConvert(task, startDate) // url url := fmt.Sprintf("%s%s?access_token=%s", h.cfg.HgApiUrl, "/distribution/api/search/flight/list", token) // 记录请求数据 //utils.Logger.WithField("task_id", task.ID).Info("请求数据: ", string(reqData)) // 调用第三方接口 resp, err := h.httpClient.RequestWithProxy(ctx, "POST", url, reqData, h.cfg.ProxyUrl) //测试使用 //resp, err := h.httpClient.RequestJSON(ctx, "POST", url, reqData) // 记录响应数据 //utils.Logger.WithField("task_id", task.ID).Info("响应数据: ", string(resp)) if err != nil { utils.Logger.WithField("task_id", task.ID).Error("API request failed: ", err) continue } //解析数据 var srchResp structs.ResSearchFlight if err = json.Unmarshal(resp, &srchResp); err != nil { utils.Logger.WithField("task_id", task.ID).Error("数据解析失败: ", err) continue } if !srchResp.Success { utils.Logger.WithField("task_id", task.ID).Error("数据请求失败: ", srchResp.Msg) continue } // 处理响应数据 err = h.processResponse(ctx, &task, &srchResp.Data, startDate) if err != nil { utils.Logger.WithField("task_id", task.ID).Error("Response processing failed: ", err) continue } } } func (h *HangguanService) processResponse(ctx context.Context, task *models.HgFlightSearchTask, response *structs.ResSearchFlightData, startDate string) error { // 实现具体的响应处理逻辑 if response.Total <= 0 { return fmt.Errorf("返回数据为空") } pushDataList := make([]structs.Datum, 0) for _, fltData := range response.Datas { if fltData.FlightInfo.IsCancel == 1 || len(fltData.CabinInfos) == 0 { //航班取消 或者 无舱位信息 或者 无实际承运航司 continue } var minPrice float64 = 0 var minPricePushData structs.Datum CabinLoop: for _, cabinPrice := range fltData.CabinInfos { //无售卖控制 或者 产品类型为 py if cabinPrice.AdtPrice.ProductType == "py" { continue } if len(cabinPrice.SaleControls) > 0 { for _, saleControls := range cabinPrice.SaleControls { // ... existing code ... if saleControls.PsNum != "" || saleControls.PsType == "ADT+CHD" || saleControls.PsIDType != "" || saleControls.PsIDNo != "" || saleControls.PsAdIdType != "" || saleControls.NewMember == 1 || saleControls.CheckThreeElement != 0 || saleControls.HasAge != "" { continue CabinLoop } if saleControls.PsAge != "" || !isRangeClose(saleControls.PsAge, 12, 100, 2, 0) { continue CabinLoop } } } if minPrice == 0 || cabinPrice.AdtPrice.Price < minPrice { //取最低价格的舱位信息 minPrice = cabinPrice.AdtPrice.Price minPricePushData = structs.Datum{ DepAir: fltData.FlightInfo.DepCode, ArrAir: fltData.FlightInfo.ArrCode, FlightStartDate: utils.DateFormmat(fltData.FlightInfo.DepDateTime, "2006-01-02"), PrintPrice: cabinPrice.AdtPrice.Price, Stock: cabinPrice.Left, FlightNo: fltData.FlightInfo.FlyNo, Cabin: cabinPrice.AdtPrice.Cabin, FlightEndDate: utils.DateFormmat(fltData.FlightInfo.ArrDateTime, "2006-01-02"), } } } if minPricePushData == (structs.Datum{}) { //无可用舱位 continue } pushDataList = append(pushDataList, minPricePushData) } // 构建推送数据 pushData := structs.PushData{ Data: pushDataList, IsPublishImmediately: 1, Query: structs.Query{ DepAir: task.Dep, ArrAir: task.Arr, DepDate: startDate, }, ServiceTag: "hgSpecail", } // 推送数据打印成 json 格式 pushDataJson, _ := json.Marshal(pushData) utils.Logger.WithField("task_id", task.ID).Info("推送数据: ", string(pushDataJson)) // 推送数据 if err := h.PushPolicyData(ctx, pushData); err != nil { utils.Logger.WithField("task_id", task.ID).Error("推送失败: ", err) return err } return nil } func (h *HangguanService) PushPolicyData(ctx context.Context, data structs.PushData) error { pushData, err := json.Marshal(data) if err != nil { return fmt.Errorf("推送数据JSON 编码失败: %w", err) } url := h.cfg.PushUrl resp, err := h.httpClient.RequestJSON(ctx, "POST", url+"/api/policy/batch-policy-import", pushData) if err != nil { return fmt.Errorf("推送请求失败: %w", err) } var result struct { Code int `json:"code"` Message string `json:"message"` } if err := json.Unmarshal(resp, &result); err != nil { return fmt.Errorf("响应解析失败: %w", err) } if result.Code != 200 { return fmt.Errorf("推送接口返回错误: %s", result.Message) } return nil } /** * 年龄限制 * 儿童期 1-12岁 * 青年 15-24 * 老年 60/65+ * 成年 18岁+ * 目标范围:[10, 100] */ // isRangeClose 检查 ranges 字符串中,是否存在一个子区间 [s,e]: // 1) 与 [targetStart,targetEnd] 有交集; // 2) |s - targetStart| ≤ startTol 且 |e - targetEnd| ≤ endTol // // ranges 格式示例:"[25,60]|[23,60]|[70,80]" // startTol、endTol 单位同年龄(比如 startTol=5 表示起点最多相差 5 岁,endTol=10 表示终点最多相差 10 岁) func isRangeClose(ranges string, targetStart, targetEnd, startTol, endTol int) bool { parts := strings.Split(ranges, "|") if len(parts) >= 4 { // 有4段年龄范围以上的过滤 return false } for _, part := range parts { part = strings.Trim(part, "[]") bounds := strings.Split(part, ",") if len(bounds) != 2 { continue } s, err1 := strconv.Atoi(strings.TrimSpace(bounds[0])) e, err2 := strconv.Atoi(strings.TrimSpace(bounds[1])) if err1 != nil || err2 != nil { continue } // 1) 必须有交集 if e < targetStart || s > targetEnd { continue } // 2) 分别判断起点、终点容差 if math.Abs(float64(s-targetStart)) <= float64(startTol) && math.Abs(float64(e-targetEnd)) <= float64(endTol) { return true } } return false }