This commit is contained in:
2026-02-12 15:16:54 +08:00
parent 07bf234b30
commit ca6dcc1b24
21 changed files with 2594 additions and 111 deletions

View File

@@ -86,6 +86,23 @@ var productHandlers = map[string]queryHandlerFunc{
"toc_VehicleMaintenanceDetail": runVehicleMaintenanceDetailReq,
"toc_VehicleClaimDetail": runVehicleClaimDetailReq,
"toc_VehicleClaimVerify": runVehicleClaimVerifyReq,
// 核验工具verify feature.md
"toc_PoliceTwoFactors": runVerifyAuthTwoReq,
"toc_PoliceThreeFactors": runVerifyAuthThreeReq,
"toc_ProfessionalCertificate": runVerifyCertReq,
"toc_PersonalConsumptionCapacityLevel": runVerifyConsumptionReq, // 个人消费能力(沿用现有 product_en
"toc_OperatorTwoFactors": runVerifyYysTwoReq,
"toc_MobileThreeFactors": runVerifyYysThreeReq,
"toc_NumberRecycle": runVerifyMobileOnlyReq,
"toc_MobileEmptyCheck": runVerifyMobileOnlyReq,
"toc_MobilePortability": runVerifyMobileOnlyReq,
"toc_MobileOnlineStatus": runVerifyMobileOnlyReq,
"toc_MobileOnlineDuration": runVerifyMobileOnlyReq,
"toc_MobileAttribution": runVerifyMobileOnlyReq,
"toc_MobileConsumptionRange": runVerifyYysConsumptionReq,
"toc_EnterpriseRelation": runVerifyEntRelationReq,
"toc_BankcardFourFactors": runVerifyBankFourReq,
"toc_BankcardBlacklist": runVerifyBankBlackReq,
}
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
@@ -242,8 +259,10 @@ func runVehicleVinCodeReq(l *QueryServiceLogic, decryptData []byte, product stri
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
return map[string]interface{}{
"vin_code": data.VinCode,
@@ -259,13 +278,15 @@ func runVehicleMileageMixedReq(l *QueryServiceLogic, decryptData []byte, product
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
return map[string]interface{}{
"vin_code": data.VinCode,
"return_url": data.ReturnURL,
"image_url": data.ImageURL,
"vin_code": data.VinCode,
"image_url": data.ImageURL,
}, nil
}
@@ -278,8 +299,10 @@ func runVehicleVinValuationReq(l *QueryServiceLogic, decryptData []byte, product
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
return map[string]interface{}{
"vin_code": data.VinCode,
@@ -297,13 +320,15 @@ func runVehicleTransferSimpleReq(l *QueryServiceLogic, decryptData []byte, produ
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
return map[string]interface{}{"vin_code": data.VinCode}, nil
}
// runVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B仅必填 vin_code, return_url
// runVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B仅必填 vin_code;回调地址后端自动生成
func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVehicleMaintenanceSimpleReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
@@ -312,16 +337,18 @@ func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, pr
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
return map[string]interface{}{
"vin_code": data.VinCode,
"return_url": data.ReturnURL,
"vin_code": data.VinCode,
}, nil
}
// runVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L仅必填 vin_code, return_url
// runVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L仅必填 vin_code;回调地址后端自动生成
func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVehicleMaintenanceDetailReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
@@ -330,16 +357,18 @@ func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, pr
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
return map[string]interface{}{
"vin_code": data.VinCode,
"return_url": data.ReturnURL,
"vin_code": data.VinCode,
}, nil
}
// runVehicleClaimDetailReq 车辆出险详版 QCXGP00W仅必填 vin_code, return_url, vlphoto_datavlphoto_data 由 API 层加密为 data
// runVehicleClaimDetailReq 车辆出险详版 QCXGP00W仅必填 vin_code, vlphoto_data;回调地址后端自动生成vlphoto_data 由 API 层加密为 data
func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVehicleClaimDetailReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
@@ -348,12 +377,14 @@ func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
return map[string]interface{}{
"vin_code": data.VinCode,
"return_url": data.ReturnURL,
"vlphoto_data": data.VlphotoData,
}, nil
}
@@ -367,12 +398,151 @@ func runVehicleClaimVerifyReq(l *QueryServiceLogic, decryptData []byte, product
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
if data.Mobile != "" && data.Code != "" {
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
return nil, verifyCodeErr
}
}
auth := data.Authorized
if auth == "" {
auth = "1"
}
return map[string]interface{}{
"vin_code": data.VINCode,
"authorized": data.Authorized,
"authorized": auth,
}, nil
}
// --------------- 核验工具 handlers ---------------
func runVerifyAuthTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyAuthTwoReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
}
func runVerifyAuthThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyAuthThreeReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"photo_data": data.PhotoData, "id_card": data.IDCard, "name": data.Name}, nil
}
func runVerifyCertReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyCertReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"id_card": data.IDCard, "name": data.Name}, nil
}
func runVerifyConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyConsumptionReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
}
func runVerifyYysTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyYysTwoReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo, "name": data.Name}, nil
}
func runVerifyYysThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyYysThreeReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
}
func runVerifyMobileOnlyReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyMobileOnlyReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo}, nil
}
func runVerifyYysConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyYysConsumptionReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{"mobile_no": data.MobileNo, "authorized": data.Authorized}, nil
}
func runVerifyEntRelationReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyEntRelationReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
out := map[string]interface{}{}
if data.IDCard != "" {
out["id_card"] = data.IDCard
}
return out, nil
}
func runVerifyBankFourReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyBankFourReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{
"mobile_no": data.MobileNo,
"id_card": data.IDCard,
"bank_card": data.BankCard,
"name": data.Name,
}, nil
}
func runVerifyBankBlackReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.TocVerifyBankBlackReq
if err := json.Unmarshal(decryptData, &data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
}
if err := validator.Validate(data); err != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
}
return map[string]interface{}{
"mobile_no": data.MobileNo,
"id_card": data.IDCard,
"name": data.Name,
"bank_card": data.BankCard,
}, nil
}

View File

@@ -0,0 +1,164 @@
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,
)
}
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取回调 Body 失败: %v", err)
}
// 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 {
apiList[i].Data = json.RawMessage(bodyBytes)
apiList[i].Success = true
apiList[i].Timestamp = nowStr
apiList[i].Error = ""
updated = true
break
}
}
if !updated {
apiList = append(apiList, service.APIResponseData{
ApiID: apiID,
Data: json.RawMessage(bodyBytes),
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
}

View File

@@ -0,0 +1,66 @@
package upload
import (
"context"
"os"
"path/filepath"
"strings"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type ServeUploadedFileLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewServeUploadedFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadedFileLogic {
return &ServeUploadedFileLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ServeUploadedFileLogic) ServeUploadedFile(req *types.ServeUploadedFileReq) (resp *types.ServeUploadedFileResp, err error) {
fileName := strings.TrimSpace(req.FileName)
if fileName == "" {
return nil, errors.Wrap(xerr.NewErrMsg("缺少文件名"), "fileName empty")
}
// 只允许文件名,禁止路径穿越
if strings.Contains(fileName, "..") || filepath.Base(fileName) != fileName {
return nil, errors.Wrap(xerr.NewErrMsg("非法文件名"), fileName)
}
candidates := []string{
"data/uploads",
"../data/uploads",
"../../data/uploads",
"../../../data/uploads",
}
for _, c := range candidates {
abs, _ := filepath.Abs(c)
fullPath := filepath.Join(abs, fileName)
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
contentType := "image/jpeg"
if strings.HasSuffix(strings.ToLower(fileName), ".png") {
contentType = "image/png"
} else if strings.HasSuffix(strings.ToLower(fileName), ".gif") {
contentType = "image/gif"
} else if strings.HasSuffix(strings.ToLower(fileName), ".webp") {
contentType = "image/webp"
}
return &types.ServeUploadedFileResp{
FilePath: fullPath,
ContentType: contentType,
}, nil
}
}
return nil, errors.Wrapf(xerr.NewErrMsg("文件不存在"), "fileName=%s", fileName)
}

View File

@@ -0,0 +1,138 @@
package upload
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"time"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
const maxImageSize = 3 * 1024 * 1024 // 3MB
const defaultTempFileMaxAgeH = 24
type UploadImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
return &UploadImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (resp *types.UploadImageResp, err error) {
decoded, decErr := base64.StdEncoding.DecodeString(req.ImageBase64)
if decErr != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("图片 base64 格式错误"), "%v", decErr)
}
if len(decoded) > maxImageSize {
return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 3M"), "size=%d", len(decoded))
}
// 按文件内容 hash 命名,相同文件复用同一 URL避免重复传输与刷流量
hashSum := sha256.Sum256(decoded)
hashHex := hex.EncodeToString(hashSum[:])
fileName := hashHex + ".jpg"
dir := l.uploadStoragePath()
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err)
}
filePath := filepath.Join(dir, fileName)
// 若已存在同 hash 文件,直接返回 URL不重复写入
if _, statErr := os.Stat(filePath); statErr == nil {
url := l.buildURL(fileName)
logx.Infof("upload image dedup by hash, file=%s", fileName)
return &types.UploadImageResp{Url: url}, nil
}
if err := os.WriteFile(filePath, decoded, 0644); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err)
}
// 异步清理过期临时文件,不阻塞响应
go l.deleteOldUploads(dir)
url := l.buildURL(fileName)
logx.Infof("upload image ok, file=%s", fileName)
return &types.UploadImageResp{Url: url}, nil
}
func (l *UploadImageLogic) buildURL(fileName string) string {
baseURL := l.svcCtx.Config.Upload.FileBaseURL
if baseURL == "" {
baseURL = l.svcCtx.Config.AdminPromotion.URLDomain
if baseURL != "" {
baseURL = baseURL + "/api/v1/upload/file"
}
}
if baseURL == "" {
return ""
}
return fmt.Sprintf("%s/%s", baseURL, fileName)
}
func (l *UploadImageLogic) uploadStoragePath() string {
candidates := []string{
"data/uploads",
"../data/uploads",
"../../data/uploads",
"../../../data/uploads",
}
for _, c := range candidates {
abs, _ := filepath.Abs(c)
if err := os.MkdirAll(abs, 0755); err == nil {
return abs
}
}
abs, _ := filepath.Abs(candidates[0])
return abs
}
// deleteOldUploads 删除目录下超过保留时长的临时文件
func (l *UploadImageLogic) deleteOldUploads(dir string) {
maxAgeH := l.svcCtx.Config.Upload.TempFileMaxAgeH
if maxAgeH <= 0 {
maxAgeH = defaultTempFileMaxAgeH
}
cutoff := time.Now().Add(-time.Duration(maxAgeH) * time.Hour)
entries, err := os.ReadDir(dir)
if err != nil {
l.Errorf("deleteOldUploads ReadDir: %v", err)
return
}
for _, e := range entries {
if e.IsDir() {
continue
}
path := filepath.Join(dir, e.Name())
info, err := os.Stat(path)
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
if err := os.Remove(path); err != nil {
l.Errorf("deleteOldUploads Remove %s: %v", path, err)
} else {
l.Infof("deleteOldUploads removed %s", path)
}
}
}
}