diff --git a/internal/container/container.go b/internal/container/container.go index ed25ea6..d49dbbf 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -412,11 +412,8 @@ func NewContainer() *Container { ) }, // AlicloudService - 阿里云服务 - func(cfg *config.Config) *alicloud.AlicloudService { - return alicloud.NewAlicloudService( - cfg.Alicloud.Host, - cfg.Alicloud.AppCode, - ) + func(cfg *config.Config) (*alicloud.AlicloudService, error) { + return alicloud.NewAlicloudServiceWithConfig(cfg) }, sharedhttp.NewGinRouter, ipgeo.NewLocator, diff --git a/internal/domains/api/services/processors/yysy/yysybe08_processor.go b/internal/domains/api/services/processors/yysy/yysybe08_processor.go index 76efd63..3045315 100644 --- a/internal/domains/api/services/processors/yysy/yysybe08_processor.go +++ b/internal/domains/api/services/processors/yysy/yysybe08_processor.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" "errors" + "strconv" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/alicloud" ) // ProcessYYSYBE08Request YYSYBE08 API处理方法 - 使用数脉二要素验证 @@ -34,31 +35,21 @@ func ProcessYYSYBE08Request(ctx context.Context, params []byte, deps *processors if err != nil { // 使用实时接口(app_id 和 app_secret)重试 respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false) - // 重试仍失败:阿里云身份证二要素兜底,返回原始 body;兜底仍失败则按阿里云错误类型返回(与 YYSY3M8S 对数脉的分流一致) + // 重试仍失败:阿里云身份证二要素兜底,并直接返回统一映射响应 if err != nil { - rawBytes, aerr := callAliyunIDCardCheckRaw(ctx, deps, paramsDto.Name, paramsDto.IDCard) - if aerr != nil { - if errors.Is(aerr, alicloud.ErrDatasource) { - return nil, errors.Join(processors.ErrDatasource, aerr) - } - if errors.Is(aerr, alicloud.ErrSystem) { - return nil, errors.Join(processors.ErrSystem, aerr) - } - return nil, errors.Join(processors.ErrSystem, aerr) - } - return rawBytes, nil + return callAliyunIDCardCheckRaw(ctx, deps, paramsDto.Name, paramsDto.IDCard) } } // 解析数脉 /v4/id_card/check 的 data 内容(CallAPIForm 返回的即 data 对象) // 数卖响应: result 0-一致 1-不一致 2-无记录(预留); desc 如 "一致"/"不一致" var shumaiData struct { - Result int `json:"result"` - OrderNo string `json:"order_no"` - Desc string `json:"desc"` - Sex string `json:"sex"` - Birthday string `json:"birthday"` - Address string `json:"address"` + Result interface{} `json:"result"` + OrderNo string `json:"order_no"` + Desc string `json:"desc"` + Sex string `json:"sex"` + Birthday string `json:"birthday"` + Address string `json:"address"` } if err := json.Unmarshal(respBytes, &shumaiData); err != nil { @@ -79,30 +70,7 @@ func ProcessYYSYBE08Request(ctx context.Context, params []byte, deps *processors // 按数卖 result 验证结果处理: 0-一致 1-不一致 2-无记录(预留) // resultCode: 0XXX=一致, 5XXX=不一致/无记录 - var resultCode, verifyResult, resultMsg string - switch shumaiData.Result { - case 0: // 一致(收费) - resultCode = "0XXX" - verifyResult = "一致" - resultMsg = shumaiData.Desc - if resultMsg == "" { - resultMsg = "成功" - } - case 1: // 不一致(收费) - resultCode = "5XXX" - verifyResult = "不一致" - resultMsg = shumaiData.Desc - if resultMsg == "" { - resultMsg = "不一致" - } - default: - resultCode = "5XXX" - verifyResult = "不一致" - resultMsg = shumaiData.Desc - if resultMsg == "" { - resultMsg = "不一致" - } - } + resultCode, verifyResult, resultMsg := mapIDCardCheckResult(shumaiData.Result, shumaiData.Desc) // 构建目标格式的响应 response := map[string]interface{}{ @@ -119,3 +87,48 @@ func ProcessYYSYBE08Request(ctx context.Context, params []byte, deps *processors return json.Marshal(response) } + +func mapIDCardCheckResult(rawResult interface{}, desc string) (resultCode, verifyResult, resultMsg string) { + if isResultZero(rawResult) { + resultCode = "0XXX" + verifyResult = "一致" + resultMsg = desc + if resultMsg == "" { + resultMsg = "成功" + } + return + } + + resultCode = "5XXX" + verifyResult = "不一致" + resultMsg = desc + if resultMsg == "" { + resultMsg = "不一致" + } + return +} + +func isResultZero(v interface{}) bool { + switch r := v.(type) { + case float64: + return r == 0 + case int: + return r == 0 + case int32: + return r == 0 + case int64: + return r == 0 + case json.Number: + n, err := r.Int64() + return err == nil && n == 0 + case string: + s := strings.TrimSpace(r) + if s == "" { + return false + } + n, err := strconv.ParseFloat(s, 64) + return err == nil && n == 0 + default: + return false + } +} diff --git a/internal/domains/api/services/processors/yysy/yysybe08test_processor.go b/internal/domains/api/services/processors/yysy/yysybe08test_processor.go index 1c29861..93e06d4 100644 --- a/internal/domains/api/services/processors/yysy/yysybe08test_processor.go +++ b/internal/domains/api/services/processors/yysy/yysybe08test_processor.go @@ -7,9 +7,10 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/alicloud" ) -// ProcessYYSYBE08testRequest 与 YYSYBE08 相同入参,底层使用阿里云市场身份证二要素校验;响应为阿里云接口原始 body(不做字段映射) +// ProcessYYSYBE08testRequest 与 YYSYBE08 相同入参,底层使用阿里云市场身份证二要素校验;响应映射为 ctidRequest.ctidAuth 格式 func ProcessYYSYBE08testRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { var paramsDto dto.YYSYBE08Req if err := json.Unmarshal(params, ¶msDto); err != nil { @@ -23,12 +24,40 @@ func ProcessYYSYBE08testRequest(ctx context.Context, params []byte, deps *proces return callAliyunIDCardCheckRaw(ctx, deps, paramsDto.Name, paramsDto.IDCard) } -// callAliyunIDCardCheckRaw POST api-mall/api/id_card/check(form: name、idcard),返回响应体原文,供 YYSYBE08TEST 与 YYSYBE08 数脉失败兜底共用 +// callAliyunIDCardCheckRaw POST api-mall/api/id_card/check(form: name、idcard),并映射为 ctidRequest.ctidAuth 响应 func callAliyunIDCardCheckRaw(ctx context.Context, deps *processors.ProcessorDependencies, name, idCard string) ([]byte, error) { _ = ctx reqData := map[string]interface{}{ "name": name, "idcard": idCard, } - return deps.AlicloudService.CallAPI("api-mall/api/id_card/check", reqData) + respBytes, err := deps.AlicloudService.CallAPI("api-mall/api/id_card/check", reqData) + if err != nil { + if errors.Is(err, alicloud.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + var aliyunData struct { + Result interface{} `json:"result"` + Desc string `json:"desc"` + } + if err := json.Unmarshal(respBytes, &aliyunData); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + resultCode, verifyResult, resultMsg := mapIDCardCheckResult(aliyunData.Result, aliyunData.Desc) + response := map[string]interface{}{ + "ctidRequest": map[string]interface{}{ + "ctidAuth": map[string]interface{}{ + "idCard": idCard, + "name": name, + "resultCode": resultCode, + "resultMsg": resultMsg, + "verifyResult": verifyResult, + }, + }, + } + return json.Marshal(response) } diff --git a/internal/infrastructure/external/alicloud/alicloud_factory.go b/internal/infrastructure/external/alicloud/alicloud_factory.go new file mode 100644 index 0000000..9259174 --- /dev/null +++ b/internal/infrastructure/external/alicloud/alicloud_factory.go @@ -0,0 +1,49 @@ +package alicloud + +import ( + "tyapi-server/internal/config" + "tyapi-server/internal/shared/external_logger" +) + +// NewAlicloudServiceWithConfig 使用配置创建阿里云服务,并启用外部服务调用日志 +func NewAlicloudServiceWithConfig(cfg *config.Config) (*AlicloudService, error) { + loggingConfig := external_logger.ExternalServiceLoggingConfig{ + Enabled: true, + LogDir: "./logs/external_services", + ServiceName: "alicloud", + UseDaily: false, + EnableLevelSeparation: true, + LevelConfigs: map[string]external_logger.ExternalServiceLevelFileConfig{ + "info": { + MaxSize: 100, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + }, + "error": { + MaxSize: 100, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + }, + "warn": { + MaxSize: 100, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + }, + }, + } + + logger, err := external_logger.NewExternalServiceLogger(loggingConfig) + if err != nil { + return nil, err + } + + return NewAlicloudService( + cfg.Alicloud.Host, + cfg.Alicloud.AppCode, + logger, + ), nil +} + diff --git a/internal/infrastructure/external/alicloud/alicloud_service.go b/internal/infrastructure/external/alicloud/alicloud_service.go index 86e5fdc..913c28d 100644 --- a/internal/infrastructure/external/alicloud/alicloud_service.go +++ b/internal/infrastructure/external/alicloud/alicloud_service.go @@ -1,6 +1,7 @@ package alicloud import ( + "crypto/md5" "errors" "fmt" "io" @@ -8,6 +9,8 @@ import ( "net/url" "strings" "time" + + "tyapi-server/internal/shared/external_logger" ) var ( @@ -24,25 +27,47 @@ type AlicloudConfig struct { // AlicloudService 阿里云服务 type AlicloudService struct { config AlicloudConfig + logger *external_logger.ExternalServiceLogger } // NewAlicloudService 创建阿里云服务实例 -func NewAlicloudService(host, appCode string) *AlicloudService { +func NewAlicloudService(host, appCode string, logger ...*external_logger.ExternalServiceLogger) *AlicloudService { + var serviceLogger *external_logger.ExternalServiceLogger + if len(logger) > 0 { + serviceLogger = logger[0] + } return &AlicloudService{ config: AlicloudConfig{ Host: host, AppCode: appCode, }, + logger: serviceLogger, } } +// generateRequestID 生成请求ID +func (a *AlicloudService) generateRequestID() string { + timestamp := time.Now().UnixNano() + hash := md5.Sum([]byte(fmt.Sprintf("%d_%s", timestamp, a.config.Host))) + return fmt.Sprintf("alicloud_%x", hash[:8]) +} + // CallAPI 调用阿里云API的通用方法 // path: API路径(如 "api-mall/api/id_card/check") // params: 请求参数 func (a *AlicloudService) CallAPI(path string, params map[string]interface{}) (respBytes []byte, err error) { + startTime := time.Now() + requestID := a.generateRequestID() + transactionID := "" + // 构建请求URL reqURL := a.config.Host + "/" + path + // 记录请求日志 + if a.logger != nil { + a.logger.LogRequest(requestID, transactionID, path, reqURL) + } + // 构建请求参数 formData := url.Values{} for key, value := range params { @@ -52,6 +77,9 @@ func (a *AlicloudService) CallAPI(path string, params map[string]interface{}) (r // 创建HTTP请求 req, err := http.NewRequest("POST", reqURL, strings.NewReader(formData.Encode())) if err != nil { + if a.logger != nil { + a.logger.LogError(requestID, transactionID, path, errors.Join(ErrSystem, err), params) + } return nil, fmt.Errorf("%w: %s", ErrSystem, err.Error()) } @@ -69,17 +97,22 @@ func (a *AlicloudService) CallAPI(path string, params map[string]interface{}) (r isTimeout := false 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" { + } else if errStr := err.Error(); errStr == "context deadline exceeded" || + errStr == "timeout" || + errStr == "Client.Timeout exceeded" || + errStr == "net/http: request canceled" { isTimeout = true } - + if isTimeout { + if a.logger != nil { + a.logger.LogError(requestID, transactionID, path, errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %s", err.Error())), params) + } return nil, fmt.Errorf("%w: API请求超时: %s", ErrDatasource, err.Error()) } + if a.logger != nil { + a.logger.LogError(requestID, transactionID, path, errors.Join(ErrSystem, err), params) + } return nil, fmt.Errorf("%w: %s", ErrSystem, err.Error()) } defer resp.Body.Close() @@ -87,9 +120,18 @@ func (a *AlicloudService) CallAPI(path string, params map[string]interface{}) (r // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { + if a.logger != nil { + a.logger.LogError(requestID, transactionID, path, errors.Join(ErrSystem, err), params) + } return nil, fmt.Errorf("%w: %s", ErrSystem, err.Error()) } + // 记录响应日志(不记录具体响应数据) + if a.logger != nil { + duration := time.Since(startTime) + a.logger.LogResponse(requestID, transactionID, path, resp.StatusCode, duration) + } + // 直接返回原始响应body,让调用方自己处理 return body, nil } @@ -97,4 +139,4 @@ func (a *AlicloudService) CallAPI(path string, params map[string]interface{}) (r // GetConfig 获取配置信息 func (a *AlicloudService) GetConfig() AlicloudConfig { return a.config -} \ No newline at end of file +}