diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index 7261c72..bb3c8cc 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -1052,7 +1052,7 @@ type IVYZFIC1Req struct { IDCard string `json:"id_card" validate:"required,validIDCard"` Name string `json:"name" validate:"required,min=1,validName"` PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"` - ImageUrl string `json:"image_url" validate:"omitempty,url"` + ImageUrl string `json:"+" validate:"omitempty,url"` } type IVYZC4R9Req struct { diff --git a/internal/domains/api/services/processors/ivyz/ivyzzqt3_processor.go b/internal/domains/api/services/processors/ivyz/ivyzzqt3_processor.go index 00f50f1..2f4be9b 100644 --- a/internal/domains/api/services/processors/ivyz/ivyzzqt3_processor.go +++ b/internal/domains/api/services/processors/ivyz/ivyzzqt3_processor.go @@ -4,10 +4,15 @@ import ( "context" "encoding/json" "errors" + "fmt" + "math" + "strconv" + "strings" + "time" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/xingwei" + "tyapi-server/internal/infrastructure/external/shumai" ) // ProcessIVYZZQT3Request IVYZZQT3 人脸比对V3API处理方法 @@ -21,31 +26,187 @@ func ProcessIVYZZQT3Request(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - // 构建请求数据,使用xingwei服务的正确字段名 - reqData := map[string]interface{}{ - "name": paramsDto.Name, - "idCardNum": paramsDto.IDCard, - "image": paramsDto.PhotoData, + // 使用数脉接口进行人脸身份证比对 + reqFormData := map[string]interface{}{ + "idcard": paramsDto.IDCard, + "name": paramsDto.Name, + "image": paramsDto.PhotoData, } - // 调用行为数据API,使用指定的project_id - projectID := "CDJ-1104321430396268544" - respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData) + apiPath := "/v4/face_id_card/compare" + + // 先尝试政务接口,再回退实时接口 + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true) if err != nil { - if errors.Is(err, xingwei.ErrNotFound) { - // 查空情况,返回特定的查空错误 - return nil, errors.Join(processors.ErrNotFound, err) - } else if errors.Is(err, xingwei.ErrDatasource) { - // 数据源错误 - return nil, errors.Join(processors.ErrDatasource, err) - } else if errors.Is(err, xingwei.ErrSystem) { - // 系统错误 - return nil, errors.Join(processors.ErrSystem, err) - } else { - // 其他未知错误 + respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false) + if err != nil { + if errors.Is(err, shumai.ErrNotFound) { + return nil, errors.Join(processors.ErrNotFound, err) + } else if errors.Is(err, shumai.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + return nil, errors.Join(processors.ErrSystem, err) + } return nil, errors.Join(processors.ErrSystem, err) } } - return respBytes, nil + outBytes, err := mapShumaiFaceCompareToIVYZZQT3(respBytes) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return outBytes, nil +} + +type shumaiFaceCompareResp struct { + OrderNo string `json:"order_no"` + Score interface{} `json:"score"` + Msg string `json:"msg"` + Incorrect interface{} `json:"incorrect"` +} + +type ivyzzqt3Out struct { + HandleTime string `json:"handleTime"` + ResultData ivyzzqt3OutResultData `json:"resultData"` + OrderNo string `json:"orderNo"` +} + +type ivyzzqt3OutResultData struct { + VerificationCode string `json:"verification_code"` + VerificationResult string `json:"verification_result"` + VerificationMessage string `json:"verification_message"` + Similarity string `json:"similarity"` +} + +func mapShumaiFaceCompareToIVYZZQT3(respBytes []byte) ([]byte, error) { + var r shumaiFaceCompareResp + if err := json.Unmarshal(respBytes, &r); err != nil { + return nil, err + } + + score := parseScoreToFloat64(r.Score) + similarity := strconv.Itoa(int(math.Round(mapScoreToSimilarity(score)))) + verificationResult := mapScoreToVerificationResult(score) + verificationMessage := strings.TrimSpace(r.Msg) + if verificationMessage == "" { + verificationMessage = mapScoreToVerificationMessage(score) + } + + out := ivyzzqt3Out{ + HandleTime: time.Now().Format("2006-01-02 15:04:05"), + OrderNo: strings.TrimSpace(r.OrderNo), + ResultData: ivyzzqt3OutResultData{ + VerificationCode: mapVerificationCode(verificationResult, r.Incorrect), + VerificationResult: verificationResult, + VerificationMessage: verificationMessage, + Similarity: similarity, + }, + } + + return json.Marshal(out) +} + +func mapScoreToVerificationResult(score float64) string { + if score >= 0.45 { + return "valid" + } + // 旧结构仅支持 valid/invalid,不能确定场景按 invalid 返回 + return "invalid" +} + +func mapScoreToVerificationMessage(score float64) string { + if score < 0.40 { + return "系统判断为不同人" + } + if score < 0.45 { + return "不能确定是否为同一人" + } + return "系统判断为同一人" +} + +func mapScoreToSimilarity(score float64) float64 { + // 将 score(0~1) 分段映射到 similarity(0~1000),并对齐业务阈值: + // 0.40 -> 600,0.45 -> 700 + if score <= 0 { + return 0 + } + if score >= 1 { + return 1000 + } + if score < 0.40 { + // [0, 0.40) -> [0, 600) + return (score / 0.40) * 600 + } + if score < 0.45 { + // [0.40, 0.45) -> [600, 700) + return 600 + ((score-0.40)/0.05)*100 + } + // [0.45, 1] -> [700, 1000] + return 700 + ((score-0.45)/0.55)*300 +} + +func parseScoreToFloat64(v interface{}) float64 { + switch t := v.(type) { + case float64: + return t + case float32: + return float64(t) + case int: + return float64(t) + case int32: + return float64(t) + case int64: + return float64(t) + case json.Number: + if f, err := t.Float64(); err == nil { + return f + } + case string: + s := strings.TrimSpace(t) + if s == "" { + return 0 + } + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } + } + return 0 +} + +func valueToString(v interface{}) string { + switch t := v.(type) { + case string: + return strings.TrimSpace(t) + case json.Number: + return t.String() + case float64: + return strconv.FormatFloat(t, 'f', -1, 64) + case float32: + return strconv.FormatFloat(float64(t), 'f', -1, 64) + case int: + return strconv.Itoa(t) + case int32: + return strconv.FormatInt(int64(t), 10) + case int64: + return strconv.FormatInt(t, 10) + default: + if v == nil { + return "" + } + return strings.TrimSpace(fmt.Sprint(v)) + } +} + +func mapVerificationCode(verificationResult string, upstreamIncorrect interface{}) string { + if verificationResult == "valid" { + return "1000" + } + if verificationResult == "invalid" { + return "2006" + } + // 兜底:若后续扩展出其它结果,保持可追溯 + if s := valueToString(upstreamIncorrect); s != "" { + return s + } + return "2006" }