diff --git a/internal/domains/api/services/processors/jrzq/jrzq8b3c_processor.go b/internal/domains/api/services/processors/jrzq/jrzq8b3c_processor.go index 20b758b..5935dfc 100644 --- a/internal/domains/api/services/processors/jrzq/jrzq8b3c_processor.go +++ b/internal/domains/api/services/processors/jrzq/jrzq8b3c_processor.go @@ -4,10 +4,13 @@ import ( "context" "encoding/json" "errors" + "math" + "strconv" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/xingwei" + "tyapi-server/internal/infrastructure/external/zhicha" ) // ProcessJRZQ8B3CRequest JRZQ8B3C API处理方法 - 个人消费能力等级 @@ -21,27 +24,173 @@ func ProcessJRZQ8B3CRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - // 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名 - reqData := map[string]interface{}{ - "name": paramsDto.Name, - "idCardNum": paramsDto.IDCard, - "phoneNumber": paramsDto.MobileNo, + encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) } - // 调用行为数据API,使用指定的project_id - projectID := "CDJ-1101695392528920576" - respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData) + encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) if err != nil { - if errors.Is(err, xingwei.ErrNotFound) { - return nil, errors.Join(processors.ErrNotFound, err) - } else if errors.Is(err, xingwei.ErrDatasource) { + return nil, errors.Join(processors.ErrSystem, err) + } + + encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + reqData := map[string]interface{}{ + "name": encryptedName, + "idCard": encryptedIDCard, + "phone": encryptedMobileNo, + "authorized": "1", + } + + respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI034", reqData) + if err != nil { + if errors.Is(err, zhicha.ErrDatasource) { return nil, errors.Join(processors.ErrDatasource, err) - } else if errors.Is(err, xingwei.ErrSystem) { - return nil, errors.Join(processors.ErrSystem, err) } else { return nil, errors.Join(processors.ErrSystem, err) } } + personIncomeIndex := "-1" + if m, ok := respData.(map[string]interface{}); ok { + personIncomeIndex = mapTap010ToIncomeIndex(m["tap010"], paramsDto.IDCard) + } + + respPayload := map[string]interface{}{ + "personincome_index_2.0": personIncomeIndex, + } + + respBytes, err := json.Marshal(respPayload) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return respBytes, nil } + +type incomeTier struct { + Score int + Low float64 + High float64 // 上界闭区间;math.Inf(1) 表示正无穷 +} + +var incomeTiers = []incomeTier{ + {Score: 100, Low: 1000, High: 2000}, + {Score: 200, Low: 2000, High: 4000}, + {Score: 300, Low: 4000, High: 6000}, + {Score: 400, Low: 6000, High: 8000}, + {Score: 500, Low: 8000, High: 10000}, + {Score: 600, Low: 10000, High: 12000}, + {Score: 700, Low: 12000, High: 15000}, + {Score: 800, Low: 15000, High: 20000}, + {Score: 900, Low: 20000, High: 25000}, + {Score: 1000, Low: 25000, High: math.Inf(1)}, +} + +func mapTap010ToIncomeIndex(rawTap010 interface{}, idCard string) string { + tap010, ok := parseTap010Level(rawTap010) + if !ok { + return "-1" + } + + mappedLow, mappedHigh := expandTap010Range(tap010) + candidateScores := intersectedTierScores(mappedLow, mappedHigh) + if len(candidateScores) == 0 { + return "-1" + } + + seed := stableSeedFromIDCard(idCard) + score := candidateScores[seed%len(candidateScores)] + return strconv.Itoa(score) +} + +func parseTap010Level(v interface{}) (int, bool) { + switch value := v.(type) { + case string: + value = strings.TrimSpace(value) + if value == "" { + return 0, false + } + n, err := strconv.Atoi(value) + if err != nil { + return 0, false + } + if n < 1 || n > 4 { + return 0, false + } + return n, true + case float64: + n := int(value) + if value != float64(n) || n < 1 || n > 4 { + return 0, false + } + return n, true + default: + return 0, false + } +} + +func expandTap010Range(level int) (float64, float64) { + // tap010 原区间: + // 1:(0,500) 2:[500,1000) 3:[1000,3000) 4:[3000,+inf) + // 按比例放大 9 倍映射到收入尺度,满足示例: (0,500)->(0,4500) + switch level { + case 1: + return 0, 4500 + case 2: + return 4500, 9000 + case 3: + return 9000, 27000 + case 4: + return 27000, math.Inf(1) + default: + return 0, 0 + } +} + +func intersectedTierScores(low, high float64) []int { + scores := make([]int, 0, len(incomeTiers)) + for _, t := range incomeTiers { + if isRangeIntersect(low, high, t.Low, t.High) { + scores = append(scores, t.Score) + } + } + return scores +} + +func isRangeIntersect(aLow, aHigh, bLow, bHigh float64) bool { + return aLow <= bHigh && bLow <= aHigh +} + +func stableSeedFromIDCard(idCard string) int { + if len(idCard) == 0 { + return 0 + } + + runes := []rune(idCard) + start := len(runes) - 4 + if start < 0 { + start = 0 + } + + seed := 0 + for _, r := range runes[start:] { + switch { + case r >= '0' && r <= '9': + seed = seed*11 + int(r-'0') + case r == 'X' || r == 'x': + seed = seed*11 + 10 + default: + seed = seed*11 + int(r)%11 + } + } + + if seed < 0 { + return -seed + } + return seed +}