Compare commits
2 Commits
15d0759cfb
...
a53727757c
| Author | SHA1 | Date | |
|---|---|---|---|
| a53727757c | |||
| 90d0324a1a |
@@ -299,6 +299,12 @@ type IVYZ3A7FReq struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ9K2LReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||||
|
}
|
||||||
|
|
||||||
type IVYZ9D2EReq struct {
|
type IVYZ9D2EReq struct {
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
||||||
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
||||||
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
||||||
|
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||||
|
|
||||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||||
|
"IVYZ9K2L": &dto.IVYZ9K2LReq{},
|
||||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||||
@@ -368,6 +369,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"plate_type": "号牌类型",
|
"plate_type": "号牌类型",
|
||||||
"vin_code": "车辆识别代号VIN码",
|
"vin_code": "车辆识别代号VIN码",
|
||||||
"return_type": "返回类型",
|
"return_type": "返回类型",
|
||||||
|
"photo_data": "人脸图片",
|
||||||
}
|
}
|
||||||
|
|
||||||
if label, exists := labelMap[jsonTag]; exists {
|
if label, exists := labelMap[jsonTag]; exists {
|
||||||
@@ -409,6 +411,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
|||||||
"plate_type": "01",
|
"plate_type": "01",
|
||||||
"vin_code": "LSGBF53M8DS123456",
|
"vin_code": "LSGBF53M8DS123456",
|
||||||
"return_type": "1",
|
"return_type": "1",
|
||||||
|
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||||
}
|
}
|
||||||
|
|
||||||
if example, exists := exampleMap[jsonTag]; exists {
|
if example, exists := exampleMap[jsonTag]; exists {
|
||||||
@@ -459,6 +462,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"plate_type": "请选择号牌类型(01或02)",
|
"plate_type": "请选择号牌类型(01或02)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||||
"return_type": "请选择返回类型",
|
"return_type": "请选择返回类型",
|
||||||
|
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||||
}
|
}
|
||||||
|
|
||||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||||
@@ -511,6 +515,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||||
|
"photo_data": "人脸图片(选填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc, exists := descMap[jsonTag]; exists {
|
if desc, exists := descMap[jsonTag]; exists {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSourceContextFromCombined(result *processors.CombinedResult) (*sourceContext, error) {
|
return reportBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
if stat.Civil.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
||||||
|
}
|
||||||
|
if stat.Criminal.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...)
|
||||||
|
}
|
||||||
|
if stat.Administrative.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...)
|
||||||
|
}
|
||||||
|
if stat.Preservation.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
|
||||||
|
}
|
||||||
|
if stat.Bankrupt.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
|
||||||
|
}
|
||||||
identification.CaseAnnouncements.Records = caseRecords
|
identification.CaseAnnouncements.Records = caseRecords
|
||||||
|
|
||||||
|
if stat.Implement.Cases != nil {
|
||||||
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
|
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 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 "高风险"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ9K2LRequest IVYZ9K2L API处理方法 - 身份认证三要素(人脸图像版)
|
||||||
|
func ProcessIVYZ9K2LRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ9K2LReq
|
||||||
|
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.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密身份证号
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成时间戳(毫秒)
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||||
|
|
||||||
|
// 获取自定义编号(从 WestDexService 配置中获取 secret_id)
|
||||||
|
config := deps.WestDexService.GetConfig()
|
||||||
|
customNumber := config.SecretID
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"timeStamp": timestamp,
|
||||||
|
"customNumber": customNumber,
|
||||||
|
"xM": encryptedName,
|
||||||
|
"gMSFZHM": encryptedIDCard,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提供了人脸图片,添加到请求数据中
|
||||||
|
if paramsDto.PhotoData != "" {
|
||||||
|
reqData["photoData"] = paramsDto.PhotoData
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "idCardThreeElements", reqData)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, westdex.ErrDatasource):
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
case errors.Is(err, westdex.ErrSystem):
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
default:
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -93,6 +94,9 @@ func RegisterCustomValidators(validate *validator.Validate) {
|
|||||||
// 企业名称验证器
|
// 企业名称验证器
|
||||||
validate.RegisterValidation("enterprise_name", validateEnterpriseName)
|
validate.RegisterValidation("enterprise_name", validateEnterpriseName)
|
||||||
validate.RegisterValidation("validEnterpriseName", validateEnterpriseName)
|
validate.RegisterValidation("validEnterpriseName", validateEnterpriseName)
|
||||||
|
|
||||||
|
// Base64图片格式验证器(JPG、BMP、PNG)
|
||||||
|
validate.RegisterValidation("validBase64Image", validateBase64Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePhone 手机号验证
|
// validatePhone 手机号验证
|
||||||
@@ -938,3 +942,51 @@ func (bv *BusinessValidator) ValidateStruct(data interface{}) error {
|
|||||||
func (bv *BusinessValidator) ValidateField(field interface{}, tag string) error {
|
func (bv *BusinessValidator) ValidateField(field interface{}, tag string) error {
|
||||||
return bv.validator.Var(field, tag)
|
return bv.validator.Var(field, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateBase64Image Base64图片格式验证器(JPG、BMP、PNG)
|
||||||
|
func validateBase64Image(fl validator.FieldLevel) bool {
|
||||||
|
base64Str := fl.Field().String()
|
||||||
|
|
||||||
|
// 如果为空,由 omitempty 处理
|
||||||
|
if base64Str == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除首尾空格
|
||||||
|
base64Str = strings.TrimSpace(base64Str)
|
||||||
|
if base64Str == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码 base64 字符串
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(base64Str)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查数据长度(至少要有文件头)
|
||||||
|
if len(decoded) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件头,判断图片格式
|
||||||
|
// JPG: FF D8 FF
|
||||||
|
// PNG: 89 50 4E 47
|
||||||
|
// BMP: 42 4D (BM)
|
||||||
|
if len(decoded) >= 3 && decoded[0] == 0xFF && decoded[1] == 0xD8 && decoded[2] == 0xFF {
|
||||||
|
// JPG格式
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decoded) >= 4 && decoded[0] == 0x89 && decoded[1] == 0x50 && decoded[2] == 0x4E && decoded[3] == 0x47 {
|
||||||
|
// PNG格式
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decoded) >= 2 && decoded[0] == 0x42 && decoded[1] == 0x4D {
|
||||||
|
// BMP格式
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user