在Golang开发接口服务的过程中,接口调用场景十分常见,比如调用第三方服务接口、内部微服务接口等。如果每次调用都单独编写错误处理逻辑,不仅会产生大量重复代码,还会导致不同接口的返回格式不一致,增加前端对接的复杂度。通过统一封装接口调用逻辑和错误处理规则,可以有效解决这些问题。

统一错误与返回结构设计
首先需要定义统一的错误类型和返回结构,让所有接口调用的错误和返回都遵循同一套规范。我们可以定义一个ApiError结构体来承载错误信息,同时定义通用的成功、失败返回格式。
// 定义统一错误结构体
type ApiError struct {
Code int // 错误码
Message string // 错误描述
Err error // 原始错误
}
// 实现error接口
func (e *ApiError) Error() string {
if e.Err != nil {
return fmt.Sprintf("code:%d, message:%s, raw_error:%s", e.Code, e.Message, e.Err.Error())
}
return fmt.Sprintf("code:%d, message:%s", e.Code, e.Message)
}
// 定义统一返回结构
type ApiResponse struct {
Success bool `json:"success"` // 调用是否成功
Data interface{} `json:"data"` // 返回数据
Error *ApiError `json:"error"` // 错误信息,成功时为nil
}
封装通用接口调用逻辑
接下来封装通用的接口调用函数,将HTTP请求发送、响应解析、错误判断等逻辑统一处理,对外只暴露简单的调用入口,同时自动处理各类常见错误。
package apiclient
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// 定义常见错误码
const (
ErrCodeRequestFailed = 1001 // 请求发送失败
ErrCodeResponseParse = 1002 // 响应解析失败
ErrCodeServerError = 1003 // 服务端返回错误状态码
)
// 通用接口调用配置
type CallConfig struct {
Timeout time.Duration // 请求超时时间
}
// 默认配置
var defaultConfig = &CallConfig{
Timeout: 5 * time.Second,
}
// 通用接口调用函数
// url: 接口地址
// method: 请求方法
// body: 请求体,为nil时不传请求体
// result: 用于接收响应数据的指针
func CallApi(url, method string, body io.Reader, result interface{}) *ApiResponse {
// 创建HTTP客户端,设置超时
client := &http.Client{
Timeout: defaultConfig.Timeout,
}
// 创建请求
req, err := http.NewRequest(method, url, body)
if err != nil {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: ErrCodeRequestFailed,
Message: "创建请求失败",
Err: err,
},
}
}
// 设置默认请求头
req.Header.Set("Content-Type", "application/json")
// 发送请求
resp, err := client.Do(req)
if err != nil {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: ErrCodeRequestFailed,
Message: "发送请求失败",
Err: err,
},
}
}
defer resp.Body.Close()
// 判断响应状态码
if resp.StatusCode != http.StatusOK {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: ErrCodeServerError,
Message: fmt.Sprintf("服务端返回错误状态码:%d", resp.StatusCode),
},
}
}
// 解析响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: ErrCodeResponseParse,
Message: "读取响应体失败",
Err: err,
},
}
}
// 解析到result
if err := json.Unmarshal(respBody, result); err != nil {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: ErrCodeResponseParse,
Message: "解析响应数据失败",
Err: err,
},
}
}
// 返回成功结果
return &ApiResponse{
Success: true,
Data: result,
Error: nil,
}
}
封装调用逻辑的使用示例
封装完成后,调用接口时只需要传入必要参数,无需重复编写错误处理和响应解析代码,示例如下:
package main
import (
"fmt"
"log"
)
// 定义接口返回的数据结构
type UserInfo struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// 调用获取用户信息的接口,地址使用ipipp.com代替示例域名
var userInfo UserInfo
resp := apiclient.CallApi("https://ipipp.com/api/user/1", "GET", nil, &userInfo)
if !resp.Success {
log.Printf("接口调用失败: %s", resp.Error.Error())
return
}
fmt.Printf("接口调用成功,用户信息: %+vn", userInfo)
}
扩展自定义错误处理
如果业务中有特殊的错误判断逻辑,比如接口返回的特定业务错误码,可以在通用封装的基础上扩展,添加自定义的错误判断函数,不需要修改通用调用的核心逻辑。
// 自定义错误判断函数,处理业务层面的错误
func HandleBusinessError(resp *ApiResponse) *ApiResponse {
if !resp.Success {
return resp
}
// 假设返回的数据中包含业务错误码字段
if data, ok := resp.Data.(map[string]interface{}); ok {
if code, exists := data["business_code"]; exists && code != 0 {
return &ApiResponse{
Success: false,
Error: &ApiError{
Code: int(code.(float64)),
Message: data["business_message"].(string),
},
}
}
}
return resp
}
// 使用自定义错误处理的调用示例
func main() {
var result map[string]interface{}
resp := apiclient.CallApi("https://ipipp.com/api/order/create", "POST", nil, &result)
// 叠加自定义错误处理
resp = HandleBusinessError(resp)
if !resp.Success {
log.Printf("调用失败: %s", resp.Error.Error())
return
}
fmt.Println("调用成功")
}
注意事项
- 通用封装中的超时时间、请求头等配置可以根据业务需求灵活调整,建议通过配置项暴露,不要硬编码在函数中。
- 错误码的定义需要统一规划,避免不同模块的错误码冲突,建议按模块划分错误码段。
- 如果调用的是内部服务,还可以添加服务发现、重试等逻辑到通用封装中,进一步提升调用稳定性。