This commit is contained in:
Mrx
2026-06-02 12:26:22 +08:00
parent aed109d589
commit 6d892643ba
9 changed files with 228 additions and 65 deletions

View File

@@ -0,0 +1,6 @@
{
"result":{
"score": 87,
"riskCode": "11001-3|21001-2"
}
}

View File

@@ -0,0 +1,16 @@
riskCode码result 参数说明
参数名 类型 说明
score Number 分数区间 0-100分数越高表明风险越大
riskCode 风险码 21001、21002、
11001、11002、11003、11004、11005、
12001、12002、
1 低风险 2 中风险 3 高风险
21001 疑似恶意借贷 恶意多方借贷、以贷养贷、蓄意制造借贷纠纷等违规行为
21002 疑似职业撸囗子 职业从事网络贷款撸口子,无还款意愿的恶意行为
11001 疑似涉黑涉赌 涉嫌参与传销、在线赌博等违法行为
11002 疑似网络投机 参与在线外汇、虚拟币、石油贵金属等风险投机行为
11003 疑似营销欺诈 在网络平台运营活动中,组团薅羊毛、套利等违规行为
11004 疑似黑中介包装 介包装伪造冒用资料、黑产中介圈团成员、老赖反催收等
11005 疑似恶意套现 信用卡恶意套现、第三方平台消费分期套现等违规行为
12001 疑似黑产设备 存在模拟器、多开、群控、代理等作弊行为的黑产设备
12002 疑似黑产账号 疑似黑产组织非法包装的手机号、身份证、支付等账号

View File

@@ -0,0 +1,14 @@
{
"sjbq_zlbz": "0",
"sjbq_ychy": "0",
"sjbq_xjzl": "0",
"sjbq_ymd": "0",
"sjbq_sfcy": "0",
"sjbq_ycxw": "0",
"sjbq_sxxw": "0",
"sjbq_zfyc": "0",
"sjbq_qtyc": "0",
"sjbq_swhjyc": "0"
}

View File

@@ -0,0 +1,14 @@
## 返回字段说明
| 检测项目 | 字段名 | 检测结果 |
|---------|--------|---------|
| 资料包装中介 | sjbq_zlbz | 0:未命中1:命中;介包装伪造冒用资料、黑产中介圈团成员、老赖反催收等 |
| 异常行业 | sjbq_ychy | 0:未命中1:命中;涉嫌参与传销、在线赌博等违法行为 |
| 虚假资料 | sjbq_xjzl | 0:未命中1:命中;疑似黑产组织非法包装的手机号、身份证、支付等账号 |
| 羊毛党 | sjbq_ymd | 0:未命中1:命中;在网络平台运营活动中,组团薅羊毛、套利等违规行为 |
| 身份信息存疑 | sjbq_sfcy | 0:未命中1:命中;存在模拟器、多开、群控、代理等作弊行为的黑产设备 |
| 严重异常行为 | sjbq_ycxw | 0:未命中1:命中;参与在线外汇、虚拟币、石油贵金属等风险投机行为 |
| 失信行为 | sjbq_sxxw | 0:未命中1:命中;职业从事网络贷款撸口子,无还款意愿的恶意行为 |
| 支付异常行为 | sjbq_zfyc | 0:未命中1:命中;信用卡恶意套现、第三方平台消费分期套现等违规行为 |
| 其他异常行为 | sjbq_qtyc | 0:未命中1:命中;恶意多方借贷、以贷养贷、蓄意制造借贷纠纷等违规行为 |
| 上网环境异常 | sjbq_swhjyc | 0:未命中1:命中;对应风险码 11001、11002、11004、12001、12002 |

View File

@@ -7,7 +7,7 @@ import (
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/xingwei"
"tyapi-server/internal/infrastructure/external/nuoer"
)
// ProcessIVYZ8I9JRequest IVYZ8I9J API处理方法 - 互联网行为推测
@@ -21,27 +21,37 @@ func ProcessIVYZ8I9JRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
reqData := map[string]interface{}{
body := map[string]string{
"name": paramsDto.Name,
"idCardNum": paramsDto.IDCard,
"phoneNumber": paramsDto.MobileNo,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.MobileNo,
}
// 调用行为数据API使用指定的project_id
projectID := "CDJ-1074522823015198720"
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
nuoerDoCheckAPIKey := "kunyu_fix_v3_tg_model"
ApiPath := "/v1/doCheck"
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
if err != nil {
if errors.Is(err, xingwei.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, xingwei.ErrDatasource) {
if errors.Is(err, nuoer.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)
}
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
// 将响应数据序列化为 JSON
respBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
// 使用 transform 函数转换响应数据
transformedBytes, err := IVYZ8I9JTransformResponseFromBytes(respBytes)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return transformedBytes, nil
}

View File

@@ -0,0 +1,108 @@
package ivyz
import (
"encoding/json"
"strings"
"github.com/tidwall/gjson"
)
// RiskCodeMapping 风险码到字段的映射
// 注意:一个风险码可能映射到多个字段
var RiskCodeMapping = map[string][]string{
"11001": {"sjbq_ychy", "sjbq_swhjyc"}, // 疑似涉黑涉诈 → 异常行业、上网环境异常
"11002": {"sjbq_ycxw", "sjbq_qtyc", "sjbq_swhjyc"}, // 疑似网络投机 → 严重异常行为、其他异常行为、上网环境异常
"11003": {"sjbq_ymd"}, // 疑似营销欺诈 → 羊毛党
"11004": {"sjbq_zlbz", "sjbq_swhjyc"}, // 疑似黑中介包装 → 资料包装中介、上网环境异常
"11005": {"sjbq_zfyc"}, // 疑似恶意套现 → 支付异常行为
"12001": {"sjbq_sfcy", "sjbq_swhjyc"}, // 疑似黑产设备 → 身份信息存疑、上网环境异常
"12002": {"sjbq_xjzl", "sjbq_swhjyc"}, // 疑似黑产账号 → 虚假资料、上网环境异常
"21001": {"sjbq_ycxw"}, // 疑似恶意借贷 → 严重异常行为
"21002": {"sjbq_sxxw"}, // 疑似职业撸口子 → 失信行为
}
// IVYZ8I9JTransformResponseFromBytes 将包含 riskCode 的格式转换为多个布尔字段格式
func IVYZ8I9JTransformResponseFromBytes(respBytes []byte) ([]byte, error) {
// 获取 result 字段中的 riskCode
riskCodeResult := gjson.GetBytes(respBytes, "result.riskCode")
if !riskCodeResult.Exists() {
// 如果没有 riskCode 字段,返回默认值
return createDefaultResponse()
}
riskCode := riskCodeResult.String()
result, err := parseRiskCodeToFields(riskCode)
if err != nil {
return createDefaultResponse()
}
return json.Marshal(result)
}
// parseRiskCodeToFields 解析 riskCode 并转换为多个字段
// riskCode 格式: "11001-3|21001-2"
// 其中 "11001" 是风险码,"3" 是风险等级1低风险、2中风险、3高风险
// 只要风险码存在,就认为命中并映射到对应字段
func parseRiskCodeToFields(riskCode string) (map[string]string, error) {
// 初始化所有字段为 "0"
result := map[string]string{
"sjbq_zlbz": "0", // 资料包装中介 - 对应 11004 疑似黑中介包装
"sjbq_ychy": "0", // 异常行业 - 对应 11001 疑似涉黑涉诈
"sjbq_xjzl": "0", // 虚假资料 - 对应 12002 疑似黑产账号
"sjbq_ymd": "0", // 羊毛党 - 对应 11003 疑似营销欺诈
"sjbq_sfcy": "0", // 身份信息存疑 - 对应 12001 疑似黑产设备
"sjbq_ycxw": "0", // 严重异常行为 - 对应 21001 疑似恶意借贷
"sjbq_sxxw": "0", // 失信行为 - 对应 21002 疑似职业撸口子
"sjbq_zfyc": "0", // 支付异常行为 - 对应 11005 疑似恶意套现
"sjbq_qtyc": "0", // 其他异常行为 - 对应 11002 疑似网络涉赌
"sjbq_swhjyc": "0", // 上网环境异常 - 对应 11001/11004/11002 涉黑涉诈/黑中介包装/网络涉赌
}
if riskCode == "" || riskCode == "-1" {
return result, nil
}
// 分割多个风险码(用 | 分隔)
riskCodes := strings.Split(riskCode, "|")
for _, rc := range riskCodes {
rc = strings.TrimSpace(rc)
if rc == "" {
continue
}
// 解析风险码和风险等级(用 - 分隔)
parts := strings.Split(rc, "-")
if len(parts) < 1 {
continue
}
code := parts[0]
// 只要风险码存在,就认为命中(不管风险等级是 1、2 还是 3
// 一个风险码可能映射到多个字段
if fieldNames, ok := RiskCodeMapping[code]; ok {
for _, fieldName := range fieldNames {
result[fieldName] = "1"
}
}
}
return result, nil
}
// createDefaultResponse 创建默认响应(所有字段为 "0"
func createDefaultResponse() ([]byte, error) {
result := map[string]string{
"sjbq_zlbz": "0",
"sjbq_ychy": "0",
"sjbq_xjzl": "0",
"sjbq_ymd": "0",
"sjbq_sfcy": "0",
"sjbq_ycxw": "0",
"sjbq_sxxw": "0",
"sjbq_zfyc": "0",
"sjbq_qtyc": "0",
"sjbq_swhjyc": "0",
}
return json.Marshal(result)
}

View File

@@ -1,20 +0,0 @@
{
"scoreywbase": "728"
}
{
"result": {
"score": "40.49"
}
}
{
"result": {
"score": "50.46"
}
}
{
"scoreywbase": "665"
}

View File

@@ -7,10 +7,10 @@ import (
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
"tyapi-server/internal/infrastructure/external/nuoer"
)
// ProcessJRZQ3c9RRequest JRZQ3c9R API处理方法 - 支付行为指数
// ProcessJRZQ3c9RRequest JRZQ3C9R API处理方法 - 支付行为指数
func ProcessJRZQ3C9RRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQ3C9RReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
@@ -21,42 +21,37 @@ func ProcessJRZQ3C9RRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
body := map[string]string{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.MobileNo,
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
nuoerDoCheckAPIKey := "paymentTagS26"
ApiPath := "/v1/doCheck"
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"phone": encryptedMobileNo,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI036", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
if errors.Is(err, nuoer.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
}
if errors.Is(err, nuoer.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
// 将响应数据序列化为 JSON
respBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
// 使用 transform 函数转换响应数据,展开 result 字段
transformedBytes, err := JRZQ3C9RTransformResponseFromBytes(respBytes)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return transformedBytes, nil
}

View File

@@ -0,0 +1,20 @@
package jrzq
import (
"github.com/tidwall/gjson"
)
// JRZQ3C9RTransformResponseFromBytes 从字节数组转换响应数据,将 result 字段展开到根级别
// respBytes: 原始响应数据的字节数组
// Returns: 转换后的数据字节数组
func JRZQ3C9RTransformResponseFromBytes(respBytes []byte) ([]byte, error) {
// 使用 gjson 获取 result 字段
resultResult := gjson.GetBytes(respBytes, "result")
if !resultResult.Exists() {
// 如果没有 result 字段,直接返回原始数据
return respBytes, nil
}
// 返回 result 字段的原始 JSON
return []byte(resultResult.Raw), nil
}