light 6 hónapja
szülő
commit
1fba38b8ad
6 módosított fájl, 209 hozzáadás és 23 törlés
  1. 1 0
      config/config.go
  2. 145 3
      services/hangguan.go
  3. 1 1
      services/token.go
  4. 24 0
      structs/push_data.go
  5. 28 19
      structs/search_flight_api.go
  6. 10 0
      utils/function.go

+ 1 - 0
config/config.go

@@ -30,6 +30,7 @@ type Config struct {
 	//业务配置
 	HgApiUrl string `env:"HG_API_URL" envDefault:"https://partner.huoli.com"`
 	ProxyUrl string `env:"PORXY_URL" envDefault:"127.0.0.1:1080"`
+	PushUrl  string `env:"POLICY_PUSH_URL" envDefault:"http://direct.ysjipiao.com:8686"`
 }
 
 func LoadConfig() (*Config, error) {

+ 145 - 3
services/hangguan.go

@@ -9,6 +9,9 @@ import (
 	"go-policy-service/models"
 	"go-policy-service/structs"
 	"go-policy-service/utils"
+	"math"
+	"strconv"
+	"strings"
 )
 
 type HangguanService struct {
@@ -75,20 +78,159 @@ func (h *HangguanService) RequestSFlightData(ctx context.Context, task models.Hg
 	}
 
 	// 处理响应数据
-	err = processResponse(srchResp.Data)
+	err = h.processResponse(ctx, &task, &srchResp.Data)
 	if err != nil {
 		utils.Logger.WithField("task_id", task.ID).Error("Response processing failed: ", err)
 		return
 	}
 }
 
-func processResponse(response structs.ResSearchFlightData) error {
+func (h *HangguanService) processResponse(ctx context.Context, task *models.HgFlightSearchTask, response *structs.ResSearchFlightData) 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,
+					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: utils.TimestampToString(int64(task.Date), "2006-01-02"),
+		},
+		ServiceTag: "hgSpecail",
+	}
+	// 推送数据
+	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)
+	}
+	if err != nil {
+		return fmt.Errorf("推送数据序列化失败: %w", err)
+	}
+
+	url := h.cfg.PushUrl
+	resp, err := h.httpClient.RequestJSON(ctx, "POST", url, 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
+}

+ 1 - 1
services/token.go

@@ -52,7 +52,7 @@ func (t *TokenManager) GetAccessToken() (string, error) {
 		if err == nil {
 			expireTimestamp := utils.StringToTimestamp(tokenData.ExpireTime, "2006-01-02 15:04:05")
 			// 提前半小时失效
-			durationSeconds := expireTimestamp - time.Now().Unix() - 1800
+			durationSeconds := expireTimestamp - time.Now().Unix()
 			// 确保最小有效期为0秒
 			if durationSeconds < 0 {
 				durationSeconds = 0

+ 24 - 0
structs/push_data.go

@@ -0,0 +1,24 @@
+package structs
+
+type PushData struct {
+	Data                 []Datum `json:"data" compare:"ignore"`
+	IsPublishImmediately int64   `json:"isPublishImmediately"`
+	Query                Query   `json:"query"`
+	ServiceTag           string  `json:"serviceTag"`
+}
+
+type Datum struct {
+	ArrAir          string  `json:"arrAir"`
+	DepAir          string  `json:"depAir"`
+	FlightEndDate   string  `json:"flightEndDate"`
+	FlightNo        string  `json:"flightNo"`
+	FlightStartDate string  `json:"flightStartDate"`
+	PrintPrice      float64 `json:"printPrice"`
+	Stock           int     `json:"stock"`
+}
+
+type Query struct {
+	ArrAir  string `json:"arrAir"`
+	DepAir  string `json:"depAir"`
+	DepDate string `json:"depDate"`
+}

+ 28 - 19
structs/search_flight_api.go

@@ -4,6 +4,7 @@ type ReqSearchFlightData struct {
 	DepCode string `json:"depCode"`
 	ArrCode string `json:"arrCode"`
 	Date    string `json:"date"`
+	FlyNo   string `json:"flight_no"`
 }
 
 type ResSearchFlight struct {
@@ -27,21 +28,22 @@ type Datas struct {
 }
 
 type FlightInfo struct {
-	FlightInfoID string `json:"flightInfoId"`
-	FlyNo        string `json:"flyNo"`
-	AcCode       string `json:"acCode"`
-	DepCode      string `json:"depCode"`
-	ArrCode      string `json:"arrCode"`
-	DepDateTime  string `json:"depDateTime"`
-	ArrDateTime  string `json:"arrDateTime"`
-	DepTerminal  string `json:"depTerminal"`
-	ArrTerminal  string `json:"arrTerminal"`
-	Model        string `json:"model"`
-	IsStop       int    `json:"isStop"`
-	IsShare      int    `json:"isShare"`
-	Meal         int    `json:"meal"`
-	IsCancel     int    `json:"isCancel"`
-	Av           int    `json:"av"`
+	FlightInfoID  string `json:"flightInfoId"`
+	FlyNo         string `json:"flyNo"`
+	AcCode        string `json:"acCode"`
+	DepCode       string `json:"depCode"`
+	ArrCode       string `json:"arrCode"`
+	DepDateTime   string `json:"depDateTime"`
+	ArrDateTime   string `json:"arrDateTime"`
+	DepTerminal   string `json:"depTerminal"`
+	ArrTerminal   string `json:"arrTerminal"`
+	Model         string `json:"model"`
+	IsStop        int    `json:"isStop"`
+	IsShare       int    `json:"isShare"`
+	Meal          int    `json:"meal"`
+	IsCancel      int    `json:"isCancel"`
+	Av            int    `json:"av"`
+	CarrierAcCode string `json:"carrierAcCode"`
 }
 
 type CabinInfos struct {
@@ -124,10 +126,17 @@ type ConsignBaggageRule struct {
 }
 
 type SaleControls struct {
-	ID       int    `json:"id"`
-	PsNum    string `json:"psNum"`
-	PsAge    string `json:"psAge"`
-	PsIDType string `json:"psIdType"`
+	ID                int    `json:"id"`
+	PsNum             string `json:"psNum"`
+	PsType            string `json:"psType,omitempty"`
+	PsIDType          string `json:"psIdType,omitempty"`
+	PsIDNo            string `json:"psIdNo,omitempty"`
+	PsAdIdType        string `json:"psAdIdType,omitempty"`
+	PsAge             string `json:"psAge,omitempty"`
+	PsGender          string `json:"psGender,omitempty"`
+	NewMember         int    `json:"newMember,omitempty"`
+	HasAge            string `json:"hasAge,omitempty"`
+	CheckThreeElement int    `json:"checkThreeElement,omitempty"`
 }
 
 type ServicePackets struct {

+ 10 - 0
utils/function.go

@@ -6,6 +6,16 @@ import (
 	"time"
 )
 
+// DateFormmat 格式化日期
+// 参数:
+// t - 日期字符串
+// format - 日期格式字符串(例如 "2006-01-02 15:04:05")
+// 返回值:
+// 格式化后的日期字符串
+func DateFormmat(t string, format string) string {
+	return TimestampToString(StringToTimestamp(t, "006-01-02 15:04"), "2006-01-02")
+}
+
 // TimestampToString 将 Unix 时间戳转换为指定格式的时间字符串
 // 参数:
 // t - Unix 时间戳(秒级)