diff --git a/config.yaml b/config.yaml index 9474966..5bbc9ba 100644 --- a/config.yaml +++ b/config.yaml @@ -399,6 +399,7 @@ WechatH5: # =========================================== # 🔍 天眼查配置 # =========================================== + tianyancha: base_url: http://open.api.tianyancha.com/services api_key: e6a43dc9-786e-4a16-bb12-392b8201d8e2 diff --git a/internal/application/api/api_application_service.go b/internal/application/api/api_application_service.go index a2533e0..61e01a1 100644 --- a/internal/application/api/api_application_service.go +++ b/internal/application/api/api_application_service.go @@ -226,13 +226,19 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co // 4. 验证IP白名单(非开发环境) if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug { + whiteListIPs := make([]string, 0, len(apiUser.WhiteList)) + for _, item := range apiUser.WhiteList { + whiteListIPs = append(whiteListIPs, item.IPAddress) + } + // 添加调试日志 s.logger.Info("开始验证白名单", zap.String("userId", apiUser.UserId), zap.String("clientIP", cmd.ClientIP), zap.Bool("isDevelopment", s.config.App.IsDevelopment()), zap.Bool("isDebug", cmd.Options.IsDebug), - zap.Int("whiteListCount", len(apiUser.WhiteList))) + zap.Int("whiteListCount", len(apiUser.WhiteList)), + zap.Strings("whiteListIPs", whiteListIPs)) // 输出白名单详细信息(用于调试) for idx, item := range apiUser.WhiteList { @@ -246,10 +252,13 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP), - zap.Int("whiteListSize", len(apiUser.WhiteList))) + zap.Int("whiteListSize", len(apiUser.WhiteList)), + zap.Strings("whiteListIPs", whiteListIPs)) return nil, ErrInvalidIP } - s.logger.Info("白名单验证通过", zap.String("ip", cmd.ClientIP)) + s.logger.Info("白名单验证通过", + zap.String("ip", cmd.ClientIP), + zap.Strings("whiteListIPs", whiteListIPs)) } // 5. 验证钱包状态 diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index f627f49..1e5da63 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -73,7 +73,7 @@ type IVYZ81NCReq struct { IDCard string `json:"id_card" validate:"required,validIDCard"` } type IVYZ2MN6Req struct { - IDCard string `json:"id_card" validate:"required,validIDCard"` + IDCard string `json:"id_card" validate:"required,validIDCard"` Name string `json:"name" validate:"required,min=1,validName"` Authorized string `json:"authorized" validate:"required,oneof=0 1"` } @@ -665,6 +665,11 @@ type QYGLDJ12Req struct { EntCode string `json:"ent_code" validate:"omitempty,validUSCI"` EntRegNo string `json:"ent_reg_no" validate:"omitempty"` } +type QYGLDJ33Req struct { + EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"` + EntCode string `json:"ent_code" validate:"omitempty,validUSCI"` + EntRegNo string `json:"ent_reg_no" validate:"omitempty"` +} type YYSY6D9AReq struct { MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` IDCard string `json:"id_card" validate:"required,validIDCard"` @@ -1048,6 +1053,13 @@ type IVYZA1B3Req struct { PhotoData string `json:"photo_data" validate:"required,validBase64Image"` } +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:"+" validate:"omitempty,url"` +} + type IVYZC4R9Req struct { IDCard string `json:"id_card" validate:"required,validIDCard"` Name string `json:"name" validate:"required,min=1,validName"` diff --git a/internal/domains/api/services/api_request_service.go b/internal/domains/api/services/api_request_service.go index af10d86..a158d94 100644 --- a/internal/domains/api/services/api_request_service.go +++ b/internal/domains/api/services/api_request_service.go @@ -248,6 +248,7 @@ func registerAllProcessors(combService *comb.CombService) { "QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询 "QYGLDJ12": qygl.ProcessQYGLDJ12Request, //企业年报信息核验 "QYGL8848": qygl.ProcessQYGL8848Request, //企业税收违法核查 + "QYGLDJ33": qygl.ProcessQYGLDJ33Request, //企业年报信息核验 // YYSY系列处理器 "YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖 @@ -318,6 +319,7 @@ func registerAllProcessors(combService *comb.CombService) { "IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2 "IVYZ9K7F": ivyz.ProcessIVYZ9K7FRequest, //身份证实名认证即时版 "IVYZA1B3": ivyz.ProcessIVYZA1B3Request, //公安三要素人脸识别 + "IVYZFIC1": ivyz.ProcessIVYZFIC1Request, //人脸身份证比对(数脉) "IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版 "IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测 "IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二 diff --git a/internal/domains/api/services/form_config_service.go b/internal/domains/api/services/form_config_service.go index 8d1b6b6..158f721 100644 --- a/internal/domains/api/services/form_config_service.go +++ b/internal/domains/api/services/form_config_service.go @@ -241,6 +241,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string "YYSYK8R3": &dto.YYSYK8R3Req{}, //手机空号检测查询 "YYSYF2T7": &dto.YYSYF2T7Req{}, //手机二次放号检测查询 "IVYZA1B3": &dto.IVYZA1B3Req{}, //公安三要素人脸识别 + "IVYZFIC1": &dto.IVYZFIC1Req{}, //人脸身份证比对(数脉) "IVYZX5QZ": &dto.IVYZX5QZReq{}, //活体识别 "IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证政务版 "YYSYH6F3": &dto.YYSYH6F3Req{}, //运营商三要素简版即时版查询 @@ -274,6 +275,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string "IVYZ48SR": &dto.IVYZ48SRReq{}, //婚姻状态核验V2(双人) "IVYZ5E22": &dto.IVYZ5E22Req{}, //双人婚姻评估查询zhicha版本 "DWBG5SAM": &dto.DWBG5SAMReq{}, //天远指迷报告 + "QYGLDJ33": &dto.QYGLDJ33Req{}, //企业年报信息核验 } // 优先返回已配置的DTO @@ -635,7 +637,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st "notice_model": "请输入车辆型号", "vlphoto_data": "请输入行驶证图片", "carplate_type": "请选择车辆号牌类型(01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)", - "image_url": "请输入行驶证图片地址", + "image_url": "请输入入参图片地址", "reg_url": "请输入车辆登记证图片地址", "token": "请输入token", "vehicle_name": "请输入车型名称", diff --git a/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor copy.go b/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor copy.go new file mode 100644 index 0000000..d0f939a --- /dev/null +++ b/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor copy.go @@ -0,0 +1,64 @@ +package ivyz + +import ( + "context" + "encoding/json" + "errors" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/muzi" +) + +// ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版 +func ProcessIVYZ3P9MRequest_2(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.IVYZ3P9MReq + 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) + } + + encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + // 处理 returnType 参数,默认为 "1" + returnType := paramsDto.ReturnType + if returnType == "" { + returnType = "1" + } + paramSign := map[string]interface{}{ + "returnType": returnType, + "realName": encryptedName, + "certCode": encryptedCertCode, + } + + reqData := map[string]interface{}{ + "realName": encryptedName, + "certCode": encryptedCertCode, + "returnType": returnType, + } + + respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic", reqData, paramSign) + if err != nil { + switch { + case errors.Is(err, muzi.ErrDatasource): + return nil, errors.Join(processors.ErrDatasource, err) + case errors.Is(err, muzi.ErrSystem): + return nil, errors.Join(processors.ErrSystem, err) + default: + return nil, errors.Join(processors.ErrSystem, err) + } + } + + return respData, nil +} diff --git a/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor.go b/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor.go index 6341304..f38ad48 100644 --- a/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor.go +++ b/internal/domains/api/services/processors/ivyz/ivyz3p9m_processor.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" "errors" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/muzi" + "tyapi-server/internal/infrastructure/external/zhicha" ) // ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版 @@ -21,45 +22,147 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name) + encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) if err != nil { return nil, errors.Join(processors.ErrSystem, err) } - encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard) + encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) if err != nil { return nil, errors.Join(processors.ErrSystem, err) } - // 处理 returnType 参数,默认为 "1" - returnType := paramsDto.ReturnType - if returnType == "" { - returnType = "1" - } - paramSign := map[string]interface{}{ - "returnType": returnType, - "realName": encryptedName, - "certCode": encryptedCertCode, - } - reqData := map[string]interface{}{ - "realName": encryptedName, - "certCode": encryptedCertCode, - "returnType": returnType, + "name": encryptedName, + "idCard": encryptedIDCard, + "authorized": "1", } - - respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign) + respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData) if err != nil { - switch { - case errors.Is(err, muzi.ErrDatasource): + if errors.Is(err, zhicha.ErrDatasource) { return nil, errors.Join(processors.ErrDatasource, err) - case errors.Is(err, muzi.ErrSystem): - return nil, errors.Join(processors.ErrSystem, err) - default: - return nil, errors.Join(processors.ErrSystem, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + out, err := mapZCI1004ToIVYZ3P9M(respData, paramsDto.Name, paramsDto.IDCard) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return json.Marshal(out) +} + +type zci1004Item struct { + EndDate string `json:"endDate"` + EducationLevel string `json:"educationLevel"` + LearningForm string `json:"learningForm"` +} + +type ivyz3p9mItem struct { + GraduationDate string `json:"graduationDate"` + StudentName string `json:"studentName"` + EducationLevel string `json:"educationLevel"` + LearningForm string `json:"learningForm"` + IDNumber string `json:"idNumber"` +} + +func mapZCI1004ToIVYZ3P9M(respData interface{}, name, idCard string) ([]ivyz3p9mItem, error) { + respBytes, err := json.Marshal(respData) + if err != nil { + return nil, err + } + + var source []zci1004Item + if err := json.Unmarshal(respBytes, &source); err != nil { + var wrapped struct { + Data []zci1004Item `json:"data"` + } + if err2 := json.Unmarshal(respBytes, &wrapped); err2 != nil { + return nil, err + } + source = wrapped.Data + } + + out := make([]ivyz3p9mItem, 0, len(source)) + for _, it := range source { + out = append(out, ivyz3p9mItem{ + GraduationDate: normalizeDateDigits(it.EndDate), + StudentName: name, + EducationLevel: mapEducationLevelToCode(it.EducationLevel), + LearningForm: mapLearningFormToCode(it.LearningForm), + IDNumber: idCard, + }) + } + + return out, nil +} + +func mapEducationLevelToCode(level string) string { + v := normalizeText(level) + switch { + case strings.Contains(v, "第二学士"): + return "5" + case strings.Contains(v, "博士"): + return "4" + case strings.Contains(v, "硕士"): + return "3" + case strings.Contains(v, "本科"): + return "2" + case strings.Contains(v, "专科"), strings.Contains(v, "大专"): + return "1" + default: + return "99" + } +} + +func mapLearningFormToCode(form string) string { + v := normalizeText(form) + switch { + case strings.Contains(v, "脱产"): + return "1" + case strings.Contains(v, "普通全日制"): + return "2" + case strings.Contains(v, "全日制"): + return "3" + case strings.Contains(v, "开放教育"), strings.Contains(v, "开放大学"): + return "4" + case strings.Contains(v, "夜大学"), strings.Contains(v, "夜大"): + return "5" + case strings.Contains(v, "函授"): + return "6" + case strings.Contains(v, "网络教育"), strings.Contains(v, "网教"), strings.Contains(v, "远程教育"): + return "7" + case strings.Contains(v, "非全日制"): + return "8" + case strings.Contains(v, "业余"): + return "9" + case strings.Contains(v, "自学考试"), strings.Contains(v, "自考"): + // 自考在既有枚举中无直对应,兼容并入“业余” + return "9" + default: + return "99" + } +} + +func normalizeDateDigits(s string) string { + trimmed := strings.TrimSpace(s) + if trimmed == "" { + return "" + } + var b strings.Builder + for _, ch := range trimmed { + if ch >= '0' && ch <= '9' { + b.WriteRune(ch) } } - - return respData, nil + return b.String() +} + +func normalizeText(s string) string { + v := strings.TrimSpace(strings.ToLower(s)) + v = strings.ReplaceAll(v, " ", "") + v = strings.ReplaceAll(v, "-", "") + v = strings.ReplaceAll(v, "_", "") + return v } diff --git a/internal/domains/api/services/processors/ivyz/ivyzfic1_processor.go b/internal/domains/api/services/processors/ivyz/ivyzfic1_processor.go new file mode 100644 index 0000000..13505ec --- /dev/null +++ b/internal/domains/api/services/processors/ivyz/ivyzfic1_processor.go @@ -0,0 +1,55 @@ +package ivyz + +import ( + "context" + "encoding/json" + "errors" + "strings" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/shumai" +) + +// ProcessIVYZFIC1Request IVYZFIC1 人脸身份证比对 API 处理方法(数脉) +func ProcessIVYZFIC1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.IVYZFIC1Req + 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) + } + + if strings.TrimSpace(paramsDto.PhotoData) == "" && strings.TrimSpace(paramsDto.ImageUrl) == "" { + return nil, errors.Join(processors.ErrInvalidParam, errors.New("image和url至少传一个")) + } + + reqFormData := map[string]interface{}{ + "idcard": paramsDto.IDCard, + "name": paramsDto.Name, + "image": paramsDto.PhotoData, + "url": paramsDto.ImageUrl, + } + + apiPath := "/v4/face_id_card/compare" + + // 先尝试政务接口,再回退实时接口 + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true) + if err != nil { + 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 +} 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" } diff --git a/internal/domains/api/services/processors/qygl/qygldj33_processor.go b/internal/domains/api/services/processors/qygl/qygldj33_processor.go new file mode 100644 index 0000000..2f7cbce --- /dev/null +++ b/internal/domains/api/services/processors/qygl/qygldj33_processor.go @@ -0,0 +1,67 @@ +package qygl + +import ( + "context" + "encoding/json" + "errors" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/shujubao" +) + +// ProcessQYGLDJ33Request QYGLDJ33 企业进出口信用核查 API 处理方法(使用数据宝服务示例) +func ProcessQYGLDJ33Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.QYGLDJ33Req + 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) + } + + // 企业名称(entName)、统一社会信用代码(creditCode)、企业注册号(entRegNo) 至少传其一;多填时优先用 creditCode 传参 + hasEntName := paramsDto.EntName != "" + hasEntCode := paramsDto.EntCode != "" + hasEntRegNo := paramsDto.EntRegNo != "" + if !hasEntName && !hasEntCode && !hasEntRegNo { // 三个都未填才报错 + return nil, errors.Join(processors.ErrInvalidParam, errors.New("ent_name、ent_code、ent_reg_no 至少需要传其中一个")) + } + + // 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData) + reqParams := map[string]interface{}{ + "key": "f51ed30b0d4208bf7e6f2ba499d49d4f", + } + if hasEntCode { + reqParams["creditCode"] = paramsDto.EntCode + } else if hasEntName { + reqParams["entName"] = paramsDto.EntName + } else if hasEntRegNo { + reqParams["regCode"] = paramsDto.EntRegNo + } + // 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197 + apiPath := "/communication/personal/10254" + data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams) + if err != nil { + if errors.Is(err, shujubao.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } + if errors.Is(err, shujubao.ErrQueryEmpty) { + return nil, errors.Join(processors.ErrNotFound, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + // 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse) + parsedResp, err := RecursiveParse(data) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err := json.Marshal(parsedResp) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return respBytes, nil +} diff --git a/internal/domains/api/services/processors/yysy/yysy8c2d_processor.go b/internal/domains/api/services/processors/yysy/yysy8c2d_processor.go index 1b828a4..f9ccc01 100644 --- a/internal/domains/api/services/processors/yysy/yysy8c2d_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy8c2d_processor.go @@ -2,46 +2,44 @@ package yysy import ( "context" - "encoding/json" - "errors" - "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/xingwei" ) // ProcessYYSY8C2DRequest YYSY8C2D API处理方法 - 运营商三要素查询 func ProcessYYSY8C2DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { - var paramsDto dto.YYSY8C2DReq - if err := json.Unmarshal(params, ¶msDto); err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } + return ProcessYYSY9A1BRequest(ctx, params, deps) - if err := deps.Validator.ValidateStruct(paramsDto); err != nil { - return nil, errors.Join(processors.ErrInvalidParam, err) - } + // var paramsDto dto.YYSY8C2DReq + // if err := json.Unmarshal(params, ¶msDto); err != nil { + // return nil, errors.Join(processors.ErrSystem, err) + // } - // 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名 - reqData := map[string]interface{}{ - "name": paramsDto.Name, - "idCardNum": paramsDto.IDCard, - "phoneNumber": paramsDto.MobileNo, - } + // if err := deps.Validator.ValidateStruct(paramsDto); err != nil { + // return nil, errors.Join(processors.ErrInvalidParam, err) + // } - // 调用行为数据API,使用指定的project_id - projectID := "CDJ-1100244702166183936" - respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData) - 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 { - return nil, errors.Join(processors.ErrSystem, err) - } - } + // // 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名 + // reqData := map[string]interface{}{ + // "name": paramsDto.Name, + // "idCardNum": paramsDto.IDCard, + // "phoneNumber": paramsDto.MobileNo, + // } - return respBytes, nil + // // 调用行为数据API,使用指定的project_id + // projectID := "CDJ-1100244702166183936" + // respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData) + // 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 { + // return nil, errors.Join(processors.ErrSystem, err) + // } + // } + + // return respBytes, nil } diff --git a/internal/domains/api/services/processors/yysy/yysy9a1b_processor.go b/internal/domains/api/services/processors/yysy/yysy9a1b_processor.go index 0f879fa..4ac2fd6 100644 --- a/internal/domains/api/services/processors/yysy/yysy9a1b_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy9a1b_processor.go @@ -55,7 +55,7 @@ func ProcessYYSY9A1BRequest(ctx context.Context, params []byte, deps *processors // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded apiPath := "/v4/mobile_three/check" - + // 先尝试使用政务接口(app_id2 和 app_secret2) respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true) if err != nil { diff --git a/internal/shared/payment/wechatpay.go b/internal/shared/payment/wechatpay.go index d4d8afe..549955b 100644 --- a/internal/shared/payment/wechatpay.go +++ b/internal/shared/payment/wechatpay.go @@ -213,11 +213,10 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, logger *zap.Logger) *We panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } - // 初始化 notify.Handler - certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) + // 初始化 notify.Handler(纯支付公钥验签) notifyHandler := notify.NewNotifyHandler( mchAPIv3Key, - verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, mchPublicKeyID, *mchPublicKey)) + verifiers.NewSHA256WithRSAPubkeyVerifier(mchPublicKeyID, *mchPublicKey)) logger.Info("微信支付客户端初始化成功(微信支付公钥方式)") return &WechatPayService{