2026-02-12 15:16:54 +08:00
|
|
|
|
package tianyuan
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"tyc-server/app/main/api/internal/service"
|
|
|
|
|
|
"tyc-server/app/main/api/internal/svc"
|
|
|
|
|
|
"tyc-server/app/main/model"
|
|
|
|
|
|
"tyc-server/common/xerr"
|
|
|
|
|
|
"tyc-server/pkg/lzkit/crypto"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// VehicleCallbackLogic 处理天远车辆类接口的异步回调
|
|
|
|
|
|
// 设计目标:
|
|
|
|
|
|
// - 按 order_no + api_id 找到对应的查询记录
|
|
|
|
|
|
// - 解密原有 query_data([]APIResponseData),更新或追加当前 api_id 的结果
|
|
|
|
|
|
// - 再次加密写回 query.query_data,必要时将 query_state 从 pending 更新为 success
|
|
|
|
|
|
type VehicleCallbackLogic struct {
|
|
|
|
|
|
logx.Logger
|
|
|
|
|
|
ctx context.Context
|
|
|
|
|
|
svcCtx *svc.ServiceContext
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewVehicleCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VehicleCallbackLogic {
|
|
|
|
|
|
return &VehicleCallbackLogic{
|
|
|
|
|
|
Logger: logx.WithContext(ctx),
|
|
|
|
|
|
ctx: ctx,
|
|
|
|
|
|
svcCtx: svcCtx,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 与 paySuccessNotify 中一致:仅通过异步回调回写结果的车辆接口
|
|
|
|
|
|
var asyncVehicleApiIDs = map[string]bool{
|
|
|
|
|
|
"QCXG1U4U": true, "QCXG3Y6B": true, "QCXG3Z3L": true, "QCXGP00W": true,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// allAsyncVehicleReceived 判断 apiList 中所有“异步车辆接口”是否均已收到回调(Success 为 true)
|
|
|
|
|
|
func allAsyncVehicleReceived(apiList []service.APIResponseData) bool {
|
|
|
|
|
|
for _, item := range apiList {
|
|
|
|
|
|
if asyncVehicleApiIDs[item.ApiID] && !item.Success {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle 入口:直接接收原始 *http.Request,方便读取 query / body
|
|
|
|
|
|
func (l *VehicleCallbackLogic) Handle(r *http.Request) error {
|
|
|
|
|
|
apiID := r.URL.Query().Get("api_id")
|
|
|
|
|
|
orderNo := r.URL.Query().Get("order_no")
|
|
|
|
|
|
|
|
|
|
|
|
if apiID == "" || orderNo == "" {
|
|
|
|
|
|
return errors.Wrapf(
|
|
|
|
|
|
xerr.NewErrMsg("缺少 api_id 或 order_no"),
|
|
|
|
|
|
"tianyuan vehicle callback, api_id=%s, order_no=%s", apiID, orderNo,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 15:42:09 +08:00
|
|
|
|
l.Infof("tianyuan vehicle callback start, api_id=%s, order_no=%s", apiID, orderNo)
|
|
|
|
|
|
|
2026-02-12 15:16:54 +08:00
|
|
|
|
bodyBytes, err := io.ReadAll(r.Body)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取回调 Body 失败: %v", err)
|
|
|
|
|
|
}
|
2026-02-12 15:42:09 +08:00
|
|
|
|
l.Infof("tianyuan vehicle callback body received, api_id=%s, order_no=%s, body_len=%d", apiID, orderNo, len(bodyBytes))
|
2026-02-12 15:16:54 +08:00
|
|
|
|
|
2026-02-12 17:29:03 +08:00
|
|
|
|
// 回调包裹格式通常为 { code:0, msg:\"成功\", order_id:\"...\", data:{...} },
|
|
|
|
|
|
// 这里只把 data 字段提取出来存到 QueryData 里,便于前端直接渲染。
|
|
|
|
|
|
type callbackWrapper struct {
|
|
|
|
|
|
Code json.RawMessage `json:"code"`
|
|
|
|
|
|
Msg json.RawMessage `json:"msg"`
|
|
|
|
|
|
OrderID json.RawMessage `json:"order_id"`
|
|
|
|
|
|
Data json.RawMessage `json:"data"`
|
|
|
|
|
|
Extra map[string]any `json:"-"` // 预留
|
|
|
|
|
|
}
|
|
|
|
|
|
var wrapper callbackWrapper
|
|
|
|
|
|
payload := bodyBytes
|
|
|
|
|
|
if len(bodyBytes) > 0 {
|
|
|
|
|
|
if err := json.Unmarshal(bodyBytes, &wrapper); err != nil {
|
|
|
|
|
|
l.Errorf("tianyuan vehicle callback unmarshal body failed, api_id=%s, order_no=%s, err=%v", apiID, orderNo, err)
|
|
|
|
|
|
} else if len(wrapper.Data) > 0 {
|
|
|
|
|
|
payload = wrapper.Data
|
|
|
|
|
|
l.Infof("tianyuan vehicle callback extracted data field, api_id=%s, order_no=%s, data_len=%d", apiID, orderNo, len(payload))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 15:16:54 +08:00
|
|
|
|
// 1. 根据订单号找到订单
|
|
|
|
|
|
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if err == model.ErrNotFound {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrMsg("未找到订单"), "order_no=%s", orderNo)
|
|
|
|
|
|
}
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败, order_no=%s, err=%v", orderNo, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 根据订单ID找到对应查询记录
|
|
|
|
|
|
query, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if err == model.ErrNotFound {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrMsg("未找到查询记录"), "order_no=%s, order_id=%d", orderNo, order.Id)
|
|
|
|
|
|
}
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 query 记录失败, order_id=%d, err=%v", order.Id, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取加密密钥
|
|
|
|
|
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
|
|
|
|
|
key, decodeErr := hex.DecodeString(secretKey)
|
|
|
|
|
|
if decodeErr != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 AES 密钥失败: %v", decodeErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 4. 解密 query_data,反序列化为 []APIResponseData
|
|
|
|
|
|
var apiList []service.APIResponseData
|
|
|
|
|
|
if query.QueryData.Valid && query.QueryData.String != "" {
|
|
|
|
|
|
decrypted, decErr := crypto.AesDecrypt(query.QueryData.String, key)
|
|
|
|
|
|
if decErr != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密 query_data 失败: %v", decErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(decrypted) > 0 {
|
|
|
|
|
|
if err := json.Unmarshal(decrypted, &apiList); err != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 query_data 为 APIResponseData 列表失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新或追加当前 api_id 的结果
|
|
|
|
|
|
nowStr := time.Now().Format("2006-01-02 15:04:05")
|
|
|
|
|
|
updated := false
|
|
|
|
|
|
for i := range apiList {
|
|
|
|
|
|
if apiList[i].ApiID == apiID {
|
2026-02-12 17:29:03 +08:00
|
|
|
|
apiList[i].Data = json.RawMessage(payload)
|
2026-02-12 15:16:54 +08:00
|
|
|
|
apiList[i].Success = true
|
|
|
|
|
|
apiList[i].Timestamp = nowStr
|
|
|
|
|
|
apiList[i].Error = ""
|
|
|
|
|
|
updated = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !updated {
|
|
|
|
|
|
apiList = append(apiList, service.APIResponseData{
|
|
|
|
|
|
ApiID: apiID,
|
2026-02-12 17:29:03 +08:00
|
|
|
|
Data: json.RawMessage(payload),
|
2026-02-12 15:16:54 +08:00
|
|
|
|
Success: true,
|
|
|
|
|
|
Timestamp: nowStr,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 重新序列化并加密,写回查询记录
|
|
|
|
|
|
merged, err := json.Marshal(apiList)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化合并后的 query_data 失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
enc, encErr := crypto.AesEncrypt(merged, key)
|
|
|
|
|
|
if encErr != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密合并后的 query_data 失败: %v", encErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
query.QueryData.String = enc
|
|
|
|
|
|
query.QueryData.Valid = true
|
|
|
|
|
|
// 仅当所有异步车辆接口均已有回调结果时,才将 pending 置为 success,并触发代理结算
|
|
|
|
|
|
wasPending := query.QueryState == "pending"
|
|
|
|
|
|
didSetSuccess := wasPending && allAsyncVehicleReceived(apiList)
|
|
|
|
|
|
if didSetSuccess {
|
|
|
|
|
|
query.QueryState = "success"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := l.svcCtx.QueryModel.UpdateWithVersion(l.ctx, nil, query); err != nil {
|
|
|
|
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新查询记录失败, query_id=%d, err=%v", query.Id, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if didSetSuccess {
|
|
|
|
|
|
if agentErr := l.svcCtx.AgentService.AgentProcess(l.ctx, order); agentErr != nil {
|
|
|
|
|
|
l.Errorf("tianyuan vehicle callback, AgentProcess failed, order_no=%s, err=%v", orderNo, agentErr)
|
|
|
|
|
|
// 不因代理处理失败而整体失败,回调已成功落库
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
l.Infof("tianyuan vehicle callback handled, order_no=%s, api_id=%s, query_id=%d", orderNo, apiID, query.Id)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|