diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index 8322758..c52998d 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -138,6 +138,11 @@ type QCXGX2X6Req struct { VehicleType string `json:"vehicle_type" validate:"omitempty"` Name string `json:"name" validate:"required,min=1,validName"` } +type QCXG7K2NReq struct { + PlateNo string `json:"plate_no" validate:"required"` + Name string `json:"name" validate:"required,min=1,validName"` + PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 20 21 22 23 24 38 51 52"` +} type QCXG1S2LReq struct { VinCode string `json:"vin_code" validate:"required"` } diff --git a/internal/domains/api/services/api_request_service.go b/internal/domains/api/services/api_request_service.go index 8e40c1f..aa3b9df 100644 --- a/internal/domains/api/services/api_request_service.go +++ b/internal/domains/api/services/api_request_service.go @@ -254,6 +254,7 @@ func registerAllProcessors(combService *comb.CombService) { "QCXG521L": qcxg.ProcessQCXG521LRequest, // 车辆静态信息查询 10479 "QCXGIJY3": qcxg.ProcessQCXGIJY3Request, // 名下车辆诺尔 "QCXG6U5G": qcxg.ProcessQCXG6U5GRequest, // 车辆核验 + "QCXG7K2N": qcxg.ProcessQCXG7K2NRequest, // 人车核验加强版 // DWBG系列处理器 - 多维报告 diff --git a/internal/domains/api/services/form_config_service.go b/internal/domains/api/services/form_config_service.go index f455cc8..4819f12 100644 --- a/internal/domains/api/services/form_config_service.go +++ b/internal/domains/api/services/form_config_service.go @@ -312,6 +312,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string "JRZQFC59": &dto.JRZQFC59Req{}, //房产核验 "IVYZX7J9": &dto.IVYZX7J9Req{}, //学籍核验 "QCXG6U5G": &dto.QCXG6U5GReq{}, //车辆核验 + "QCXG7K2N": &dto.QCXG7K2NReq{}, //人车核验加强版 } // 优先返回已配置的DTO diff --git a/internal/domains/api/services/processors/qcxg/qcxg7k2n_plate_type.go b/internal/domains/api/services/processors/qcxg/qcxg7k2n_plate_type.go new file mode 100644 index 0000000..d814b48 --- /dev/null +++ b/internal/domains/api/services/processors/qcxg/qcxg7k2n_plate_type.go @@ -0,0 +1,76 @@ +package qcxg + +import "strings" + +const defaultPlateType = "02" + +func normalizePlateType(plateType string) string { + code := strings.TrimSpace(plateType) + if code == "" { + return defaultPlateType + } + return code +} + +func etcPlateColorParam(plateType string) (color string, include bool) { + code := strings.TrimSpace(plateType) + if code == "" || code == "02" { + return "", false + } + color, ok := plateTypePlateColorMap[code] + if !ok || color == "0" { + return "", false + } + return color, true +} + +var plateTypePlateColorMap = map[string]string{ + "01": "1", + "02": "0", + "03": "2", + "04": "2", + "05": "2", + "06": "2", + "07": "1", + "08": "0", + "09": "2", + "10": "2", + "11": "2", + "12": "2", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "20": "7", + "21": "7", + "22": "7", + "23": "3", + "24": "3", + "38": "0", + "51": "5", + "52": "11", +} + +func buildETCShujubaoParams(plateNo, name, plateType string) map[string]interface{} { + params := map[string]interface{}{ + "key": "a2f32fc54b44ebc85b97a2aaff1734ec", + "carNo": plateNo, + "name": name, + } + if color, include := etcPlateColorParam(plateType); include { + params["plateColor"] = color + } + return params +} + +func buildETCTianyuanParams(plateNo, name, plateType string) map[string]string { + params := map[string]string{ + "plate_no": plateNo, + "name": name, + } + if color, include := etcPlateColorParam(plateType); include { + params["plate_color"] = color + } + return params +} diff --git a/internal/domains/api/services/processors/qcxg/qcxg7k2n_processor.go b/internal/domains/api/services/processors/qcxg/qcxg7k2n_processor.go new file mode 100644 index 0000000..82f8935 --- /dev/null +++ b/internal/domains/api/services/processors/qcxg/qcxg7k2n_processor.go @@ -0,0 +1,58 @@ +package qcxg + +import ( + "context" + "encoding/json" + "errors" + + "hyapi-server/internal/domains/api/dto" + "hyapi-server/internal/domains/api/services/processors" +) + +// ProcessQCXG7K2NRequest QCXG7K2N 人车核验加强版: +// 优先经天远调用 QCXG3M7Z(人车关系核验 ETC),查无结果时降级调用本地 QCXGX2X6(行驶证信息核验V2)。 +func ProcessQCXG7K2NRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.QCXG7K2NReq + if err := json.Unmarshal(params, ¶msDto); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + if err := deps.Validator.ValidateStruct(paramsDto); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + + plateType := normalizePlateType(paramsDto.PlateType) + + etcParams, err := json.Marshal(buildETCTianyuanParams(paramsDto.PlateNo, paramsDto.Name, paramsDto.PlateType)) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err := processors.CallTianyuanByProduct(ctx, deps, "QCXG3M7Z", etcParams) + if err == nil { + return respBytes, nil + } + if !errors.Is(err, processors.ErrNotFound) { + return nil, err + } + + x2x6Params, err := json.Marshal(dto.QCXGX2X6Req{ + PlateNo: paramsDto.PlateNo, + Name: paramsDto.Name, + VehicleType: plateType, + }) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err = ProcessQCXGX2X6Request(ctx, x2x6Params, deps) + if err != nil { + return nil, err + } + + transformed, err := transformX2X6To3M7Z(respBytes) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return transformed, nil +} diff --git a/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform.go b/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform.go new file mode 100644 index 0000000..779545c --- /dev/null +++ b/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform.go @@ -0,0 +1,29 @@ +package qcxg + +import ( + "encoding/json" + "fmt" +) + +// transformX2X6To3M7Z 将行驶证信息核验V2响应转换为 ETC 人车关系核验格式。 +// checkResult: 00 姓名一致 -> status 1;01 姓名不一致 -> status 2。 +func transformX2X6To3M7Z(respBytes []byte) ([]byte, error) { + var raw map[string]interface{} + if err := json.Unmarshal(respBytes, &raw); err != nil { + return nil, err + } + + status := mapCheckResultToStatus(raw["checkResult"]) + return json.Marshal(map[string]string{"status": status}) +} + +func mapCheckResultToStatus(checkResult interface{}) string { + switch fmt.Sprint(checkResult) { + case "00": + return "1" + case "01": + return "2" + default: + return "2" + } +} diff --git a/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform_test.go b/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform_test.go new file mode 100644 index 0000000..a0b7a5b --- /dev/null +++ b/internal/domains/api/services/processors/qcxg/qcxg7k2n_transform_test.go @@ -0,0 +1,38 @@ +package qcxg + +import ( + "encoding/json" + "testing" +) + +func TestTransformX2X6To3M7Z(t *testing.T) { + tests := []struct { + name string + input string + wantStat string + }{ + {"姓名一致", `{"checkResult":"00","plate":"桂A12345"}`, "1"}, + {"姓名不一致", `{"checkResult":"01","plate":"桂A12345"}`, "2"}, + {"未知结果默认不一致", `{"checkResult":"99"}`, "2"}, + {"缺少checkResult默认不一致", `{"plate":"桂A12345"}`, "2"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := transformX2X6To3M7Z([]byte(tt.input)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + var out map[string]string + if err := json.Unmarshal(got, &out); err != nil { + t.Fatalf("invalid json: %v", err) + } + if len(out) != 1 { + t.Fatalf("expected only status field, got %v", out) + } + if out["status"] != tt.wantStat { + t.Fatalf("status = %q, want %q", out["status"], tt.wantStat) + } + }) + } +}