GO: 统一Response返回类型| 青训营笔记

1,222 阅读5分钟

前言

在Web Application中,响应体是一种常见的数据结构,用于表示服务端接口返回的结果。Go 作为一种后起的强类型编程语言,提供了丰富的 HTTP 库和框架,开发者可以方便地创建 RESTful API 和 Web 服务。在构建这些服务时,统一 Response 返回类型是一个重要的问题,本文将从以下两个方面详细探讨:

  1. 为什么需要统一 Response 返回类型?
  2. 统一响应体的好处是什么?

为什么需要统一 Response 返回类型?

在编写 Web 应用程序时,通常需要定义服务端接口,并实现对应的处理逻辑。当客户端调用服务端接口时,服务端会返回一个包含状态码、消息体等元素的响应体。在前后端分离的架构中,服务端接口的返回值往往是一种标准化的数据格式,以方便客户端解析和处理。而在 Go 语言中,为了保证 API 的可读性和可维护性,也需要统一 Response 返回类型。

具体来说,统一 Response 返回类型有以下几个好处:

  1. 增加代码复用性和可维护性

通过统一的 Response 返回类型,开发者可以将相同或类似的业务逻辑进行封装和抽象,实现代码的复用和可维护性。例如,可以在某个公共库中实现一个通用的错误响应类型,然后在不同的服务和模块中进行调用。这样一来,在出现问题时,只需要修改公共库中的代码即可,而不需要去每个服务和模块中都进行修改。

  1. 简化客户端的解析逻辑

通过统一 Response 返回类型,客户端可以更方便地解析服务端返回的数据,从而更快地进行开发和调试工作。例如,客户端可以定义一个通用的 Status 结构体,包含 状态码(Code)、消息体(Message)和响应体(Body) 三个字段,然后在不同的服务和接口中,调用这个结构体并传递对应的参数。客户端只需要根据 Status 的格式进行解析和处理,就能够实现对服务端返回值的处理。

统一响应体的好处是什么?

除了需要统一 Response 返回类型,还需要统一响应体。在 Go 语言中,响应体通常包含三个元素:状态码(Code)、消息体(Message)和响应体(Body)。通过规范和统一这些元素,可以带来以下几个好处:

1. 提高服务的稳定性和可靠性

在 Web Application中,响应体中包含的状态码是非常重要的。通过统一状态码的定义和使用方式,可以提高服务的稳定性和可靠性,减少因为状态码定义不清、使用不当而引发的错误和异常情况。例如,在 RESTful API 中,2xx 状态码表示请求成功,4xx 状态码表示客户端错误,5xx 状态码表示服务器错误。通过使用这些标准化的状态码,可以更好地理解和处理服务端的返回结果。

2. 统一消息体格式

在响应体中,消息体通常是最重要的部分。通过统一消息体的格式,可以大大简化客户端的解析工作,并提高响应数据的可读性和易用性。通常情况下,需要保证消息体的格式清晰明了,字段名称和数据类型都符合约定和规范。例如,使用结构体来表示消息体,定义好每个字段的含义和取值范围,以及对应的数据类型和序列化方式等信息。

3. 统一响应头

除了状态码和消息体之外,响应头也是非常重要的一部分。通过统一响应头的设置方式和内容,可以减少客户端的配置和调试工作,并提高服务端的安全性和可靠性。例如,可以设置响应头的 Content-TypeCache-ControlAccess-Control-Allow-Origin 等字段,以确保客户端能够正确解析响应数据,并避免潜在的安全风险。

统一 Response 返回类型和响应体,是构建高效、稳定和可靠 Web Application的重要实践方式。通过规范和统一响应体中的元素和格式,可以提高服务的可读性、可维护性和可扩展性,从而更好地满足客户端的需求,提升用户体验。

实践

Go

  1. 定义Response
type Status struct {
	Code    int    `json:"code"`    // 业务码
	Message string `json:"message"` // 响应消息
	Body    any    `json:"body"`    // 消息体
}
  1. 使用Response响应体返回示例:

    1.定义Controller控制跳转

    query := c.Query("query")
    // 请求全部列表
    if query == "all" {
    filter := bson.D{{}}
    	specialties, err := service.College{}.GetList(filter)
    	if err != nil {
    		c.AbortWithStatusJSON(http.StatusOK, err)
    		return
    	}
    	c.JSON(http.StatusOK, specialties)
    }
    c.AbortWithStatusJSON(http.StatusOK, err)
    
    1. 定义逻辑处理:
    // GetList
    /* @description 获取学院列表
    * @since 17/03/2023 9:18 pm
    * @param filter {bson.D} 查询条件
    * @return 标准响应状态体 {schema.Status}
    * @return 错误消息 {error}
    *  */
    func (College) GetList(filter bson.D) (schema.Status, error) {
            var specialtyList []schema.College
            result, err := db.Mongo{}.Find(
                    schema.College{}.Collection(),
                    schema.College{}.CollectionName(),
                    filter,
                    specialtyList,
            )
            if err != nil {
                    return result, err
            }
            return result, nil
    }
    

Web

  1. 定义Response类型
export type AlertColor = 'success' | 'info' | 'warning' | 'error'
export type Status<T> = {
  code: number
  message: string
  body: T
  state: AlertColor
}
  1. 请求示例
type Base = {
  id?: string
  name: string
  description: string
}

const {data, isLoading, isError} = useQuery({
    queryKey: ['base'],
    queryFn: () => {
      return fetcher<Base[]>(import.meta.env.VITE_APP_BASE)('all')
        .then((res) => {
           // 获取数据成功状态
          if (res.code >= 200 && res.code <= 299) {
            return res.body
          }
          // 错误状态: 401 未登录状态
          else if (res.code === 401) {
            throw new CustomError(res.code, '未登录', body)
          }
          // 错误状态: 403 无权限状态
          else if (res.code === 403) {
            throw new CustomError(res.code, '无权限', body)
          }
          // 错误状态: 其他错误状态
          throw new CustomError(res.code, '网络请求错误', body)
        })
        .catch((error) => {
          throw new CustomError(error.code,error.message,body||null)
        })
    },
  })
  
  return (
    <Autocomplete
      getOptionLabel={(option) => option.name}
      id="asynchronous"
      isOptionEqualToValue={(option, value) => option.name === value.name}
      open={open}
      options={data}
      renderInput={(params) => (
        <TextField
          {...params}
          InputProps={{
            ...params.InputProps,
            endAdornment: <p>{params.InputProps.endAdornment}</p>,
          }}
          label="base"
        />
      )}
      sx={{width: 300}}
      onChange={(
        _event: SyntheticEvent<Element, Event>,
        newValue: College | null,
      ) => updateCollege(newValue?.name || '')}
      onClose={() => {
        setOpen(false)
      }}
      onOpen={() => {
        setOpen(true)
      }}
    />
  )

响应示例:

image.png