Compare commits

...

37 Commits

Author SHA1 Message Date
Mrx
d7a5589873 f 2026-03-09 11:31:40 +08:00
Mrx
b0ec75d1af f 2026-03-06 16:39:00 +08:00
Mrx
57d18be972 f 2026-03-06 16:28:25 +08:00
Mrx
3d8775b6dc f 2026-03-06 15:20:27 +08:00
Mrx
f40950f890 f 2026-03-06 15:12:58 +08:00
Mrx
ba21a8f965 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-06 10:56:37 +08:00
Mrx
7d2716da7a f 2026-03-06 10:56:36 +08:00
9a7bda9527 f 2026-03-05 19:03:18 +08:00
abdae033f0 f 2026-03-05 18:44:17 +08:00
96abacd392 f 2026-03-05 18:41:00 +08:00
4e6c93413e Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 17:44:59 +08:00
9c8dbd458f f 2026-03-05 17:44:50 +08:00
Mrx
9e9cee02f5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-05 16:24:56 +08:00
Mrx
360bd579ce f 2026-03-05 16:24:53 +08:00
db889ccba0 f 2026-03-05 14:17:48 +08:00
25a4961328 f 2026-03-05 12:26:02 +08:00
Mrx
578e68a76b f 2026-03-05 11:05:01 +08:00
Mrx
019e47896d f 2026-03-05 10:57:06 +08:00
Mrx
c0898e6829 f 2026-03-05 10:54:16 +08:00
Mrx
4ee6e891cd f 2026-03-04 14:10:30 +08:00
Mrx
44b5f6b145 f 2026-03-04 13:20:44 +08:00
Mrx
677b7362cf f 2026-03-04 13:19:55 +08:00
Mrx
02dbc02fe8 f 2026-03-04 12:59:45 +08:00
Mrx
374143995e f 2026-03-04 12:41:08 +08:00
Mrx
7a957a6b87 f 2026-03-04 12:39:01 +08:00
Mrx
c885d562ee f 2026-03-02 19:21:23 +08:00
Mrx
9f36cd8b63 f 2026-03-02 12:21:25 +08:00
Mrx
4122f874fc f 2026-03-02 12:16:27 +08:00
Mrx
9a32387b21 f 2026-03-02 11:56:47 +08:00
Mrx
7bf9150cfc f 2026-03-02 11:39:53 +08:00
Mrx
fecd5a38fd Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-03-02 11:38:30 +08:00
Mrx
2636d9dff6 f 2026-03-02 11:38:19 +08:00
927b08b871 f 2026-02-28 14:49:42 +08:00
dedd4a60a4 f 2026-02-28 14:05:54 +08:00
a54a19e439 f 2026-02-27 16:49:45 +08:00
6dd392f673 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-02-27 16:43:06 +08:00
8d5da9d88e f 2026-02-27 16:42:38 +08:00
30 changed files with 878 additions and 273 deletions

View File

@@ -276,6 +276,8 @@ wallet:
default_credit_limit: 50.00 default_credit_limit: 50.00
min_amount: "100.00" # 生产环境最低充值金额 min_amount: "100.00" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
# 支付宝充值赠送配置 # 支付宝充值赠送配置
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 1000.00 # 充值1000元 - recharge_amount: 1000.00 # 充值1000元

View File

@@ -108,6 +108,8 @@ wallet:
default_credit_limit: 0.01 default_credit_limit: 0.01
min_amount: "0.01" # 生产环境最低充值金额 min_amount: "0.01" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
# 支付宝充值赠送配置 # 支付宝充值赠送配置
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 0.01 # 充值1000元 - recharge_amount: 0.01 # 充值1000元

View File

@@ -109,7 +109,9 @@ wallet:
default_credit_limit: 50.00 default_credit_limit: 50.00
min_amount: "100.00" # 生产环境最低充值金额 min_amount: "100.00" # 生产环境最低充值金额
max_amount: "100000.00" # 单次最高充值金额 max_amount: "100000.00" # 单次最高充值金额
# 支付宝充值赠送配置 recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
# 支付宝充值赠送配置recharge_bonus_enabled 为 true 时生效)
alipay_recharge_bonus: alipay_recharge_bonus:
- recharge_amount: 1000.00 # 充值1000元 - recharge_amount: 1000.00 # 充值1000元
bonus_amount: 50.00 # 赠送50元 bonus_amount: 50.00 # 赠送50元

View File

@@ -609,8 +609,6 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
// 转换为响应DTO // 转换为响应DTO
var items []dto.ApiCallRecordResponse var items []dto.ApiCallRecordResponse
for _, call := range calls { for _, call := range calls {
// 出于安全考虑,不再在数据库中存储或解密真实请求参数
// 这里只保留数据库中的原始占位值(通常为空字符串)
requestParamsStr := call.RequestParams requestParamsStr := call.RequestParams
item := dto.ApiCallRecordResponse{ item := dto.ApiCallRecordResponse{

View File

@@ -110,6 +110,8 @@ type AlipayRechargeOrderResponse struct {
type RechargeConfigResponse struct { type RechargeConfigResponse struct {
MinAmount string `json:"min_amount"` // 最低充值金额 MinAmount string `json:"min_amount"` // 最低充值金额
MaxAmount string `json:"max_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"` AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
} }

View File

@@ -1337,16 +1337,25 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
// GetRechargeConfig 获取充值配置 // GetRechargeConfig 获取充值配置
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) { func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus)) 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 { for _, rule := range s.config.Wallet.AliPayRechargeBonus {
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{ bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
RechargeAmount: rule.RechargeAmount, RechargeAmount: rule.RechargeAmount,
BonusAmount: rule.BonusAmount, BonusAmount: rule.BonusAmount,
}) })
} }
}
tip := s.config.Wallet.ApiStoreRechargeTip
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
}
return &responses.RechargeConfigResponse{ return &responses.RechargeConfigResponse{
MinAmount: s.config.Wallet.MinAmount, MinAmount: s.config.Wallet.MinAmount,
MaxAmount: s.config.Wallet.MaxAmount, MaxAmount: s.config.Wallet.MaxAmount,
RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
ApiStoreRechargeTip: tip,
AlipayRechargeBonus: bonus, AlipayRechargeBonus: bonus,
}, nil }, nil
} }
@@ -1651,9 +1660,9 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
return nil return nil
} }
// 计算充值赠送金额(复用支付宝的赠送逻辑) // 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制
bonusAmount := decimal.Zero 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-- { for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
rule := s.config.Wallet.AliPayRechargeBonus[i] rule := s.config.Wallet.AliPayRechargeBonus[i]
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) { if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {

View File

@@ -334,6 +334,8 @@ type WalletConfig struct {
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"` DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
MinAmount string `mapstructure:"min_amount"` // 最低充值金额 MinAmount string `mapstructure:"min_amount"` // 最低充值金额
MaxAmount string `mapstructure:"max_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"` AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"` BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
} }

View File

@@ -113,6 +113,19 @@ type QYGL2ACDReq struct {
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"` LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"` 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 { type YYSYK9R4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` 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"` 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 { type QCXG9F5CReq struct {
PlateNo string `json:"plate_no" validate:"required"` PlateNo string `json:"plate_no" validate:"required"`
} }
@@ -403,7 +420,7 @@ type COMENT01Req struct {
} }
type JRZQ09J8Req 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"` IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"` 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"` 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 { type YYSY8B1CReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` 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 { type YYSY6D9AReq struct {
MobileNo string `json:"mobile_no" validate:"required,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"` IDCard string `json:"id_card" validate:"required,validIDCard"`

View File

@@ -80,7 +80,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
AccessId: accessId, AccessId: accessId,
TransactionId: GenerateTransactionID(), TransactionId: GenerateTransactionID(),
ClientIp: clientIp, ClientIp: clientIp,
RequestParams: "", RequestParams: requestParams,
Status: ApiCallStatusPending, Status: ApiCallStatusPending,
StartAt: time.Now(), StartAt: time.Now(),
}, nil }, nil

View File

@@ -149,6 +149,8 @@ func registerAllProcessors(combService *comb.CombService) {
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版 "JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1 "JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询2 "JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询2
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
// QYGL系列处理器 // QYGL系列处理器
"QYGL8261": qygl.ProcessQYGL8261Request, "QYGL8261": qygl.ProcessQYGL8261Request,
@@ -178,6 +180,9 @@ func registerAllProcessors(combService *comb.CombService) {
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息 "QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透 "QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I "QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
"YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖
// YYSY系列处理器 // YYSY系列处理器
"YYSYD50F": yysy.ProcessYYSYD50FRequest, "YYSYD50F": yysy.ProcessYYSYD50FRequest,
@@ -248,6 +253,8 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版 "IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测 "IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二 "IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
// COMB系列处理器 - 只注册有自定义逻辑的组合包 // COMB系列处理器 - 只注册有自定义逻辑的组合包
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode "COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode

View File

@@ -255,6 +255,13 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版 "YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验ETC10093 月更 "QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验ETC10093 月更
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询2 "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 // 优先返回已配置的DTO
@@ -438,6 +445,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"ent_name": "企业名称", "ent_name": "企业名称",
"legal_person": "法人姓名", "legal_person": "法人姓名",
"ent_code": "企业代码", "ent_code": "企业代码",
"ent_reg_no": "企业注册号",
"auth_date": "授权日期", "auth_date": "授权日期",
"date_range": "日期范围", "date_range": "日期范围",
"time_range": "时间范围", "time_range": "时间范围",
@@ -459,7 +467,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"plate_type": "号牌类型", "plate_type": "号牌类型",
"vin_code": "车辆识别代号VIN码", "vin_code": "车辆识别代号VIN码",
"return_type": "返回类型", "return_type": "返回类型",
"photo_data": "人脸图片", "photo_data": "入参图片base64编码",
"owner_type": "企业主类型", "owner_type": "企业主类型",
"type": "查询类型", "type": "查询类型",
"query_reason_id": "查询原因ID", "query_reason_id": "查询原因ID",
@@ -471,7 +479,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"notice_model": "车辆型号", "notice_model": "车辆型号",
"vlphoto_data": "行驶证图片", "vlphoto_data": "行驶证图片",
"carplate_type": "车辆号牌类型", "carplate_type": "车辆号牌类型",
"image_url": "行驶证图片地址", "image_url": "入参图片地址",
"reg_url": "车辆登记证图片地址", "reg_url": "车辆登记证图片地址",
"token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询", "token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询",
"vehicle_name": "车型名称", "vehicle_name": "车型名称",
@@ -500,6 +508,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
"ent_name": "示例企业有限公司", "ent_name": "示例企业有限公司",
"legal_person": "王五", "legal_person": "王五",
"ent_code": "91110000123456789X", "ent_code": "91110000123456789X",
"ent_reg_no": "110000000123456",
"auth_date": "20240101-20241231", "auth_date": "20240101-20241231",
"date_range": "20240101-20241231", "date_range": "20240101-20241231",
"time_range": "09:00-18:00", "time_range": "09:00-18:00",
@@ -571,6 +580,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
"ent_name": "请输入企业全称", "ent_name": "请输入企业全称",
"legal_person": "请输入法人真实姓名", "legal_person": "请输入法人真实姓名",
"ent_code": "请输入统一社会信用代码", "ent_code": "请输入统一社会信用代码",
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
"auth_date": "请输入授权日期范围YYYYMMDD-YYYYMMDD", "auth_date": "请输入授权日期范围YYYYMMDD-YYYYMMDD",
"date_range": "请输入日期范围YYYYMMDD-YYYYMMDD", "date_range": "请输入日期范围YYYYMMDD-YYYYMMDD",
"time_range": "请输入时间范围HH:MM-HH:MM", "time_range": "请输入时间范围HH:MM-HH:MM",
@@ -592,7 +602,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
"plate_type": "请选择号牌类型01或02", "plate_type": "请选择号牌类型01或02",
"vin_code": "请输入17位车辆识别代号VIN码", "vin_code": "请输入17位车辆识别代号VIN码",
"return_type": "请选择返回类型", "return_type": "请选择返回类型",
"photo_data": "请输入base64编码的人脸图片支持JPG、BMP、PNG格式", "photo_data": "请输入base64编码的入参图片支持JPG、BMP、PNG格式",
"ownerType": "请选择企业主类型", "ownerType": "请选择企业主类型",
"type": "请选择查询类型", "type": "请选择查询类型",
"query_reason_id": "请选择查询原因ID", "query_reason_id": "请选择查询原因ID",
@@ -644,6 +654,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
"ent_name": "请输入企业全称", "ent_name": "请输入企业全称",
"legal_person": "请输入法人真实姓名", "legal_person": "请输入法人真实姓名",
"ent_code": "请输入统一社会信用代码", "ent_code": "请输入统一社会信用代码",
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
"auth_date": "请输入授权日期范围格式YYYYMMDD-YYYYMMDD且日期范围必须包括今天", "auth_date": "请输入授权日期范围格式YYYYMMDD-YYYYMMDD且日期范围必须包括今天",
"date_range": "请输入日期范围格式YYYYMMDD-YYYYMMDD", "date_range": "请输入日期范围格式YYYYMMDD-YYYYMMDD",
"time_range": "请输入时间范围格式HH:MM-HH:MM", "time_range": "请输入时间范围格式HH:MM-HH:MM",
@@ -665,7 +676,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
"plate_type": "号牌类型01-小型汽车02-大型汽车(可选)", "plate_type": "号牌类型01-小型汽车02-大型汽车(可选)",
"vin_code": "请输入17位车辆识别代号VIN码Vehicle Identification Number", "vin_code": "请输入17位车辆识别代号VIN码Vehicle Identification Number",
"return_type": "返回类型1-专业和学校名称数据返回编码形式默认2-专业和学校名称数据返回中文名称", "return_type": "返回类型1-专业和学校名称数据返回编码形式默认2-专业和学校名称数据返回中文名称",
"photo_data": "人脸图片(必填)base64编码的图片数据仅支持JPG、BMP、PNG三种格式", "photo_data": "入参图片base64编码的图片数据仅支持JPG、BMP、PNG三种格式",
"owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他", "owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他",
"type": "查询类型per-人员ent-企业 ", "type": "查询类型per-人员ent-企业 ",
"query_reason_id": "查询原因ID1-授信审批2-贷中管理3-贷后管理4-异议处理5-担保查询6-租赁资质审查7-融资租赁审批8-借贷撮合查询9-保险审批10-资质审核11-风控审核12-企业背调", "query_reason_id": "查询原因ID1-授信审批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": "车辆型号", "notice_model": "车辆型号",
"vlphoto_data": "行驶证图片:base64编码的图片数据仅支持JPG、BMP、PNG三种格式", "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-新能源小型车", "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地址", "reg_url": "车辆登记证图片地址非必填请提供车辆登记证的图片URL地址",
"token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询", "token": "token采集及获取结果时所使用的凭证有效期2个小时在此时效内应用侧可以发起采集请求重复的采集所触发的结果会被忽略和结果查询",
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版", "vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",

View File

@@ -1524,7 +1524,7 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
} }
detail["belongRisks"] = belongRisks detail["belongRisks"] = belongRisks
// 公安重点人员核验产品highRiskFlag keyPersonCheckList // 公安重点人员核验产品highRiskFlag keyPersonCheckList 五项是否命中决定
flxgdea9Data := getMapValue(apiData, "FLXGDEA9") flxgdea9Data := getMapValue(apiData, "FLXGDEA9")
keyPersonCheckList := make(map[string]interface{}) keyPersonCheckList := make(map[string]interface{})
keyPersonCheckList["num"] = "1" keyPersonCheckList["num"] = "1"
@@ -1538,7 +1538,6 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok { if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok {
level, ok := flxgdea9Map["level"].(string) level, ok := flxgdea9Map["level"].(string)
if !ok { if !ok {
// 尝试从其他可能的字段获取
if levelVal, exists := flxgdea9Map["level"]; exists { if levelVal, exists := flxgdea9Map["level"]; exists {
if levelStr, ok := levelVal.(string); ok { if levelStr, ok := levelVal.(string); ok {
level = levelStr level = levelStr
@@ -1548,19 +1547,14 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
} }
} }
// 仅根据 level 解析并填充 keyPersonCheckList 五项
if level != "" && level != "0" { if level != "" && level != "0" {
// 有风险设置highRiskFlag为1高风险
detail["highRiskFlag"] = 1
// 解析level字段填充keyPersonCheckList
levelParts := strings.Split(level, ",") levelParts := strings.Split(level, ",")
for _, part := range levelParts { for _, part := range levelParts {
part = strings.TrimSpace(part) part = strings.TrimSpace(part)
if part == "" || part == "0" { if part == "" || part == "0" {
continue continue
} }
// 根据level代码判断风险类型
if strings.HasPrefix(part, "A") { if strings.HasPrefix(part, "A") {
keyPersonCheckList["fontFlag"] = 1 keyPersonCheckList["fontFlag"] = 1
} }
@@ -1577,14 +1571,27 @@ func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Log
keyPersonCheckList["sheJiaoTongFlag"] = 1 keyPersonCheckList["sheJiaoTongFlag"] = 1
} }
} }
} else {
// 无风险设置highRiskFlag为2低风险
detail["highRiskFlag"] = 2
} }
} }
} }
detail["keyPersonCheckList"] = keyPersonCheckList 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 { if _, exists := detail["sjsysFlag"]; !exists {
detail["sjsysFlag"] = 0 detail["sjsysFlag"] = 0
@@ -3862,40 +3869,73 @@ func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger)
assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum) assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum)
} }
// 判断风险标识:如果有申请记录,设置为高风险 // 仅使用近12月Last12对应 m12的次数作为判断依据
hasApplication := false // totalCount 取身份证维度和手机号维度中的较大值,而不是两者相加
for _, apiPeriod := range periodMap { totalCount := 0
fieldName := fmt.Sprintf("alc_%s_id_allnum", apiPeriod)
// 尝试多种类型string、int、float64 idKey12 := "alc_m12_id_allnum"
if allnum, ok := jrzq1d09Map[fieldName].(string); ok && allnum != "" && allnum != "0" { cellKey12 := "alc_m12_cell_allnum"
hasApplication = true
break idCount := 0
} else if allnum, ok := jrzq1d09Map[fieldName].(int); ok && allnum > 0 { cellCount := 0
hasApplication = true
break // 身份证维度近12月
} else if allnum, ok := jrzq1d09Map[fieldName].(float64); ok && allnum > 0 { if v, ok := jrzq1d09Map[idKey12]; ok {
hasApplication = true switch vv := v.(type) {
break case string:
} if vv != "" && vv != "0" {
// 也检查cell字段 if parsed, err := strconv.Atoi(vv); err == nil {
fieldNameCell := fmt.Sprintf("alc_%s_cell_allnum", apiPeriod) idCount = parsed
if allnum, ok := jrzq1d09Map[fieldNameCell].(string); ok && allnum != "" && allnum != "0" {
hasApplication = true
break
} else if allnum, ok := jrzq1d09Map[fieldNameCell].(int); ok && allnum > 0 {
hasApplication = true
break
} else if allnum, ok := jrzq1d09Map[fieldNameCell].(float64); ok && allnum > 0 {
hasApplication = true
break
} }
} }
// 风险标识:如果有申请记录,风险较高;如果没有申请记录,风险较低 case float64:
// 注意riskFlag的值0=未查得1=低风险2=高风险 if vv > 0 {
if hasApplication { idCount = int(vv)
assessment["riskFlag"] = 2 // 有申请记录,风险较高 }
case int:
if vv > 0 {
idCount = vv
}
}
}
// 手机号维度近12月
if v, ok := jrzq1d09Map[cellKey12]; ok {
switch vv := v.(type) {
case string:
if vv != "" && vv != "0" {
if parsed, err := strconv.Atoi(vv); err == nil {
cellCount = parsed
}
}
case float64:
if vv > 0 {
cellCount = int(vv)
}
case int:
if vv > 0 {
cellCount = vv
}
}
}
// 使用身份证和手机号两个维度中的较大值作为近12月总次数
if idCount >= cellCount {
totalCount = idCount
} else { } else {
assessment["riskFlag"] = 1 // 无申请记录,风险较低 totalCount = cellCount
}
// 根据近12月总申请次数设置风险标识
// - totalCount == 0 -> 0 无风险 / 未查得
// - 0 < totalCount <= 10 -> 2 低风险(有少量租赁申请)
// - totalCount > 10 -> 1 高风险(租赁申请很多)
if totalCount == 0 {
assessment["riskFlag"] = 0
} else if totalCount <= 10 {
assessment["riskFlag"] = 2
} else {
assessment["riskFlag"] = 1
} }
} }
} }
@@ -3912,6 +3952,22 @@ func getMapValue(data map[string]interface{}, key string) interface{} {
return nil 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 { func maskName(name string) string {
if name == "" { if name == "" {
return "" return ""
@@ -3955,8 +4011,10 @@ func calculateAgeAndSex(idCard string) (int, string) {
return 0, "" return 0, ""
} }
now := time.Now()
// 计算年龄(简化处理,使用当前年份) // 计算年龄(简化处理,使用当前年份)
age := 2024 - year age := now.Year() - year
// 提取性别第17位奇数为男偶数为女 // 提取性别第17位奇数为男偶数为女
sexCode := idCard[16:17] sexCode := idCard[16:17]
@@ -4114,7 +4172,7 @@ func extractProvinceFromAddress(address string) string {
// 处理"XX省"格式(最后处理,因为可能与其他模式冲突) // 处理"XX省"格式(最后处理,因为可能与其他模式冲突)
{"省", func(addr string) string { {"省", func(addr string) string {
if idx := strings.Index(addr, "省"); idx > 0 { if idx := strings.Index(addr, "省"); idx > 0 {
return addr[:idx+1] return addr[:idx+len("省")]
} }
return "" return ""
}}, }},

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -21,20 +21,23 @@ func ProcessQYGL5S1IRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil { if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err) 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)
} }
// 优先使用企业名称,否则使用统一信用代码 // 按企业名称时传 enterpriseNo加密名按统一信用代码时传 enterpriseName加密代码
var enterpriseNo, enterpriseName string reqData := map[string]interface{}{}
if paramsDto.EntName != "" { if paramsDto.EntName != "" {
enterpriseName = paramsDto.EntName reqData["enterpriseName"] = encryptedEntName
} else {
enterpriseNo = paramsDto.EntCode
} }
reqData := map[string]interface{}{ if paramsDto.EntCode != "" {
"enterpriseNo": enterpriseNo, reqData["enterpriseNo"] = encryptedEntCode
"enterpriseName": enterpriseName,
} }
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI088", reqData) respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI088", reqData)

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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字节 // 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData) respBytes, err := json.Marshal(respData)
if err != nil { if err != nil {

View File

@@ -15,9 +15,9 @@ import (
"tyapi-server/internal/shared/interfaces" "tyapi-server/internal/shared/interfaces"
) )
// calculateAlipayRechargeBonus 计算支付宝充值赠送金额 // calculateAlipayRechargeBonus 计算支付宝充值赠送金额(受 recharge_bonus_enabled 开关控制)
func calculateAlipayRechargeBonus(rechargeAmount decimal.Decimal, walletConfig *config.WalletConfig) decimal.Decimal { 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 return decimal.Zero
} }

View File

@@ -10,8 +10,9 @@ import (
) )
func TestCalculateAlipayRechargeBonus(t *testing.T) { func TestCalculateAlipayRechargeBonus(t *testing.T) {
// 创建测试配置 // 创建测试配置(开启赠送)
walletConfig := &config.WalletConfig{ walletConfig := &config.WalletConfig{
RechargeBonusEnabled: true,
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{ AliPayRechargeBonus: []config.AliPayRechargeBonusRule{
{RechargeAmount: 1000.00, BonusAmount: 50.00}, // 充1000送50 {RechargeAmount: 1000.00, BonusAmount: 50.00}, // 充1000送50
{RechargeAmount: 5000.00, BonusAmount: 300.00}, // 充5000送300 {RechargeAmount: 5000.00, BonusAmount: 300.00}, // 充5000送300
@@ -74,6 +75,7 @@ func TestCalculateAlipayRechargeBonus(t *testing.T) {
func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) { func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
// 测试空配置 // 测试空配置
walletConfig := &config.WalletConfig{ walletConfig := &config.WalletConfig{
RechargeBonusEnabled: true,
AliPayRechargeBonus: []config.AliPayRechargeBonusRule{}, AliPayRechargeBonus: []config.AliPayRechargeBonusRule{},
} }
@@ -85,4 +87,17 @@ func TestCalculateAlipayRechargeBonus_EmptyConfig(t *testing.T) {
assert.True(t, bonus.Equal(decimal.Zero), "nil配置应该返回零赠送金额") 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), "关闭赠送时应返回零")
}

View File

@@ -17,12 +17,50 @@ import (
"tyapi-server/internal/shared/external_logger" "tyapi-server/internal/shared/external_logger"
) )
const (
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
maxLogParamValueLen = 300
)
var ( var (
ErrDatasource = errors.New("数据源异常") ErrDatasource = errors.New("数据源异常")
ErrSystem = errors.New("系统异常") ErrSystem = errors.New("系统异常")
ErrQueryEmpty = 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 通用响应(按实际文档调整) // ShujubaoResp 数据宝 API 通用响应(按实际文档调整)
type ShujubaoResp struct { type ShujubaoResp struct {
Code string `json:"code"` Code string `json:"code"`
@@ -187,7 +225,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
if err != nil { if err != nil {
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, err, params) s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
} }
return nil, err return nil, err
} }
@@ -216,7 +254,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
} }
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, err, params) s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
} }
return nil, err return nil, err
} }
@@ -226,7 +264,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
if err != nil { if err != nil {
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, err, params) s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
} }
return nil, err return nil, err
} }
@@ -239,7 +277,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode)) err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode))
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, err, params) s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
} }
return nil, err 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 { if err := json.Unmarshal(respBody, &shujubaoResp); err != nil {
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err)) err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, err, params) s.logger.LogError(requestID, transactionID, apiPath, err, paramsForLog(params))
} }
return nil, err return nil, err
} }
@@ -261,7 +299,7 @@ func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params ma
if code != "10000" { if code != "10000" {
shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message) shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message)
if s.logger != nil { 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) return nil, errors.Join(ErrDatasource, shujubaoErr)
} }

View File

@@ -16,12 +16,52 @@ import (
"tyapi-server/internal/shared/external_logger" "tyapi-server/internal/shared/external_logger"
) )
const (
// 错误日志中单条入参值的最大长度,避免 base64 等长内容打满日志
maxLogParamValueLen = 300
// 错误日志中 response_body 的最大长度
maxLogResponseBodyLen = 500
)
var ( var (
ErrDatasource = errors.New("数据源异常") ErrDatasource = errors.New("数据源异常")
ErrSystem = errors.New("系统异常") ErrSystem = errors.New("系统异常")
ErrNotFound = 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 通用响应(占位,按实际文档调整) // ShumaiResponse 数脉 API 通用响应(占位,按实际文档调整)
type ShumaiResponse struct { type ShumaiResponse struct {
Code int `json:"code"` // 状态码 Code int `json:"code"` // 状态码
@@ -188,7 +228,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
if err != nil { if err != nil {
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
if s.logger != nil { 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 return nil, err
} }
@@ -216,7 +256,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
} }
if s.logger != nil { 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 return nil, err
} }
@@ -227,7 +267,7 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
if err != nil { if err != nil {
err = errors.Join(ErrSystem, err) err = errors.Join(ErrSystem, err)
if s.logger != nil { 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 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)) err = errors.Join(ErrDatasource, fmt.Errorf("HTTP %d", resp.StatusCode))
if s.logger != nil { if s.logger != nil {
errorPayload := map[string]interface{}{ errorPayload := map[string]interface{}{
"request_params": reqFormData, "request_params": requestParamsForLog(reqFormData),
"response_body": string(raw), "response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
} }
s.logger.LogError(requestID, transactionID, apiPath, err, errorPayload) 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)) parseErr := errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, parseErr, map[string]interface{}{ s.logger.LogError(requestID, transactionID, apiPath, parseErr, map[string]interface{}{
"request_params": reqFormData, "request_params": requestParamsForLog(reqFormData),
"response_body": string(raw), "response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
}) })
} }
return nil, parseErr return nil, parseErr
@@ -273,8 +313,8 @@ func (s *ShumaiService) CallAPIForm(ctx context.Context, apiPath string, reqForm
} }
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, shumaiErr, map[string]interface{}{ s.logger.LogError(requestID, transactionID, apiPath, shumaiErr, map[string]interface{}{
"request_params": reqFormData, "request_params": requestParamsForLog(reqFormData),
"response_body": string(raw), "response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
}) })
} }
if shumaiErr.IsNoRecord() { 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)) marshalErr := errors.Join(ErrSystem, fmt.Errorf("data 序列化失败: %w", err))
if s.logger != nil { if s.logger != nil {
s.logger.LogError(requestID, transactionID, apiPath, marshalErr, map[string]interface{}{ s.logger.LogError(requestID, transactionID, apiPath, marshalErr, map[string]interface{}{
"request_params": reqFormData, "request_params": requestParamsForLog(reqFormData),
"response_body": string(raw), "response_body": truncateForLog(string(raw), maxLogResponseBodyLen),
}) })
} }
return nil, marshalErr return nil, marshalErr

View File

@@ -210,8 +210,13 @@ func (z *ZhichaService) CallAPI(ctx context.Context, proID string, params map[st
return nil, ErrDatasource return nil, ErrDatasource
} }
// 201 表示查询为空,返回空对象 // 201 表示查询为空,兼容其它情况如果data也为空返回空对象
if zhichaResp.Code == "201" { if zhichaResp.Code == "201" {
// 先做类型断言
dataMap, ok := zhichaResp.Data.(map[string]interface{})
if ok && len(dataMap) > 0 {
return dataMap, nil
}
return map[string]interface{}{}, nil return map[string]interface{}{}, nil
} }

View File

@@ -84,20 +84,25 @@ func TestEncryptWithInvalidKey(t *testing.T) {
} }
func TestDecryptWithInvalidData(t *testing.T) { func TestDecryptWithInvalidData(t *testing.T) {
key := "1234567890abcdef1234567890abcdef" key := "af4ca0098e6a202a5c08c413ebd9fd62"
// 测试无效的加密数据 // 测试无效的加密数据
invalidData := []string{ invalidData := []string{
"", // 空数据 "", // 空数据
"invalid_base64", // 无效的Base64 "invalid_base64", // 无效的Base64
"dGVzdA==", // 有效的Base64但不是AES加密数据 "dGVzdA==", // 有效的Base64但不是AES加密数据
"i96w+SDjwENjuvsokMFbLw==",
"oaihmICgEcszWMk0gXoB12E/ygF4g78x0/sC3/KHnBk=",
"5bx+WvXvdNRVVOp9UuNFHg==",
} }
for _, data := range invalidData { for _, data := range invalidData {
_, err := Decrypt(data, key) decrypted, err := Decrypt(data, key)
if err == nil { if err == nil {
t.Errorf("使用无效数据 %s 应该返回错误", data) t.Errorf("使用无效数据 %s 应该返回错误", data)
} }
fmt.Println("data: ", data)
fmt.Println("decrypted: ", decrypted)
} }
} }

Binary file not shown.