diff --git a/internal/domains/api/services/processors/dwbg/1.md b/internal/domains/api/services/processors/dwbg/1.md new file mode 100644 index 0000000..43dbea7 --- /dev/null +++ b/internal/domains/api/services/processors/dwbg/1.md @@ -0,0 +1,33 @@ +| +| 产品编码 | 产品名称 | 价格 | +| :--- | :--- | :--- | +|| mobile4Verify | 运营商状态查询 | ¥0.04 / 次 | +|| mobileDuration | 手机入网时长 | ¥0.10 / 次 | +|| mobile3Verify | 运营商三要素验证 | ¥0.15 / 次 | +|| realNameAuth | 实名认证 | ¥0.02 / 次 | +|| blackListV121_3 | 债务欺诈黑名单V3 | ¥0.22 / 次 | +|| loanRiskTagV10 | 风险变量V10 | ¥0.38 / 次 | +|| loanRiskTagV21 | 信用全景V21 | ¥0.30 / 次 | +|| loanRiskTagV11 | 借贷意向查询 | ¥0.20 / 次 | +|| loanRiskTagV12 | 特殊名单 | ¥0.00 / 次 | +|| mobileRiskV709 | 投诉风险筛查V709 | ¥0.20 / 次 | +|| blackListV110 | 特殊名单V110 | ¥0.20 / 次 | +|| personalLawsuit_cv1 | 个人法院涉诉定制版 | ¥0.20 / 次 | + + + +| 产品编码 | 产品名称 | 价格 | API接口名称 | +| :--- | :--- | :--- | :--- | +|| YYSYE7V5 | 手机在网状态V即时版 | ¥0.03 / 次 | /v1/mobile_status/check | +|| YYSYP0T4 | 手机号码在网时长V即时版 | ¥0.15 / 次 | /v2/mobile_online/check | +|| YYSYK9R4 | 全网手机三要素验证 | ¥0.16 / 次 | /communication/personal/1979 | +|| IVYZN2P8 | 公安二要素政务版 | ¥0.02 / 次 | /v4/id_card/check | +|| JRZQV3HM | 债务欺诈黑名单V3 | ¥0.22 / 次 | blackListV121_3 | +|| JRZQ4B6C | 探针C | ¥0.38 / 次 | loanRiskTagV10 | +|| JRZQ5E9F | 借选指数 | ¥0.30 / 次 | loanRiskTagV21 | +|| JRZQ3C7B | 借贷意向验证 | ¥0.20 / 次 | loanRiskTagV11 | +|| JRZQV7MD | 特殊名单 | ¥0.00 / 次 | loanRiskTagV12 | +|| JRZQVT43 | 投诉风险筛查V709(投诉黑名单) | ¥0.20 / 次 | mobileRiskV709 | +|| JRZQV0MD | 行为黑名单 | ¥0.20 / 次 | blackListV110 | +|| FLXG7E8F | 个人司法数据查询 | ¥0.20 / 次 | personalLawsuit_cv1 | + diff --git a/internal/domains/api/services/processors/dwbg/dwbg9fb3.md b/internal/domains/api/services/processors/dwbg/dwbg9fb3.md new file mode 100644 index 0000000..e1f8512 --- /dev/null +++ b/internal/domains/api/services/processors/dwbg/dwbg9fb3.md @@ -0,0 +1,174 @@ +# DWBG9FB3 个人风险档案 — 响应结构说明 + +## 概述 + +`DWBG9FB3`(个人风险档案)本身是一个独立接口,接收三要素入参后,**并发**调用 12 个子产品处理器,将各子接口原始返回数据**直接拼接融合**为一个 JSON 对象返回。 + +- 不使用 `success` / `data` / `error` 等包装结构 +- 顶层 key 为简短**英文单词**字段名 +- 每个大字段的 value 即为对应子处理器的原始返回对象 +- 子产品调用失败时,该字段值为 `null`,不影响其他字段 + +## 字段映射表 + +| 产品编码 | 产品名称 | 英文字段名 | 子处理器 | +| :--- | :--- | :--- | :--- | +| YYSYE7V5 | 手机在网状态V即时版 | `presence` | `ProcessYYSYE7V5Request` | +| YYSYP0T4 | 手机号码在网时长V即时版 | `duration` | `ProcessYYSYP0T4Request` | +| YYSYK9R4 | 全网手机三要素验证 | `triple` | `ProcessYYSYK9R4Request` | +| IVYZN2P8 | 公安二要素政务版 | `identity` | `ProcessIVYZN2P8Request` | +| JRZQV3HM | 债务欺诈黑名单V3 | `fraud` | `ProcessJRZQV3HMRequest` | +| JRZQ4B6C | 探针C | `probe` | `ProcessJRZQ4B6CRequest` | +| JRZQ5E9F | 借选指数 | `rating` | `ProcessJRZQ5E9FRequest` | +| JRZQ3C7B | 借贷意向验证 | `intent` | `ProcessJRZQ3C7BRequest` | +| JRZQV7MD | 特殊名单 | `special` | `ProcessJRZQV7MDRequest` | +| JRZQVT43 | 投诉风险筛查V709 | `complaint` | `ProcessJRZQVT43Request` | +| JRZQV0MD | 行为黑名单 | `behavior` | `ProcessJRZQV0MDRequest` | +| FLXG7E8F | 个人司法数据查询 | `judicial` | `ProcessFLXG7E8FRequest` | + +## 请求参数 + +```json +{ + "id_card": "110101199001011234", + "name": "张三", + "mobile_no": "13800138000" +} +``` + +## 响应结构 + +顶层为一个扁平对象,12 个字段并列,每个字段 value 为子处理器原始返回的 JSON 对象: + +```json +{ + "presence": { }, + "duration": { }, + "triple": { }, + "identity": { }, + "fraud": { }, + "probe": { }, + "rating": { }, + "intent": { }, + "special": { }, + "complaint": { }, + "behavior": { }, + "judicial": { } +} +``` + +## 响应示例 + +```json +{ + "presence": { + "status": "1", + "operator": "1" + }, + "duration": { + "result": "3", + "desc": "在网时长12-24个月" + }, + "triple": { + "state": "1", + "operator": "1" + }, + "identity": { + "result": 0, + "desc": "一致", + "sex": "男", + "birthday": "1990-01-01", + "address": "北京市东城区" + }, + "fraud": { + "hit": 0 + }, + "probe": { + "score": 650, + "risk_level": "B" + }, + "rating": { + "score": 720, + "level": "A" + }, + "intent": { + "apply_loan": { + "d7": { "id": 0, "cell": 1 }, + "m1": { "id": 1, "cell": 2 } + } + }, + "special": { + "hit": 0 + }, + "complaint": { + "hit": 0 + }, + "behavior": { + "hit": 0 + }, + "judicial": { + "judicial_data": { + "lawsuitStat": { + "count_total": 0, + "count_jie_total": 0, + "count_wei_total": 0 + }, + "breachCaseList": [], + "consumptionRestrictionList": [] + } + } +} +``` + +## 部分失败示例 + +某个子产品查无记录或异常时,对应字段为 `null`,其余字段正常返回: + +```json +{ + "presence": { + "status": "1", + "operator": "1" + }, + "judicial": null +} +``` + +## 调用流程 + +```mermaid +flowchart LR + A[DWBG9FB3 请求] --> B[参数校验] + B --> C[并发调用 12 个子处理器] + C --> D1[presence] + C --> D2[duration] + C --> D3[triple] + C --> D4[identity] + C --> D5[fraud] + C --> D6[probe] + C --> D7[rating] + C --> D8[intent] + C --> D9[special] + C --> D10[complaint] + C --> D11[behavior] + C --> D12[judicial] + D1 --> E[拼接融合为一个对象] + D2 --> E + D3 --> E + D4 --> E + D5 --> E + D6 --> E + D7 --> E + D8 --> E + D9 --> E + D10 --> E + D11 --> E + D12 --> E + E --> F[返回 JSON] +``` + +## 实现说明 + +- 复用 `dwbg8b4d` 中的 `callProcessor`,通过 `CombService.GetProcessor` 按产品编码路由。 +- 12 个 goroutine 并发执行,结果写入 `map[英文字段名]子处理器原始数据`。 +- 子产品数据保持各处理器原有返回结构,不做二次转换。 diff --git a/internal/domains/api/services/processors/dwbg/dwbg9fb3_processor.go b/internal/domains/api/services/processors/dwbg/dwbg9fb3_processor.go index b726255..85be30d 100644 --- a/internal/domains/api/services/processors/dwbg/dwbg9fb3_processor.go +++ b/internal/domains/api/services/processors/dwbg/dwbg9fb3_processor.go @@ -4,13 +4,34 @@ import ( "context" "encoding/json" "errors" + "fmt" + "sync" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/nuoer" + "tyapi-server/internal/shared/logger" + + "go.uber.org/zap" ) -// ProcessDWBG9FB3Request DWBG9FB3 API处理方法 - 个人风险档案 +// dwbg9FB3FieldNames 子产品编码 → 响应字段名(单词缩写) +var dwbg9FB3FieldNames = map[string]string{ + "YYSYE7V5": "presence", // 在网状态 + "YYSYP0T4": "duration", // 在网时长 + "YYSYK9R4": "triple", // 三要素验证 + "IVYZN2P8": "identity", // 二要素认证 + "JRZQV3HM": "fraud", // 债务欺诈黑名单 + "JRZQ4B6C": "probe", // 探针C + "JRZQ5E9F": "rating", // 借选指数 + "JRZQ3C7B": "intent", // 借贷意向 + "JRZQV7MD": "special", // 特殊名单 + "JRZQVT43": "complaint", // 投诉风险 + "JRZQV0MD": "behavior", // 行为黑名单 + "FLXG7E8F": "judicial", // 司法数据 +} + +// ProcessDWBG9FB3Request DWBG9FB3 API处理方法 - 个人风险档案 +// 并发调用子产品处理器,将各子接口返回数据以大数据英文字段名拼接融合为一个对象 func ProcessDWBG9FB3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { var paramsDto dto.DWBG9FB3Req if err := json.Unmarshal(params, ¶msDto); err != nil { @@ -21,30 +42,193 @@ func ProcessDWBG9FB3Request(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - body := map[string]string{ - "idCard": paramsDto.IDCard, - "name": paramsDto.Name, - "mobile": paramsDto.MobileNo, - } + log := logger.GetGlobalLogger() + log.Info("开始处理个人风险档案请求", + zap.String("name", paramsDto.Name), + zap.String("id_card", maskIDCard(paramsDto.IDCard)), + zap.String("mobile_no", maskMobile(paramsDto.MobileNo)), + ) - nuoerDoCheckAPIKey := "gamaReportPageP01" - ApiPath := "/v1/doCheck" + result := collectDWBG9FB3SubProducts(ctx, paramsDto, deps, log) - resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body) - if err != nil { - if errors.Is(err, nuoer.ErrNotFound) { - return nil, errors.Join(processors.ErrNotFound, err) - } - if errors.Is(err, nuoer.ErrDatasource) { - return nil, errors.Join(processors.ErrDatasource, err) - } - return nil, errors.Join(processors.ErrSystem, err) - } - - respBytes, err := json.Marshal(resp.Data) + respBytes, err := json.Marshal(result) if err != nil { return nil, errors.Join(processors.ErrSystem, err) } + log.Info("个人风险档案处理完成", zap.Int("field_count", len(result))) return respBytes, nil } + +type dwbg9fb3APICall struct { + apiCode string + fieldName string + params map[string]interface{} +} + +func collectDWBG9FB3SubProducts( + ctx context.Context, + params dto.DWBG9FB3Req, + deps *processors.ProcessorDependencies, + log *zap.Logger, +) map[string]interface{} { + apiCalls := []dwbg9fb3APICall{ + {apiCode: "YYSYE7V5", fieldName: dwbg9FB3FieldNames["YYSYE7V5"], params: map[string]interface{}{"mobile_no": params.MobileNo}}, + {apiCode: "YYSYP0T4", fieldName: dwbg9FB3FieldNames["YYSYP0T4"], params: map[string]interface{}{"mobile_no": params.MobileNo}}, + { + apiCode: "YYSYK9R4", + fieldName: dwbg9FB3FieldNames["YYSYK9R4"], + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + }, + }, + { + apiCode: "IVYZN2P8", + fieldName: dwbg9FB3FieldNames["IVYZN2P8"], + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + }, + }, + { + apiCode: "JRZQV3HM", + fieldName: dwbg9FB3FieldNames["JRZQV3HM"], + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "mobile_no": params.MobileNo, + }, + }, + { + apiCode: "JRZQ4B6C", + fieldName: dwbg9FB3FieldNames["JRZQ4B6C"], + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + { + apiCode: "JRZQ5E9F", + fieldName: dwbg9FB3FieldNames["JRZQ5E9F"], + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + { + apiCode: "JRZQ3C7B", + fieldName: dwbg9FB3FieldNames["JRZQ3C7B"], + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + { + apiCode: "JRZQV7MD", + fieldName: dwbg9FB3FieldNames["JRZQV7MD"], + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "mobile_no": params.MobileNo, + }, + }, + { + apiCode: "JRZQVT43", + fieldName: dwbg9FB3FieldNames["JRZQVT43"], + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "mobile_no": params.MobileNo, + }, + }, + { + apiCode: "JRZQV0MD", + fieldName: dwbg9FB3FieldNames["JRZQV0MD"], + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "mobile_no": params.MobileNo, + }, + }, + { + apiCode: "FLXG7E8F", + fieldName: dwbg9FB3FieldNames["FLXG7E8F"], + params: map[string]interface{}{ + "name": params.Name, + "id_card": params.IDCard, + "mobile_no": params.MobileNo, + }, + }, + } + + type callResult struct { + fieldName string + data interface{} + err error + } + + results := make(chan callResult, len(apiCalls)) + var wg sync.WaitGroup + + for _, apiCall := range apiCalls { + wg.Add(1) + go func(ac dwbg9fb3APICall) { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + log.Error("调用子产品处理器时发生panic", + zap.String("api_code", ac.apiCode), + zap.String("field_name", ac.fieldName), + zap.Any("panic", r), + ) + results <- callResult{ac.fieldName, nil, fmt.Errorf("处理器panic: %v", r)} + } + }() + + paramsBytes, err := json.Marshal(ac.params) + if err != nil { + results <- callResult{ac.fieldName, nil, err} + return + } + + data, err := callProcessor(ctx, ac.apiCode, paramsBytes, deps) + results <- callResult{ac.fieldName, data, err} + }(apiCall) + } + + go func() { + wg.Wait() + close(results) + }() + + output := make(map[string]interface{}, len(apiCalls)) + successCount := 0 + for result := range results { + if result.err != nil { + log.Warn("子产品调用失败,该字段置空", + zap.String("field_name", result.fieldName), + zap.Error(result.err), + ) + output[result.fieldName] = nil + continue + } + output[result.fieldName] = result.data + successCount++ + } + + log.Info("子产品调用完成", + zap.Int("total", len(apiCalls)), + zap.Int("success", successCount), + zap.Int("failed", len(apiCalls)-successCount), + ) + + return output +}