diff --git a/internal/domains/api/services/processors/comb/combhzy2_processor.go b/internal/domains/api/services/processors/comb/combhzy2_processor.go index 9a58267..94a33cf 100644 --- a/internal/domains/api/services/processors/comb/combhzy2_processor.go +++ b/internal/domains/api/services/processors/comb/combhzy2_processor.go @@ -4,61 +4,145 @@ import ( "context" "encoding/json" "errors" - "fmt" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/shared/logger" + + "go.uber.org/zap" ) // ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求 func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + log := logger.GetGlobalLogger() + var req dto.COMBHZY2Req if err := json.Unmarshal(params, &req); err != nil { + log.Error("COMBHZY2请求参数反序列化失败", + zap.Error(err), + zap.String("params", string(params)), + zap.String("api_code", "COMBHZY2"), + ) return nil, errors.Join(processors.ErrSystem, err) } if err := deps.Validator.ValidateStruct(req); err != nil { + log.Error("COMBHZY2请求参数验证失败", + zap.Error(err), + zap.String("api_code", "COMBHZY2"), + ) return nil, errors.Join(processors.ErrInvalidParam, err) } combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2") if err != nil { + log.Error("COMBHZY2组合包服务调用失败", + zap.Error(err), + zap.String("api_code", "COMBHZY2"), + ) return nil, err } - sourceCtx, err := buildSourceContextFromCombined(combinedResult) + if combinedResult == nil { + log.Error("COMBHZY2组合包响应为空", + zap.String("api_code", "COMBHZY2"), + ) + return nil, errors.New("组合包响应为空") + } + + log.Info("COMBHZY2组合包服务调用成功", + zap.Int("子产品数量", len(combinedResult.Responses)), + zap.String("api_code", "COMBHZY2"), + ) + + sourceCtx, err := buildSourceContextFromCombined(ctx, combinedResult) if err != nil { + log.Error("COMBHZY2构建源数据上下文失败", + zap.Error(err), + zap.String("api_code", "COMBHZY2"), + ) return nil, err } - report := buildTargetReport(sourceCtx) - return json.Marshal(report) + report := buildTargetReport(ctx, sourceCtx) + + reportBytes, err := json.Marshal(report) + if err != nil { + log.Error("COMBHZY2报告序列化失败", + zap.Error(err), + zap.String("api_code", "COMBHZY2"), + ) + return nil, errors.Join(processors.ErrSystem, err) + } + + return reportBytes, nil } -func buildSourceContextFromCombined(result *processors.CombinedResult) (*sourceContext, error) { +func buildSourceContextFromCombined(ctx context.Context, result *processors.CombinedResult) (*sourceContext, error) { + log := logger.GetGlobalLogger() + if result == nil { + log.Error("组合包响应为空", zap.String("api_code", "COMBHZY2")) return nil, errors.New("组合包响应为空") } src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))} + successCount := 0 + failedCount := 0 + for _, resp := range result.Responses { if !resp.Success { + log.Warn("子产品调用失败,跳过", + zap.String("api_code", resp.ApiCode), + zap.String("error", resp.Error), + zap.String("parent_api_code", "COMBHZY2"), + ) + failedCount++ continue } + + if resp.Data == nil { + log.Warn("子产品数据为空,跳过", + zap.String("api_code", resp.ApiCode), + zap.String("parent_api_code", "COMBHZY2"), + ) + failedCount++ + continue + } + raw, err := json.Marshal(resp.Data) if err != nil { - return nil, fmt.Errorf("序列化子产品(%s)数据失败: %w", resp.ApiCode, err) + log.Error("序列化子产品数据失败", + zap.Error(err), + zap.String("api_code", resp.ApiCode), + zap.String("parent_api_code", "COMBHZY2"), + ) + failedCount++ + continue } + src.Responses = append(src.Responses, sourceResponse{ ApiCode: resp.ApiCode, Data: raw, Success: resp.Success, }) + successCount++ } + log.Info("组合包子产品处理完成", + zap.Int("成功数量", successCount), + zap.Int("失败数量", failedCount), + zap.Int("总数量", len(result.Responses)), + zap.String("api_code", "COMBHZY2"), + ) + if len(src.Responses) == 0 { + log.Error("组合包子产品全部调用失败", + zap.Int("总数量", len(result.Responses)), + zap.String("api_code", "COMBHZY2"), + ) return nil, errors.New("组合包子产品全部调用失败") } - return buildSourceContext(src) + return buildSourceContext(ctx, src) } diff --git a/internal/domains/api/services/processors/comb/combhzy2_transform.go b/internal/domains/api/services/processors/comb/combhzy2_transform.go index 05dc161..daca5dc 100644 --- a/internal/domains/api/services/processors/comb/combhzy2_transform.go +++ b/internal/domains/api/services/processors/comb/combhzy2_transform.go @@ -1,14 +1,18 @@ package comb import ( + "context" "crypto/rand" "encoding/hex" "encoding/json" - "errors" "fmt" "strconv" "strings" "time" + + "tyapi-server/internal/shared/logger" + + "go.uber.org/zap" ) // ======================= @@ -468,46 +472,105 @@ type sourceContext struct { // ======================= // buildSourceContext 根据 source.json 解析出各子产品的结构化数据 -func buildSourceContext(src sourceFile) (*sourceContext, error) { - ctx := &sourceContext{} +func buildSourceContext(ctx context.Context, src sourceFile) (*sourceContext, error) { + log := logger.GetGlobalLogger() + result := &sourceContext{} + for _, resp := range src.Responses { if !resp.Success { continue } + // 检查数据是否为空 + if len(resp.Data) == 0 { + log.Warn("子产品数据为空,跳过解析", + zap.String("api_code", resp.ApiCode), + zap.String("parent_api_code", "COMBHZY2"), + ) + continue + } + switch strings.ToUpper(resp.ApiCode) { case "DWBG8B4D": var data baseProductData if err := json.Unmarshal(resp.Data, &data); err != nil { - return nil, fmt.Errorf("解析DWBG8B4D数据失败: %w", err) + log.Error("解析DWBG8B4D数据失败,使用兼容处理", + zap.Error(err), + zap.String("api_code", resp.ApiCode), + zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])), + ) + // 尝试部分解析,即使失败也继续 + if partialErr := json.Unmarshal(resp.Data, &data); partialErr != nil { + log.Warn("DWBG8B4D数据格式异常,将使用空结构", + zap.Error(partialErr), + zap.String("api_code", resp.ApiCode), + ) + // 使用空结构,不返回错误 + data = baseProductData{} + } } - ctx.BaseData = &data + result.BaseData = &data case "FLXG7E8F": var data judicialProductData if err := json.Unmarshal(resp.Data, &data); err != nil { - return nil, fmt.Errorf("解析FLXG7E8F数据失败: %w", err) + log.Warn("解析FLXG7E8F数据失败,使用兼容处理", + zap.Error(err), + zap.String("api_code", resp.ApiCode), + zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])), + ) + // 使用空结构,不返回错误 + data = judicialProductData{} } - ctx.JudicialData = &data + result.JudicialData = &data case "JRZQ9D4E": var data contentsProductData if err := json.Unmarshal(resp.Data, &data); err != nil { - return nil, fmt.Errorf("解析JRZQ9D4E数据失败: %w", err) + log.Warn("解析JRZQ9D4E数据失败,使用兼容处理", + zap.Error(err), + zap.String("api_code", resp.ApiCode), + zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])), + ) + // 使用空结构,不返回错误 + data = contentsProductData{} } - ctx.ContentsData = &data + result.ContentsData = &data case "JRZQ6F2A": var data riskScreenProductData if err := json.Unmarshal(resp.Data, &data); err != nil { - return nil, fmt.Errorf("解析JRZQ6F2A数据失败: %w", err) + log.Warn("解析JRZQ6F2A数据失败,使用兼容处理", + zap.Error(err), + zap.String("api_code", resp.ApiCode), + zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])), + ) + // 使用空结构,不返回错误 + data = riskScreenProductData{} } - ctx.RiskScreen = &data + result.RiskScreen = &data + default: + log.Debug("未知的子产品API代码,跳过", + zap.String("api_code", resp.ApiCode), + zap.String("parent_api_code", "COMBHZY2"), + ) } } - if ctx.BaseData == nil { - return nil, errors.New("未获取到DWBG8B4D核心数据") + if result.BaseData == nil { + log.Warn("未获取到DWBG8B4D核心数据,将使用空结构", + zap.String("api_code", "COMBHZY2"), + ) + // 使用空结构,不返回错误,让后续处理能够继续 + result.BaseData = &baseProductData{} } - return ctx, nil + return result, nil +} + +// min 辅助函数,返回两个整数中的较小值 +func min(a, b int) int { + if a < b { + return a + } + return b } // ======================= @@ -515,22 +578,35 @@ func buildSourceContext(src sourceFile) (*sourceContext, error) { // ======================= // buildTargetReport 将上下文数据映射为 target.json 完整结构 -func buildTargetReport(ctx *sourceContext) targetReport { +func buildTargetReport(ctx context.Context, sourceCtx *sourceContext) targetReport { + log := logger.GetGlobalLogger() + + // 使用recover捕获panic,确保不会导致整个请求失败 + defer func() { + if r := recover(); r != nil { + log.Error("构建目标报告时发生panic,使用默认值", + zap.Any("panic", r), + zap.String("api_code", "COMBHZY2"), + ) + } + }() + report := targetReport{ - ReportSummary: buildReportSummary(ctx), - BasicInfo: buildBasicInfo(ctx), - RiskIdentification: buildRiskIdentification(ctx), - CreditAssessment: buildCreditAssessment(ctx), - LeasingRiskAssessment: buildLeasingRiskAssessment(ctx), - ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx), - ReportFooter: buildReportFooter(ctx), + ReportSummary: buildReportSummary(ctx, sourceCtx), + BasicInfo: buildBasicInfo(ctx, sourceCtx), + RiskIdentification: buildRiskIdentification(ctx, sourceCtx), + CreditAssessment: buildCreditAssessment(ctx, sourceCtx), + LeasingRiskAssessment: buildLeasingRiskAssessment(ctx, sourceCtx), + ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx, sourceCtx), + ReportFooter: buildReportFooter(ctx, sourceCtx), } return report } // buildReportSummary 组装 reportSummary,包括规则、反欺诈信息 -func buildReportSummary(ctx *sourceContext) reportSummary { +func buildReportSummary(ctx context.Context, sourceCtx *sourceContext) reportSummary { + log := logger.GetGlobalLogger() const strategyCode = "STR0042314/贷前-经营性租赁全量策略" summary := reportSummary{ @@ -551,40 +627,66 @@ func buildReportSummary(ctx *sourceContext) reportSummary { }, } - if ctx.BaseData == nil { + if sourceCtx == nil || sourceCtx.BaseData == nil { + log.Warn("BaseData为空,使用默认summary值", + zap.String("api_code", "COMBHZY2"), + ) return summary } - verifyResult := strings.TrimSpace(ctx.BaseData.VerifyRule) + // 兼容处理:安全访问字段 + verifyResult := "" + if sourceCtx.BaseData.VerifyRule != "" { + verifyResult = strings.TrimSpace(sourceCtx.BaseData.VerifyRule) + } if verifyResult == "" { verifyResult = "未命中" } summary.RuleValidation.Result = verifyResult - scoreLevel := riskLevelFromScore(ctx.BaseData.FraudScore) + scoreLevel := riskLevelFromScore(sourceCtx.BaseData.FraudScore) summary.AntiFraudScore.Level = scoreLevel - fraudRuleLevel := strings.TrimSpace(ctx.BaseData.FraudRule) + + fraudRuleLevel := "" + if sourceCtx.BaseData.FraudRule != "" { + fraudRuleLevel = strings.TrimSpace(sourceCtx.BaseData.FraudRule) + } if fraudRuleLevel == "" || fraudRuleLevel == "-" { fraudRuleLevel = "未命中" } summary.AntiFraudRule.Level = fraudRuleLevel - riskCount := ctx.BaseData.RiskWarning.TotalRiskCounts + // 兼容处理:安全访问RiskWarning + riskCount := 0 + if sourceCtx.BaseData.RiskWarning.TotalRiskCounts > 0 { + riskCount = sourceCtx.BaseData.RiskWarning.TotalRiskCounts + } summary.AbnormalRulesHit.Count = riskCount - summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(ctx.BaseData.RiskWarning) + summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(sourceCtx.BaseData.RiskWarning) return summary } // buildBasicInfo 组装 basicInfo,包含基础信息与核验列表 -func buildBasicInfo(ctx *sourceContext) reportBasicInfo { - base := ctx.BaseData.BaseInfo +func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicInfo { + log := logger.GetGlobalLogger() + + // 兼容处理:安全访问BaseData和BaseInfo + var base baseInfo + if sourceCtx != nil && sourceCtx.BaseData != nil { + base = sourceCtx.BaseData.BaseInfo + } else { + log.Warn("BaseData或BaseInfo为空,使用默认值", + zap.String("api_code", "COMBHZY2"), + ) + base = baseInfo{} + } reportID := generateReportID() verifications := make([]verificationItem, 0, 5) - elementResult, elementDetails := buildElementVerificationResult(ctx) + elementResult, elementDetails := buildElementVerificationResult(sourceCtx) verifications = append(verifications, verificationItem{ Item: "要素核查", Description: "使用姓名、手机号、身份证信息进行三要素核验", @@ -592,7 +694,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { Details: elementDetails, }) - carrierResult, carrierDetails := buildCarrierVerificationResult(ctx) + carrierResult, carrierDetails := buildCarrierVerificationResult(sourceCtx) verifications = append(verifications, verificationItem{ Item: "运营商检验", Description: "检查手机号在运营商处的状态及在线时长", @@ -600,25 +702,27 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { Details: carrierDetails, }) + // 兼容处理:安全访问JudicialData var stat *lawsuitStat - if ctx.JudicialData != nil { - stat = &ctx.JudicialData.JudicialData.LawsuitStat + if sourceCtx != nil && sourceCtx.JudicialData != nil { + stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat } totalCaseCount := 0 totalCriminal := 0 totalExecution := 0 if stat != nil { - totalCriminal = len(stat.Criminal.Cases) - totalCaseCount = totalCriminal + len(stat.Civil.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases) - totalExecution = len(stat.Implement.Cases) + // 兼容处理:安全访问Cases数组 + totalCriminal = safeLen(stat.Criminal.Cases) + totalCaseCount = totalCriminal + safeLen(stat.Civil.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases) + totalExecution = safeLen(stat.Implement.Cases) } totalDishonest := 0 totalRestriction := 0 - if ctx.JudicialData != nil { - totalDishonest = len(ctx.JudicialData.JudicialData.BreachCaseList) - totalRestriction = len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList) + if sourceCtx != nil && sourceCtx.JudicialData != nil { + totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList) + totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList) } if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { @@ -629,11 +733,11 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count)) } } - addCaseDetail("刑事案件", len(stat.Criminal.Cases)) - addCaseDetail("民事案件", len(stat.Civil.Cases)) - addCaseDetail("行政案件", len(stat.Administrative.Cases)) - addCaseDetail("非诉保全审查案件", len(stat.Preservation.Cases)) - addCaseDetail("强制清算与破产案件", len(stat.Bankrupt.Cases)) + addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases)) + addCaseDetail("民事案件", safeLen(stat.Civil.Cases)) + addCaseDetail("行政案件", safeLen(stat.Administrative.Cases)) + addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases)) + addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases)) } if totalExecution > 0 { detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution)) @@ -659,7 +763,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { }) } - loanRiskResult, loanRiskDetails := buildLoanRiskResult(ctx) + loanRiskResult, loanRiskDetails := buildLoanRiskResult(sourceCtx) verifications = append(verifications, verificationItem{ Item: "借贷评估", Description: "综合近12个月借贷申请情况评估风险", @@ -667,7 +771,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { Details: loanRiskDetails, }) - otherDetails := gatherOtherRiskDetails(ctx) + otherDetails := gatherOtherRiskDetails(sourceCtx) if otherDetails != "" { verifications = append(verifications, verificationItem{ Item: "其他", @@ -685,8 +789,18 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo { } } +// safeLen 安全获取切片长度,避免nil指针 +func safeLen[T any](s []T) int { + if s == nil { + return 0 + } + return len(s) +} + // buildRiskIdentification 组装 riskIdentification,各列表对标 target.json -func buildRiskIdentification(ctx *sourceContext) riskIdentification { +func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) riskIdentification { + log := logger.GetGlobalLogger() + identification := riskIdentification{ Title: "风险识别产品", CaseAnnouncements: caseAnnouncementSection{ @@ -707,35 +821,58 @@ func buildRiskIdentification(ctx *sourceContext) riskIdentification { }, } - if ctx.JudicialData == nil { + // 兼容处理:安全访问JudicialData + if sourceCtx == nil || sourceCtx.JudicialData == nil { + log.Debug("JudicialData为空,返回空的风险识别数据", + zap.String("api_code", "COMBHZY2"), + ) return identification } - stat := ctx.JudicialData.JudicialData.LawsuitStat + stat := sourceCtx.JudicialData.JudicialData.LawsuitStat baseName := "" baseID := "" - if ctx.BaseData != nil { - baseName = ctx.BaseData.BaseInfo.Name - baseID = ctx.BaseData.BaseInfo.IdCard + if sourceCtx.BaseData != nil { + baseName = sourceCtx.BaseData.BaseInfo.Name + baseID = sourceCtx.BaseData.BaseInfo.IdCard } + // 兼容处理:安全访问Cases数组 caseRecords := make([]caseAnnouncementRecord, 0) - caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...) - caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...) - caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...) - caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...) - caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...) + if stat.Civil.Cases != nil { + caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...) + } + if stat.Criminal.Cases != nil { + caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...) + } + if stat.Administrative.Cases != nil { + caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...) + } + if stat.Preservation.Cases != nil { + caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...) + } + if stat.Bankrupt.Cases != nil { + caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...) + } identification.CaseAnnouncements.Records = caseRecords - identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases) - identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(ctx.JudicialData.JudicialData.BreachCaseList, baseName, baseID) - identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(ctx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID) + if stat.Implement.Cases != nil { + identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases) + } + if sourceCtx.JudicialData.JudicialData.BreachCaseList != nil { + identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(sourceCtx.JudicialData.JudicialData.BreachCaseList, baseName, baseID) + } + if sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList != nil { + identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID) + } return identification } // buildCreditAssessment 组装 creditAssessment,包含客户类型与异常时间段 -func buildCreditAssessment(ctx *sourceContext) creditAssessment { +func buildCreditAssessment(ctx context.Context, sourceCtx *sourceContext) creditAssessment { + log := logger.GetGlobalLogger() + assessment := creditAssessment{ Title: "信贷评估产品", LoanIntentionByCustomerType: assessmentSection[loanIntentionRecord]{ @@ -748,8 +885,12 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment { }, } - metrics := extractApplyLoanMetrics(ctx) + // 兼容处理:安全提取指标 + metrics := extractApplyLoanMetrics(sourceCtx) if len(metrics) == 0 { + log.Debug("未获取到借贷指标数据,返回空的信贷评估", + zap.String("api_code", "COMBHZY2"), + ) return assessment } @@ -854,7 +995,9 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment { } // buildLeasingRiskAssessment 组装 leasingRiskAssessment 中的 3C 多头信息 -func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment { +func buildLeasingRiskAssessment(ctx context.Context, sourceCtx *sourceContext) leasingRiskAssessment { + log := logger.GetGlobalLogger() + assessment := leasingRiskAssessment{ Title: "租赁风险评估产品", MultiLender3C: assessmentSection[multiLenderRecord]{ @@ -863,11 +1006,15 @@ func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment { }, } - if ctx.ContentsData == nil || len(ctx.ContentsData.Contents) == 0 { + // 兼容处理:安全访问ContentsData + if sourceCtx == nil || sourceCtx.ContentsData == nil || len(sourceCtx.ContentsData.Contents) == 0 { + log.Debug("ContentsData为空或Contents为空,返回空的租赁风险评估", + zap.String("api_code", "COMBHZY2"), + ) return assessment } - contents := ctx.ContentsData.Contents + contents := sourceCtx.ContentsData.Contents // 消费金融机构指标,优先使用近12个月的机构数,其次退化到近一年内的其它统计 consumerApplied := pickFirstInt(contents, "BH_A074", "BH_A065", "BH_A055") @@ -935,13 +1082,18 @@ func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment { } // buildComprehensiveAnalysis 汇总最终的文字结论(仅输出存在风险的要点) -func buildComprehensiveAnalysis(ctx *sourceContext) []string { +func buildComprehensiveAnalysis(ctx context.Context, sourceCtx *sourceContext) []string { + log := logger.GetGlobalLogger() + analysis := make([]string, 0, 8) - if ctx.BaseData == nil { + if sourceCtx == nil || sourceCtx.BaseData == nil { + log.Debug("BaseData为空,返回空的综合分析", + zap.String("api_code", "COMBHZY2"), + ) return analysis } - summary := buildReportSummary(ctx) + summary := buildReportSummary(ctx, sourceCtx) highRiskDetected := false mediumRiskDetected := false @@ -957,32 +1109,33 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string { mediumRiskDetected = mediumRiskDetected || medium } - judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(ctx) + judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(sourceCtx) if judicialBullet != "" { analysis = append(analysis, judicialBullet) highRiskDetected = highRiskDetected || judicialHigh mediumRiskDetected = mediumRiskDetected || judicialMedium } - if msg, high, medium := buildLoanAssessmentBullet(ctx); msg != "" { + if msg, high, medium := buildLoanAssessmentBullet(ctx, sourceCtx); msg != "" { analysis = append(analysis, msg) highRiskDetected = highRiskDetected || high mediumRiskDetected = mediumRiskDetected || medium } - if msg, high, medium := buildOtherRiskBullet(ctx, judicialBullet != ""); msg != "" { + if msg, high, medium := buildOtherRiskBullet(sourceCtx, judicialBullet != ""); msg != "" { analysis = append(analysis, msg) highRiskDetected = highRiskDetected || high mediumRiskDetected = mediumRiskDetected || medium } - if msg, high, medium := buildMultiLenderBullet(ctx); msg != "" { + if msg, high, medium := buildMultiLenderBullet(ctx, sourceCtx); msg != "" { analysis = append(analysis, msg) highRiskDetected = highRiskDetected || high mediumRiskDetected = mediumRiskDetected || medium } - risk := ctx.BaseData.RiskWarning + // 兼容处理:安全访问RiskWarning + risk := sourceCtx.BaseData.RiskWarning if hasHighRiskHit(risk) { highRiskDetected = true } else if hasMediumRiskHit(risk) { @@ -999,7 +1152,7 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string { } // buildReportFooter 填充数据来源与免责声明 -func buildReportFooter(ctx *sourceContext) reportFooter { +func buildReportFooter(ctx context.Context, sourceCtx *sourceContext) reportFooter { return reportFooter{ DataSource: "天远数据报告", GenerationTime: time.Now().Format("2006-01-02"), @@ -1109,12 +1262,13 @@ func mapRiskFlag(flag int) string { } // gatherOtherRiskDetails 汇总其它风险命中项的说明文本 -func gatherOtherRiskDetails(ctx *sourceContext) string { - if ctx.BaseData == nil { +func gatherOtherRiskDetails(sourceCtx *sourceContext) string { + if sourceCtx == nil || sourceCtx.BaseData == nil { return "" } hits := make([]string, 0, 4) - risk := ctx.BaseData.RiskWarning + // 兼容处理:安全访问RiskWarning + risk := sourceCtx.BaseData.RiskWarning if risk.IsAntiFraudInfo > 0 { hits = append(hits, "涉赌涉诈风险") } @@ -1145,12 +1299,13 @@ func gatherOtherRiskDetails(ctx *sourceContext) string { if risk.VeryFrequentRentalApplications > 0 { hits = append(hits, "租赁机构申请次数极多") } - if ctx.JudicialData != nil { - stat := ctx.JudicialData.JudicialData.LawsuitStat - totalCase := len(stat.Civil.Cases) + len(stat.Criminal.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases) - totalExecution := len(stat.Implement.Cases) - totalDishonest := len(ctx.JudicialData.JudicialData.BreachCaseList) - totalRestriction := len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList) + // 兼容处理:安全访问JudicialData + if sourceCtx != nil && sourceCtx.JudicialData != nil { + stat := sourceCtx.JudicialData.JudicialData.LawsuitStat + totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases) + totalExecution := safeLen(stat.Implement.Cases) + totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList) + totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList) if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { hits = append(hits, "存在司法风险记录") } @@ -1266,8 +1421,8 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) { return sentence, true, false } -func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) { - assessment := buildCreditAssessment(ctx) +func buildLoanAssessmentBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) { + assessment := buildCreditAssessment(ctx, sourceCtx) records := assessment.LoanIntentionByCustomerType.Records if len(records) == 0 { return "", false, false @@ -1311,8 +1466,8 @@ func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) { return sentence, high, medium } -func buildMultiLenderBullet(ctx *sourceContext) (string, bool, bool) { - assessment := buildCreditAssessment(ctx) +func buildMultiLenderBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) { + assessment := buildCreditAssessment(ctx, sourceCtx) records := assessment.LoanIntentionAbnormalTimes.Records if len(records) == 0 { return "", false, false @@ -1596,12 +1751,13 @@ func addThousandsSeparator(value string) string { } // buildLoanRiskResult 根据 riskWarning 命中情况生成借贷评估结果 -func buildLoanRiskResult(ctx *sourceContext) (string, string) { - if ctx.BaseData == nil { +func buildLoanRiskResult(sourceCtx *sourceContext) (string, string) { + if sourceCtx == nil || sourceCtx.BaseData == nil { return "正常", "" } - risk := ctx.BaseData.RiskWarning + // 兼容处理:安全访问RiskWarning + risk := sourceCtx.BaseData.RiskWarning hits := make([]string, 0, 3) if risk.HitHighRiskBankLastTwoYears > 0 { @@ -1639,11 +1795,12 @@ func buildCaseDetails(parts []string) string { } // buildElementVerificationResult 根据 riskWarning 的要素相关项生成结果 -func buildElementVerificationResult(ctx *sourceContext) (string, string) { - if ctx.BaseData == nil { +func buildElementVerificationResult(sourceCtx *sourceContext) (string, string) { + if sourceCtx == nil || sourceCtx.BaseData == nil { return "正常", "" } - risk := ctx.BaseData.RiskWarning + // 兼容处理:安全访问RiskWarning + risk := sourceCtx.BaseData.RiskWarning hits := make([]string, 0, 2) if risk.IdCardTwoElementMismatch > 0 { hits = append(hits, "身份证二要素不一致") @@ -1658,11 +1815,12 @@ func buildElementVerificationResult(ctx *sourceContext) (string, string) { } // buildCarrierVerificationResult 根据 riskWarning 的运营商相关项生成结果 -func buildCarrierVerificationResult(ctx *sourceContext) (string, string) { - if ctx.BaseData == nil { +func buildCarrierVerificationResult(sourceCtx *sourceContext) (string, string) { + if sourceCtx == nil || sourceCtx.BaseData == nil { return "正常", "" } - risk := ctx.BaseData.RiskWarning + // 兼容处理:安全访问RiskWarning + risk := sourceCtx.BaseData.RiskWarning hits := make([]string, 0, 4) if risk.ShortPhoneDuration > 0 { hits = append(hits, "手机在网时长极短") @@ -1742,13 +1900,23 @@ func hasMediumRiskHit(r riskWarning) bool { r.MoreFrequentNonBankApplications > 0 } -func extractApplyLoanMetrics(ctx *sourceContext) map[string]int { - if ctx.RiskScreen == nil { +func extractApplyLoanMetrics(sourceCtx *sourceContext) map[string]int { + // 兼容处理:安全访问RiskScreen + if sourceCtx == nil || sourceCtx.RiskScreen == nil { return nil } - for _, variable := range ctx.RiskScreen.RiskScreenV2.Variables { + // 兼容处理:安全访问Variables数组 + if sourceCtx.RiskScreen.RiskScreenV2.Variables == nil { + return nil + } + + for _, variable := range sourceCtx.RiskScreen.RiskScreenV2.Variables { if strings.EqualFold(variable.VariableName, "bairong_applyloan_extend") { + // 兼容处理:安全访问VariableValue + if variable.VariableValue == nil { + continue + } results := make(map[string]int, len(variable.VariableValue)) for key, val := range variable.VariableValue { results[key] = parseMetricValue(val) @@ -1821,3 +1989,4 @@ func riskLevelFromStrictCount(count int) string { return "高风险" } } +