Skip to content

统一响应体设计

概述

统一响应体是一种标准化 API 响应格式的设计模式。本规范定义了基于 Ant Design Pro 标准的响应体格式,适用于前后端分离的 Web 应用。

设计原则

为什么需要统一响应体

统一响应体的优势

  • 前端处理简化:前端可用统一逻辑处理所有响应
  • 错误处理标准化:成功和失败响应结构一致
  • 类型安全:TypeScript 可准确推断响应类型
  • 易于扩展:支持分页、元数据等扩展字段

对比示例

go
// ❌ 不统一的响应格式
// 成功响应
{"data": {"id": 1, "name": "张三"}}

// 错误响应
{"error": "用户不存在"}

// 另一个接口的错误响应
{"message": "参数错误", "code": 400}

// ✅ 统一响应格式
// 成功响应
{"success": true, "data": {"id": 1, "name": "张三"}}

// 错误响应
{"success": false, "errorMessage": "用户不存在", "errorCode": 2001}

Ant Design Pro 响应格式

标准格式定义

Ant Design Pro 定义了以下标准响应格式:

成功响应(单条数据)

json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "张三",
    "email": "user@example.com"
  }
}

成功响应(列表数据,不分页)

json
{
  "success": true,
  "data": [
    {"id": 1, "name": "商品1"},
    {"id": 2, "name": "商品2"}
  ]
}

成功响应(分页数据)

json
{
  "success": true,
  "data": [
    {"id": 1, "name": "用户1"},
    {"id": 2, "name": "用户2"}
  ],
  "total": 100,
  "current": 1,
  "pageSize": 20
}

失败响应

json
{
  "success": false,
  "errorMessage": "参数格式错误",
  "errorCode": 1000
}

字段说明

字段类型必填说明
successboolean请求是否成功,true 成功,false 失败
dataany响应数据,成功时返回
errorMessagestring错误信息,失败时返回
errorCodenumber错误码,失败时返回
totalnumber总记录数,分页时返回
currentnumber当前页码,分页时返回
pageSizenumber每页大小,分页时返回

Go 后端实现

响应体结构体定义

go
// httpapi/response/response.go
package response

// BaseResponse 基础统一响应体
type BaseResponse struct {
    Success      bool        `json:"success"`                    // 响应状态
    Data         interface{} `json:"data,omitempty"`             // 响应数据(成功时返回)
    ErrorMessage string      `json:"errorMessage,omitempty"`     // 错误信息(失败时返回)
    ErrorCode    int         `json:"errorCode,omitempty"`        // 错误码(失败时返回)
}

// PagedResponse 分页统一响应体
type PagedResponse struct {
    Success      bool        `json:"success"`                    // 响应状态
    Data         interface{} `json:"data,omitempty"`             // 响应数据列表
    Total        int64       `json:"total,omitempty"`            // 总记录数
    Current      int         `json:"current,omitempty"`          // 当前页码
    PageSize     int         `json:"pageSize,omitempty"`         // 每页大小
    ErrorMessage string      `json:"errorMessage,omitempty"`     // 错误信息
    ErrorCode    int         `json:"errorCode,omitempty"`        // 错误码
}

构造函数

go
// Success 创建成功响应
func Success(data interface{}) BaseResponse {
    return BaseResponse{
        Success: true,
        Data:    data,
    }
}

// Fail 创建失败响应
func Fail(errorCode int, errorMessage string) BaseResponse {
    return BaseResponse{
        Success:      false,
        ErrorMessage: errorMessage,
        ErrorCode:    errorCode,
    }
}

// SuccessPaged 创建分页成功响应
func SuccessPaged(data interface{}, total int64, current, pageSize int) PagedResponse {
    return PagedResponse{
        Success:  true,
        Data:     data,
        Total:    total,
        Current:  current,
        PageSize: pageSize,
    }
}

// FailPaged 创建分页失败响应
func FailPaged(errorCode int, errorMessage string) PagedResponse {
    return PagedResponse{
        Success:      false,
        ErrorMessage: errorMessage,
        ErrorCode:    errorCode,
    }
}

Handler 使用示例

单条数据响应

go
// handlers/user.go
package handlers

import (
    "net/http"

    "your-project/httpapi/response"
    "github.com/labstack/echo/v4"
)

type UserHandler struct {
    userService *services.UserService
}

// Get 获取用户详情
func (h *UserHandler) Get(c echo.Context) error {
    id := c.Param("id")

    // 调用 Service
    user, err := h.userService.GetByID(c.Request().Context(), id)
    if err != nil {
        // ✅ 使用 response.Fail 返回错误响应
        return c.JSON(http.StatusNotFound, response.Fail(2001, "用户不存在"))
    }

    // ✅ 使用 response.Success 返回成功响应
    return c.JSON(http.StatusOK, response.Success(user))
}

响应示例

json
// 成功时
{
  "success": true,
  "data": {
    "id": 1,
    "name": "张三",
    "email": "user@example.com"
  }
}

// 失败时
{
  "success": false,
  "errorMessage": "用户不存在",
  "errorCode": 2001
}

创建资源响应

go
// Create 创建用户
func (h *UserHandler) Create(c echo.Context) error {
    req := new(CreateUserRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, response.Fail(1000, "参数格式错误"))
    }

    if err := c.Validate(req); err != nil {
        return c.JSON(http.StatusBadRequest, response.Fail(1001, err.Error()))
    }

    user, err := h.userService.Create(c.Request().Context(), req)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, response.Fail(5000, "创建用户失败"))
    }

    // ✅ 创建成功返回 201 状态码
    return c.JSON(http.StatusCreated, response.Success(user))
}

分页列表响应

go
// List 分页查询用户列表
func (h *UserHandler) List(c echo.Context) error {
    page := c.QueryParam("current")    // 当前页码
    pageSize := c.QueryParam("pageSize") // 每页大小

    // 调用 Service
    users, total, err := h.userService.List(c.Request().Context(), page, pageSize)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, response.Fail(5000, "查询失败"))
    }

    // ✅ 使用 response.SuccessPaged 返回分页响应
    return c.JSON(http.StatusOK, response.SuccessPaged(users, total, page, pageSize))
}

响应示例

json
{
  "success": true,
  "data": [
    {"id": 1, "name": "用户1", "email": "user1@example.com"},
    {"id": 2, "name": "用户2", "email": "user2@example.com"}
  ],
  "total": 100,
  "current": 1,
  "pageSize": 20
}

删除操作响应

go
// Delete 删除用户
func (h *UserHandler) Delete(c echo.Context) error {
    id := c.Param("id")

    err := h.userService.Delete(c.Request().Context(), id)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, response.Fail(5000, "删除失败"))
    }

    // ✅ 删除成功返回空数据
    return c.JSON(http.StatusOK, response.Success(nil))
}

响应示例

json
{
  "success": true
}

TypeScript 前端实现

类型定义

typescript
// types/response.ts

// 基础响应类型
export interface BaseResponse<T = any> {
  success: boolean
  data?: T
  errorMessage?: string
  errorCode?: number
}

// 分页响应类型
export interface PagedResponse<T = any> {
  success: boolean
  data?: T[]
  total?: number
  current?: number
  pageSize?: number
  errorMessage?: string
  errorCode?: number
}

// 分页请求参数
export interface PageParams {
  current: number
  pageSize: number
}

前端请求封装

typescript
// utils/request.ts
import axios, { AxiosResponse } from 'axios'
import type { BaseResponse, PagedResponse } from '@/types/response'

// 创建 axios 实例
const request = axios.create({
  baseURL: '/api',
  timeout: 10000,
})

// 响应拦截器
request.interceptors.response.use(
  (response: AxiosResponse<BaseResponse>) => {
    const data = response.data

    // ✅ 统一处理响应
    if (data.success) {
      return data  // 成功时返回整个响应对象
    } else {
      // 失败时抛出错误
      const error = new Error(data.errorMessage || '请求失败')
      error.code = data.errorCode
      throw error
    }
  },
  (error) => {
    // 处理 HTTP 错误
    return Promise.reject(error)
  }
)

export default request

前端 API 调用

typescript
// services/user.ts
import request from '@/utils/request'
import type { BaseResponse, PagedResponse, PageParams } from '@/types/response'

// 用户类型定义
export interface User {
  id: number
  name: string
  email: string
}

// 获取用户详情
export async function getUser(id: number): Promise<User> {
  const res = await request.get<BaseResponse<User>>(`/users/${id}`)
  return res.data!  // 类型安全:success=true 时 data 必定存在
}

// 创建用户
export async function createUser(params: {
  name: string
  email: string
}): Promise<User> {
  const res = await request.post<BaseResponse<User>>('/users', params)
  return res.data!
}

// 分页查询用户列表
export async function getUserList(params: PageParams): Promise<PagedResponse<User>> {
  const res = await request.get<PagedResponse<User>>('/users', { params })
  return res  // 返回完整的分页响应(包含 data、total、current、pageSize)
}

// 删除用户
export async function deleteUser(id: number): Promise<void> {
  await request.delete(`/users/${id}`)
}

React 组件使用

typescript
// pages/UserList.tsx
import React, { useEffect, useState } from 'react'
import { Table, message } from 'antd'
import { getUserList } from '@/services/user'
import type { User } from '@/services/user'

const UserList: React.FC = () => {
  const [loading, setLoading] = useState(false)
  const [users, setUsers] = useState<User[]>([])
  const [total, setTotal] = useState(0)
  const [current, setCurrent] = useState(1)
  const [pageSize, setPageSize] = useState(20)

  const fetchUsers = async (page: number, size: number) => {
    setLoading(true)
    try {
      // ✅ 调用 API,自动处理统一响应格式
      const res = await getUserList({ current: page, pageSize: size })

      setUsers(res.data || [])
      setTotal(res.total || 0)
      setCurrent(res.current || 1)
      setPageSize(res.pageSize || 20)
    } catch (error) {
      // ❌ 错误处理:显示 errorMessage
      message.error(error.message || '查询失败')
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    fetchUsers(current, pageSize)
  }, [])

  return (
    <Table
      dataSource={users}
      loading={loading}
      pagination={{
        current,
        pageSize,
        total,
        onChange: (page, size) => fetchUsers(page, size),
      }}
      columns={[
        { title: 'ID', dataIndex: 'id' },
        { title: '姓名', dataIndex: 'name' },
        { title: '邮箱', dataIndex: 'email' },
      ]}
    />
  )
}

export default UserList

错误码规范

错误码分类

统一的错误码设计

错误码范围分类示例
1000-1999通用错误1000: 参数错误, 1001: 参数校验失败
2000-2999认证错误2000: 未登录, 2001: 用户不存在, 2002: 密码错误
3000-3999权限错误3000: 无权限, 3001: 账号已禁用
4000-4999业务错误4000: 库存不足, 4001: 订单已关闭
5000-5999系统错误5000: 服务器内部错误, 5001: 数据库错误

详见 错误码规范

Go 错误码定义

go
// constants/error_codes.go
package constants

const (
    // 通用错误 1000-1999
    ErrCodeInvalidParams     = 1000  // 参数格式错误
    ErrCodeValidationFailed  = 1001  // 参数校验失败

    // 认证错误 2000-2999
    ErrCodeNotLogin          = 2000  // 未登录
    ErrCodeUserNotFound      = 2001  // 用户不存在
    ErrCodeWrongPassword     = 2002  // 密码错误
    ErrCodeTokenExpired      = 2003  // Token 过期

    // 权限错误 3000-3999
    ErrCodeNoPermission      = 3000  // 无权限
    ErrCodeAccountDisabled   = 3001  // 账号已禁用

    // 业务错误 4000-4999
    ErrCodeInsufficientStock = 4000  // 库存不足
    ErrCodeOrderClosed       = 4001  // 订单已关闭

    // 系统错误 5000-5999
    ErrCodeInternalError     = 5000  // 服务器内部错误
    ErrCodeDatabaseError     = 5001  // 数据库错误
)

Handler 使用错误码

go
func (h *UserHandler) Login(c echo.Context) error {
    req := new(LoginRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest,
            response.Fail(constants.ErrCodeInvalidParams, "参数格式错误"))
    }

    user, err := h.userService.Login(c.Request().Context(), req.Email, req.Password)
    if err != nil {
        // 根据不同错误返回不同错误码
        if err.Error() == "用户不存在" {
            return c.JSON(http.StatusUnauthorized,
                response.Fail(constants.ErrCodeUserNotFound, "用户名或密码错误"))
        }
        if err.Error() == "密码错误" {
            return c.JSON(http.StatusUnauthorized,
                response.Fail(constants.ErrCodeWrongPassword, "用户名或密码错误"))
        }
        return c.JSON(http.StatusInternalServerError,
            response.Fail(constants.ErrCodeInternalError, "登录失败"))
    }

    return c.JSON(http.StatusOK, response.Success(user))
}

最佳实践

1. 统一使用响应体

所有 Handler 必须使用统一响应体

go
// ✅ 正确
return c.JSON(http.StatusOK, response.Success(user))

// ❌ 错误:直接返回业务数据
return c.JSON(http.StatusOK, user)

// ❌ 错误:自定义响应格式
return c.JSON(http.StatusOK, map[string]interface{}{
    "code": 200,
    "data": user,
})

2. HTTP 状态码与 success 字段的关系

遵循以下规则

successHTTP 状态码说明
true200/201成功响应
false400/401/403/404/500错误响应
go
// ✅ 正确:success=true 时使用 2xx 状态码
return c.JSON(http.StatusOK, response.Success(data))           // 200
return c.JSON(http.StatusCreated, response.Success(data))      // 201

// ✅ 正确:success=false 时使用 4xx/5xx 状态码
return c.JSON(http.StatusBadRequest, response.Fail(1000, "参数错误"))        // 400
return c.JSON(http.StatusUnauthorized, response.Fail(2000, "未登录"))        // 401
return c.JSON(http.StatusForbidden, response.Fail(3000, "无权限"))           // 403
return c.JSON(http.StatusNotFound, response.Fail(2001, "用户不存在"))        // 404
return c.JSON(http.StatusInternalServerError, response.Fail(5000, "服务器错误"))  // 500

3. 空数据处理

成功但无数据时返回 null 或空数组

go
// 删除操作:返回 null
return c.JSON(http.StatusOK, response.Success(nil))

// 查询列表为空:返回空数组
return c.JSON(http.StatusOK, response.SuccessPaged([]User{}, 0, 1, 20))

4. 分页参数命名

使用 Ant Design Pro 的分页参数命名

参数说明类型
current当前页码(从 1 开始)number
pageSize每页大小number
total总记录数number

检查清单

实施统一响应体时,确保以下要点:

  • [ ] 所有 Handler 使用 response.Successresponse.Fail
  • [ ] 不直接返回业务数据或自定义响应格式
  • [ ] 错误响应包含 errorCodeerrorMessage
  • [ ] 分页响应包含 datatotalcurrentpageSize
  • [ ] HTTP 状态码与 success 字段保持一致
  • [ ] 错误码遵循统一分类规范

相关规范