This commit is contained in:
2025-11-20 20:01:49 +08:00
parent 90d0324a1a
commit a53727757c
2 changed files with 361 additions and 108 deletions

View File

@@ -4,61 +4,145 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/shared/logger"
"go.uber.org/zap"
) )
// ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求 // ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求
func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
log := logger.GetGlobalLogger()
var req dto.COMBHZY2Req var req dto.COMBHZY2Req
if err := json.Unmarshal(params, &req); err != nil { 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) return nil, errors.Join(processors.ErrSystem, err)
} }
if err := deps.Validator.ValidateStruct(req); err != nil { 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) return nil, errors.Join(processors.ErrInvalidParam, err)
} }
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2") combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2")
if err != nil { if err != nil {
log.Error("COMBHZY2组合包服务调用失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, err 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 { if err != nil {
log.Error("COMBHZY2构建源数据上下文失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, err return nil, err
} }
report := buildTargetReport(sourceCtx) report := buildTargetReport(ctx, sourceCtx)
return json.Marshal(report)
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 { if result == nil {
log.Error("组合包响应为空", zap.String("api_code", "COMBHZY2"))
return nil, errors.New("组合包响应为空") return nil, errors.New("组合包响应为空")
} }
src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))} src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))}
successCount := 0
failedCount := 0
for _, resp := range result.Responses { for _, resp := range result.Responses {
if !resp.Success { if !resp.Success {
log.Warn("子产品调用失败,跳过",
zap.String("api_code", resp.ApiCode),
zap.String("error", resp.Error),
zap.String("parent_api_code", "COMBHZY2"),
)
failedCount++
continue 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) raw, err := json.Marshal(resp.Data)
if err != nil { 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{ src.Responses = append(src.Responses, sourceResponse{
ApiCode: resp.ApiCode, ApiCode: resp.ApiCode,
Data: raw, Data: raw,
Success: resp.Success, 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 { if len(src.Responses) == 0 {
log.Error("组合包子产品全部调用失败",
zap.Int("总数量", len(result.Responses)),
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.New("组合包子产品全部调用失败") return nil, errors.New("组合包子产品全部调用失败")
} }
return buildSourceContext(src) return buildSourceContext(ctx, src)
} }

View File

@@ -1,14 +1,18 @@
package comb package comb
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"tyapi-server/internal/shared/logger"
"go.uber.org/zap"
) )
// ======================= // =======================
@@ -468,46 +472,105 @@ type sourceContext struct {
// ======================= // =======================
// buildSourceContext 根据 source.json 解析出各子产品的结构化数据 // buildSourceContext 根据 source.json 解析出各子产品的结构化数据
func buildSourceContext(src sourceFile) (*sourceContext, error) { func buildSourceContext(ctx context.Context, src sourceFile) (*sourceContext, error) {
ctx := &sourceContext{} log := logger.GetGlobalLogger()
result := &sourceContext{}
for _, resp := range src.Responses { for _, resp := range src.Responses {
if !resp.Success { if !resp.Success {
continue 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) { switch strings.ToUpper(resp.ApiCode) {
case "DWBG8B4D": case "DWBG8B4D":
var data baseProductData var data baseProductData
if err := json.Unmarshal(resp.Data, &data); err != nil { 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": case "FLXG7E8F":
var data judicialProductData var data judicialProductData
if err := json.Unmarshal(resp.Data, &data); err != nil { 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": case "JRZQ9D4E":
var data contentsProductData var data contentsProductData
if err := json.Unmarshal(resp.Data, &data); err != nil { 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": case "JRZQ6F2A":
var data riskScreenProductData var data riskScreenProductData
if err := json.Unmarshal(resp.Data, &data); err != nil { 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 { if result.BaseData == nil {
return nil, errors.New("未获取到DWBG8B4D核心数据") 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 完整结构 // 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{ report := targetReport{
ReportSummary: buildReportSummary(ctx), ReportSummary: buildReportSummary(ctx, sourceCtx),
BasicInfo: buildBasicInfo(ctx), BasicInfo: buildBasicInfo(ctx, sourceCtx),
RiskIdentification: buildRiskIdentification(ctx), RiskIdentification: buildRiskIdentification(ctx, sourceCtx),
CreditAssessment: buildCreditAssessment(ctx), CreditAssessment: buildCreditAssessment(ctx, sourceCtx),
LeasingRiskAssessment: buildLeasingRiskAssessment(ctx), LeasingRiskAssessment: buildLeasingRiskAssessment(ctx, sourceCtx),
ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx), ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx, sourceCtx),
ReportFooter: buildReportFooter(ctx), ReportFooter: buildReportFooter(ctx, sourceCtx),
} }
return report return report
} }
// buildReportSummary 组装 reportSummary包括规则、反欺诈信息 // buildReportSummary 组装 reportSummary包括规则、反欺诈信息
func buildReportSummary(ctx *sourceContext) reportSummary { func buildReportSummary(ctx context.Context, sourceCtx *sourceContext) reportSummary {
log := logger.GetGlobalLogger()
const strategyCode = "STR0042314/贷前-经营性租赁全量策略" const strategyCode = "STR0042314/贷前-经营性租赁全量策略"
summary := reportSummary{ 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 return summary
} }
verifyResult := strings.TrimSpace(ctx.BaseData.VerifyRule) // 兼容处理:安全访问字段
verifyResult := ""
if sourceCtx.BaseData.VerifyRule != "" {
verifyResult = strings.TrimSpace(sourceCtx.BaseData.VerifyRule)
}
if verifyResult == "" { if verifyResult == "" {
verifyResult = "未命中" verifyResult = "未命中"
} }
summary.RuleValidation.Result = verifyResult summary.RuleValidation.Result = verifyResult
scoreLevel := riskLevelFromScore(ctx.BaseData.FraudScore) scoreLevel := riskLevelFromScore(sourceCtx.BaseData.FraudScore)
summary.AntiFraudScore.Level = scoreLevel summary.AntiFraudScore.Level = scoreLevel
fraudRuleLevel := strings.TrimSpace(ctx.BaseData.FraudRule)
fraudRuleLevel := ""
if sourceCtx.BaseData.FraudRule != "" {
fraudRuleLevel = strings.TrimSpace(sourceCtx.BaseData.FraudRule)
}
if fraudRuleLevel == "" || fraudRuleLevel == "-" { if fraudRuleLevel == "" || fraudRuleLevel == "-" {
fraudRuleLevel = "未命中" fraudRuleLevel = "未命中"
} }
summary.AntiFraudRule.Level = 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.Count = riskCount
summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(ctx.BaseData.RiskWarning) summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(sourceCtx.BaseData.RiskWarning)
return summary return summary
} }
// buildBasicInfo 组装 basicInfo包含基础信息与核验列表 // buildBasicInfo 组装 basicInfo包含基础信息与核验列表
func buildBasicInfo(ctx *sourceContext) reportBasicInfo { func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicInfo {
base := ctx.BaseData.BaseInfo 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() reportID := generateReportID()
verifications := make([]verificationItem, 0, 5) verifications := make([]verificationItem, 0, 5)
elementResult, elementDetails := buildElementVerificationResult(ctx) elementResult, elementDetails := buildElementVerificationResult(sourceCtx)
verifications = append(verifications, verificationItem{ verifications = append(verifications, verificationItem{
Item: "要素核查", Item: "要素核查",
Description: "使用姓名、手机号、身份证信息进行三要素核验", Description: "使用姓名、手机号、身份证信息进行三要素核验",
@@ -592,7 +694,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
Details: elementDetails, Details: elementDetails,
}) })
carrierResult, carrierDetails := buildCarrierVerificationResult(ctx) carrierResult, carrierDetails := buildCarrierVerificationResult(sourceCtx)
verifications = append(verifications, verificationItem{ verifications = append(verifications, verificationItem{
Item: "运营商检验", Item: "运营商检验",
Description: "检查手机号在运营商处的状态及在线时长", Description: "检查手机号在运营商处的状态及在线时长",
@@ -600,25 +702,27 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
Details: carrierDetails, Details: carrierDetails,
}) })
// 兼容处理安全访问JudicialData
var stat *lawsuitStat var stat *lawsuitStat
if ctx.JudicialData != nil { if sourceCtx != nil && sourceCtx.JudicialData != nil {
stat = &ctx.JudicialData.JudicialData.LawsuitStat stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat
} }
totalCaseCount := 0 totalCaseCount := 0
totalCriminal := 0 totalCriminal := 0
totalExecution := 0 totalExecution := 0
if stat != nil { if stat != nil {
totalCriminal = len(stat.Criminal.Cases) // 兼容处理:安全访问Cases数组
totalCaseCount = totalCriminal + len(stat.Civil.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases) totalCriminal = safeLen(stat.Criminal.Cases)
totalExecution = len(stat.Implement.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 totalDishonest := 0
totalRestriction := 0 totalRestriction := 0
if ctx.JudicialData != nil { if sourceCtx != nil && sourceCtx.JudicialData != nil {
totalDishonest = len(ctx.JudicialData.JudicialData.BreachCaseList) totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
totalRestriction = len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList) totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
} }
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { 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)) detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
} }
} }
addCaseDetail("刑事案件", len(stat.Criminal.Cases)) addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases))
addCaseDetail("民事案件", len(stat.Civil.Cases)) addCaseDetail("民事案件", safeLen(stat.Civil.Cases))
addCaseDetail("行政案件", len(stat.Administrative.Cases)) addCaseDetail("行政案件", safeLen(stat.Administrative.Cases))
addCaseDetail("非诉保全审查案件", len(stat.Preservation.Cases)) addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases))
addCaseDetail("强制清算与破产案件", len(stat.Bankrupt.Cases)) addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases))
} }
if totalExecution > 0 { if totalExecution > 0 {
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution)) 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{ verifications = append(verifications, verificationItem{
Item: "借贷评估", Item: "借贷评估",
Description: "综合近12个月借贷申请情况评估风险", Description: "综合近12个月借贷申请情况评估风险",
@@ -667,7 +771,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
Details: loanRiskDetails, Details: loanRiskDetails,
}) })
otherDetails := gatherOtherRiskDetails(ctx) otherDetails := gatherOtherRiskDetails(sourceCtx)
if otherDetails != "" { if otherDetails != "" {
verifications = append(verifications, verificationItem{ verifications = append(verifications, verificationItem{
Item: "其他", 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 // buildRiskIdentification 组装 riskIdentification各列表对标 target.json
func buildRiskIdentification(ctx *sourceContext) riskIdentification { func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) riskIdentification {
log := logger.GetGlobalLogger()
identification := riskIdentification{ identification := riskIdentification{
Title: "风险识别产品", Title: "风险识别产品",
CaseAnnouncements: caseAnnouncementSection{ 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 return identification
} }
stat := ctx.JudicialData.JudicialData.LawsuitStat stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
baseName := "" baseName := ""
baseID := "" baseID := ""
if ctx.BaseData != nil { if sourceCtx.BaseData != nil {
baseName = ctx.BaseData.BaseInfo.Name baseName = sourceCtx.BaseData.BaseInfo.Name
baseID = ctx.BaseData.BaseInfo.IdCard baseID = sourceCtx.BaseData.BaseInfo.IdCard
} }
// 兼容处理安全访问Cases数组
caseRecords := make([]caseAnnouncementRecord, 0) caseRecords := make([]caseAnnouncementRecord, 0)
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...) if stat.Civil.Cases != nil {
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "事案件")...) caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "事案件")...)
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...) }
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...) if stat.Criminal.Cases != nil {
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...) 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.CaseAnnouncements.Records = caseRecords
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases) if stat.Implement.Cases != nil {
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(ctx.JudicialData.JudicialData.BreachCaseList, baseName, baseID) identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(ctx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID) }
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 return identification
} }
// buildCreditAssessment 组装 creditAssessment包含客户类型与异常时间段 // buildCreditAssessment 组装 creditAssessment包含客户类型与异常时间段
func buildCreditAssessment(ctx *sourceContext) creditAssessment { func buildCreditAssessment(ctx context.Context, sourceCtx *sourceContext) creditAssessment {
log := logger.GetGlobalLogger()
assessment := creditAssessment{ assessment := creditAssessment{
Title: "信贷评估产品", Title: "信贷评估产品",
LoanIntentionByCustomerType: assessmentSection[loanIntentionRecord]{ LoanIntentionByCustomerType: assessmentSection[loanIntentionRecord]{
@@ -748,8 +885,12 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment {
}, },
} }
metrics := extractApplyLoanMetrics(ctx) // 兼容处理:安全提取指标
metrics := extractApplyLoanMetrics(sourceCtx)
if len(metrics) == 0 { if len(metrics) == 0 {
log.Debug("未获取到借贷指标数据,返回空的信贷评估",
zap.String("api_code", "COMBHZY2"),
)
return assessment return assessment
} }
@@ -854,7 +995,9 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment {
} }
// buildLeasingRiskAssessment 组装 leasingRiskAssessment 中的 3C 多头信息 // buildLeasingRiskAssessment 组装 leasingRiskAssessment 中的 3C 多头信息
func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment { func buildLeasingRiskAssessment(ctx context.Context, sourceCtx *sourceContext) leasingRiskAssessment {
log := logger.GetGlobalLogger()
assessment := leasingRiskAssessment{ assessment := leasingRiskAssessment{
Title: "租赁风险评估产品", Title: "租赁风险评估产品",
MultiLender3C: assessmentSection[multiLenderRecord]{ 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 return assessment
} }
contents := ctx.ContentsData.Contents contents := sourceCtx.ContentsData.Contents
// 消费金融机构指标优先使用近12个月的机构数其次退化到近一年内的其它统计 // 消费金融机构指标优先使用近12个月的机构数其次退化到近一年内的其它统计
consumerApplied := pickFirstInt(contents, "BH_A074", "BH_A065", "BH_A055") consumerApplied := pickFirstInt(contents, "BH_A074", "BH_A065", "BH_A055")
@@ -935,13 +1082,18 @@ func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment {
} }
// buildComprehensiveAnalysis 汇总最终的文字结论(仅输出存在风险的要点) // buildComprehensiveAnalysis 汇总最终的文字结论(仅输出存在风险的要点)
func buildComprehensiveAnalysis(ctx *sourceContext) []string { func buildComprehensiveAnalysis(ctx context.Context, sourceCtx *sourceContext) []string {
log := logger.GetGlobalLogger()
analysis := make([]string, 0, 8) 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 return analysis
} }
summary := buildReportSummary(ctx) summary := buildReportSummary(ctx, sourceCtx)
highRiskDetected := false highRiskDetected := false
mediumRiskDetected := false mediumRiskDetected := false
@@ -957,32 +1109,33 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string {
mediumRiskDetected = mediumRiskDetected || medium mediumRiskDetected = mediumRiskDetected || medium
} }
judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(ctx) judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(sourceCtx)
if judicialBullet != "" { if judicialBullet != "" {
analysis = append(analysis, judicialBullet) analysis = append(analysis, judicialBullet)
highRiskDetected = highRiskDetected || judicialHigh highRiskDetected = highRiskDetected || judicialHigh
mediumRiskDetected = mediumRiskDetected || judicialMedium mediumRiskDetected = mediumRiskDetected || judicialMedium
} }
if msg, high, medium := buildLoanAssessmentBullet(ctx); msg != "" { if msg, high, medium := buildLoanAssessmentBullet(ctx, sourceCtx); msg != "" {
analysis = append(analysis, msg) analysis = append(analysis, msg)
highRiskDetected = highRiskDetected || high highRiskDetected = highRiskDetected || high
mediumRiskDetected = mediumRiskDetected || medium mediumRiskDetected = mediumRiskDetected || medium
} }
if msg, high, medium := buildOtherRiskBullet(ctx, judicialBullet != ""); msg != "" { if msg, high, medium := buildOtherRiskBullet(sourceCtx, judicialBullet != ""); msg != "" {
analysis = append(analysis, msg) analysis = append(analysis, msg)
highRiskDetected = highRiskDetected || high highRiskDetected = highRiskDetected || high
mediumRiskDetected = mediumRiskDetected || medium mediumRiskDetected = mediumRiskDetected || medium
} }
if msg, high, medium := buildMultiLenderBullet(ctx); msg != "" { if msg, high, medium := buildMultiLenderBullet(ctx, sourceCtx); msg != "" {
analysis = append(analysis, msg) analysis = append(analysis, msg)
highRiskDetected = highRiskDetected || high highRiskDetected = highRiskDetected || high
mediumRiskDetected = mediumRiskDetected || medium mediumRiskDetected = mediumRiskDetected || medium
} }
risk := ctx.BaseData.RiskWarning // 兼容处理:安全访问RiskWarning
risk := sourceCtx.BaseData.RiskWarning
if hasHighRiskHit(risk) { if hasHighRiskHit(risk) {
highRiskDetected = true highRiskDetected = true
} else if hasMediumRiskHit(risk) { } else if hasMediumRiskHit(risk) {
@@ -999,7 +1152,7 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string {
} }
// buildReportFooter 填充数据来源与免责声明 // buildReportFooter 填充数据来源与免责声明
func buildReportFooter(ctx *sourceContext) reportFooter { func buildReportFooter(ctx context.Context, sourceCtx *sourceContext) reportFooter {
return reportFooter{ return reportFooter{
DataSource: "天远数据报告", DataSource: "天远数据报告",
GenerationTime: time.Now().Format("2006-01-02"), GenerationTime: time.Now().Format("2006-01-02"),
@@ -1109,12 +1262,13 @@ func mapRiskFlag(flag int) string {
} }
// gatherOtherRiskDetails 汇总其它风险命中项的说明文本 // gatherOtherRiskDetails 汇总其它风险命中项的说明文本
func gatherOtherRiskDetails(ctx *sourceContext) string { func gatherOtherRiskDetails(sourceCtx *sourceContext) string {
if ctx.BaseData == nil { if sourceCtx == nil || sourceCtx.BaseData == nil {
return "" return ""
} }
hits := make([]string, 0, 4) hits := make([]string, 0, 4)
risk := ctx.BaseData.RiskWarning // 兼容处理:安全访问RiskWarning
risk := sourceCtx.BaseData.RiskWarning
if risk.IsAntiFraudInfo > 0 { if risk.IsAntiFraudInfo > 0 {
hits = append(hits, "涉赌涉诈风险") hits = append(hits, "涉赌涉诈风险")
} }
@@ -1145,12 +1299,13 @@ func gatherOtherRiskDetails(ctx *sourceContext) string {
if risk.VeryFrequentRentalApplications > 0 { if risk.VeryFrequentRentalApplications > 0 {
hits = append(hits, "租赁机构申请次数极多") hits = append(hits, "租赁机构申请次数极多")
} }
if ctx.JudicialData != nil { // 兼容处理安全访问JudicialData
stat := ctx.JudicialData.JudicialData.LawsuitStat if sourceCtx != nil && sourceCtx.JudicialData != nil {
totalCase := len(stat.Civil.Cases) + len(stat.Criminal.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases) stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
totalExecution := len(stat.Implement.Cases) totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
totalDishonest := len(ctx.JudicialData.JudicialData.BreachCaseList) totalExecution := safeLen(stat.Implement.Cases)
totalRestriction := len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList) totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 { if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
hits = append(hits, "存在司法风险记录") hits = append(hits, "存在司法风险记录")
} }
@@ -1266,8 +1421,8 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
return sentence, true, false return sentence, true, false
} }
func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) { func buildLoanAssessmentBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) {
assessment := buildCreditAssessment(ctx) assessment := buildCreditAssessment(ctx, sourceCtx)
records := assessment.LoanIntentionByCustomerType.Records records := assessment.LoanIntentionByCustomerType.Records
if len(records) == 0 { if len(records) == 0 {
return "", false, false return "", false, false
@@ -1311,8 +1466,8 @@ func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) {
return sentence, high, medium return sentence, high, medium
} }
func buildMultiLenderBullet(ctx *sourceContext) (string, bool, bool) { func buildMultiLenderBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) {
assessment := buildCreditAssessment(ctx) assessment := buildCreditAssessment(ctx, sourceCtx)
records := assessment.LoanIntentionAbnormalTimes.Records records := assessment.LoanIntentionAbnormalTimes.Records
if len(records) == 0 { if len(records) == 0 {
return "", false, false return "", false, false
@@ -1596,12 +1751,13 @@ func addThousandsSeparator(value string) string {
} }
// buildLoanRiskResult 根据 riskWarning 命中情况生成借贷评估结果 // buildLoanRiskResult 根据 riskWarning 命中情况生成借贷评估结果
func buildLoanRiskResult(ctx *sourceContext) (string, string) { func buildLoanRiskResult(sourceCtx *sourceContext) (string, string) {
if ctx.BaseData == nil { if sourceCtx == nil || sourceCtx.BaseData == nil {
return "正常", "" return "正常", ""
} }
risk := ctx.BaseData.RiskWarning // 兼容处理:安全访问RiskWarning
risk := sourceCtx.BaseData.RiskWarning
hits := make([]string, 0, 3) hits := make([]string, 0, 3)
if risk.HitHighRiskBankLastTwoYears > 0 { if risk.HitHighRiskBankLastTwoYears > 0 {
@@ -1639,11 +1795,12 @@ func buildCaseDetails(parts []string) string {
} }
// buildElementVerificationResult 根据 riskWarning 的要素相关项生成结果 // buildElementVerificationResult 根据 riskWarning 的要素相关项生成结果
func buildElementVerificationResult(ctx *sourceContext) (string, string) { func buildElementVerificationResult(sourceCtx *sourceContext) (string, string) {
if ctx.BaseData == nil { if sourceCtx == nil || sourceCtx.BaseData == nil {
return "正常", "" return "正常", ""
} }
risk := ctx.BaseData.RiskWarning // 兼容处理:安全访问RiskWarning
risk := sourceCtx.BaseData.RiskWarning
hits := make([]string, 0, 2) hits := make([]string, 0, 2)
if risk.IdCardTwoElementMismatch > 0 { if risk.IdCardTwoElementMismatch > 0 {
hits = append(hits, "身份证二要素不一致") hits = append(hits, "身份证二要素不一致")
@@ -1658,11 +1815,12 @@ func buildElementVerificationResult(ctx *sourceContext) (string, string) {
} }
// buildCarrierVerificationResult 根据 riskWarning 的运营商相关项生成结果 // buildCarrierVerificationResult 根据 riskWarning 的运营商相关项生成结果
func buildCarrierVerificationResult(ctx *sourceContext) (string, string) { func buildCarrierVerificationResult(sourceCtx *sourceContext) (string, string) {
if ctx.BaseData == nil { if sourceCtx == nil || sourceCtx.BaseData == nil {
return "正常", "" return "正常", ""
} }
risk := ctx.BaseData.RiskWarning // 兼容处理:安全访问RiskWarning
risk := sourceCtx.BaseData.RiskWarning
hits := make([]string, 0, 4) hits := make([]string, 0, 4)
if risk.ShortPhoneDuration > 0 { if risk.ShortPhoneDuration > 0 {
hits = append(hits, "手机在网时长极短") hits = append(hits, "手机在网时长极短")
@@ -1742,13 +1900,23 @@ func hasMediumRiskHit(r riskWarning) bool {
r.MoreFrequentNonBankApplications > 0 r.MoreFrequentNonBankApplications > 0
} }
func extractApplyLoanMetrics(ctx *sourceContext) map[string]int { func extractApplyLoanMetrics(sourceCtx *sourceContext) map[string]int {
if ctx.RiskScreen == nil { // 兼容处理安全访问RiskScreen
if sourceCtx == nil || sourceCtx.RiskScreen == nil {
return 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") { if strings.EqualFold(variable.VariableName, "bairong_applyloan_extend") {
// 兼容处理安全访问VariableValue
if variable.VariableValue == nil {
continue
}
results := make(map[string]int, len(variable.VariableValue)) results := make(map[string]int, len(variable.VariableValue))
for key, val := range variable.VariableValue { for key, val := range variable.VariableValue {
results[key] = parseMetricValue(val) results[key] = parseMetricValue(val)
@@ -1821,3 +1989,4 @@ func riskLevelFromStrictCount(count int) string {
return "高风险" return "高风险"
} }
} }