light преди 6 месеца
родител
ревизия
ad6e1e8ac1
променени са 12 файла, в които са добавени 354 реда и са изтрити 199 реда
  1. 3 0
      config/config.go
  2. 1 1
      main.go
  3. 27 24
      services/hangguan.go
  4. 4 6
      services/processor.go
  5. 19 32
      services/token.go
  6. 3 12
      services/validate.go
  7. 99 82
      structs/search_flight_api.go
  8. 11 9
      structs/token.go
  9. 45 1
      utils/function.go
  10. 104 31
      utils/http_client.go
  11. 37 0
      utils/init_service.go
  12. 1 1
      utils/logger.go

+ 3 - 0
config/config.go

@@ -23,10 +23,13 @@ type Config struct {
 	RedisPassword   string `env:"REDIS_PASSWORD" envDefault:""`
 	RedisDB         int    `env:"REDIS_DB" envDefault:"0"`
 	IsSkipSSLVerify bool   `env:"IS_SKIP_SSL_VERIFY" envDefault:"false"`
+	//MongoDB配置
+	MongoURI string `env:"MONGO_URI" envDefault:"mongodb://localhost:27017"`
 	//日志配置
 	LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
 	//业务配置
 	HgApiUrl string `env:"HG_API_URL" envDefault:"https://partner.huoli.com"`
+	ProxyUrl string `env:"PORXY_URL" envDefault:"127.0.0.1:1080"`
 }
 
 func LoadConfig() (*Config, error) {

+ 1 - 1
main.go

@@ -22,7 +22,7 @@ func main() {
 
 	// 创建处理器
 	processor := services.NewTaskProcessor(
-		utils.NewHttpClient(30*time.Second, cfg.IsSkipSSLVerify),
+		utils.NewHttpClient(30*time.Second, cfg.IsSkipSSLVerify, cfg.ProxyUrl),
 		cfg,
 	)
 	processor.Run(cfg)

+ 27 - 24
services/hangguan.go

@@ -30,7 +30,7 @@ func NewHangguanService(
 	}
 }
 
-func (s *HangguanService) requestSFDataConvert(task models.HgFlightSearchTask) (string, error) {
+func (s *HangguanService) requestSFDataConvert(task models.HgFlightSearchTask) ([]byte, error) {
 	// 将 uint 日期转换为 YYYY-MM-DD 格式字符串)
 	req := structs.ReqSearchFlightData{
 		DepCode: task.Dep,
@@ -39,11 +39,11 @@ func (s *HangguanService) requestSFDataConvert(task models.HgFlightSearchTask) (
 	}
 
 	// 将结构体转换为 JSON
-	jsonData, err := json.Marshal(req)
+	jsonData, err := utils.StringJsonConvert(req)
 	if err != nil {
-		return "", fmt.Errorf("JSON 编码失败: %w", err)
+		return []byte{}, fmt.Errorf("SF 请求数据转化失败: %w", err)
 	}
-	return string(jsonData), nil
+	return jsonData, nil
 }
 
 func (h *HangguanService) RequestSFlightData(ctx context.Context, task models.HgFlightSearchTask) {
@@ -53,39 +53,42 @@ func (h *HangguanService) RequestSFlightData(ctx context.Context, task models.Hg
 	}
 	// 构建请求数据
 	reqData, _ := h.requestSFDataConvert(task)
+	// url
+	url := fmt.Sprintf("%s%s?access_token=%s", h.cfg.HgApiUrl, "/distribution/api/search/flight/list", token)
 	// 调用第三方接口
-	resp, err := h.httpClient.PostJSON(ctx, h.cfg.HgApiUrl+"distribution/api/shopping/flight/list?token="+token, reqData)
+	resp, err := h.httpClient.RequestWithProxy(ctx, "POST", url, reqData, h.cfg.ProxyUrl) //测试使用
+	//resp, err := h.httpClient.RequestJSON(ctx, "POST", url, reqData)
 	if err != nil {
 		utils.Logger.WithField("task_id", task.ID).Error("API request failed: ", err)
 		return
 	}
-
-	// 处理响应数据
-	processedData, err := processResponse(resp)
-	if err != nil {
-		utils.Logger.WithField("task_id", task.ID).Error("Response processing failed: ", err)
-		//models.UpdateTaskStatus(ctx, task.ID, "failed", task.Attempts+1)
+	//解析数据
+	var srchResp structs.ResSearchFlight
+	if err = json.Unmarshal(resp, &srchResp); err != nil {
+		utils.Logger.WithField("task_id", task.ID).Error("数据解析失败: ", err)
 		return
 	}
 
-	// 保存处理后的数据
-	if err := models.SaveProcessedData(ctx, &models.ProcessedData{
-		TaskID: task.ID,
-		Data:   processedData,
-	}); err != nil {
-		utils.Logger.WithField("task_id", task.ID).Error("Failed to save processed data: ", err)
+	if !srchResp.Success {
+		utils.Logger.WithField("task_id", task.ID).Error("数据请求失败: ", srchResp.Msg)
 		return
 	}
 
-	// 更新任务状态
-	// if err := models.UpdateTaskStatus(ctx, task.ID, "completed", task.Attempts+1); err != nil {
-	// 	utils.Logger.WithField("task_id", task.ID).Error("Failed to update task status: ", err)
-	// }
+	// 处理响应数据
+	err = processResponse(srchResp.Data)
+	if err != nil {
+		utils.Logger.WithField("task_id", task.ID).Error("Response processing failed: ", err)
+		return
+	}
 }
 
-func processResponse(response []byte) (string, error) {
+func processResponse(response structs.ResSearchFlightData) error {
 	// 实现具体的响应处理逻辑
+	if response.Total <= 0 {
+		return fmt.Errorf("返回数据为空")
+	}
+
+	
 
-	// 示例:直接返回原始响应
-	return string(response), nil
+	return nil
 }

+ 4 - 6
services/processor.go

@@ -60,9 +60,7 @@ func NewTaskProcessor(
 func (p *TaskProcessor) Run(cfg *config.Config) {
 	// 启动Token刷新任务
 	tokenMgr := NewTokenManager(utils.Rdb, HgConfig, p.httpClient, cfg.HgApiUrl)
-	go func() {
-		tokenMgr.RefreshAccessToken()
-	}()
+
 	// 初始化HangguanService
 	p.hgService = NewHangguanService(cfg, p.httpClient, tokenMgr)
 
@@ -72,7 +70,7 @@ func (p *TaskProcessor) Run(cfg *config.Config) {
 	// AddFunc 只返回 EntryID
 	entryID := c.AddFunc(scheduleSpec, func() {
 		utils.Logger.Info("Starting scheduled task processing...")
-		ctx, cancel := context.WithTimeout(context.Background(), 9*time.Minute)
+		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
 		defer cancel()
 
 		if err := p.ProcessTasks(ctx); err != nil {
@@ -128,9 +126,9 @@ func (p *TaskProcessor) worker(ctx context.Context, tasks <-chan models.HgFlight
 			return
 		default:
 			// 可以访问p.cfg获取配置
-			if err := validateTask(task, p.cfg); !valid {
+			if err := validateTask(task); err != nil {
 				// 处理无效任务
-				utils.Logger.WithField("task_id", task.ID).Error("Invalid task parameters")
+				utils.Logger.WithField("task_id", task.ID).Errorf("Invalid task parameters:%v", err)
 				continue
 			}
 			// 请求数据

+ 19 - 32
services/token.go

@@ -39,42 +39,29 @@ func (t *TokenManager) GetAccessToken() (string, error) {
 	tokenKey := fmt.Sprintf("60s-hgapi-%s", t.hgConfig.Account)
 	ctx := context.Background()
 
-	// 重试机制
-	const maxRetries = 10
-	for i := 0; i < maxRetries; i++ {
-		token, err := t.rdb.Get(ctx, tokenKey).Result()
-		if err == nil {
-			return token, nil
-		}
-		log.Printf("获取hgApi token失败,剩余重试次数: %d, 错误: %v", maxRetries-i-1, err)
-		if i < maxRetries-1 {
-			time.Sleep(5 * time.Second)
-		}
-	}
-	log.Println("hgApi token获取彻底失败,请检查接口状态")
-	return "", fmt.Errorf("failed to get token after %d attempts", maxRetries)
-}
-
-func (t *TokenManager) RefreshAccessToken() error {
-	tokenKey := fmt.Sprintf("60s-hgapi-%s", t.hgConfig.Account)
-	ctx := context.Background()
-
 	// 尝试从 Redis 获取现有 token
-	_, err := t.rdb.Get(ctx, tokenKey).Result()
+	token, err := t.rdb.Get(ctx, tokenKey).Result()
 	if err == nil {
-		return nil
+		return token, nil
 	}
 
 	// 重试机制
 	const maxRetries = 3
 	for i := 0; i < maxRetries; i++ {
-		token, err := t.requestToken()
+		tokenData, err := t.requestToken()
 		if err == nil {
+			expireTimestamp := utils.StringToTimestamp(tokenData.ExpireTime, "2006-01-02 15:04:05")
+			// 提前半小时失效
+			durationSeconds := expireTimestamp - time.Now().Unix() - 1800
+			// 确保最小有效期为0秒
+			if durationSeconds < 0 {
+				durationSeconds = 0
+			}
 			// 成功获取 token 后存入 Redis
-			if setErr := t.rdb.SetEX(ctx, tokenKey, token, 3600*time.Second).Err(); setErr != nil {
+			if setErr := t.rdb.SetEX(ctx, tokenKey, tokenData.AccessToken, time.Duration(durationSeconds)*time.Second).Err(); setErr != nil {
 				log.Printf("Failed to set token in Redis: %v", setErr)
 			}
-			return nil
+			return tokenData.AccessToken, nil
 		}
 
 		log.Printf("获取hgApi token失败,剩余重试次数: %d, 错误: %v", maxRetries-i-1, err)
@@ -84,26 +71,26 @@ func (t *TokenManager) RefreshAccessToken() error {
 		}
 	}
 	log.Println("hgApi token获取彻底失败,请检查接口状态")
-	return fmt.Errorf("failed to get token after %d attempts", maxRetries)
+	return "", fmt.Errorf("failed to get token after %d attempts", maxRetries)
 }
 
-func (t *TokenManager) requestToken() (string, error) {
+func (t *TokenManager) requestToken() (structs.TokenData, error) {
 	url := fmt.Sprintf("%s/gateway/oauth/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
 		t.apiUrl, t.hgConfig.Account, t.hgConfig.Password)
 
-	respBytes, err := t.httpClient.PostJSON(context.Background(), url, nil)
+	respBytes, err := t.httpClient.RequestJSON(context.Background(), "GET", url, nil)
 	if err != nil {
-		return "", fmt.Errorf("HTTP请求失败: %w", err)
+		return structs.TokenData{}, fmt.Errorf("HTTP请求失败: %w", err)
 	}
 
 	var tokenResp structs.TokenResponse
 	if err := json.Unmarshal(respBytes, &tokenResp); err != nil {
-		return "", fmt.Errorf("JSON解析失败: %w", err)
+		return structs.TokenData{}, fmt.Errorf("JSON解析失败: %w", err)
 	}
 
 	if !tokenResp.Success || tokenResp.Data.AccessToken == "" {
-		return "", fmt.Errorf("接口返回无效响应")
+		return structs.TokenData{}, fmt.Errorf("接口返回无效响应")
 	}
 
-	return tokenResp.Data.AccessToken, nil
+	return tokenResp.Data, nil
 }

+ 3 - 12
services/validate.go

@@ -2,22 +2,13 @@ package services
 
 import (
 	"fmt"
-	"go-policy-service/config"
 	"go-policy-service/models"
-	"go-policy-service/utils"
 )
 
-func validateTask(task models.HgFlightSearchTask, cfg *config.Config) error {
-	// 服务标签
-	serviceTag := []string{"3uSpecial", "lowPriceFlashSale", "carrieFlight", "muCarrieFlight", "discountPriceC1", "discountPriceB1"}
-
+func validateTask(task models.HgFlightSearchTask) error {
 	// 验证参数
-	if task.Dep == "" || task.Arr == "" || task.Date == 0 || task.ServiceTag == "" {
-		return fmt.Errorf("dep, arr, date, service_tag参数不能为空: %v", task)
-	}
-	// 验证服务标签
-	if !utils.Contains(serviceTag, task.ServiceTag) {
-		return fmt.Errorf("service_tag参数不在serviceTag中: %v", serviceTag)
+	if task.Dep == "" || task.Arr == "" || task.Date == 0 {
+		return fmt.Errorf("dep, arr, date, 参数不能为空: %v", task)
 	}
 
 	return nil

+ 99 - 82
structs/search_flight_api.go

@@ -6,115 +6,132 @@ type ReqSearchFlightData struct {
 	Date    string `json:"date"`
 }
 
+type ResSearchFlight struct {
+	Code    int64               `json:"code"`
+	Msg     string              `json:"msg"`
+	Data    ResSearchFlightData `json:"data"`
+	TraceID string              `json:"traceId"`
+	Success bool                `json:"success"`
+}
 type ResSearchFlightData struct {
-	Code    int64                   `json:"code"`
-	Msg     string                  `json:"msg"`
-	Data    ResSearchFlightDataData `json:"data"`
-	TraceID string                  `json:"traceId"`
-	Success bool                    `json:"success"`
+	Total          int              `json:"total"`
+	Status         int              `json:"status"`
+	Datas          []Datas          `json:"datas"`
+	ServicePackets []ServicePackets `json:"servicePackets"`
 }
 
-type ResSearchFlightDataData struct {
-	Total          int64           `json:"total"`
-	Status         int64           `json:"status"`
-	Datas          []DataElement   `json:"datas"`
-	ServicePackets []ServicePacket `json:"servicePackets"`
+type Datas struct {
+	FlightParam string       `json:"flightParam"`
+	FlightInfo  FlightInfo   `json:"flightInfo,omitempty"`
+	CabinInfos  []CabinInfos `json:"cabinInfos"`
 }
 
-type DataElement struct {
-	FlightInfo FlightInfo  `json:"flightInfo"`
-	CabinInfos []CabinInfo `json:"cabinInfos"`
+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"`
 }
 
-type CabinInfo struct {
+type CabinInfos struct {
 	PriceInfoID      string           `json:"priceInfoId"`
 	BaseCabin        string           `json:"baseCabin"`
-	Voucher          int64            `json:"voucher"`
-	ExpectTicketTime int64            `json:"expectTicketTime"`
+	BaseCodeLevel    int              `json:"baseCodeLevel"`
+	Voucher          int              `json:"voucher"`
 	Discount         float64          `json:"discount"`
-	Left             int64            `json:"left"`
-	ProductAttribute int64            `json:"productAttribute"`
-	SaleControl      []SaleControl    `json:"saleControl"`
-	ServicePackets   []int64          `json:"servicePackets"`
-	CabinProductDesc CabinProductDesc `json:"cabinProductDesc"`
-	ADTPrice         Price            `json:"adtPrice"`
-	ChdPrice         Price            `json:"chdPrice"`
+	Left             int              `json:"left"`
+	ServicePackets   []interface{}    `json:"servicePackets"`
+	AdtPrice         AdtPrice         `json:"adtPrice,omitempty"`
+	ChdPrice         ChdPrice         `json:"chdPrice,omitempty"`
 	BusinessField    string           `json:"businessField"`
+	ExpectTicketTime int              `json:"expectTicketTime,omitempty"`
+	SaleControls     []SaleControls   `json:"saleControls,omitempty"`
+	CabinProductDesc CabinProductDesc `json:"cabinProductDesc"`
+}
+
+type CabinProductDesc struct {
+	ID          int    `json:"id"`
+	ProductDesc string `json:"productDesc"`
+}
+
+type AdtPrice struct {
+	ProductType   string  `json:"productType"`
+	Cabin         string  `json:"cabin"`
+	Price         float64 `json:"price"`
+	TicketPrice   float64 `json:"ticketPrice"`
+	RulePrice     float64 `json:"rulePrice"`
+	OilFee        float64 `json:"oilFee"`
+	AirportFee    float64 `json:"airportFee"`
+	FareBasisCode string  `json:"fareBasisCode"`
+	Rule          Rule    `json:"rule"`
 }
 
-type Price struct {
-	ProductType   string `json:"productType"`
-	Cabin         string `json:"cabin"`
-	Price         int64  `json:"price"`
-	TicketPrice   int64  `json:"ticketPrice"`
-	RulePrice     int64  `json:"rulePrice"`
-	OilFee        *int64 `json:"oilFee,omitempty"`
-	AirportFee    *int64 `json:"airportFee,omitempty"`
-	FareBasisCode string `json:"fareBasisCode"`
-	Rule          Rule   `json:"rule"`
+type ChdPrice struct {
+	ProductType string  `json:"productType"`
+	Cabin       string  `json:"cabin"`
+	Price       float64 `json:"price"`
+	TicketPrice float64 `json:"ticketPrice"`
+	RulePrice   float64 `json:"rulePrice"`
+	OilFee      float64 `json:"oilFee"`
+	AirportFee  float64 `json:"airportFee"`
+	Rule        Rule    `json:"rule"`
 }
 
 type Rule struct {
-	RefundRule         string      `json:"refundRule"`
-	ChangeRule         string      `json:"changeRule"`
-	TransferRule       string      `json:"transferRule"`
-	RefundExp          []Exp       `json:"refundExp"`
-	ChangeExp          []Exp       `json:"changeExp"`
-	HandBaggageRule    BaggageRule `json:"handBaggageRule"`
-	ConsignBaggageRule BaggageRule `json:"consignBaggageRule"`
+	RefundRule         string             `json:"refundRule"`
+	ChangeRule         string             `json:"changeRule"`
+	TransferRule       string             `json:"transferRule"`
+	RefundExp          []RefundExp        `json:"refundExp"`
+	ChangeExp          []ChangeExp        `json:"changeExp"`
+	HandBaggageRule    HandBaggageRule    `json:"handBaggageRule"`
+	ConsignBaggageRule ConsignBaggageRule `json:"consignBaggageRule"`
 }
 
-type Exp struct {
+type RefundExp struct {
 	TimeTxt string `json:"timeTxt"`
 	TimeExp string `json:"timeExp"`
-	Price   int64  `json:"price"`
-	Percent int64  `json:"percent"`
+	Price   int    `json:"price"`
+	Percent int    `json:"percent"`
 }
-
-type BaggageRule struct {
-	Txt    string `json:"txt"`
-	Pieces int64  `json:"pieces"`
-	Weight int64  `json:"weight"`
-	Volume string `json:"volume"`
+type ChangeExp struct {
+	TimeTxt string `json:"timeTxt"`
+	TimeExp string `json:"timeExp"`
+	Price   int    `json:"price"`
+	Percent int    `json:"percent"`
 }
-
-type CabinProductDesc struct {
-	ID          int64  `json:"id"`
-	ProductDesc string `json:"productDesc"`
+type HandBaggageRule struct {
+	Txt    string  `json:"txt"`
+	Pieces int     `json:"pieces"`
+	Weight float64 `json:"weight"`
+	Volume string  `json:"volume"`
 }
-
-type SaleControl struct {
-	ID                int64   `json:"id"`
-	PSNum             *string `json:"psNum,omitempty"`
-	PSType            string  `json:"psType"`
-	PSIDType          *string `json:"psIdType,omitempty"`
-	PSIDNo            *string `json:"psIdNo,omitempty"`
-	PSAge             *string `json:"psAge,omitempty"`
-	PSGender          *string `json:"psGender,omitempty"`
-	NewMember         *int64  `json:"newMember,omitempty"`
-	HasAge            *string `json:"hasAge,omitempty"`
-	CheckThreeElement *int64  `json:"checkThreeElement,omitempty"`
+type ConsignBaggageRule struct {
+	Txt    string  `json:"txt"`
+	Pieces int     `json:"pieces"`
+	Weight float64 `json:"weight"`
+	Volume string  `json:"volume"`
 }
 
-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       int64  `json:"isStop"`
-	IsShare      int64  `json:"isShare"`
-	Meal         int64  `json:"meal"`
-	IsCancel     int64  `json:"isCancel"`
+type SaleControls struct {
+	ID       int    `json:"id"`
+	PsNum    string `json:"psNum"`
+	PsAge    string `json:"psAge"`
+	PsIDType string `json:"psIdType"`
 }
 
-type ServicePacket struct {
-	ID    int64  `json:"id"`
+type ServicePackets struct {
+	ID    int    `json:"id"`
 	Title string `json:"title"`
 	Desc  string `json:"desc"`
 }

+ 11 - 9
structs/token.go

@@ -1,13 +1,15 @@
 package structs
 
 type TokenResponse struct {
-	Success bool   `json:"success"`
-	Code    int    `json:"code"`
-	Msg     string `json:"msg"`
-	Data    struct {
-		AccessToken string `json:"access_token"`
-		ExpiresIn   int    `json:"expires_in"`
-		ExpireTime  string `json:"expireTime"`
-	} `json:"data"`
-	TraceID string `json:"traceId"`
+	Success bool      `json:"success"`
+	Code    int       `json:"code"`
+	Msg     string    `json:"msg"`
+	Data    TokenData `json:"data"`
+	TraceID string    `json:"traceId"`
+}
+
+type TokenData struct {
+	AccessToken string `json:"access_token"`
+	ExpiresIn   int    `json:"expires_in"`
+	ExpireTime  string `json:"expireTime"`
 }

+ 45 - 1
utils/function.go

@@ -1,6 +1,10 @@
 package utils
 
-import "time"
+import (
+	"encoding/json"
+	"fmt"
+	"time"
+)
 
 // TimestampToString 将 Unix 时间戳转换为指定格式的时间字符串
 // 参数:
@@ -13,6 +17,26 @@ func TimestampToString(t int64, format string) string {
 	return t_obj.Format(format)
 }
 
+// StringToTimestamp 将指定格式的时间字符串转换为 Unix 时间戳
+// 参数:
+// t - 时间字符串
+// format - 时间格式字符串(例如 "2006-01-02 15:04:05")
+// 返回值:
+// Unix 时间戳(秒级)
+func StringToTimestamp(t string, format string) int64 {
+	t_obj, err := time.ParseInLocation(format, t, time.Local)
+	if err != nil {
+		return 0
+	}
+	return t_obj.Unix()
+}
+
+// Contains 检查切片中是否包含指定元素
+// 参数:
+// slice - 待检查的切片
+// item - 要查找的元素
+// 返回值:
+// 如果切片中包含指定元素,则返回 true;否则返回 false
 func Contains[T comparable](slice []T, item T) bool {
 	for _, v := range slice {
 		if v == item {
@@ -21,3 +45,23 @@ func Contains[T comparable](slice []T, item T) bool {
 	}
 	return false
 }
+
+func StringJsonConvert[T comparable](jsonSt T) ([]byte, error) {
+	// 将结构体转换为 JSON
+	jsonData, err := json.Marshal(jsonSt)
+	if err != nil {
+		return []byte{}, fmt.Errorf("JSON 编码失败: %w", err)
+	}
+
+	// 去除转义字符的方法:先反序列化再序列化
+	var temp interface{}
+	if err = json.Unmarshal(jsonData, &temp); err != nil {
+		return []byte{}, fmt.Errorf("JSON 反序列化失败: %w", err)
+	}
+	cleanJson, err := json.Marshal(temp)
+	if err != nil {
+		return []byte{}, fmt.Errorf("JSON 重新序列化失败: %w", err)
+	}
+
+	return cleanJson, nil
+}

+ 104 - 31
utils/http_client.go

@@ -9,70 +9,143 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"net/url"
+	"strings"
 	"time"
 )
 
+// 扩展 HTTPClient 接口
 type HTTPClient interface {
-	PostJSON(ctx context.Context, url string, body interface{}) ([]byte, error)
+	RequestJSON(ctx context.Context, method string, reqURL string, body interface{}) ([]byte, error)
+	RequestWithProxy(ctx context.Context, method string, reqURL string, body interface{}, proxyURL string) ([]byte, error)
 }
 
+// 修改 httpClient 结构体
 type httpClient struct {
-	client *http.Client
+	client        *http.Client
+	defaultProxy  string // 新增默认代理配置
+	skipSSLVerify bool
 }
 
-func NewHttpClient(timeout time.Duration, isSkipSSLVerify bool) HTTPClient {
+func NewHttpClient(timeout time.Duration, isSkipSSLVerify bool, defaultProxy string) HTTPClient {
+	baseTransport := &http.Transport{
+		TLSClientConfig: &tls.Config{
+			InsecureSkipVerify: isSkipSSLVerify,
+		},
+		MaxIdleConns:       100,
+		IdleConnTimeout:    90 * time.Second,
+		DisableCompression: true,
+	}
+
 	return &httpClient{
 		client: &http.Client{
-			Timeout: timeout,
-			Transport: &http.Transport{
-				TLSClientConfig: &tls.Config{
-					InsecureSkipVerify: isSkipSSLVerify, // 跳过 SSL 验证
-				},
-				MaxIdleConns:       100,
-				IdleConnTimeout:    90 * time.Second,
-				DisableCompression: true,
-			},
+			Timeout:   timeout,
+			Transport: baseTransport,
 		},
+		defaultProxy:  defaultProxy,
+		skipSSLVerify: isSkipSSLVerify,
+	}
+}
+
+// 改进后的带代理请求方法
+func (c *httpClient) RequestWithProxy(ctx context.Context, method string, reqURL string, body interface{}, proxyURL string) ([]byte, error) {
+	// 自动补充协议前缀(支持多种代理协议)
+	if !strings.Contains(proxyURL, "://") {
+		proxyURL = "http://" + proxyURL
+		fmt.Printf("自动补充HTTP协议前缀: %s\n", proxyURL)
+	}
+
+	// 校验并解析代理URL
+	parsedProxy, err := url.Parse(proxyURL)
+	if err != nil {
+		return nil, fmt.Errorf("代理地址格式错误(需要包含协议,示例:socks5://127.0.0.1:1080 或 http://proxy.com): %w", err)
+	}
+
+	// 配置代理传输
+	proxyTransport := c.client.Transport.(*http.Transport).Clone()
+	proxyTransport.Proxy = http.ProxyURL(parsedProxy)
+
+	// 添加Transport配置验证
+	if proxyTransport.Proxy == nil {
+		return nil, fmt.Errorf("代理配置未正确应用到Transport")
+	}
+
+	// 创建带缓存的客户端
+	proxyClient := &http.Client{
+		Transport: proxyTransport,
+		Timeout:   c.client.Timeout,
+	}
+
+	// 执行请求并处理错误
+	respData, err := c.doRequest(ctx, proxyClient, method, reqURL, body)
+	if err != nil {
+		return nil, fmt.Errorf("代理请求失败(%s): %w", proxyURL, err)
 	}
+	return respData, nil
 }
 
-func (c *httpClient) PostJSON(ctx context.Context, url string, body interface{}) ([]byte, error) {
-	// 创建请求体
+// 修改方法签名
+func (c *httpClient) RequestJSON(ctx context.Context, method string, reqURL string, body interface{}) ([]byte, error) {
+	return c.doRequest(ctx, c.client, method, reqURL, body)
+}
+
+// 重命名并修改doPostRequest
+func (c *httpClient) doRequest(ctx context.Context, client *http.Client, method string, reqURL string, body interface{}) ([]byte, error) {
 	var reqBody []byte
-	if body != nil {
+	if body != nil && method != "GET" {
 		var err error
-		reqBody, err = json.Marshal(body)
-		if err != nil {
-			return nil, fmt.Errorf("JSON序列化失败: %w", err)
+		if byteBody, ok := body.([]byte); ok {
+			reqBody = byteBody
+		} else {
+			if reqBody, err = json.Marshal(body); err != nil {
+				return nil, fmt.Errorf("JSON序列化失败: %w", err)
+			}
 		}
 	}
 
-	// 创建HTTP请求
-	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(reqBody))
+	if method == "GET" && body != nil {
+		if params, ok := body.(map[string]string); ok {
+			query := url.Values{}
+			for k, v := range params {
+				query.Add(k, v)
+			}
+			reqURL = fmt.Sprintf("%s?%s", reqURL, query.Encode())
+		}
+	}
+
+	req, err := http.NewRequestWithContext(ctx, method, reqURL, bytes.NewReader(reqBody))
 	if err != nil {
 		return nil, fmt.Errorf("创建请求失败: %w", err)
 	}
 
-	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
 	req.Header.Set("Accept", "application/json")
+	// 原有脱敏日志
+	// Logger.Printf("[HTTP请求] %s %s\nHeaders: %+v\nBody: %.100s",
+	// 	method,
+	// 	strings.ReplaceAll(reqURL, "access_token=[^\"]*", "access_token=***"),
+	// 	req.Header,
+	// 	string(reqBody))
 
-	// 发送请求
-	resp, err := c.client.Do(req)
+	resp, err := client.Do(req)
+	if resp != nil {
+		// 记录响应日志(脱敏处理)
+		bodyBytes, _ := io.ReadAll(resp.Body)
+		resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
+		// Logger.Printf("[HTTP响应] Status: %d\nHeaders: %+v\nBody: %.200s",
+		// 	resp.StatusCode,
+		// 	resp.Header,
+		// 	string(bodyBytes))
+	}
 	if err != nil {
 		return nil, fmt.Errorf("网络请求失败: %w", err)
 	}
 	defer resp.Body.Close()
 
-	// 检查状态码
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+		Logger.WithField("status", resp.StatusCode).Warn("非成功状态码")
 		return nil, fmt.Errorf("非成功状态码: %d", resp.StatusCode)
 	}
 
-	// 读取响应内容
-	respBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, fmt.Errorf("读取响应失败: %w", err)
-	}
-
-	return respBody, nil
+	return io.ReadAll(resp.Body)
 }

+ 37 - 0
utils/init_service.go

@@ -4,15 +4,20 @@ import (
 	"context"
 	"fmt"
 	"go-policy-service/config"
+	"log"
+	"os"
 	"time"
 
 	"github.com/go-redis/redis/v8"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
 	"gorm.io/driver/mysql"
 	"gorm.io/gorm"
 )
 
 var Db *gorm.DB
 var Rdb *redis.Client
+var Mongo *mongo.Client
 
 func InitDB(cfg *config.Config) error {
 	dsn := getDSN(cfg)
@@ -49,3 +54,35 @@ func InitRedis(cfg *config.Config) error {
 
 	return nil
 }
+
+func InitMongoDB(cfg *config.Config) {
+	// 从环境变量中获取 MongoDB 连接信息
+	dsn := os.Getenv("MONGO_DSN")
+	host := os.Getenv("MONGO_HOST")
+	port := os.Getenv("MONGO_PORT")
+
+	// 如果存在 MONGO_DSN 环境变量,则使用它来建立连接
+	var client *mongo.Client
+	var err error
+	if dsn != "" {
+		client, err = mongo.Connect(context.TODO(), options.Client().ApplyURI(dsn))
+	} else {
+		// 否则,使用 host 和 port 构建连接 URI
+		uri := fmt.Sprintf("mongodb://%s:%s", host, port)
+		client, err = mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
+	}
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// 检查连接
+	err = client.Ping(context.TODO(), nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	Mongo = client
+
+	fmt.Println("Connected to MongoDB!")
+}

+ 1 - 1
utils/logger.go

@@ -17,4 +17,4 @@ func InitLogger(level string) {
 	} else {
 		Logger.SetLevel(logLevel)
 	}
-}
+}