This commit is contained in:
2026-07-07 17:48:49 +08:00
parent 29bb73b1e8
commit bc42c7f24a
9 changed files with 283 additions and 43 deletions

View File

@@ -335,7 +335,7 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
parentApiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, link.ParentUserID) parentApiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, link.ParentUserID)
if err != nil { if err != nil {
s.logger.Error("加载主账号API用户失败", s.logger.Error("加载主账号API用户失败",
zap.String("child_user_id", apiUser.UserId), zap.String("child_user_id", apiUser.UserId),hjk,mgumygi,b
zap.String("parent_user_id", link.ParentUserID), zap.String("parent_user_id", link.ParentUserID),
zap.Error(err)) zap.Error(err))
return nil, ErrSystem return nil, ErrSystem

View File

@@ -187,6 +187,9 @@ type YYSYXF7JReq struct {
type QCXGA8V3Req struct { type QCXGA8V3Req struct {
VinCode string `json:"vin_code" validate:"required"` VinCode string `json:"vin_code" validate:"required"`
} }
type QCXGCP77Req struct {
VinCode string `json:"vin_code" validate:"required"`
}
type YYSYP72DReq struct { type YYSYP72DReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`

View File

@@ -414,6 +414,7 @@ func registerAllProcessors(combService *comb.CombService) {
"QYGLVR76": qygl.ProcessQYGLVR76Request, //名下企业诺尔 "QYGLVR76": qygl.ProcessQYGLVR76Request, //名下企业诺尔
"QCXG1S2L": qcxg.ProcessQCXG1S2LRequest, //车辆车五项信息核验V2 "QCXG1S2L": qcxg.ProcessQCXG1S2LRequest, //车辆车五项信息核验V2
"QCXGA8V3": qcxg.ProcessQCXGA8V3Request, //全国车辆配置查验(车五项+使用性质+承保) "QCXGA8V3": qcxg.ProcessQCXGA8V3Request, //全国车辆配置查验(车五项+使用性质+承保)
"QCXGCP77": qcxg.ProcessQCXGCP77Request, //全国车辆配置查验(车辆详情)
"QCXG6U5G": qcxg.ProcessQCXG6U5GRequest, //名下车辆核验 "QCXG6U5G": qcxg.ProcessQCXG6U5GRequest, //名下车辆核验
// DWBG系列处理器 - 多维报告 // DWBG系列处理器 - 多维报告
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest, "DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,

View File

@@ -338,6 +338,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"YYSYXF7J": &dto.YYSYXF7JReq{}, //手机消费区间验证 "YYSYXF7J": &dto.YYSYXF7JReq{}, //手机消费区间验证
"QCXG1S2L": &dto.QCXG1S2LReq{}, //车辆车五项信息核验V2 "QCXG1S2L": &dto.QCXG1S2LReq{}, //车辆车五项信息核验V2
"QCXGA8V3": &dto.QCXGA8V3Req{}, //全国车辆配置查验(车五项+使用性质+承保) "QCXGA8V3": &dto.QCXGA8V3Req{}, //全国车辆配置查验(车五项+使用性质+承保)
"QCXGCP77": &dto.QCXGCP77Req{}, //全国车辆配置查验(车辆详情)
"JRZQ5T8S": &dto.JRZQ5T8SReq{}, //借贷意向验证V22 "JRZQ5T8S": &dto.JRZQ5T8SReq{}, //借贷意向验证V22
"JRZQT57Z": &dto.JRZQT57ZReq{}, //团伙欺诈风险识别 "JRZQT57Z": &dto.JRZQT57ZReq{}, //团伙欺诈风险识别
"IVYZX7J9": &dto.IVYZX7J9Req{}, //学历核验政务 "IVYZX7J9": &dto.IVYZX7J9Req{}, //学历核验政务

View File

@@ -40,10 +40,9 @@ func ProcessIVYZ5E3FRequest(ctx context.Context, params []byte, deps *processors
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData) respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData)
if err != nil { if err != nil {
if errors.Is(err, zhicha.ErrDatasource) { if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err) return fallback5E3FViaHy87(ctx, deps, paramsDto.Name, paramsDto.IDCard)
} else {
return nil, errors.Join(processors.ErrSystem, err)
} }
return nil, errors.Join(processors.ErrSystem, err)
} }
// 将响应数据转换为JSON字节 // 将响应数据转换为JSON字节

View File

@@ -40,57 +40,24 @@ func ProcessIVYZ81NCRequest(ctx context.Context, params []byte, deps *processors
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData) respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData)
if err != nil { if err != nil {
if errors.Is(err, zhicha.ErrDatasource) { if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err) return fallback81NCViaHy87(ctx, deps, paramsDto.Name, paramsDto.IDCard)
} else {
return nil, errors.Join(processors.ErrSystem, err)
} }
return nil, errors.Join(processors.ErrSystem, err)
} }
// 解析响应数据,期望格式为 {"state": "1"}
var stateResp struct { var stateResp struct {
State string `json:"state"` State string `json:"state"`
RegTime string `json:"regTime"` RegTime string `json:"regTime"`
} }
// 将 respData 转换为 JSON 字节再解析
respDataBytes, err := json.Marshal(respData) respDataBytes, err := json.Marshal(respData)
if err != nil { if err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
if err := json.Unmarshal(respDataBytes, &stateResp); err != nil { if err := json.Unmarshal(respDataBytes, &stateResp); err != nil {
return nil, errors.Join(processors.ErrSystem, err) return nil, errors.Join(processors.ErrSystem, err)
} }
// 根据 state 值转换为 81nc 格式 opType, opTypeDesc := zhichaStateTo81NCOp(stateResp.State)
var opType, opTypeDesc string return json.Marshal(build81NCResponse(stateResp.RegTime, opType, opTypeDesc))
switch stateResp.State {
case "1": // 已婚
opType = "IA"
opTypeDesc = "结婚"
case "2": // 未婚/未在民政局登记
opType = "INR"
opTypeDesc = "匹配不成功"
case "3": // 离异
opType = "IB"
opTypeDesc = "离婚"
default:
opType = "INR"
opTypeDesc = "匹配不成功"
}
// 构建 81nc 格式响应
result := map[string]interface{}{
"code": "0",
"data": map[string]interface{}{
"op_date": stateResp.RegTime,
"op_type": opType,
"op_type_desc": opTypeDesc,
},
"message": "成功",
"seqNo": "",
}
// 返回 81nc 格式响应
return json.Marshal(result)
} }

View File

@@ -0,0 +1,148 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/nuoer"
)
const hy87MarriageAPIKey = "qualificateMarry"
const hy87MarriageAPIPath = "/v1/doCheck"
// queryHy87MarriageStatus 调用 IVYZHY87 同源诺尔婚姻状态接口。
// status: 0=未婚, 1=已婚, 2=离异, 3=婚姻冷静期
func queryHy87MarriageStatus(ctx context.Context, deps *processors.ProcessorDependencies, name, idCard string) (int, error) {
if deps.NuoerService == nil {
return 0, errors.Join(processors.ErrSystem, errors.New("诺尔服务未初始化"))
}
body := map[string]string{
"idCard": idCard,
"name": name,
}
resp, err := deps.NuoerService.CallAPI(ctx, hy87MarriageAPIKey, hy87MarriageAPIPath, body)
if err != nil {
if errors.Is(err, nuoer.ErrNotFound) {
return 0, errors.Join(processors.ErrNotFound, err)
}
if errors.Is(err, nuoer.ErrDatasource) {
return 0, errors.Join(processors.ErrDatasource, err)
}
return 0, errors.Join(processors.ErrSystem, err)
}
return parseHy87MarriageStatus(resp.Data)
}
func parseHy87MarriageStatus(data interface{}) (int, error) {
dataBytes, err := json.Marshal(data)
if err != nil {
return 0, errors.Join(processors.ErrSystem, err)
}
var parsed struct {
Result struct {
Status int `json:"status"`
} `json:"result"`
}
if err := json.Unmarshal(dataBytes, &parsed); err != nil {
return 0, errors.Join(processors.ErrSystem, err)
}
return parsed.Result.Status, nil
}
// hy87StatusTo5E3FState 将 HY87 婚姻状态映射为 5E3F 的 state 字段。
// 5E3F: 1=已婚, 2=未婚/未在民政局登记, 3=离异
func hy87StatusTo5E3FState(status int) string {
switch status {
case 1:
return "1"
case 2:
return "3"
case 3:
return "1"
default:
return "2"
}
}
func build5E3FResponseFromHy87(status int) map[string]string {
return map[string]string{
"state": hy87StatusTo5E3FState(status),
}
}
func zhichaStateTo81NCOp(state string) (opType, opTypeDesc string) {
switch state {
case "1":
return "IA", "结婚"
case "2":
return "INR", "匹配不成功"
case "3":
return "IB", "离婚"
default:
return "INR", "匹配不成功"
}
}
func hy87StatusTo81NCOp(status int) (opType, opTypeDesc string) {
switch status {
case 1:
return "IA", "结婚"
case 2:
return "IB", "离婚"
case 3:
return "IA", "结婚"
default:
return "INR", "匹配不成功"
}
}
func build81NCResponse(opDate, opType, opTypeDesc string) map[string]interface{} {
return map[string]interface{}{
"code": "0",
"data": map[string]interface{}{
"op_date": opDate,
"op_type": opType,
"op_type_desc": opTypeDesc,
},
"message": "成功",
"seqNo": "",
}
}
func build81NCResponseFromHy87(status int) map[string]interface{} {
opType, opTypeDesc := hy87StatusTo81NCOp(status)
return build81NCResponse("", opType, opTypeDesc)
}
func fallback5E3FViaHy87(ctx context.Context, deps *processors.ProcessorDependencies, name, idCard string) ([]byte, error) {
status, err := queryHy87MarriageStatus(ctx, deps, name, idCard)
if err != nil {
return nil, err
}
respBytes, err := json.Marshal(build5E3FResponseFromHy87(status))
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}
func fallback81NCViaHy87(ctx context.Context, deps *processors.ProcessorDependencies, name, idCard string) ([]byte, error) {
status, err := queryHy87MarriageStatus(ctx, deps, name, idCard)
if err != nil {
return nil, err
}
respBytes, err := json.Marshal(build81NCResponseFromHy87(status))
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,77 @@
package ivyz
import (
"encoding/json"
"testing"
)
func TestHy87StatusTo5E3FState(t *testing.T) {
tests := []struct {
status int
want string
}{
{0, "2"},
{1, "1"},
{2, "3"},
{3, "1"},
{9, "2"},
}
for _, tt := range tests {
if got := hy87StatusTo5E3FState(tt.status); got != tt.want {
t.Fatalf("status %d => %q, want %q", tt.status, got, tt.want)
}
}
}
func TestHy87StatusTo81NCOp(t *testing.T) {
tests := []struct {
status int
wantType string
wantTypeDesc string
}{
{0, "INR", "匹配不成功"},
{1, "IA", "结婚"},
{2, "IB", "离婚"},
{3, "IA", "结婚"},
}
for _, tt := range tests {
gotType, gotDesc := hy87StatusTo81NCOp(tt.status)
if gotType != tt.wantType || gotDesc != tt.wantTypeDesc {
t.Fatalf("status %d => (%q, %q), want (%q, %q)", tt.status, gotType, gotDesc, tt.wantType, tt.wantTypeDesc)
}
}
}
func TestParseHy87MarriageStatus(t *testing.T) {
data := map[string]interface{}{
"result": map[string]interface{}{
"status": float64(1),
},
}
status, err := parseHy87MarriageStatus(data)
if err != nil {
t.Fatalf("parseHy87MarriageStatus() error = %v", err)
}
if status != 1 {
t.Fatalf("status = %d, want 1", status)
}
}
func TestBuild81NCResponseFromHy87(t *testing.T) {
resp := build81NCResponseFromHy87(2)
data, ok := resp["data"].(map[string]interface{})
if !ok {
t.Fatalf("data type = %T, want map[string]interface{}", resp["data"])
}
if data["op_type"] != "IB" || data["op_type_desc"] != "离婚" || data["op_date"] != "" {
t.Fatalf("unexpected 81nc response: %#v", resp)
}
respBytes, err := json.Marshal(resp)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}
if string(respBytes) == "" {
t.Fatal("empty response bytes")
}
}

View File

@@ -0,0 +1,44 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/haiyuapi"
)
// ProcessQCXGCP77Request QCXGCP77 全国车辆配置查验车辆详情API 处理方法海宇API
func ProcessQCXGCP77Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXGCP77Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
if deps.HaiyuapiService == nil {
return nil, errors.Join(processors.ErrSystem, errors.New("海宇API服务未初始化"))
}
reqParams := map[string]interface{}{
"vin_code": paramsDto.VinCode,
}
respBytes, err := deps.HaiyuapiService.CallAPI(ctx, "QCXGCP77", reqParams)
if err != nil {
if errors.Is(err, haiyuapi.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
if errors.Is(err, haiyuapi.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}