Compare commits
35 Commits
6dd392f673
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d7a5589873 | |||
| b0ec75d1af | |||
| 57d18be972 | |||
| 3d8775b6dc | |||
| f40950f890 | |||
| ba21a8f965 | |||
| 7d2716da7a | |||
| 9a7bda9527 | |||
| abdae033f0 | |||
| 96abacd392 | |||
| 4e6c93413e | |||
| 9c8dbd458f | |||
| 9e9cee02f5 | |||
| 360bd579ce | |||
| db889ccba0 | |||
| 25a4961328 | |||
| 578e68a76b | |||
| 019e47896d | |||
| c0898e6829 | |||
| 4ee6e891cd | |||
| 44b5f6b145 | |||
| 677b7362cf | |||
| 02dbc02fe8 | |||
| 374143995e | |||
| 7a957a6b87 | |||
| c885d562ee | |||
| 9f36cd8b63 | |||
| 4122f874fc | |||
| 9a32387b21 | |||
| 7bf9150cfc | |||
| fecd5a38fd | |||
| 2636d9dff6 | |||
| 927b08b871 | |||
| dedd4a60a4 | |||
| a54a19e439 |
@@ -276,6 +276,8 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
|
||||
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
|
||||
@@ -108,6 +108,8 @@ wallet:
|
||||
default_credit_limit: 0.01
|
||||
min_amount: "0.01" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 0.01 # 充值1000元
|
||||
|
||||
@@ -109,7 +109,9 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
# 支付宝充值赠送配置
|
||||
recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置(recharge_bonus_enabled 为 true 时生效)
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
bonus_amount: 50.00 # 赠送50元
|
||||
|
||||
@@ -609,8 +609,6 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
||||
// 转换为响应DTO
|
||||
var items []dto.ApiCallRecordResponse
|
||||
for _, call := range calls {
|
||||
// 出于安全考虑,不再在数据库中存储或解密真实请求参数
|
||||
// 这里只保留数据库中的原始占位值(通常为空字符串)
|
||||
requestParamsStr := call.RequestParams
|
||||
|
||||
item := dto.ApiCallRecordResponse{
|
||||
|
||||
@@ -108,8 +108,10 @@ type AlipayRechargeOrderResponse struct {
|
||||
|
||||
// RechargeConfigResponse 充值配置响应
|
||||
type RechargeConfigResponse struct {
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
|
||||
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
|
||||
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1337,17 +1337,26 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
|
||||
// GetRechargeConfig 获取充值配置
|
||||
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0)
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
bonus = make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
tip := s.config.Wallet.ApiStoreRechargeTip
|
||||
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
|
||||
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
}
|
||||
return &responses.RechargeConfigResponse{
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
AlipayRechargeBonus: bonus,
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
|
||||
ApiStoreRechargeTip: tip,
|
||||
AlipayRechargeBonus: bonus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1651,9 +1660,9 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制)
|
||||
bonusAmount := decimal.Zero
|
||||
if len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
||||
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
||||
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
||||
|
||||
@@ -339,19 +339,19 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
@@ -411,19 +411,19 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
|
||||
@@ -331,11 +331,13 @@ type SignConfig struct {
|
||||
|
||||
// WalletConfig 钱包配置
|
||||
type WalletConfig struct {
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示
|
||||
ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务)
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
}
|
||||
|
||||
// BalanceAlertConfig 余额预警配置
|
||||
|
||||
@@ -113,6 +113,19 @@ type QYGL2ACDReq struct {
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
type QYGLUY3SReq struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntRegno string `json:"ent_reg_no" validate:"omitempty"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
|
||||
type JRZQOCRYReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type JRZQOCREReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
|
||||
type YYSYK9R4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
@@ -120,6 +133,10 @@ type YYSYK9R4Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type YYSY35TAReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type QCXG9F5CReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
@@ -403,7 +420,7 @@ type COMENT01Req struct {
|
||||
}
|
||||
|
||||
type JRZQ09J8Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
@@ -584,10 +601,18 @@ type YYSY6F2BReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZOCR1Req struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
type YYSY8B1CReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type QYGLJ0Q1Req struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
type YYSY6D9AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
|
||||
@@ -80,7 +80,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
AccessId: accessId,
|
||||
TransactionId: GenerateTransactionID(),
|
||||
ClientIp: clientIp,
|
||||
RequestParams: "",
|
||||
RequestParams: requestParams,
|
||||
Status: ApiCallStatusPending,
|
||||
StartAt: time.Now(),
|
||||
}, nil
|
||||
|
||||
@@ -145,10 +145,12 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
|
||||
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
|
||||
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
|
||||
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
||||
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
||||
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
|
||||
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
|
||||
|
||||
// QYGL系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
@@ -178,6 +180,9 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
||||
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
||||
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
|
||||
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
|
||||
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
|
||||
"YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
@@ -248,6 +253,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
|
||||
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
|
||||
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
|
||||
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
|
||||
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
|
||||
@@ -255,6 +255,13 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
|
||||
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验(ETC)10093 月更
|
||||
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询(2)
|
||||
"IVYZOCR1": &dto.IVYZOCR1Req{}, //身份证OCR
|
||||
"IVYZOCR2": &dto.IVYZOCR1Req{}, //身份证OCR2数卖
|
||||
"QYGLJ0Q1": &dto.QYGLJ0Q1Req{}, //企业股权结构全景查询
|
||||
"QYGLUY3S": &dto.QYGLUY3SReq{}, //企业全量信息核验V2 可用
|
||||
"JRZQOCRE": &dto.JRZQOCREReq{}, //银行卡OCR数卖
|
||||
"JRZQOCRY": &dto.JRZQOCRYReq{}, //银行卡OCR数据宝
|
||||
"YYSY35TA": &dto.YYSY35TAReq{}, //运营商归属地数卖
|
||||
}
|
||||
|
||||
// 优先返回已配置的DTO
|
||||
@@ -438,6 +445,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"ent_name": "企业名称",
|
||||
"legal_person": "法人姓名",
|
||||
"ent_code": "企业代码",
|
||||
"ent_reg_no": "企业注册号",
|
||||
"auth_date": "授权日期",
|
||||
"date_range": "日期范围",
|
||||
"time_range": "时间范围",
|
||||
@@ -459,7 +467,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"plate_type": "号牌类型",
|
||||
"vin_code": "车辆识别代号VIN码",
|
||||
"return_type": "返回类型",
|
||||
"photo_data": "人脸图片",
|
||||
"photo_data": "入参图片base64编码",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
@@ -471,7 +479,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片",
|
||||
"carplate_type": "车辆号牌类型",
|
||||
"image_url": "行驶证图片地址",
|
||||
"image_url": "入参图片地址",
|
||||
"reg_url": "车辆登记证图片地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称",
|
||||
@@ -500,6 +508,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
"ent_name": "示例企业有限公司",
|
||||
"legal_person": "王五",
|
||||
"ent_code": "91110000123456789X",
|
||||
"ent_reg_no": "110000000123456",
|
||||
"auth_date": "20240101-20241231",
|
||||
"date_range": "20240101-20241231",
|
||||
"time_range": "09:00-18:00",
|
||||
@@ -571,6 +580,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"date_range": "请输入日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
||||
@@ -592,7 +602,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"plate_type": "请选择号牌类型(01或02)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||
"return_type": "请选择返回类型",
|
||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||
"photo_data": "请输入base64编码的入参图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
@@ -644,6 +654,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
||||
"date_range": "请输入日期范围,格式:YYYYMMDD-YYYYMMDD",
|
||||
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
||||
@@ -665,7 +676,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||
"photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"photo_data": "入参图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||
"type": "查询类型:per-人员,ent-企业 ",
|
||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||
@@ -677,7 +688,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||
"image_url": "行驶证图片地址(必填):请提供行驶证的图片URL地址",
|
||||
"image_url": "入参图片url地址",
|
||||
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||
|
||||
@@ -109,7 +109,7 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
{
|
||||
apiCode: "FLXG8B4D",
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
@@ -117,9 +117,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
{
|
||||
apiCode: "JRZQ8A2D",
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
@@ -127,9 +127,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
{
|
||||
apiCode: "JRZQ1D09",
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
@@ -155,9 +155,9 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
{
|
||||
apiCode: "JRZQ5E9F",
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
@@ -165,8 +165,8 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
{
|
||||
apiCode: "FLXGDEA9",
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
@@ -211,7 +211,7 @@ func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processor
|
||||
}()
|
||||
|
||||
paramsBytes, err := json.Marshal(ac.params)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
log.Warn("序列化API参数失败",
|
||||
zap.String("api_code", ac.apiCode),
|
||||
zap.Error(err),
|
||||
@@ -266,7 +266,7 @@ func callProcessor(ctx context.Context, apiCode string, params []byte, deps *pro
|
||||
return nil, fmt.Errorf("未找到处理器: %s", apiCode)
|
||||
}
|
||||
respBytes, err := processor(ctx, params, deps)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data interface{}
|
||||
@@ -387,7 +387,7 @@ func buildBaseInfo(apiData map[string]interface{}, params dto.DWBG8B4DReq, log *
|
||||
// 从运营商三要素获取户籍所在地(address字段)
|
||||
if address, ok := yysyMap["address"].(string); ok && address != "" {
|
||||
baseInfo["location"] = address
|
||||
} else {
|
||||
} else {
|
||||
// 如果address不存在,尝试从身份证号获取
|
||||
if location := getLocationFromIDCard(params.IDCard); location != "" {
|
||||
baseInfo["location"] = location
|
||||
@@ -496,7 +496,7 @@ func buildStandLiveInfo(apiData map[string]interface{}, log *zap.Logger) map[str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if status != "" {
|
||||
// "一致" -> "0", "不一致" -> "1"
|
||||
if status == "一致" {
|
||||
@@ -629,7 +629,7 @@ func buildVerifyRule(apiData map[string]interface{}, log *zap.Logger) string {
|
||||
// buildFraudRule 构建反欺诈规则(综合考虑fraudScore、特殊名单和借选指数风险)
|
||||
func buildFraudRule(apiData map[string]interface{}, log *zap.Logger) string {
|
||||
fraudScore := getFraudScore(apiData)
|
||||
|
||||
|
||||
// 如果fraudScore无效,检查特殊名单和借选指数
|
||||
if fraudScore == -1 {
|
||||
// 检查特殊名单
|
||||
@@ -654,7 +654,7 @@ func buildFraudRule(apiData map[string]interface{}, log *zap.Logger) string {
|
||||
}
|
||||
return "低风险"
|
||||
}
|
||||
|
||||
|
||||
// 根据fraudScore判断风险等级
|
||||
if fraudScore >= 80 {
|
||||
return "高风险"
|
||||
@@ -1039,13 +1039,13 @@ func buildRiskWarning(apiData map[string]interface{}, log *zap.Logger) map[strin
|
||||
if variableValue, ok := variable["variableValue"].(map[string]interface{}); ok {
|
||||
// 检查近期申请频率(近7天、近15天、近1个月)
|
||||
checkRecentApplicationFrequency(variableValue, &riskWarning)
|
||||
|
||||
|
||||
// 检查银行/非银申请次数
|
||||
checkBankApplicationFrequency(variableValue, &riskWarning)
|
||||
|
||||
|
||||
// 检查偿债压力(从借选指数评估获取)
|
||||
checkDebtPressure(apiData, &riskWarning)
|
||||
|
||||
|
||||
// 计算借贷评估风险计数
|
||||
calculateLoanRiskCounts(variableValue, &riskWarning)
|
||||
}
|
||||
@@ -1152,21 +1152,21 @@ func buildRiskWarning(apiData map[string]interface{}, log *zap.Logger) map[strin
|
||||
// 计算总风险点数量(排除Counts字段和level字段)
|
||||
totalRiskCounts := 0
|
||||
excludeFields := map[string]bool{
|
||||
"totalRiskCounts": true,
|
||||
"totalRiskCounts": true,
|
||||
"level": true,
|
||||
"sfhyfxRiskCounts": true,
|
||||
"sfhyfxRiskHighCounts": true,
|
||||
"sfhyfxRiskMiddleCounts": true,
|
||||
"gazdyrhyRiskCounts": true,
|
||||
"sfhyfxRiskCounts": true,
|
||||
"sfhyfxRiskHighCounts": true,
|
||||
"sfhyfxRiskMiddleCounts": true,
|
||||
"gazdyrhyRiskCounts": true,
|
||||
"gazdyrhyRiskHighCounts": true,
|
||||
"gazdyrhyRiskMiddleCounts": true,
|
||||
"yqfxRiskCounts": true,
|
||||
"yqfxRiskHighCounts": true,
|
||||
"yqfxRiskMiddleCounts": true,
|
||||
"zlfxpgRiskCounts": true,
|
||||
"zlfxpgRiskHighCounts": true,
|
||||
"zlfxpgRiskMiddleCounts": true,
|
||||
"jdpgRiskCounts": true,
|
||||
"yqfxRiskCounts": true,
|
||||
"yqfxRiskHighCounts": true,
|
||||
"yqfxRiskMiddleCounts": true,
|
||||
"zlfxpgRiskCounts": true,
|
||||
"zlfxpgRiskHighCounts": true,
|
||||
"zlfxpgRiskMiddleCounts": true,
|
||||
"jdpgRiskCounts": true,
|
||||
"jdpgRiskHighCounts": true,
|
||||
"jdpgRiskMiddleCounts": true,
|
||||
"idCardRiskCounts": true,
|
||||
@@ -1277,7 +1277,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if status != "" {
|
||||
if status == "一致" {
|
||||
detail["sfzeysFlag"] = 2 // 低风险
|
||||
@@ -1313,7 +1313,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if status != "" {
|
||||
personCheckDetails["result"] = status
|
||||
}
|
||||
@@ -1365,7 +1365,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
// 如果没有data字段,直接使用flxgMap(新格式)
|
||||
data = flxgMap
|
||||
}
|
||||
|
||||
|
||||
if moneyLaundering, ok := data["moneyLaundering"].(string); ok {
|
||||
antiFraudInfo["moneyLaundering"] = moneyLaundering
|
||||
}
|
||||
@@ -1448,7 +1448,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
if channel, ok := yysy4b21Map["channel"].(string); ok {
|
||||
phoneVailRisks["phoneCompany"] = convertChannelName(channel)
|
||||
}
|
||||
|
||||
|
||||
// 获取在网时长(status=0时也要提取)
|
||||
if yysy8b1cData != nil {
|
||||
if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok {
|
||||
@@ -1471,11 +1471,11 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
// 身份证号手机号归属地(belongRiskFlag 和 belongRisks)
|
||||
belongRisks := make(map[string]interface{})
|
||||
belongRisks["num"] = "1"
|
||||
|
||||
|
||||
// 重新获取数据
|
||||
yysy9e4aDataForBelong := getMapValue(apiData, "YYSY9E4A")
|
||||
yysyDataForBelong := getMapValue(apiData, "YYSYH6D2")
|
||||
|
||||
|
||||
if yysy9e4aDataForBelong != nil && yysyDataForBelong != nil {
|
||||
if yysy9e4aMap, ok := yysy9e4aDataForBelong.(map[string]interface{}); ok {
|
||||
if yysyMapForBelong, ok := yysyDataForBelong.(map[string]interface{}); ok {
|
||||
@@ -1524,7 +1524,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
}
|
||||
detail["belongRisks"] = belongRisks
|
||||
|
||||
// 公安重点人员核验产品(highRiskFlag 和 keyPersonCheckList)
|
||||
// 公安重点人员核验产品(highRiskFlag 由 keyPersonCheckList 五项是否命中决定)
|
||||
flxgdea9Data := getMapValue(apiData, "FLXGDEA9")
|
||||
keyPersonCheckList := make(map[string]interface{})
|
||||
keyPersonCheckList["num"] = "1"
|
||||
@@ -1538,7 +1538,6 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok {
|
||||
level, ok := flxgdea9Map["level"].(string)
|
||||
if !ok {
|
||||
// 尝试从其他可能的字段获取
|
||||
if levelVal, exists := flxgdea9Map["level"]; exists {
|
||||
if levelStr, ok := levelVal.(string); ok {
|
||||
level = levelStr
|
||||
@@ -1548,19 +1547,14 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
}
|
||||
}
|
||||
|
||||
// 仅根据 level 解析并填充 keyPersonCheckList 五项
|
||||
if level != "" && level != "0" {
|
||||
// 有风险,设置highRiskFlag为1(高风险)
|
||||
detail["highRiskFlag"] = 1
|
||||
|
||||
// 解析level字段,填充keyPersonCheckList
|
||||
levelParts := strings.Split(level, ",")
|
||||
for _, part := range levelParts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" || part == "0" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据level代码判断风险类型
|
||||
if strings.HasPrefix(part, "A") {
|
||||
keyPersonCheckList["fontFlag"] = 1
|
||||
}
|
||||
@@ -1577,14 +1571,27 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
|
||||
keyPersonCheckList["sheJiaoTongFlag"] = 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 无风险,设置highRiskFlag为2(低风险)
|
||||
detail["highRiskFlag"] = 2
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
detail["keyPersonCheckList"] = keyPersonCheckList
|
||||
|
||||
// 仅根据 keyPersonCheckList 五项判定 highRiskFlag(不看是否有数据):
|
||||
// 五项均未命中 → 0 无风险;仅 sheJiaoTongFlag 命中 → 2 低风险;其他任一项命中 → 1 高风险
|
||||
otherHit := keyPersonFlagEq1(keyPersonCheckList, "fontFlag") ||
|
||||
keyPersonFlagEq1(keyPersonCheckList, "jingJiFontFlag") ||
|
||||
keyPersonFlagEq1(keyPersonCheckList, "fangAiFlag") ||
|
||||
keyPersonFlagEq1(keyPersonCheckList, "zhongDianFlag")
|
||||
sheJiaoHit := keyPersonFlagEq1(keyPersonCheckList, "sheJiaoTongFlag")
|
||||
if otherHit {
|
||||
detail["highRiskFlag"] = 1 // 高风险
|
||||
} else if sheJiaoHit {
|
||||
detail["highRiskFlag"] = 2 // 低风险:仅涉交通命中
|
||||
} else {
|
||||
detail["highRiskFlag"] = 0 // 无风险:五项均未命中
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if _, exists := detail["sjsysFlag"]; !exists {
|
||||
detail["sjsysFlag"] = 0
|
||||
@@ -1614,7 +1621,7 @@ func buildRiskSupervision(apiData map[string]interface{}, log *zap.Logger) map[s
|
||||
|
||||
// 从3C租赁申请意向获取数据
|
||||
jrzq1d09Data := getMapValue(apiData, "JRZQ1D09")
|
||||
|
||||
|
||||
// 设置默认值
|
||||
riskSupervision["rentalRiskListIdCardRelationsPhones"] = 0
|
||||
riskSupervision["rentalRiskListPhoneRelationsIdCards"] = 0
|
||||
@@ -1889,15 +1896,15 @@ func buildOverdueRiskProduct(apiData map[string]interface{}, log *zap.Logger) ma
|
||||
|
||||
// 判断风险标识
|
||||
hasOverdue := overdueRiskProduct["hasUnsettledOverdue"] == "逾期"
|
||||
hasRecentOverdue := overdueRiskProduct["overdueLast7Days"] == "逾期" ||
|
||||
overdueRiskProduct["overdueLast14Days"] == "逾期" ||
|
||||
hasRecentOverdue := overdueRiskProduct["overdueLast7Days"] == "逾期" ||
|
||||
overdueRiskProduct["overdueLast14Days"] == "逾期" ||
|
||||
overdueRiskProduct["overdueLast30Days"] == "逾期"
|
||||
|
||||
|
||||
if hasOverdue || hasRecentOverdue {
|
||||
overdueRiskProduct["lyjlhyFlag"] = 1 // 高风险
|
||||
overdueRiskProduct["lyjlhyFlag"] = 1 // 高风险
|
||||
overdueRiskProduct["dkzhktjFlag"] = 1 // 高风险
|
||||
} else {
|
||||
overdueRiskProduct["lyjlhyFlag"] = 2 // 低风险
|
||||
overdueRiskProduct["lyjlhyFlag"] = 2 // 低风险
|
||||
overdueRiskProduct["dkzhktjFlag"] = 2 // 低风险
|
||||
}
|
||||
}
|
||||
@@ -2021,7 +2028,7 @@ func buildMultCourtInfo(apiData map[string]interface{}, log *zap.Logger) map[str
|
||||
if lawsuitStat, ok := judicialData["lawsuitStat"].(map[string]interface{}); ok {
|
||||
// 收集所有涉案公告案件(民事、刑事、行政、保全、破产)- 都归到legalCases
|
||||
legalCases := make([]interface{}, 0)
|
||||
|
||||
|
||||
// 处理民事案件(civil)- 结构:civil.cases[]
|
||||
if civilVal, exists := lawsuitStat["civil"]; exists && civilVal != nil {
|
||||
if civil, ok := civilVal.(map[string]interface{}); ok && len(civil) > 0 {
|
||||
@@ -2171,7 +2178,7 @@ func buildMultCourtInfo(apiData map[string]interface{}, log *zap.Logger) map[str
|
||||
// convertCivilCase 转换民事案件
|
||||
func convertCivilCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2252,7 +2259,7 @@ func convertCivilCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
// convertExecutionCase 转换执行案件
|
||||
func convertExecutionCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2386,7 +2393,7 @@ func convertExecutionCase(caseMap map[string]interface{}) map[string]interface{}
|
||||
// convertBreachCase 转换失信案件
|
||||
func convertBreachCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if caseNumber, ok := caseMap["caseNumber"].(string); ok {
|
||||
caseInfo["caseNumber"] = caseNumber
|
||||
@@ -2463,7 +2470,7 @@ func convertBreachCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
// convertLimitCase 转换限高案件
|
||||
func convertLimitCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if caseNumber, ok := caseMap["caseNumber"].(string); ok {
|
||||
caseInfo["caseNumber"] = caseNumber
|
||||
@@ -2506,7 +2513,7 @@ func convertLimitCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
// convertPreservationCase 转换保全审查案件
|
||||
func convertPreservationCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2600,7 +2607,7 @@ func convertPreservationCase(caseMap map[string]interface{}) map[string]interfac
|
||||
// convertCriminalCase 转换刑事案件
|
||||
func convertCriminalCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2682,7 +2689,7 @@ func convertCriminalCase(caseMap map[string]interface{}) map[string]interface{}
|
||||
// convertAdministrativeCase 转换行政案件
|
||||
func convertAdministrativeCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2765,7 +2772,7 @@ func convertAdministrativeCase(caseMap map[string]interface{}) map[string]interf
|
||||
// convertBankruptCase 转换破产清算案件
|
||||
func convertBankruptCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseInfo := make(map[string]interface{})
|
||||
|
||||
|
||||
// 案号
|
||||
if cAh, ok := caseMap["c_ah"].(string); ok {
|
||||
caseInfo["caseNumber"] = cAh
|
||||
@@ -2912,11 +2919,11 @@ func buildOrganLoanPerformances(variableValue map[string]interface{}) []interfac
|
||||
|
||||
// 时间周期映射
|
||||
periodMap := map[string]string{
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last12Month": "m12",
|
||||
}
|
||||
|
||||
@@ -2976,11 +2983,11 @@ func buildCustomerLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
}
|
||||
|
||||
periodMap := map[string]string{
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last12Month": "m12",
|
||||
}
|
||||
|
||||
@@ -2994,7 +3001,7 @@ func buildCustomerLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_orgnum", apiPeriod, customerType.prefix))
|
||||
idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_allnum", apiPeriod, customerType.prefix))
|
||||
cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_allnum", apiPeriod, customerType.prefix))
|
||||
|
||||
|
||||
if idOrgnum == "" {
|
||||
idOrgnum = "0"
|
||||
}
|
||||
@@ -3007,7 +3014,7 @@ func buildCustomerLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
if cellAllnum == "" {
|
||||
cellAllnum = "0"
|
||||
}
|
||||
|
||||
|
||||
perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum)
|
||||
perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum)
|
||||
}
|
||||
@@ -3036,11 +3043,11 @@ func buildBusinessLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
}
|
||||
|
||||
periodMap := map[string]string{
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last12Month": "m12",
|
||||
}
|
||||
|
||||
@@ -3054,7 +3061,7 @@ func buildBusinessLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_orgnum", apiPeriod, businessType.prefix))
|
||||
idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_allnum", apiPeriod, businessType.prefix))
|
||||
cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_allnum", apiPeriod, businessType.prefix))
|
||||
|
||||
|
||||
if idOrgnum == "" {
|
||||
idOrgnum = "0"
|
||||
}
|
||||
@@ -3067,7 +3074,7 @@ func buildBusinessLoanPerformances(variableValue map[string]interface{}) []inter
|
||||
if cellAllnum == "" {
|
||||
cellAllnum = "0"
|
||||
}
|
||||
|
||||
|
||||
perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum)
|
||||
perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum)
|
||||
}
|
||||
@@ -3095,11 +3102,11 @@ func buildTimeLoanPerformances(variableValue map[string]interface{}) []interface
|
||||
}
|
||||
|
||||
periodMap := map[string]string{
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last7Day": "d7",
|
||||
"last15Day": "d15",
|
||||
"last1Month": "m1",
|
||||
"last3Month": "m3",
|
||||
"last6Month": "m6",
|
||||
"last12Month": "m12",
|
||||
}
|
||||
|
||||
@@ -3113,7 +3120,7 @@ func buildTimeLoanPerformances(variableValue map[string]interface{}) []interface
|
||||
cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_%s_orgnum", apiPeriod, timeType.orgType, timeType.timeType))
|
||||
idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_%s_allnum", apiPeriod, timeType.orgType, timeType.timeType))
|
||||
cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_%s_allnum", apiPeriod, timeType.orgType, timeType.timeType))
|
||||
|
||||
|
||||
if idOrgnum == "" {
|
||||
idOrgnum = "0"
|
||||
}
|
||||
@@ -3126,7 +3133,7 @@ func buildTimeLoanPerformances(variableValue map[string]interface{}) []interface
|
||||
if cellAllnum == "" {
|
||||
cellAllnum = "0"
|
||||
}
|
||||
|
||||
|
||||
perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum)
|
||||
perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum)
|
||||
}
|
||||
@@ -3144,7 +3151,7 @@ func checkLoanRisk(variableValue map[string]interface{}) bool {
|
||||
for _, period := range periods {
|
||||
bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period))
|
||||
nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period))
|
||||
|
||||
|
||||
if bankAllnum != "" && bankAllnum != "0" {
|
||||
if count, err := strconv.Atoi(bankAllnum); err == nil && count >= 10 {
|
||||
return true
|
||||
@@ -3586,7 +3593,7 @@ func checkRecentApplicationFrequency(variableValue map[string]interface{}, riskW
|
||||
for _, period := range periods {
|
||||
bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period.apiPeriod))
|
||||
nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period.apiPeriod))
|
||||
|
||||
|
||||
// 如果近7天申请次数>=10,认为是极为频繁(调高风险阈值,进一步提高阈值)
|
||||
if bankAllnum != "" && bankAllnum != "0" {
|
||||
if count, err := strconv.Atoi(bankAllnum); err == nil && count >= 10 {
|
||||
@@ -3619,7 +3626,7 @@ func checkBankApplicationFrequency(variableValue map[string]interface{}, riskWar
|
||||
for _, period := range periods {
|
||||
bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period.apiPeriod))
|
||||
nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period.apiPeriod))
|
||||
|
||||
|
||||
if bankAllnum != "" && bankAllnum != "0" {
|
||||
if count, err := strconv.Atoi(bankAllnum); err == nil {
|
||||
bankTotal += count
|
||||
@@ -3656,7 +3663,7 @@ func checkDebtPressure(apiData map[string]interface{}, riskWarning *map[string]i
|
||||
// 检查当前逾期金额和机构数
|
||||
currentOverdueAmount := getStringValue(jrzq5e9fMap, "xyp_cpl0072")
|
||||
currentOverdueInstitutionCount := getStringValue(jrzq5e9fMap, "xyp_cpl0071")
|
||||
|
||||
|
||||
// 如果当前逾期金额较大或机构数较多,认为偿债压力极高
|
||||
if currentOverdueAmount != "" && currentOverdueAmount != "0" && currentOverdueAmount != "1" {
|
||||
(*riskWarning)["highDebtPressure"] = 1
|
||||
@@ -3711,7 +3718,7 @@ func checkRentalApplicationFrequency(jrzq1d09Map map[string]interface{}, riskWar
|
||||
for _, period := range periods {
|
||||
idAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_allnum", period.apiPeriod))
|
||||
cellAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_allnum", period.apiPeriod))
|
||||
|
||||
|
||||
// 取较大的值
|
||||
idCount := 0
|
||||
cellCount := 0
|
||||
@@ -3725,7 +3732,7 @@ func checkRentalApplicationFrequency(jrzq1d09Map map[string]interface{}, riskWar
|
||||
cellCount = count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if idCount > cellCount {
|
||||
totalCount += idCount
|
||||
} else {
|
||||
@@ -3772,15 +3779,15 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
|
||||
// - Institution(3C机构): 使用id(身份证)查询的数据
|
||||
// - Platform(3C平台): 使用cell(手机号)查询的数据
|
||||
// 格式: "身份证/手机号",如果没有匹配的就是0
|
||||
|
||||
|
||||
// 时间周期映射:3Days->d3, 7Days->d7, 14Days->d14, Month->m1, 3Months->m3, 6Months->m6, 12Months->m12
|
||||
periodMap := map[string]string{
|
||||
"3Days": "d3",
|
||||
"7Days": "d7",
|
||||
"14Days": "d14",
|
||||
"Month": "m1",
|
||||
"3Months": "m3",
|
||||
"6Months": "m6",
|
||||
"3Days": "d3",
|
||||
"7Days": "d7",
|
||||
"14Days": "d14",
|
||||
"Month": "m1",
|
||||
"3Months": "m3",
|
||||
"6Months": "m6",
|
||||
"12Months": "m12",
|
||||
}
|
||||
|
||||
@@ -3862,29 +3869,32 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
|
||||
assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum)
|
||||
}
|
||||
|
||||
// 仅使用近12月(Last12,对应 m12)的总次数作为判断依据,
|
||||
// 因为近3月等短周期已经被近12月统计包含,避免重复放大
|
||||
// 仅使用近12月(Last12,对应 m12)的次数作为判断依据,
|
||||
// totalCount 取身份证维度和手机号维度中的较大值,而不是两者相加
|
||||
totalCount := 0
|
||||
|
||||
idKey12 := "alc_m12_id_allnum"
|
||||
cellKey12 := "alc_m12_cell_allnum"
|
||||
|
||||
idCount := 0
|
||||
cellCount := 0
|
||||
|
||||
// 身份证维度(近12月)
|
||||
if v, ok := jrzq1d09Map[idKey12]; ok {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
if vv != "" && vv != "0" {
|
||||
if parsed, err := strconv.Atoi(vv); err == nil {
|
||||
totalCount += parsed
|
||||
idCount = parsed
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
if vv > 0 {
|
||||
totalCount += int(vv)
|
||||
idCount = int(vv)
|
||||
}
|
||||
case int:
|
||||
if vv > 0 {
|
||||
totalCount += vv
|
||||
idCount = vv
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3895,20 +3905,27 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
|
||||
case string:
|
||||
if vv != "" && vv != "0" {
|
||||
if parsed, err := strconv.Atoi(vv); err == nil {
|
||||
totalCount += parsed
|
||||
cellCount = parsed
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
if vv > 0 {
|
||||
totalCount += int(vv)
|
||||
cellCount = int(vv)
|
||||
}
|
||||
case int:
|
||||
if vv > 0 {
|
||||
totalCount += vv
|
||||
cellCount = vv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用身份证和手机号两个维度中的较大值作为近12月总次数
|
||||
if idCount >= cellCount {
|
||||
totalCount = idCount
|
||||
} else {
|
||||
totalCount = cellCount
|
||||
}
|
||||
|
||||
// 根据近12月总申请次数设置风险标识:
|
||||
// - totalCount == 0 -> 0 无风险 / 未查得
|
||||
// - 0 < totalCount <= 10 -> 2 低风险(有少量租赁申请)
|
||||
@@ -3935,6 +3952,22 @@ func getMapValue(data map[string]interface{}, key string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyPersonFlagEq1 判断 keyPersonCheckList 中某标识是否为 1(支持 int/float64)
|
||||
func keyPersonFlagEq1(m map[string]interface{}, key string) bool {
|
||||
v, ok := m[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case int:
|
||||
return vv == 1
|
||||
case float64:
|
||||
return vv == 1
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func maskName(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
@@ -3978,8 +4011,10 @@ func calculateAgeAndSex(idCard string) (int, string) {
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// 计算年龄(简化处理,使用当前年份)
|
||||
age := 2024 - year
|
||||
age := now.Year() - year
|
||||
|
||||
// 提取性别(第17位,奇数为男,偶数为女)
|
||||
sexCode := idCard[16:17]
|
||||
@@ -4017,21 +4052,21 @@ func normalizeProvinceName(province string) string {
|
||||
if province == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// 去除前后空格
|
||||
province = strings.TrimSpace(province)
|
||||
|
||||
|
||||
// 特殊处理:内蒙古(必须在最前面)
|
||||
if strings.Contains(province, "内蒙古") {
|
||||
return "内蒙古"
|
||||
}
|
||||
|
||||
|
||||
// 处理自治区、特别行政区、直辖市等后缀
|
||||
suffixes := []string{
|
||||
"壮族自治区", "回族自治区", "维吾尔自治区", "自治区",
|
||||
"特别行政区", "省", "市",
|
||||
}
|
||||
|
||||
|
||||
normalized := province
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(normalized, suffix) {
|
||||
@@ -4039,7 +4074,7 @@ func normalizeProvinceName(province string) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -4049,15 +4084,15 @@ func normalizeCityName(city string) string {
|
||||
if city == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// 去除前后空格
|
||||
city = strings.TrimSpace(city)
|
||||
|
||||
|
||||
// 处理各种后缀
|
||||
suffixes := []string{
|
||||
"地区", "市", "自治州", "盟", "县", "区",
|
||||
}
|
||||
|
||||
|
||||
normalized := city
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(normalized, suffix) {
|
||||
@@ -4065,7 +4100,7 @@ func normalizeCityName(city string) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -4074,12 +4109,12 @@ func extractProvinceFromAddress(address string) string {
|
||||
if address == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// 特殊处理:内蒙古(必须在最前面,因为"内蒙古"可能被其他模式误匹配)
|
||||
if strings.HasPrefix(address, "内蒙古") {
|
||||
return "内蒙古自治区"
|
||||
}
|
||||
|
||||
|
||||
// 处理直辖市(必须在自治区之前)
|
||||
if strings.HasPrefix(address, "北京") {
|
||||
return "北京市"
|
||||
@@ -4093,7 +4128,7 @@ func extractProvinceFromAddress(address string) string {
|
||||
if strings.HasPrefix(address, "重庆") {
|
||||
return "重庆市"
|
||||
}
|
||||
|
||||
|
||||
// 处理各种省份格式(按长度从长到短,避免误匹配)
|
||||
patterns := []struct {
|
||||
pattern string
|
||||
@@ -4137,12 +4172,12 @@ func extractProvinceFromAddress(address string) string {
|
||||
// 处理"XX省"格式(最后处理,因为可能与其他模式冲突)
|
||||
{"省", func(addr string) string {
|
||||
if idx := strings.Index(addr, "省"); idx > 0 {
|
||||
return addr[:idx+1]
|
||||
return addr[:idx+len("省")]
|
||||
}
|
||||
return ""
|
||||
}},
|
||||
}
|
||||
|
||||
|
||||
for _, p := range patterns {
|
||||
if strings.Contains(address, p.pattern) {
|
||||
if result := p.extract(address); result != "" {
|
||||
@@ -4150,7 +4185,7 @@ func extractProvinceFromAddress(address string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -4159,16 +4194,16 @@ func extractCityFromAddress(address string) string {
|
||||
if address == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// 先提取省份,然后从剩余部分提取城市
|
||||
province := extractProvinceFromAddress(address)
|
||||
if province == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// 去除省份部分
|
||||
cityPart := address[len(province):]
|
||||
|
||||
|
||||
// 处理各种城市格式
|
||||
patterns := []string{"地区", "市", "自治州", "盟"}
|
||||
for _, pattern := range patterns {
|
||||
@@ -4176,7 +4211,7 @@ func extractCityFromAddress(address string) string {
|
||||
return cityPart[:idx+len(pattern)]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -4188,7 +4223,7 @@ func compareLocation(address1, province2, city2 string) bool {
|
||||
if address1 == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 如果号码归属地只有省份,检查省份是否出现在户籍所在地中
|
||||
if province2 != "" && city2 == "" {
|
||||
// 标准化省份名称,去除后缀
|
||||
@@ -4196,20 +4231,20 @@ func compareLocation(address1, province2, city2 string) bool {
|
||||
// 检查标准化后的省份名称是否出现在户籍所在地中
|
||||
return strings.Contains(address1, province2Norm)
|
||||
}
|
||||
|
||||
|
||||
// 如果号码归属地有省份和城市,检查两者是否都出现在户籍所在地中
|
||||
if province2 != "" && city2 != "" {
|
||||
// 标准化省份和城市名称,去除后缀
|
||||
province2Norm := normalizeProvinceName(province2)
|
||||
city2Norm := normalizeCityName(city2)
|
||||
|
||||
|
||||
// 检查省份和城市是否都出现在户籍所在地中
|
||||
provinceMatch := strings.Contains(address1, province2Norm)
|
||||
cityMatch := strings.Contains(address1, city2Norm)
|
||||
|
||||
|
||||
return provinceMatch && cityMatch
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4229,7 +4264,7 @@ func convertChannel(channel string) string {
|
||||
// convertChannelName 转换运营商名称(处理中文名称)
|
||||
func convertChannelName(channel string) string {
|
||||
// 如果已经是中文名称,直接返回
|
||||
if strings.Contains(channel, "移动") || strings.Contains(channel, "联通") ||
|
||||
if strings.Contains(channel, "移动") || strings.Contains(channel, "联通") ||
|
||||
strings.Contains(channel, "电信") || strings.Contains(channel, "广电") {
|
||||
return channel
|
||||
}
|
||||
@@ -4243,7 +4278,7 @@ func convertStatusFromOnlineStatus(data map[string]interface{}) int {
|
||||
// 获取status字段(0-在网,1-不在网)
|
||||
var statusVal interface{}
|
||||
var ok bool
|
||||
|
||||
|
||||
// status可能是int或float64类型
|
||||
if statusVal, ok = data["status"]; !ok {
|
||||
return -1 // 未查得
|
||||
@@ -4329,7 +4364,7 @@ func getFraudScore(apiData map[string]interface{}) int {
|
||||
// 从涉赌涉诈风险评估获取基础反欺诈评分
|
||||
flxgData := getMapValue(apiData, "FLXG8B4D")
|
||||
baseScore := -1
|
||||
|
||||
|
||||
if flxgData != nil {
|
||||
flxgMap, ok := flxgData.(map[string]interface{})
|
||||
if ok {
|
||||
@@ -4472,7 +4507,7 @@ func getCreditScore(apiData map[string]interface{}) int {
|
||||
return 900
|
||||
}
|
||||
parsed, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
// 解析失败,返回900(默认信用较好)
|
||||
return 900
|
||||
}
|
||||
@@ -4513,7 +4548,7 @@ func isDevelopmentMode() bool {
|
||||
if useMock := os.Getenv("DWBG_USE_MOCK_DATA"); useMock != "" {
|
||||
return useMock == "1" || useMock == "true" || useMock == "yes"
|
||||
}
|
||||
|
||||
|
||||
env := os.Getenv("ENV")
|
||||
if env == "" {
|
||||
env = os.Getenv("CONFIG_ENV")
|
||||
@@ -4521,12 +4556,12 @@ func isDevelopmentMode() bool {
|
||||
if env == "" {
|
||||
env = os.Getenv("APP_ENV")
|
||||
}
|
||||
|
||||
|
||||
// 默认开发环境
|
||||
if env == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return env == "development"
|
||||
}
|
||||
|
||||
@@ -4538,32 +4573,32 @@ func loadMockAPIDataFromFile(log *zap.Logger) map[string]interface{} {
|
||||
// 默认使用最新的导出文件
|
||||
mockDataPath = "api_data_export/api_data_4522_20260214_152339.json"
|
||||
}
|
||||
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(mockDataPath); os.IsNotExist(err) {
|
||||
log.Warn("开发模式:模拟数据文件不存在", zap.String("path", mockDataPath))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// 读取文件内容
|
||||
fileData, err := os.ReadFile(mockDataPath)
|
||||
if err != nil {
|
||||
log.Warn("开发模式:读取模拟数据文件失败", zap.String("path", mockDataPath), zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// 解析JSON
|
||||
var apiData map[string]interface{}
|
||||
if err := json.Unmarshal(fileData, &apiData); err != nil {
|
||||
log.Warn("开发模式:解析模拟数据JSON失败", zap.String("path", mockDataPath), zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("开发模式:成功加载模拟数据",
|
||||
|
||||
log.Info("开发模式:成功加载模拟数据",
|
||||
zap.String("path", mockDataPath),
|
||||
zap.Int("api_count", len(apiData)),
|
||||
)
|
||||
|
||||
|
||||
return apiData
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZOCR1Request IVYZOCR1 身份证OCR API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZOCR1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZOCR1Req
|
||||
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)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "8782f2a32463f75b53096323461df735",
|
||||
"imageId": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/trade/user/1985"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZOCR2Request IVYZOCR2 OCR识别API处理方法数卖
|
||||
func ProcessIVYZOCR2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZOCR1Req
|
||||
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)
|
||||
}
|
||||
|
||||
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||
}
|
||||
|
||||
// 2选1:有值的用对应 key,空则用另一个
|
||||
reqFormData := make(map[string]interface{})
|
||||
if paramsDto.PhotoData != "" {
|
||||
reqFormData["image"] = paramsDto.PhotoData
|
||||
} else {
|
||||
reqFormData["url"] = paramsDto.ImageUrl
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/idcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessJRZQOCREERequest JRZQOCRE 银行卡OCR API 数卖服务示例
|
||||
func ProcessJRZQOCREERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQOCREReq
|
||||
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)
|
||||
}
|
||||
|
||||
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||
}
|
||||
|
||||
// 2选1:有值的用对应 key,空则用另一个
|
||||
reqFormData := make(map[string]interface{})
|
||||
if paramsDto.PhotoData != "" {
|
||||
reqFormData["image"] = paramsDto.PhotoData
|
||||
} else {
|
||||
reqFormData["url"] = paramsDto.ImageUrl
|
||||
}
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v2/bankcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessJRZQOCRYERequest JRZQOCRY 银行卡OCR API 处理方法(使用数据宝服务示例)
|
||||
func ProcessJRZQOCRYERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQOCRYReq
|
||||
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)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "3ee8e7a7a71870db2c0bf98e7e6b8b5c",
|
||||
"imageId": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/trade/user/1986"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -21,20 +21,23 @@ func ProcessQYGL5S1IRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.EntCode == "" && paramsDto.EntName == "" {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("必须提供企业统一信用代码或企业名称中的一个"))
|
||||
|
||||
encryptedEntName, err := deps.ZhichaService.Encrypt(paramsDto.EntName)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
encryptedEntCode, err := deps.ZhichaService.Encrypt(paramsDto.EntCode)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 优先使用企业名称,否则使用统一信用代码
|
||||
var enterpriseNo, enterpriseName string
|
||||
// 按企业名称时传 enterpriseNo(加密名),按统一信用代码时传 enterpriseName(加密代码)
|
||||
reqData := map[string]interface{}{}
|
||||
if paramsDto.EntName != "" {
|
||||
enterpriseName = paramsDto.EntName
|
||||
} else {
|
||||
enterpriseNo = paramsDto.EntCode
|
||||
reqData["enterpriseName"] = encryptedEntName
|
||||
}
|
||||
reqData := map[string]interface{}{
|
||||
"enterpriseNo": enterpriseNo,
|
||||
"enterpriseName": enterpriseName,
|
||||
if paramsDto.EntCode != "" {
|
||||
reqData["enterpriseNo"] = encryptedEntCode
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI088", reqData)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessQYGLJ0Q1Request QYGLJ0Q1 企业股权结构全景查询 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessQYGLJ0Q1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGLJ0Q1Req
|
||||
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)
|
||||
}
|
||||
|
||||
// 二选一:企业名称(entName) 与 统一社会信用代码(creditCode) 必须且仅能传其一
|
||||
hasEntName := paramsDto.EntName != ""
|
||||
hasEntCode := paramsDto.EntCode != ""
|
||||
if hasEntName == hasEntCode { // 两个都填或两个都未填
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("ent_name 与 ent_code 二选一,必须且仅能传其中一个"))
|
||||
}
|
||||
|
||||
// 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "adac456f7b4ced764b606c8b07fed4d3",
|
||||
}
|
||||
if hasEntName {
|
||||
reqParams["entName"] = paramsDto.EntName
|
||||
} else {
|
||||
reqParams["creditCode"] = paramsDto.EntCode
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10216"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse)
|
||||
parsedResp, err := RecursiveParse(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(parsedResp)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessQYGLUY3SRequest QYGLUY3S 企业全量信息核验V2 可用 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessQYGLUY3SRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGLUY3SReq
|
||||
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)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "5131227a847c06c111f624a22ebacc06",
|
||||
"entName": paramsDto.EntName,
|
||||
"regno": paramsDto.EntRegno,
|
||||
"creditcode": paramsDto.EntCode,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10195"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse)
|
||||
parsedResp, err := RecursiveParse(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(parsedResp)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package yysy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessYYSY35TARequest YYSY35TA API 运营商归属地数卖处理方法数脉
|
||||
func ProcessYYSY35TARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.YYSY35TAReq
|
||||
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)
|
||||
}
|
||||
|
||||
reqFormData := map[string]interface{}{
|
||||
"mobile": paramsDto.MobileNo,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/phone/number" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -39,6 +39,14 @@ func ProcessYYSY9E4ARequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容上游有时返回 JSON 字符串的情况:如果是字符串则尝试再反序列化一次
|
||||
if str, ok := respData.(string); ok && str != "" {
|
||||
var parsed interface{}
|
||||
if err := json.Unmarshal([]byte(str), &parsed); err == nil {
|
||||
respData = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// calculateAlipayRechargeBonus 计算支付宝充值赠送金额
|
||||
// calculateAlipayRechargeBonus 计算支付宝充值赠送金额(受 recharge_bonus_enabled 开关控制)
|
||||
func calculateAlipayRechargeBonus(rechargeAmount decimal.Decimal, walletConfig *config.WalletConfig) decimal.Decimal {
|
||||
if walletConfig == nil || len(walletConfig.AliPayRechargeBonus) == 0 {
|
||||
if walletConfig == nil || !walletConfig.RechargeBonusEnabled || len(walletConfig.AliPayRechargeBonus) == 0 {
|
||||
return decimal.Zero
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func TestCalculateAlipayRechargeBonus(t *testing.T) {
|
||||
// 创建测试配置
|
||||
// 创建测试配置(开启赠送)
|
||||
walletConfig := &config.WalletConfig{
|
||||
RechargeBonusEnabled: true,
|
||||
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
|
||||
{RechargeAmount: 1000.00, BonusAmount: 50.00}, // 充1000送50
|
||||
{RechargeAmount: 5000.00, BonusAmount: 300.00}, // 充5000送300
|
||||
@@ -74,6 +75,7 @@ func TestCalculateAlipayRechargeBonus(t *testing.T) {
|
||||
func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
|
||||
// 测试空配置
|
||||
walletConfig := &config.WalletConfig{
|
||||
RechargeBonusEnabled: true,
|
||||
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{},
|
||||
}
|
||||
|
||||
@@ -85,4 +87,17 @@ func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
|
||||
assert.True(t, bonus.Equal(decimal.Zero), "nil配置应该返回零赠送金额")
|
||||
}
|
||||
|
||||
func TestCalculateAlipayRechargeBonus_Disabled(t *testing.T) {
|
||||
// 关闭赠送时,任意金额均不赠送
|
||||
walletConfig := &config.WalletConfig{
|
||||
RechargeBonusEnabled: false,
|
||||
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
|
||||
{RechargeAmount: 1000.00, BonusAmount: 50.00},
|
||||
{RechargeAmount: 10000.00, BonusAmount: 800.00},
|
||||
},
|
||||
}
|
||||
bonus := calculateAlipayRechargeBonus(decimal.NewFromFloat(10000.00), walletConfig)
|
||||
assert.True(t, bonus.Equal(decimal.Zero), "关闭赠送时应返回零")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UsersTable = "users"
|
||||
UsersTable = "users"
|
||||
UserCacheTTL = 30 * 60 // 30分钟
|
||||
)
|
||||
|
||||
@@ -415,7 +415,7 @@ func (r *GormUserRepository) GetSystemUserStatsByDateRange(ctx context.Context,
|
||||
// GetSystemDailyUserStats 获取系统每日用户统计
|
||||
func (r *GormUserRepository) GetSystemDailyUserStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
var results []map[string]interface{}
|
||||
|
||||
|
||||
sql := `
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
@@ -426,19 +426,19 @@ func (r *GormUserRepository) GetSystemDailyUserStats(ctx context.Context, startD
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
`
|
||||
|
||||
|
||||
err := r.GetDB(ctx).Raw(sql, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetSystemMonthlyUserStats 获取系统每月用户统计
|
||||
func (r *GormUserRepository) GetSystemMonthlyUserStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
var results []map[string]interface{}
|
||||
|
||||
|
||||
sql := `
|
||||
SELECT
|
||||
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||
@@ -449,19 +449,19 @@ func (r *GormUserRepository) GetSystemMonthlyUserStats(ctx context.Context, star
|
||||
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||
ORDER BY month ASC
|
||||
`
|
||||
|
||||
|
||||
err := r.GetDB(ctx).Raw(sql, startDate, endDate).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetSystemDailyCertificationStats 获取系统每日认证用户统计(基于is_certified字段)
|
||||
func (r *GormUserRepository) GetSystemDailyCertificationStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
var results []map[string]interface{}
|
||||
|
||||
|
||||
sql := `
|
||||
SELECT
|
||||
DATE(updated_at) as date,
|
||||
@@ -473,19 +473,19 @@ func (r *GormUserRepository) GetSystemDailyCertificationStats(ctx context.Contex
|
||||
GROUP BY DATE(updated_at)
|
||||
ORDER BY date ASC
|
||||
`
|
||||
|
||||
|
||||
err := r.GetDB(ctx).Raw(sql, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetSystemMonthlyCertificationStats 获取系统每月认证用户统计(基于is_certified字段)
|
||||
func (r *GormUserRepository) GetSystemMonthlyCertificationStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
var results []map[string]interface{}
|
||||
|
||||
|
||||
sql := `
|
||||
SELECT
|
||||
TO_CHAR(updated_at, 'YYYY-MM') as month,
|
||||
@@ -497,12 +497,12 @@ func (r *GormUserRepository) GetSystemMonthlyCertificationStats(ctx context.Cont
|
||||
GROUP BY TO_CHAR(updated_at, 'YYYY-MM')
|
||||
ORDER BY month ASC
|
||||
`
|
||||
|
||||
|
||||
err := r.GetDB(ctx).Raw(sql, startDate, endDate).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ func (r *GormUserRepository) GetSystemMonthlyCertificationStats(ctx context.Cont
|
||||
func (r *GormUserRepository) GetUserCallRankingByCalls(ctx context.Context, period string, limit int) ([]map[string]interface{}, error) {
|
||||
var sql string
|
||||
var args []interface{}
|
||||
|
||||
|
||||
switch period {
|
||||
case "today":
|
||||
sql = `
|
||||
@@ -565,13 +565,13 @@ func (r *GormUserRepository) GetUserCallRankingByCalls(ctx context.Context, peri
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的时间周期: %s", period)
|
||||
}
|
||||
|
||||
|
||||
var results []map[string]interface{}
|
||||
err := r.GetDB(ctx).Raw(sql, args...).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -579,7 +579,7 @@ func (r *GormUserRepository) GetUserCallRankingByCalls(ctx context.Context, peri
|
||||
func (r *GormUserRepository) GetUserCallRankingByConsumption(ctx context.Context, period string, limit int) ([]map[string]interface{}, error) {
|
||||
var sql string
|
||||
var args []interface{}
|
||||
|
||||
|
||||
switch period {
|
||||
case "today":
|
||||
sql = `
|
||||
@@ -634,13 +634,13 @@ func (r *GormUserRepository) GetUserCallRankingByConsumption(ctx context.Context
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的时间周期: %s", period)
|
||||
}
|
||||
|
||||
|
||||
var results []map[string]interface{}
|
||||
err := r.GetDB(ctx).Raw(sql, args...).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -648,7 +648,7 @@ func (r *GormUserRepository) GetUserCallRankingByConsumption(ctx context.Context
|
||||
func (r *GormUserRepository) GetRechargeRanking(ctx context.Context, period string, limit int) ([]map[string]interface{}, error) {
|
||||
var sql string
|
||||
var args []interface{}
|
||||
|
||||
|
||||
switch period {
|
||||
case "today":
|
||||
sql = `
|
||||
@@ -709,12 +709,12 @@ func (r *GormUserRepository) GetRechargeRanking(ctx context.Context, period stri
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的时间周期: %s", period)
|
||||
}
|
||||
|
||||
|
||||
var results []map[string]interface{}
|
||||
err := r.GetDB(ctx).Raw(sql, args...).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,50 @@ import (
|
||||
"tyapi-server/internal/shared/external_logger"
|
||||
)
|
||||
|
||||
const (
|
||||
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
|
||||
maxLogParamValueLen = 300
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDatasource = errors.New("数据源异常")
|
||||
ErrSystem = errors.New("系统异常")
|
||||
ErrQueryEmpty = errors.New("查询为空")
|
||||
)
|
||||
|
||||
// truncateForLog 将字符串截断到指定长度,用于错误日志,避免 base64 等过长内容
|
||||
func truncateForLog(s string, maxLen int) string {
|
||||
if maxLen <= 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "...[truncated, total " + strconv.Itoa(len(s)) + " chars]"
|
||||
}
|
||||
|
||||
// paramsForLog 返回适合写入错误日志的入参副本(长字符串会被截断)
|
||||
func paramsForLog(params map[string]interface{}) map[string]interface{} {
|
||||
if params == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]interface{}, len(params))
|
||||
for k, v := range params {
|
||||
if v == nil {
|
||||
out[k] = nil
|
||||
continue
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
out[k] = truncateForLog(val, maxLogParamValueLen)
|
||||
default:
|
||||
s := fmt.Sprint(v)
|
||||
out[k] = truncateForLog(s, maxLogParamValueLen)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ShujubaoResp 数据宝 API 通用响应(按实际文档调整)
|
||||
type ShujubaoResp struct {
|
||||
Code string `json:"code"`
|
||||
@@ -187,7 +225,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
if err != nil {
|
||||
err = errors.Join(ErrSystem, err)
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -216,7 +254,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
err = errors.Join(ErrSystem, err)
|
||||
}
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -226,7 +264,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
if err != nil {
|
||||
err = errors.Join(ErrSystem, err)
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -239,7 +277,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
if response.StatusCode != http.StatusOK {
|
||||
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode))
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,7 +286,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
if err := json.Unmarshal(respBody, &shujubaoResp); err != nil {
|
||||
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -261,7 +299,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
|
||||
if code != "10000" {
|
||||
shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message)
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, shujubaoErr, params)
|
||||
s.logger.LogError(requestID, transactionID, apiPath, shujubaoErr, paramsForLog(params))
|
||||
}
|
||||
return nil, errors.Join(ErrDatasource, shujubaoErr)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,52 @@ import (
|
||||
"tyapi-server/internal/shared/external_logger"
|
||||
)
|
||||
|
||||
const (
|
||||
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
|
||||
maxLogParamValueLen = 300
|
||||
// 错误日志中 response_body 的最大长度
|
||||
maxLogResponseBodyLen = 500
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDatasource = errors.New("数据源异常")
|
||||
ErrSystem = errors.New("系统异常")
|
||||
ErrNotFound = errors.New("查询为空")
|
||||
)
|
||||
|
||||
// truncateForLog 将字符串截断到指定长度,用于错误日志,避免 base64 等过长内容
|
||||
func truncateForLog(s string, maxLen int) string {
|
||||
if maxLen <= 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "...[truncated, total " + strconv.Itoa(len(s)) + " chars]"
|
||||
}
|
||||
|
||||
// requestParamsForLog 返回适合写入错误日志的入参副本(长字符串会被截断)
|
||||
func requestParamsForLog(reqFormData map[string]interface{}) map[string]interface{} {
|
||||
if reqFormData == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]interface{}, len(reqFormData))
|
||||
for k, v := range reqFormData {
|
||||
if v == nil {
|
||||
out[k] = nil
|
||||
continue
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
out[k] = truncateForLog(val, maxLogParamValueLen)
|
||||
default:
|
||||
s := fmt.Sprint(v)
|
||||
out[k] = truncateForLog(s, maxLogParamValueLen)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ShumaiResponse 数脉 API 通用响应(占位,按实际文档调整)
|
||||
type ShumaiResponse struct {
|
||||
Code int `json:"code"` // 状态码
|
||||
@@ -188,7 +228,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
if err != nil {
|
||||
err = errors.Join(ErrSystem, err)
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -216,7 +256,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
err = errors.Join(ErrSystem, err)
|
||||
}
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -227,7 +267,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
if err != nil {
|
||||
err = errors.Join(ErrSystem, err)
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": reqFormData})
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, map[string]interface{}{"request_params": requestParamsForLog(reqFormData)})
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,8 +276,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP %d", resp.StatusCode))
|
||||
if s.logger != nil {
|
||||
errorPayload := map[string]interface{}{
|
||||
"request_params": reqFormData,
|
||||
"response_body": string(raw),
|
||||
"request_params": requestParamsForLog(reqFormData),
|
||||
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||
}
|
||||
s.logger.LogError(requestID, transactionID, apiPath, err, errorPayload)
|
||||
}
|
||||
@@ -253,8 +293,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
parseErr := errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, parseErr, map[string]interface{}{
|
||||
"request_params": reqFormData,
|
||||
"response_body": string(raw),
|
||||
"request_params": requestParamsForLog(reqFormData),
|
||||
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||
})
|
||||
}
|
||||
return nil, parseErr
|
||||
@@ -273,8 +313,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
}
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, shumaiErr, map[string]interface{}{
|
||||
"request_params": reqFormData,
|
||||
"response_body": string(raw),
|
||||
"request_params": requestParamsForLog(reqFormData),
|
||||
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||
})
|
||||
}
|
||||
if shumaiErr.IsNoRecord() {
|
||||
@@ -292,8 +332,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
|
||||
marshalErr := errors.Join(ErrSystem, fmt.Errorf("data 序列化失败: %w", err))
|
||||
if s.logger != nil {
|
||||
s.logger.LogError(requestID, transactionID, apiPath, marshalErr, map[string]interface{}{
|
||||
"request_params": reqFormData,
|
||||
"response_body": string(raw),
|
||||
"request_params": requestParamsForLog(reqFormData),
|
||||
"response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
|
||||
})
|
||||
}
|
||||
return nil, marshalErr
|
||||
|
||||
@@ -210,8 +210,13 @@ func (z *ZhichaService) CallAPI(ctx context.Context, proID string, params map[st
|
||||
return nil, ErrDatasource
|
||||
}
|
||||
|
||||
// 201 表示查询为空,返回空对象
|
||||
// 201 表示查询为空,兼容其它情况如果data也为空,则返回空对象
|
||||
if zhichaResp.Code == "201" {
|
||||
// 先做类型断言
|
||||
dataMap, ok := zhichaResp.Data.(map[string]interface{})
|
||||
if ok && len(dataMap) > 0 {
|
||||
return dataMap, nil
|
||||
}
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,9 @@ func TestDecryptWithInvalidData(t *testing.T) {
|
||||
"", // 空数据
|
||||
"invalid_base64", // 无效的Base64
|
||||
"dGVzdA==", // 有效的Base64但不是AES加密数据
|
||||
"ZmJIKRQz6j+IboulkSfq0g==",
|
||||
"qBijvRmmm3bbLEdaCMw2XvHc8SDq3oGh6lD6BwELyhU=",
|
||||
"ITtZBkPMZQ88UTHWJuWjSA==",
|
||||
"i96w+SDjwENjuvsokMFbLw==",
|
||||
"oaihmICgEcszWMk0gXoB12E/ygF4g78x0/sC3/KHnBk=",
|
||||
"5bx+WvXvdNRVVOp9UuNFHg==",
|
||||
}
|
||||
|
||||
for _, data := range invalidData {
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user