f
This commit is contained in:
37
config.yaml
37
config.yaml
@@ -593,3 +593,40 @@ shumai:
|
|||||||
max_backups: 5
|
max_backups: 5
|
||||||
max_age: 30
|
max_age: 30
|
||||||
compress: true
|
compress: true
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# ✨ 数据宝配置走实时接口
|
||||||
|
# ===========================================
|
||||||
|
shujubao:
|
||||||
|
url: "https://api.chinadatapay.com"
|
||||||
|
app_secret: "iOk0ALBX0BSdTSTf"
|
||||||
|
sign_method: "md5" # 签名方法:md5 或 hmac,默认 hmac
|
||||||
|
timeout: 60s # 请求超时时间,默认 60 秒
|
||||||
|
# 数据宝日志配置
|
||||||
|
logging:
|
||||||
|
enabled: true
|
||||||
|
log_dir: "logs/external_services"
|
||||||
|
service_name: "shujubao"
|
||||||
|
use_daily: true
|
||||||
|
enable_level_separation: true
|
||||||
|
# 各级别配置
|
||||||
|
level_configs:
|
||||||
|
info:
|
||||||
|
max_size: 100
|
||||||
|
max_backups: 5
|
||||||
|
max_age: 30
|
||||||
|
compress: true
|
||||||
|
error:
|
||||||
|
max_size: 200
|
||||||
|
max_backups: 10
|
||||||
|
max_age: 90
|
||||||
|
compress: true
|
||||||
|
warn:
|
||||||
|
max_size: 100
|
||||||
|
max_backups: 5
|
||||||
|
max_age: 30
|
||||||
|
compress: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type Config struct {
|
|||||||
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
||||||
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
||||||
Shumai ShumaiConfig `mapstructure:"shumai"`
|
Shumai ShumaiConfig `mapstructure:"shumai"`
|
||||||
|
Shujubao ShujubaoConfig `mapstructure:"shujubao"`
|
||||||
PDFGen PDFGenConfig `mapstructure:"pdfgen"`
|
PDFGen PDFGenConfig `mapstructure:"pdfgen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,6 +583,33 @@ type ShumaiLevelFileConfig struct {
|
|||||||
Compress bool `mapstructure:"compress"`
|
Compress bool `mapstructure:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShujubaoConfig 数据宝配置
|
||||||
|
type ShujubaoConfig struct {
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
AppSecret string `mapstructure:"app_secret"`
|
||||||
|
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||||
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
|
|
||||||
|
Logging ShujubaoLoggingConfig `mapstructure:"logging"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShujubaoLoggingConfig 数据宝日志配置
|
||||||
|
type ShujubaoLoggingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
LogDir string `mapstructure:"log_dir"`
|
||||||
|
UseDaily bool `mapstructure:"use_daily"`
|
||||||
|
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||||
|
LevelConfigs map[string]ShujubaoLevelFileConfig `mapstructure:"level_configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShujubaoLevelFileConfig 数据宝级别文件配置
|
||||||
|
type ShujubaoLevelFileConfig struct {
|
||||||
|
MaxSize int `mapstructure:"max_size"`
|
||||||
|
MaxBackups int `mapstructure:"max_backups"`
|
||||||
|
MaxAge int `mapstructure:"max_age"`
|
||||||
|
Compress bool `mapstructure:"compress"`
|
||||||
|
}
|
||||||
|
|
||||||
// PDFGenConfig PDF生成服务配置
|
// PDFGenConfig PDF生成服务配置
|
||||||
type PDFGenConfig struct {
|
type PDFGenConfig struct {
|
||||||
DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址
|
DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
"tyapi-server/internal/infrastructure/external/ocr"
|
"tyapi-server/internal/infrastructure/external/ocr"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
"tyapi-server/internal/infrastructure/external/shumai"
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
"tyapi-server/internal/infrastructure/external/sms"
|
"tyapi-server/internal/infrastructure/external/sms"
|
||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
@@ -375,6 +376,10 @@ func NewContainer() *Container {
|
|||||||
func(cfg *config.Config) (*shumai.ShumaiService, error) {
|
func(cfg *config.Config) (*shumai.ShumaiService, error) {
|
||||||
return shumai.NewShumaiServiceWithConfig(cfg)
|
return shumai.NewShumaiServiceWithConfig(cfg)
|
||||||
},
|
},
|
||||||
|
// ShujubaoService - 数据宝服务
|
||||||
|
func(cfg *config.Config) (*shujubao.ShujubaoService, error) {
|
||||||
|
return shujubao.NewShujubaoServiceWithConfig(cfg)
|
||||||
|
},
|
||||||
func(cfg *config.Config) *yushan.YushanService {
|
func(cfg *config.Config) *yushan.YushanService {
|
||||||
return yushan.NewYushanService(
|
return yushan.NewYushanService(
|
||||||
cfg.Yushan.URL,
|
cfg.Yushan.URL,
|
||||||
|
|||||||
@@ -100,11 +100,44 @@ type JRZQDCBEReq struct {
|
|||||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
|
type JRZQACABReq struct {
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// shujubao
|
||||||
type QYGL2ACDReq struct {
|
type QYGL2ACDReq struct {
|
||||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||||
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 YYSYK9R4Req struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG9F5CReq struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXG3B8ZReq struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXM7R9Req struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXGP1W3Req struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXG5U0ZReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXGY7F2Req struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
}
|
||||||
type QYGL6F2DReq struct {
|
type QYGL6F2DReq struct {
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
"tyapi-server/internal/infrastructure/external/shumai"
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
@@ -53,6 +54,7 @@ type ApiRequestService struct {
|
|||||||
|
|
||||||
func NewApiRequestService(
|
func NewApiRequestService(
|
||||||
westDexService *westdex.WestDexService,
|
westDexService *westdex.WestDexService,
|
||||||
|
shujubaoService *shujubao.ShujubaoService,
|
||||||
muziService *muzi.MuziService,
|
muziService *muzi.MuziService,
|
||||||
yushanService *yushan.YushanService,
|
yushanService *yushan.YushanService,
|
||||||
tianYanChaService *tianyancha.TianYanChaService,
|
tianYanChaService *tianyancha.TianYanChaService,
|
||||||
@@ -69,7 +71,7 @@ func NewApiRequestService(
|
|||||||
combService := comb.NewCombService(productManagementService)
|
combService := comb.NewCombService(productManagementService)
|
||||||
|
|
||||||
// 创建处理器依赖容器
|
// 创建处理器依赖容器
|
||||||
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, shumaiService, validator, combService)
|
processorDeps := processors.NewProcessorDependencies(westDexService, shujubaoService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, shumaiService, validator, combService)
|
||||||
|
|
||||||
// 统一注册所有处理器
|
// 统一注册所有处理器
|
||||||
registerAllProcessors(combService)
|
registerAllProcessors(combService)
|
||||||
@@ -121,6 +123,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||||
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
|
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
|
||||||
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
|
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
|
||||||
|
"JRZQACAB": jrzq.ProcessJRZQACABERequest, // 银行卡四要素
|
||||||
"JRZQ09J8": jrzq.ProcessJRZQ09J8Request,
|
"JRZQ09J8": jrzq.ProcessJRZQ09J8Request,
|
||||||
"JRZQ1D09": jrzq.ProcessJRZQ1D09Request,
|
"JRZQ1D09": jrzq.ProcessJRZQ1D09Request,
|
||||||
"JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest,
|
"JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest,
|
||||||
@@ -266,7 +269,14 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
||||||
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
||||||
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
||||||
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, // 车辆里程记录(混合查询)
|
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, //
|
||||||
|
"QCXG9F5C": qcxg.ProcessQCXG9F5CERequest, //疑似营运车辆注册平台数 10386
|
||||||
|
"QCXG3B8Z": qcxg.ProcessQCXG3B8ZRequest, //疑似运营车辆查询(月度里程)10268
|
||||||
|
"QCXGP1W3": qcxg.ProcessQCXGP1W3Request, //疑似运营车辆查询(季度里程)10269
|
||||||
|
"QCXM7R9": qcxg.ProcessQCXM7R9Request, //疑似运营车辆查询(半年度里程)10270
|
||||||
|
"QCXGH1E6": qcxg.ProcessQCXGH1E6Request, // 全国车辆VIN基础信息查验 9054
|
||||||
|
"QCXG5U0Z": qcxg.ProcessQCXG5U0ZRequest, // 车辆静态信息查询 10479
|
||||||
|
"QCXGY7F2": qcxg.ProcessQCXGY7F2Request, // 二手车VIN估值 10443
|
||||||
|
|
||||||
// DWBG系列处理器 - 多维报告
|
// DWBG系列处理器 - 多维报告
|
||||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||||
|
|||||||
@@ -244,6 +244,15 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
|
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
|
||||||
"PDFG01GZ": &dto.PDFG01GZReq{}, //
|
"PDFG01GZ": &dto.PDFG01GZReq{}, //
|
||||||
"QYGL5S1I": &dto.QYGL5S1IReq{}, //企业司法涉诉V2
|
"QYGL5S1I": &dto.QYGL5S1IReq{}, //企业司法涉诉V2
|
||||||
|
"JRZQACAB": &dto.JRZQACABReq{}, //银行卡四要素
|
||||||
|
"QCXG9F5C": &dto.QCXG9F5CReq{}, //疑似营运车辆注册平台数 10386
|
||||||
|
"QCXG3B8Z": &dto.QCXG3B8ZReq{}, //疑似运营车辆查询(月度里程)10268
|
||||||
|
"QCXGP1W3": &dto.QCXGP1W3Req{}, //疑似运营车辆查询(季度里程)10269
|
||||||
|
"QCXM7R9": &dto.QCXM7R9Req{}, //疑似运营车辆查询(半年度里程)10270
|
||||||
|
"QCXGH1E6": &dto.QCXGH1E6Req{}, //全国车辆VIN基础信息查验 9054
|
||||||
|
"QCXG5U0Z": &dto.QCXG5U0ZReq{}, //车辆静态信息查询 10479
|
||||||
|
"QCXGY7F2": &dto.QCXGY7F2Req{}, //二手车VIN估值 10443
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先返回已配置的DTO
|
// 优先返回已配置的DTO
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/shumai"
|
"tyapi-server/internal/infrastructure/external/shumai"
|
||||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
"tyapi-server/internal/infrastructure/external/yushan"
|
"tyapi-server/internal/infrastructure/external/yushan"
|
||||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
@@ -28,6 +29,7 @@ type CallContext struct {
|
|||||||
// ProcessorDependencies 处理器依赖容器
|
// ProcessorDependencies 处理器依赖容器
|
||||||
type ProcessorDependencies struct {
|
type ProcessorDependencies struct {
|
||||||
WestDexService *westdex.WestDexService
|
WestDexService *westdex.WestDexService
|
||||||
|
ShujubaoService *shujubao.ShujubaoService
|
||||||
MuziService *muzi.MuziService
|
MuziService *muzi.MuziService
|
||||||
YushanService *yushan.YushanService
|
YushanService *yushan.YushanService
|
||||||
TianYanChaService *tianyancha.TianYanChaService
|
TianYanChaService *tianyancha.TianYanChaService
|
||||||
@@ -45,6 +47,7 @@ type ProcessorDependencies struct {
|
|||||||
// NewProcessorDependencies 创建处理器依赖容器
|
// NewProcessorDependencies 创建处理器依赖容器
|
||||||
func NewProcessorDependencies(
|
func NewProcessorDependencies(
|
||||||
westDexService *westdex.WestDexService,
|
westDexService *westdex.WestDexService,
|
||||||
|
shujubaoService *shujubao.ShujubaoService,
|
||||||
muziService *muzi.MuziService,
|
muziService *muzi.MuziService,
|
||||||
yushanService *yushan.YushanService,
|
yushanService *yushan.YushanService,
|
||||||
tianYanChaService *tianyancha.TianYanChaService,
|
tianYanChaService *tianyancha.TianYanChaService,
|
||||||
@@ -58,6 +61,7 @@ func NewProcessorDependencies(
|
|||||||
) *ProcessorDependencies {
|
) *ProcessorDependencies {
|
||||||
return &ProcessorDependencies{
|
return &ProcessorDependencies{
|
||||||
WestDexService: westDexService,
|
WestDexService: westDexService,
|
||||||
|
ShujubaoService: shujubaoService,
|
||||||
MuziService: muziService,
|
MuziService: muziService,
|
||||||
YushanService: yushanService,
|
YushanService: yushanService,
|
||||||
TianYanChaService: tianYanChaService,
|
TianYanChaService: tianYanChaService,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQACABERequest JRZQACAB 银行卡四要素 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessJRZQACABERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQACABReq
|
||||||
|
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": "7eb69f73a855e41875e22f139b934c3c",
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"idcard": paramsDto.IDCard,
|
||||||
|
"mobile": paramsDto.MobileNo,
|
||||||
|
"acc_no": paramsDto.BankCard,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/9442"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,45 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG3B8ZRequest QCXG3B8Z 疑似运营车辆查询(月度里程)10268 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQCXG3B8ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG3B8ZReq
|
||||||
|
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": "c94605174cfe29bb2a62e2600b7d1596",
|
||||||
|
"carNo": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/10268"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,43 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG5U0ZRequest QCXG5U0Z 车辆静态信息查询 10479 API 处理方法(使用数据宝服务)
|
||||||
|
func ProcessQCXG5U0ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG5U0ZReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "7c8122677476dd2621f574976f1a9fde",
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
apiPath := "/communication/personal/10479"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,45 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG9F5CERequest QCXG9F5C 疑似营运车辆注册平台数 10386 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQCXG9F5CERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG9F5CReq
|
||||||
|
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": "27ab7048dda23d9a56178a2e5d4300ec",
|
||||||
|
"carNo": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/10386"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,45 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXM7R9Request QCXGM7R9 疑似运营车辆查询(半年度里程)10270 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQCXM7R9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXM7R9Req
|
||||||
|
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": "fc335ea4308add7454ac0858b08bef72",
|
||||||
|
"carNo": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/personal/10270"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,45 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGP1W3Request QCXGP1W3 疑似运营车辆查询(季度里程)10269 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessQCXGP1W3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGP1W3Req
|
||||||
|
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": "ecd6f3485322b0c706fc1dce330fe26e",
|
||||||
|
"carNo": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/10269"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,43 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGY7F2Request QCXGY7F2 二手车VIN估值 10443 API 处理方法(使用数据宝服务)
|
||||||
|
func ProcessQCXGY7F2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGY7F2Req
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqParams := map[string]interface{}{
|
||||||
|
"key": "463cea654a0a99d5d04c62f98ac882c0",
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
apiPath := "/government/traffic/10443"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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,47 @@
|
|||||||
|
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/shujubao"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessYYSYK9R4Request JRZQACAB 全网手机三要素验证1979周更新版 API 处理方法(使用数据宝服务示例)
|
||||||
|
func ProcessYYSYK9R4Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.YYSYK9R4Req
|
||||||
|
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": "c115708d915451da8f34a23e144dda6b",
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"idcard": paramsDto.IDCard,
|
||||||
|
"mobile": paramsDto.MobileNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||||
|
apiPath := "/communication/personal/1979"
|
||||||
|
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shujubao.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, 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
|
||||||
|
}
|
||||||
47
internal/infrastructure/external/shujubao/crypto.go
vendored
Normal file
47
internal/infrastructure/external/shujubao/crypto.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package shujubao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignMethod 签名方法
|
||||||
|
type SignMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SignMethodMD5 SignMethod = "md5"
|
||||||
|
SignMethodHMACMD5 SignMethod = "hmac"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateSignMD5 使用 MD5 生成签名:md5(app_secret + timestamp),32 位小写
|
||||||
|
func GenerateSignMD5(appSecret, timestamp string) string {
|
||||||
|
h := md5.Sum([]byte(appSecret + timestamp))
|
||||||
|
sign := strings.ToLower(hex.EncodeToString(h[:]))
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignHMAC 使用 HMAC-MD5 生成签名(仅 timestamp,兼容旧逻辑)
|
||||||
|
func GenerateSignHMAC(appSecret, timestamp string) string {
|
||||||
|
mac := hmac.New(md5.New, []byte(appSecret))
|
||||||
|
mac.Write([]byte(timestamp))
|
||||||
|
sign := strings.ToLower(hex.EncodeToString(mac.Sum(nil)))
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignFromParamsMD5 根据入参生成签名:入参按 ASCII 排序组合后与 app_secret 做 MD5。
|
||||||
|
// sortedParamStr 格式为 key1=value1&key2=value2&...(key 按字母序)。
|
||||||
|
func GenerateSignFromParamsMD5(appSecret, sortedParamStr string) string {
|
||||||
|
h := md5.Sum([]byte(appSecret + sortedParamStr))
|
||||||
|
sign := strings.ToLower(hex.EncodeToString(h[:]))
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignFromParamsHMAC 根据入参生成签名:入参按 ASCII 排序组合后与 app_secret 做 HMAC-MD5。
|
||||||
|
func GenerateSignFromParamsHMAC(appSecret, sortedParamStr string) string {
|
||||||
|
mac := hmac.New(md5.New, []byte(appSecret))
|
||||||
|
mac.Write([]byte(sortedParamStr))
|
||||||
|
sign := strings.ToLower(hex.EncodeToString(mac.Sum(nil)))
|
||||||
|
return sign
|
||||||
|
}
|
||||||
121
internal/infrastructure/external/shujubao/shujubao_errors.go
vendored
Normal file
121
internal/infrastructure/external/shujubao/shujubao_errors.go
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package shujubao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShujubaoError 数据宝服务错误
|
||||||
|
type ShujubaoError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实现 error 接口
|
||||||
|
func (e *ShujubaoError) Error() string {
|
||||||
|
return fmt.Sprintf("数据宝错误 [%s]: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess 检查是否成功
|
||||||
|
func (e *ShujubaoError) IsSuccess() bool {
|
||||||
|
return e.Code == "200" || e.Code == "0" || e.Code == "10000"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShujubaoError 创建新的数据宝错误
|
||||||
|
func NewShujubaoError(code, message string) *ShujubaoError {
|
||||||
|
return &ShujubaoError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据宝全系统错误码与描述映射(Code -> Desc)
|
||||||
|
var systemErrorCodeDesc = map[string]string{
|
||||||
|
"10000": "成功",
|
||||||
|
"10001": "参数传入有误",
|
||||||
|
"10002": "查询失败",
|
||||||
|
"10003": "系统处理异常",
|
||||||
|
"10004": "系统处理超时",
|
||||||
|
"10005": "服务异常",
|
||||||
|
"10006": "查无",
|
||||||
|
"10020": "同一参数请求次数超限",
|
||||||
|
"99999": "其他错误",
|
||||||
|
"999": "接口处理异常",
|
||||||
|
"000": "key参数不能为空",
|
||||||
|
"001": "找不到这个key",
|
||||||
|
"002": "调用次数已用完",
|
||||||
|
"003": "用户该接口状态不可用",
|
||||||
|
"004": "接口信息不存在",
|
||||||
|
"005": "你没有认证信息",
|
||||||
|
"008": "当前接口只允许“企业认证”通过的账户进行调用,请在数据宝官网个人中心进行企业认证后再进行调用,谢谢!",
|
||||||
|
"009": "触发风控",
|
||||||
|
"011": "接口缺少参数",
|
||||||
|
"012": "没有ip访问权限",
|
||||||
|
"013": "接口模板不存在",
|
||||||
|
"015": "该接口已下架",
|
||||||
|
"020": "调用第三方产生异常",
|
||||||
|
"022": "调用第三方返回的数据格式错误",
|
||||||
|
"025": "你没有购买此接口",
|
||||||
|
"026": "用户信息不存在",
|
||||||
|
"027": "请求第三方地址超时,请稍后再试",
|
||||||
|
"028": "请求第三方地址被拒绝,请稍后再试",
|
||||||
|
"034": "签名不合法",
|
||||||
|
"035": "请求参数加密有误",
|
||||||
|
"036": "验签失败",
|
||||||
|
"037": "timestamp不能为空",
|
||||||
|
"038": "请求繁忙,请稍后联系管理员再试",
|
||||||
|
"039": "请在个人中心接口设置加密状态",
|
||||||
|
"040": "timestamp不合法",
|
||||||
|
"041": "timestamp已过期",
|
||||||
|
"042": "身份证手机号姓名银行卡等不符合规则",
|
||||||
|
"043": "该号段不支持验证",
|
||||||
|
"047": "请在个人中心获取密钥",
|
||||||
|
"048": "找不到这个secretKey",
|
||||||
|
"049": "用户还未申购该产品",
|
||||||
|
"050": "请联系客服开启验签",
|
||||||
|
"051": "超过当日调用次数",
|
||||||
|
"052": "机房限制调用,请联系客服切换其他机房",
|
||||||
|
"053": "系统错误",
|
||||||
|
"054": "token无效",
|
||||||
|
"055": "配置信息未完善,请联系数据宝工作人员",
|
||||||
|
"056": "apiName参数不能为空",
|
||||||
|
"057": "并发量超过限制,请联系客服",
|
||||||
|
"058": "撞库风控预警,请联系客服",
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemErrorDesc 根据错误码获取系统错误描述(支持带 SYSTEM_ 前缀或纯数字)
|
||||||
|
func GetSystemErrorDesc(code string) string {
|
||||||
|
// 去掉 SYSTEM_ 前缀
|
||||||
|
key := code
|
||||||
|
if len(code) > 7 && code[:7] == "SYSTEM_" {
|
||||||
|
key = code[7:]
|
||||||
|
}
|
||||||
|
if desc, ok := systemErrorCodeDesc[key]; ok {
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShujubaoErrorFromCode 根据状态码创建错误
|
||||||
|
func NewShujubaoErrorFromCode(code, message string) *ShujubaoError {
|
||||||
|
if message != "" {
|
||||||
|
return NewShujubaoError(code, message)
|
||||||
|
}
|
||||||
|
if desc := GetSystemErrorDesc(code); desc != "" {
|
||||||
|
return NewShujubaoError(code, desc)
|
||||||
|
}
|
||||||
|
return NewShujubaoError(code, "未知错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsShujubaoError 检查是否是数据宝错误
|
||||||
|
func IsShujubaoError(err error) bool {
|
||||||
|
_, ok := err.(*ShujubaoError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShujubaoError 获取数据宝错误
|
||||||
|
func GetShujubaoError(err error) *ShujubaoError {
|
||||||
|
if shujubaoErr, ok := err.(*ShujubaoError); ok {
|
||||||
|
return shujubaoErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
66
internal/infrastructure/external/shujubao/shujubao_factory.go
vendored
Normal file
66
internal/infrastructure/external/shujubao/shujubao_factory.go
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package shujubao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/config"
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewShujubaoServiceWithConfig 使用配置创建数据宝服务
|
||||||
|
func NewShujubaoServiceWithConfig(cfg *config.Config) (*ShujubaoService, error) {
|
||||||
|
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||||
|
Enabled: cfg.Shujubao.Logging.Enabled,
|
||||||
|
LogDir: cfg.Shujubao.Logging.LogDir,
|
||||||
|
ServiceName: "shujubao",
|
||||||
|
UseDaily: cfg.Shujubao.Logging.UseDaily,
|
||||||
|
EnableLevelSeparation: cfg.Shujubao.Logging.EnableLevelSeparation,
|
||||||
|
LevelConfigs: make(map[string]external_logger.ExternalServiceLevelFileConfig),
|
||||||
|
}
|
||||||
|
for k, v := range cfg.Shujubao.Logging.LevelConfigs {
|
||||||
|
loggingConfig.LevelConfigs[k] = external_logger.ExternalServiceLevelFileConfig{
|
||||||
|
MaxSize: v.MaxSize,
|
||||||
|
MaxBackups: v.MaxBackups,
|
||||||
|
MaxAge: v.MaxAge,
|
||||||
|
Compress: v.Compress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var signMethod SignMethod
|
||||||
|
if cfg.Shujubao.SignMethod == "md5" {
|
||||||
|
signMethod = SignMethodMD5
|
||||||
|
} else {
|
||||||
|
signMethod = SignMethodHMACMD5
|
||||||
|
}
|
||||||
|
timeout := 60 * time.Second
|
||||||
|
if cfg.Shujubao.Timeout > 0 {
|
||||||
|
timeout = cfg.Shujubao.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewShujubaoService(
|
||||||
|
cfg.Shujubao.URL,
|
||||||
|
cfg.Shujubao.AppSecret,
|
||||||
|
signMethod,
|
||||||
|
timeout,
|
||||||
|
logger,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShujubaoServiceWithLogging 使用自定义日志配置创建数据宝服务
|
||||||
|
func NewShujubaoServiceWithLogging(url, appSecret string, signMethod SignMethod, timeout time.Duration, loggingConfig external_logger.ExternalServiceLoggingConfig) (*ShujubaoService, error) {
|
||||||
|
loggingConfig.ServiceName = "shujubao"
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewShujubaoService(url, appSecret, signMethod, timeout, logger), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShujubaoServiceSimple 创建无日志的数据宝服务
|
||||||
|
func NewShujubaoServiceSimple(url, appSecret string, signMethod SignMethod, timeout time.Duration) *ShujubaoService {
|
||||||
|
return NewShujubaoService(url, appSecret, signMethod, timeout, nil)
|
||||||
|
}
|
||||||
265
internal/infrastructure/external/shujubao/shujubao_service.go
vendored
Normal file
265
internal/infrastructure/external/shujubao/shujubao_service.go
vendored
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
package shujubao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDatasource = errors.New("数据源异常")
|
||||||
|
ErrSystem = errors.New("系统异常")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShujubaoResp 数据宝 API 通用响应(按实际文档调整)
|
||||||
|
type ShujubaoResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShujubaoConfig 数据宝服务配置
|
||||||
|
type ShujubaoConfig struct {
|
||||||
|
URL string
|
||||||
|
AppSecret string
|
||||||
|
SignMethod SignMethod
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShujubaoService 数据宝服务
|
||||||
|
type ShujubaoService struct {
|
||||||
|
config ShujubaoConfig
|
||||||
|
logger *external_logger.ExternalServiceLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShujubaoService 创建数据宝服务实例
|
||||||
|
func NewShujubaoService(url, appSecret string, signMethod SignMethod, timeout time.Duration, logger *external_logger.ExternalServiceLogger) *ShujubaoService {
|
||||||
|
if signMethod == "" {
|
||||||
|
signMethod = SignMethodHMACMD5
|
||||||
|
}
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
return &ShujubaoService{
|
||||||
|
config: ShujubaoConfig{
|
||||||
|
URL: url,
|
||||||
|
AppSecret: appSecret,
|
||||||
|
SignMethod: signMethod,
|
||||||
|
Timeout: timeout,
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRequestID 生成请求 ID
|
||||||
|
func (s *ShujubaoService) generateRequestID() string {
|
||||||
|
timestamp := time.Now().UnixNano()
|
||||||
|
hash := md5.Sum([]byte(fmt.Sprintf("%d_%s", timestamp, s.config.AppSecret)))
|
||||||
|
return fmt.Sprintf("shujubao_%x", hash[:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSortedParamStr 将入参按 key 的 ASCII 排序组合为 key1=value1&key2=value2&...
|
||||||
|
func buildSortedParamStr(params map[string]interface{}) string {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
var b strings.Builder
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte('&')
|
||||||
|
}
|
||||||
|
v := params[k]
|
||||||
|
var vs string
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
vs = val
|
||||||
|
case nil:
|
||||||
|
vs = ""
|
||||||
|
default:
|
||||||
|
vs = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
b.WriteString(k)
|
||||||
|
b.WriteByte('=')
|
||||||
|
b.WriteString(vs)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildFormUrlEncodedBody 按 key 的 ASCII 排序构建 application/x-www-form-urlencoded 请求体(键与值均已 URL 编码)
|
||||||
|
func buildFormUrlEncodedBody(params map[string]interface{}) string {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
var b strings.Builder
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte('&')
|
||||||
|
}
|
||||||
|
v := params[k]
|
||||||
|
var vs string
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
vs = val
|
||||||
|
case nil:
|
||||||
|
vs = ""
|
||||||
|
default:
|
||||||
|
vs = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
b.WriteString(url.QueryEscape(k))
|
||||||
|
b.WriteByte('=')
|
||||||
|
b.WriteString(url.QueryEscape(vs))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateSign 根据入参与时间戳生成签名。入参按 ASCII 排序组合后与 app_secret 做 MD5/HMAC。
|
||||||
|
// 对于开启了加密的接口需传 sign 与 timestamp;明文传输的接口则无需传这两个参数。
|
||||||
|
func (s *ShujubaoService) generateSign(timestamp string, params map[string]interface{}) string {
|
||||||
|
// 合并 timestamp 到入参后参与排序
|
||||||
|
merged := make(map[string]interface{}, len(params)+1)
|
||||||
|
for k, v := range params {
|
||||||
|
merged[k] = v
|
||||||
|
}
|
||||||
|
merged["timestamp"] = timestamp
|
||||||
|
sortedStr := buildSortedParamStr(merged)
|
||||||
|
switch s.config.SignMethod {
|
||||||
|
case SignMethodMD5:
|
||||||
|
return GenerateSignFromParamsMD5(s.config.AppSecret, sortedStr)
|
||||||
|
default:
|
||||||
|
return GenerateSignFromParamsHMAC(s.config.AppSecret, sortedStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRequestURL 拼接接口地址得到最终请求 URL,如 https://api.chinadatapay.com/communication/personal/197
|
||||||
|
func (s *ShujubaoService) buildRequestURL(apiPath string) string {
|
||||||
|
base := strings.TrimSuffix(s.config.URL, "/")
|
||||||
|
if apiPath == "" {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
return base + "/" + strings.TrimPrefix(apiPath, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAPI 调用数据宝 API(POST)。最终请求地址 = url + 拼接接口地址值;body 为业务参数;sign、timestamp 按原样传 header。
|
||||||
|
func (s *ShujubaoService) CallAPI(ctx context.Context, apiPath string, params map[string]interface{}) (data interface{}, err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
requestID := s.generateRequestID()
|
||||||
|
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
|
||||||
|
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 /personal/197
|
||||||
|
requestURL := s.buildRequestURL(apiPath)
|
||||||
|
|
||||||
|
var transactionID string
|
||||||
|
if id, ok := ctx.Value("transaction_id").(string); ok {
|
||||||
|
transactionID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.LogRequest(requestID, transactionID, apiPath, requestURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 application/x-www-form-urlencoded,贵司接口暂不支持 JSON 入参
|
||||||
|
formBody := buildFormUrlEncodedBody(params)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", requestURL, strings.NewReader(formBody))
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Join(ErrSystem, err)
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("timestamp", timestamp)
|
||||||
|
req.Header.Set("sign", s.generateSign(timestamp, params))
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: s.config.Timeout}
|
||||||
|
response, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
isTimeout := false
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
isTimeout = true
|
||||||
|
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
|
||||||
|
isTimeout = true
|
||||||
|
} else if errStr := err.Error(); errStr == "context deadline exceeded" ||
|
||||||
|
errStr == "timeout" ||
|
||||||
|
errStr == "Client.Timeout exceeded" ||
|
||||||
|
errStr == "net/http: request canceled" {
|
||||||
|
isTimeout = true
|
||||||
|
}
|
||||||
|
if isTimeout {
|
||||||
|
err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err))
|
||||||
|
} else {
|
||||||
|
err = errors.Join(ErrSystem, err)
|
||||||
|
}
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Join(ErrSystem, err)
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.LogError(requestID, transactionID, apiPath, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.logger != nil {
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
s.logger.LogResponse(requestID, transactionID, apiPath, response.StatusCode, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var shujubaoResp ShujubaoResp
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
code := shujubaoResp.Code
|
||||||
|
if code != "10000" && code != "10006" {
|
||||||
|
shujubaoErr := NewShujubaoErrorFromCode(code, shujubaoResp.Message)
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.LogError(requestID, transactionID, apiPath, shujubaoErr, params)
|
||||||
|
}
|
||||||
|
return nil, errors.Join(ErrDatasource, shujubaoErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return shujubaoResp.Data, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user