新增:新增极光源,极光婚姻接口(测试)
This commit is contained in:
@@ -38,6 +38,7 @@ type Config struct {
|
|||||||
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
||||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||||
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
||||||
|
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig HTTP服务器配置
|
// ServerConfig HTTP服务器配置
|
||||||
@@ -520,6 +521,35 @@ type XingweiLevelFileConfig struct {
|
|||||||
Compress bool `mapstructure:"compress"`
|
Compress bool `mapstructure:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JiguangConfig 极光配置
|
||||||
|
type JiguangConfig struct {
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
AppID string `mapstructure:"app_id"`
|
||||||
|
AppSecret string `mapstructure:"app_secret"`
|
||||||
|
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||||
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
|
|
||||||
|
// 极光日志配置
|
||||||
|
Logging JiguangLoggingConfig `mapstructure:"logging"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangLoggingConfig 极光日志配置
|
||||||
|
type JiguangLoggingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
LogDir string `mapstructure:"log_dir"`
|
||||||
|
UseDaily bool `mapstructure:"use_daily"`
|
||||||
|
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||||
|
LevelConfigs map[string]JiguangLevelFileConfig `mapstructure:"level_configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangLevelFileConfig 极光级别文件配置
|
||||||
|
type JiguangLevelFileConfig struct {
|
||||||
|
MaxSize int `mapstructure:"max_size"`
|
||||||
|
MaxBackups int `mapstructure:"max_backups"`
|
||||||
|
MaxAge int `mapstructure:"max_age"`
|
||||||
|
Compress bool `mapstructure:"compress"`
|
||||||
|
}
|
||||||
|
|
||||||
// DomainConfig 域名配置
|
// DomainConfig 域名配置
|
||||||
type DomainConfig struct {
|
type DomainConfig struct {
|
||||||
API string `mapstructure:"api"` // API域名
|
API string `mapstructure:"api"` // API域名
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/ocr"
|
"tyapi-server/internal/infrastructure/external/ocr"
|
||||||
"tyapi-server/internal/infrastructure/external/sms"
|
"tyapi-server/internal/infrastructure/external/sms"
|
||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"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/xingwei"
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
@@ -366,6 +367,10 @@ func NewContainer() *Container {
|
|||||||
func(cfg *config.Config) (*xingwei.XingweiService, error) {
|
func(cfg *config.Config) (*xingwei.XingweiService, error) {
|
||||||
return xingwei.NewXingweiServiceWithConfig(cfg)
|
return xingwei.NewXingweiServiceWithConfig(cfg)
|
||||||
},
|
},
|
||||||
|
// JiguangService - 极光服务
|
||||||
|
func(cfg *config.Config) (*jiguang.JiguangService, error) {
|
||||||
|
return jiguang.NewJiguangServiceWithConfig(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,
|
||||||
|
|||||||
@@ -723,6 +723,11 @@ type IVYZ6M8PReq struct {
|
|||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ9H2MReq struct {
|
||||||
|
IDNo string `json:"id_no" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
type YYSY9E4AReq struct {
|
type YYSY9E4AReq 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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"tyapi-server/internal/domains/api/services/processors/yysy"
|
"tyapi-server/internal/domains/api/services/processors/yysy"
|
||||||
"tyapi-server/internal/domains/product/services"
|
"tyapi-server/internal/domains/product/services"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
@@ -54,6 +55,7 @@ func NewApiRequestService(
|
|||||||
alicloudService *alicloud.AlicloudService,
|
alicloudService *alicloud.AlicloudService,
|
||||||
zhichaService *zhicha.ZhichaService,
|
zhichaService *zhicha.ZhichaService,
|
||||||
xingweiService *xingwei.XingweiService,
|
xingweiService *xingwei.XingweiService,
|
||||||
|
jiguangService *jiguang.JiguangService,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
productManagementService *services.ProductManagementService,
|
productManagementService *services.ProductManagementService,
|
||||||
) *ApiRequestService {
|
) *ApiRequestService {
|
||||||
@@ -61,7 +63,7 @@ func NewApiRequestService(
|
|||||||
combService := comb.NewCombService(productManagementService)
|
combService := comb.NewCombService(productManagementService)
|
||||||
|
|
||||||
// 创建处理器依赖容器
|
// 创建处理器依赖容器
|
||||||
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, validator, combService)
|
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, validator, combService)
|
||||||
|
|
||||||
// 统一注册所有处理器
|
// 统一注册所有处理器
|
||||||
registerAllProcessors(combService)
|
registerAllProcessors(combService)
|
||||||
@@ -206,6 +208,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||||
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||||
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||||
|
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询(V2版)
|
||||||
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
|
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
|
||||||
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
|
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
|
||||||
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
|
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||||
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||||
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||||
|
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询(V2版)
|
||||||
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||||
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
||||||
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"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/xingwei"
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
@@ -32,6 +33,7 @@ type ProcessorDependencies struct {
|
|||||||
AlicloudService *alicloud.AlicloudService
|
AlicloudService *alicloud.AlicloudService
|
||||||
ZhichaService *zhicha.ZhichaService
|
ZhichaService *zhicha.ZhichaService
|
||||||
XingweiService *xingwei.XingweiService
|
XingweiService *xingwei.XingweiService
|
||||||
|
JiguangService *jiguang.JiguangService
|
||||||
Validator interfaces.RequestValidator
|
Validator interfaces.RequestValidator
|
||||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||||
Options *commands.ApiCallOptions // 添加Options支持
|
Options *commands.ApiCallOptions // 添加Options支持
|
||||||
@@ -47,6 +49,7 @@ func NewProcessorDependencies(
|
|||||||
alicloudService *alicloud.AlicloudService,
|
alicloudService *alicloud.AlicloudService,
|
||||||
zhichaService *zhicha.ZhichaService,
|
zhichaService *zhicha.ZhichaService,
|
||||||
xingweiService *xingwei.XingweiService,
|
xingweiService *xingwei.XingweiService,
|
||||||
|
jiguangService *jiguang.JiguangService,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
combService CombServiceInterface, // Changed to interface
|
combService CombServiceInterface, // Changed to interface
|
||||||
) *ProcessorDependencies {
|
) *ProcessorDependencies {
|
||||||
@@ -58,6 +61,7 @@ func NewProcessorDependencies(
|
|||||||
AlicloudService: alicloudService,
|
AlicloudService: alicloudService,
|
||||||
ZhichaService: zhichaService,
|
ZhichaService: zhichaService,
|
||||||
XingweiService: xingweiService,
|
XingweiService: xingweiService,
|
||||||
|
JiguangService: jiguangService,
|
||||||
Validator: validator,
|
Validator: validator,
|
||||||
CombService: combService,
|
CombService: combService,
|
||||||
Options: nil, // 初始化为nil,在调用时设置
|
Options: nil, // 初始化为nil,在调用时设置
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ9H2MRequest IVYZ9H2M API处理方法 - 极光个人婚姻查询(V2版)
|
||||||
|
func ProcessIVYZ9H2MRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ9H2MReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"idNo": paramsDto.IDNo,
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API,apiCode为 marriage-single-v2
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "marriage-single-v2", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
47
internal/infrastructure/external/jiguang/crypto.go
vendored
Normal file
47
internal/infrastructure/external/jiguang/crypto.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignMethod 签名方法类型
|
||||||
|
type SignMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SignMethodMD5 SignMethod = "md5"
|
||||||
|
SignMethodHMACMD5 SignMethod = "hmac"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateSign 生成签名
|
||||||
|
// 根据 signMethod 参数选择使用 MD5 或 HMAC-MD5 算法
|
||||||
|
// MD5: md5(timestamp + "&appSecret=" + appSecret),然后转大写十六进制
|
||||||
|
// HMAC-MD5: hmac_md5(timestamp, appSecret),然后转大写十六进制
|
||||||
|
func GenerateSign(timestamp string, appSecret string, signMethod SignMethod) (string, error) {
|
||||||
|
var hashBytes []byte
|
||||||
|
|
||||||
|
switch signMethod {
|
||||||
|
case SignMethodMD5:
|
||||||
|
// MD5算法:在待签名字符串后面加上 &appSecret=xxx 再进行计算
|
||||||
|
signStr := timestamp + "&appSecret=" + appSecret
|
||||||
|
hash := md5.Sum([]byte(signStr))
|
||||||
|
hashBytes = hash[:]
|
||||||
|
case SignMethodHMACMD5:
|
||||||
|
// HMAC-MD5算法:使用 appSecret 初始化摘要算法再进行计算
|
||||||
|
mac := hmac.New(md5.New, []byte(appSecret))
|
||||||
|
mac.Write([]byte(timestamp))
|
||||||
|
hashBytes = mac.Sum(nil)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("不支持的签名方法: %s", signMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将二进制转化为大写的十六进制(正确签名应该为32大写字符串)
|
||||||
|
return hex.EncodeToString(hashBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignWithDefault 使用默认的 HMAC-MD5 方法生成签名
|
||||||
|
func GenerateSignWithDefault(timestamp string, appSecret string) (string, error) {
|
||||||
|
return GenerateSign(timestamp, appSecret, SignMethodHMACMD5)
|
||||||
|
}
|
||||||
149
internal/infrastructure/external/jiguang/jiguang_errors.go
vendored
Normal file
149
internal/infrastructure/external/jiguang/jiguang_errors.go
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JiguangError 极光服务错误
|
||||||
|
type JiguangError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实现error接口
|
||||||
|
func (e *JiguangError) Error() string {
|
||||||
|
return fmt.Sprintf("极光错误 [%d]: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess 检查是否成功
|
||||||
|
func (e *JiguangError) IsSuccess() bool {
|
||||||
|
return e.Code == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQueryFailed 检查是否查询失败
|
||||||
|
func (e *JiguangError) IsQueryFailed() bool {
|
||||||
|
return e.Code == 922
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNoRecord 检查是否查无记录
|
||||||
|
func (e *JiguangError) IsNoRecord() bool {
|
||||||
|
return e.Code == 921
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParamError 检查是否是参数相关错误
|
||||||
|
func (e *JiguangError) IsParamError() bool {
|
||||||
|
return e.Code == 400 || e.Code == 906 || e.Code == 914 || e.Code == 918
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthError 检查是否是认证相关错误
|
||||||
|
func (e *JiguangError) IsAuthError() bool {
|
||||||
|
return e.Code == 902 || e.Code == 903 || e.Code == 904 || e.Code == 905
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSystemError 检查是否是系统错误
|
||||||
|
func (e *JiguangError) IsSystemError() bool {
|
||||||
|
return e.Code == 405 || e.Code == 911 || e.Code == 912 || e.Code == 915 || e.Code == 916 || e.Code == 917 || e.Code == 919 || e.Code == 923
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预定义错误常量
|
||||||
|
var (
|
||||||
|
// 成功状态
|
||||||
|
ErrSuccess = &JiguangError{Code: 0, Message: "请求成功"}
|
||||||
|
|
||||||
|
// 参数错误
|
||||||
|
ErrParamInvalid = &JiguangError{Code: 400, Message: "请求参数不正确"}
|
||||||
|
ErrMethodInvalid = &JiguangError{Code: 405, Message: "请求方法不正确"}
|
||||||
|
ErrParamFormInvalid = &JiguangError{Code: 906, Message: "请求参数形式不正确"}
|
||||||
|
ErrBodyIncomplete = &JiguangError{Code: 914, Message: "Body 请求参数不完整"}
|
||||||
|
ErrBodyNotSupported = &JiguangError{Code: 918, Message: "Body 请求参数不支持"}
|
||||||
|
|
||||||
|
// 认证错误
|
||||||
|
ErrAppIDInvalid = &JiguangError{Code: 902, Message: "错误的 appId/账户已删除"}
|
||||||
|
ErrTimestampInvalid = &JiguangError{Code: 903, Message: "错误的时间戳/时间误差大于 10 分钟"}
|
||||||
|
ErrSignMethodInvalid = &JiguangError{Code: 904, Message: "无法识别的签名方法"}
|
||||||
|
ErrSignInvalid = &JiguangError{Code: 905, Message: "签名不合法"}
|
||||||
|
|
||||||
|
// 系统错误
|
||||||
|
ErrAccountStatusError = &JiguangError{Code: 911, Message: "账户状态异常"}
|
||||||
|
ErrInterfaceDisabled = &JiguangError{Code: 912, Message: "接口状态不可用"}
|
||||||
|
ErrAPICallError = &JiguangError{Code: 915, Message: "API 接口调用有误"}
|
||||||
|
ErrInternalError = &JiguangError{Code: 916, Message: "内部接口调用错误,请联系相关人员"}
|
||||||
|
ErrTimeout = &JiguangError{Code: 917, Message: "请求超时"}
|
||||||
|
ErrBusinessDisabled = &JiguangError{Code: 919, Message: "业务状态不可用"}
|
||||||
|
ErrInterfaceException = &JiguangError{Code: 923, Message: "接口异常"}
|
||||||
|
|
||||||
|
// 业务错误
|
||||||
|
ErrNoRecord = &JiguangError{Code: 921, Message: "查无记录"}
|
||||||
|
ErrQueryFailed = &JiguangError{Code: 922, Message: "查询失败"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewJiguangError 创建新的极光错误
|
||||||
|
func NewJiguangError(code int, message string) *JiguangError {
|
||||||
|
return &JiguangError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangErrorFromCode 根据状态码创建错误
|
||||||
|
func NewJiguangErrorFromCode(code int) *JiguangError {
|
||||||
|
switch code {
|
||||||
|
case 0:
|
||||||
|
return ErrSuccess
|
||||||
|
case 400:
|
||||||
|
return ErrParamInvalid
|
||||||
|
case 405:
|
||||||
|
return ErrMethodInvalid
|
||||||
|
case 902:
|
||||||
|
return ErrAppIDInvalid
|
||||||
|
case 903:
|
||||||
|
return ErrTimestampInvalid
|
||||||
|
case 904:
|
||||||
|
return ErrSignMethodInvalid
|
||||||
|
case 905:
|
||||||
|
return ErrSignInvalid
|
||||||
|
case 906:
|
||||||
|
return ErrParamFormInvalid
|
||||||
|
case 911:
|
||||||
|
return ErrAccountStatusError
|
||||||
|
case 912:
|
||||||
|
return ErrInterfaceDisabled
|
||||||
|
case 914:
|
||||||
|
return ErrBodyIncomplete
|
||||||
|
case 915:
|
||||||
|
return ErrAPICallError
|
||||||
|
case 916:
|
||||||
|
return ErrInternalError
|
||||||
|
case 917:
|
||||||
|
return ErrTimeout
|
||||||
|
case 918:
|
||||||
|
return ErrBodyNotSupported
|
||||||
|
case 919:
|
||||||
|
return ErrBusinessDisabled
|
||||||
|
case 921:
|
||||||
|
return ErrNoRecord
|
||||||
|
case 922:
|
||||||
|
return ErrQueryFailed
|
||||||
|
case 923:
|
||||||
|
return ErrInterfaceException
|
||||||
|
default:
|
||||||
|
return &JiguangError{
|
||||||
|
Code: code,
|
||||||
|
Message: fmt.Sprintf("未知错误码: %d", code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsJiguangError 检查是否是极光错误
|
||||||
|
func IsJiguangError(err error) bool {
|
||||||
|
_, ok := err.(*JiguangError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJiguangError 获取极光错误
|
||||||
|
func GetJiguangError(err error) *JiguangError {
|
||||||
|
if jiguangErr, ok := err.(*JiguangError); ok {
|
||||||
|
return jiguangErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
85
internal/infrastructure/external/jiguang/jiguang_factory.go
vendored
Normal file
85
internal/infrastructure/external/jiguang/jiguang_factory.go
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/config"
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewJiguangServiceWithConfig 使用配置创建极光服务
|
||||||
|
func NewJiguangServiceWithConfig(cfg *config.Config) (*JiguangService, error) {
|
||||||
|
// 将配置类型转换为通用外部服务日志配置
|
||||||
|
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||||
|
Enabled: cfg.Jiguang.Logging.Enabled,
|
||||||
|
LogDir: cfg.Jiguang.Logging.LogDir,
|
||||||
|
ServiceName: "jiguang",
|
||||||
|
UseDaily: cfg.Jiguang.Logging.UseDaily,
|
||||||
|
EnableLevelSeparation: cfg.Jiguang.Logging.EnableLevelSeparation,
|
||||||
|
LevelConfigs: make(map[string]external_logger.ExternalServiceLevelFileConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换级别配置
|
||||||
|
for key, value := range cfg.Jiguang.Logging.LevelConfigs {
|
||||||
|
loggingConfig.LevelConfigs[key] = external_logger.ExternalServiceLevelFileConfig{
|
||||||
|
MaxSize: value.MaxSize,
|
||||||
|
MaxBackups: value.MaxBackups,
|
||||||
|
MaxAge: value.MaxAge,
|
||||||
|
Compress: value.Compress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建通用外部服务日志器
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析签名方法
|
||||||
|
var signMethod SignMethod
|
||||||
|
if cfg.Jiguang.SignMethod == "md5" {
|
||||||
|
signMethod = SignMethodMD5
|
||||||
|
} else {
|
||||||
|
signMethod = SignMethodHMACMD5 // 默认使用 HMAC-MD5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析超时时间
|
||||||
|
timeout := 60 * time.Second
|
||||||
|
if cfg.Jiguang.Timeout > 0 {
|
||||||
|
timeout = cfg.Jiguang.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建极光服务
|
||||||
|
service := NewJiguangService(
|
||||||
|
cfg.Jiguang.URL,
|
||||||
|
cfg.Jiguang.AppID,
|
||||||
|
cfg.Jiguang.AppSecret,
|
||||||
|
signMethod,
|
||||||
|
timeout,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangServiceWithLogging 使用自定义日志配置创建极光服务
|
||||||
|
func NewJiguangServiceWithLogging(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration, loggingConfig external_logger.ExternalServiceLoggingConfig) (*JiguangService, error) {
|
||||||
|
// 设置服务名称
|
||||||
|
loggingConfig.ServiceName = "jiguang"
|
||||||
|
|
||||||
|
// 创建通用外部服务日志器
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建极光服务
|
||||||
|
service := NewJiguangService(url, appID, appSecret, signMethod, timeout, logger)
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangServiceSimple 创建简单的极光服务(无日志)
|
||||||
|
func NewJiguangServiceSimple(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration) *JiguangService {
|
||||||
|
return NewJiguangService(url, appID, appSecret, signMethod, timeout, nil)
|
||||||
|
}
|
||||||
265
internal/infrastructure/external/jiguang/jiguang_service.go
vendored
Normal file
265
internal/infrastructure/external/jiguang/jiguang_service.go
vendored
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDatasource = errors.New("数据源异常")
|
||||||
|
ErrSystem = errors.New("系统异常")
|
||||||
|
ErrNotFound = errors.New("查询为空")
|
||||||
|
)
|
||||||
|
|
||||||
|
// JiguangResponse 极光API响应结构
|
||||||
|
type JiguangResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
OrderID string `json:"order_id"`
|
||||||
|
Data map[string]interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangConfig 极光服务配置
|
||||||
|
type JiguangConfig struct {
|
||||||
|
URL string
|
||||||
|
AppID string
|
||||||
|
AppSecret string
|
||||||
|
SignMethod SignMethod // 签名方法:md5 或 hmac
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangService 极光服务
|
||||||
|
type JiguangService struct {
|
||||||
|
config JiguangConfig
|
||||||
|
logger *external_logger.ExternalServiceLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangService 创建一个新的极光服务实例
|
||||||
|
func NewJiguangService(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration, logger *external_logger.ExternalServiceLogger) *JiguangService {
|
||||||
|
// 如果没有指定签名方法,默认使用 HMAC-MD5
|
||||||
|
if signMethod == "" {
|
||||||
|
signMethod = SignMethodHMACMD5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有指定超时时间,默认使用 60 秒
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JiguangService{
|
||||||
|
config: JiguangConfig{
|
||||||
|
URL: url,
|
||||||
|
AppID: appID,
|
||||||
|
AppSecret: appSecret,
|
||||||
|
SignMethod: signMethod,
|
||||||
|
Timeout: timeout,
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRequestID 生成请求ID
|
||||||
|
func (j *JiguangService) generateRequestID() string {
|
||||||
|
timestamp := time.Now().UnixNano()
|
||||||
|
hash := md5.Sum([]byte(fmt.Sprintf("%d_%s", timestamp, j.config.AppID)))
|
||||||
|
return fmt.Sprintf("jiguang_%x", hash[:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAPI 调用极光API
|
||||||
|
// apiCode: API服务编码(如 marriage-single-v2)
|
||||||
|
// params: 请求参数(会作为JSON body发送)
|
||||||
|
func (j *JiguangService) CallAPI(ctx context.Context, apiCode string, params map[string]interface{}) (resp []byte, err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
requestID := j.generateRequestID()
|
||||||
|
|
||||||
|
// 生成时间戳(毫秒)
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||||
|
|
||||||
|
// 从ctx中获取transactionId
|
||||||
|
var transactionID string
|
||||||
|
if ctxTransactionID, ok := ctx.Value("transaction_id").(string); ok {
|
||||||
|
transactionID = ctxTransactionID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成签名
|
||||||
|
sign, signErr := GenerateSign(timestamp, j.config.AppSecret, j.config.SignMethod)
|
||||||
|
if signErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("生成签名失败: %w", signErr))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求日志
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogRequest(requestID, transactionID, apiCode, j.config.URL, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将请求参数转换为JSON
|
||||||
|
jsonData, marshalErr := json.Marshal(params)
|
||||||
|
if marshalErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, marshalErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP POST请求
|
||||||
|
req, newRequestErr := http.NewRequestWithContext(ctx, "POST", j.config.URL, bytes.NewBuffer(jsonData))
|
||||||
|
if newRequestErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, newRequestErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("appId", j.config.AppID)
|
||||||
|
req.Header.Set("apiCode", apiCode)
|
||||||
|
req.Header.Set("timestamp", timestamp)
|
||||||
|
req.Header.Set("signMethod", string(j.config.SignMethod))
|
||||||
|
req.Header.Set("sign", sign)
|
||||||
|
|
||||||
|
// 创建HTTP客户端
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: j.config.Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
httpResp, clientDoErr := client.Do(req)
|
||||||
|
if clientDoErr != nil {
|
||||||
|
// 检查是否是超时错误
|
||||||
|
isTimeout := false
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
isTimeout = true
|
||||||
|
} else if netErr, ok := clientDoErr.(interface{ Timeout() bool }); ok && netErr.Timeout() {
|
||||||
|
isTimeout = true
|
||||||
|
} else if errStr := clientDoErr.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", clientDoErr))
|
||||||
|
} else {
|
||||||
|
err = errors.Join(ErrSystem, clientDoErr)
|
||||||
|
}
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
closeErr := Body.Close()
|
||||||
|
if closeErr != nil {
|
||||||
|
// 记录关闭错误
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, errors.Join(ErrSystem, fmt.Errorf("关闭响应体失败: %w", closeErr)), params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(httpResp.Body)
|
||||||
|
|
||||||
|
// 计算请求耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
bodyBytes, readErr := io.ReadAll(httpResp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, readErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if httpResp.StatusCode != http.StatusOK {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("极光请求失败,状态码: %d", httpResp.StatusCode))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应结构
|
||||||
|
var jiguangResp JiguangResponse
|
||||||
|
if err := json.Unmarshal(bodyBytes, &jiguangResp); err != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录响应日志
|
||||||
|
if j.logger != nil {
|
||||||
|
if jiguangResp.OrderID != "" {
|
||||||
|
j.logger.LogResponseWithID(requestID, transactionID, apiCode, httpResp.StatusCode, bodyBytes, duration, jiguangResp.OrderID)
|
||||||
|
} else {
|
||||||
|
j.logger.LogResponse(requestID, transactionID, apiCode, httpResp.StatusCode, bodyBytes, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查业务状态码
|
||||||
|
if jiguangResp.Code != 0 {
|
||||||
|
// 创建极光错误
|
||||||
|
jiguangErr := NewJiguangErrorFromCode(jiguangResp.Code)
|
||||||
|
if jiguangErr.Message == fmt.Sprintf("未知错误码: %d", jiguangResp.Code) && jiguangResp.Msg != "" {
|
||||||
|
jiguangErr.Message = jiguangResp.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录错误日志
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogErrorWithResponseID(requestID, transactionID, apiCode, jiguangErr, params, jiguangResp.OrderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据错误类型返回不同的错误
|
||||||
|
if jiguangErr.IsNoRecord() {
|
||||||
|
return nil, errors.Join(ErrNotFound, jiguangErr)
|
||||||
|
} else if jiguangErr.IsQueryFailed() {
|
||||||
|
return nil, errors.Join(ErrDatasource, jiguangErr)
|
||||||
|
} else if jiguangErr.IsSystemError() {
|
||||||
|
return nil, errors.Join(ErrSystem, jiguangErr)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(ErrDatasource, jiguangErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功响应,返回data字段
|
||||||
|
if jiguangResp.Data == nil {
|
||||||
|
return []byte("{}"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将data转换为JSON字节
|
||||||
|
dataBytes, err := json.Marshal(jiguangResp.Data)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("data字段序列化失败: %w", err))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogErrorWithResponseID(requestID, transactionID, apiCode, err, params, jiguangResp.OrderID)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取配置信息
|
||||||
|
func (j *JiguangService) GetConfig() JiguangConfig {
|
||||||
|
return j.config
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user