Files
go-common/docs/http.md

19 KiB
Raw Blame History

HTTP Restful工具文档

概述

HTTP Restful工具提供了标准化的HTTP请求和响应处理功能提供公共方法供外部调用保持低耦合。

功能特性

  • 低耦合设计提供公共方法不封装Handler结构
  • 标准化的响应结构{code, message, timestamp, data}
  • 分离HTTP状态码和业务状态码
  • 支持分页响应
  • 提供便捷的请求参数解析方法
  • 支持JSON请求体解析
  • Factory黑盒模式:推荐使用 factory.Success() 等方法

响应结构

标准响应结构

{
    "code": 0,
    "message": "success",
    "timestamp": 1704067200,
    "data": {}
}

结构体类型(暴露在 factory 中):

// 在 factory 包中可以直接使用
type Response struct {
    Code      int         `json:"code"`      // 业务状态码0表示成功
    Message   string      `json:"message"`   // 响应消息
    Timestamp int64       `json:"timestamp"` // 时间戳
    Data      interface{} `json:"data"`      // 响应数据
}

分页响应结构

{
    "code": 0,
    "message": "success",
    "timestamp": 1704067200,
    "data": {
        "list": [],
        "total": 100,
        "page": 1,
        "pageSize": 10
    }
}

结构体类型(暴露在 factory 中):

// 在 factory 包中可以直接使用
type PageData struct {
    List     interface{} `json:"list"`     // 数据列表
    Total    int64       `json:"total"`   // 总记录数
    Page     int         `json:"page"`    // 当前页码
    PageSize int         `json:"pageSize"` // 每页大小
}

type PageResponse struct {
    Code      int       `json:"code"`
    Message   string    `json:"message"`
    Timestamp int64     `json:"timestamp"`
    Data      *PageData `json:"data"`
}

使用暴露的结构体

外部项目可以直接使用 factory.Responsefactory.PageData 等类型:

import "git.toowon.com/jimmy/go-common/factory"

// 创建标准响应对象
response := factory.Response{
    Code:    0,
    Message: "success",
    Data:    userData,
}

// 创建分页数据对象
pageData := &factory.PageData{
    List:     users,
    Total:    100,
    Page:     1,
    PageSize: 20,
}

// 传递给 Success 方法
fac.Success(w, pageData)

使用方法

方式一使用Factory黑盒方法推荐

这是最简单的方式,直接使用 factory.Success() 等方法:

import (
    "net/http"
    "git.toowon.com/jimmy/go-common/factory"
    commonhttp "git.toowon.com/jimmy/go-common/http"
)

func GetUser(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 获取查询参数(使用公共方法)
    id := commonhttp.GetQueryInt64(r, "id", 0)
    
    // 返回成功响应使用factory方法
    fac.Success(w, data)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 解析JSON使用公共方法
    var req struct {
        Name string `json:"name"`
    }
    if err := commonhttp.ParseJSON(r, &req); err != nil {
        commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
        return
    }
    
    // 返回成功响应使用factory方法
    fac.Success(w, data, "创建成功")
}

func GetUserList(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 获取分页参数使用factory方法推荐
    pagination := fac.ParsePaginationRequest(r)
    page := pagination.GetPage()
    pageSize := pagination.GetSize()
    
    // 获取查询参数使用factory方法推荐
    keyword := fac.GetQuery(r, "keyword", "")
    
    // 查询数据
    list, total := getDataList(keyword, page, pageSize)
    
    // 返回分页响应使用factory方法
    fac.SuccessPage(w, list, total, page, pageSize)
}

// 注册路由
http.HandleFunc("/user", GetUser)
http.HandleFunc("/users", GetUserList)

方式二:直接使用公共方法

如果不想使用Factory可以直接使用 http 包的公共方法:

import (
    "net/http"
    commonhttp "git.toowon.com/jimmy/go-common/http"
)

func GetUser(w http.ResponseWriter, r *http.Request) {
    // 获取查询参数
    id := commonhttp.GetQueryInt64(r, "id", 0)
    
    // 返回成功响应
    commonhttp.Success(w, data)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
    // 解析JSON
    var req struct {
        Name string `json:"name"`
    }
    if err := commonhttp.ParseJSON(r, &req); err != nil {
        commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
        return
    }
    
    // 返回成功响应
    commonhttp.Success(w, data, "创建成功")
}

成功响应

// 使用Factory推荐
fac.Success(w, data)                    // 只有数据,使用默认消息 "success"
fac.Success(w, data, "操作成功")        // 数据+消息

// 或直接使用公共方法
commonhttp.Success(w, data)                    // 只有数据
commonhttp.Success(w, data, "操作成功")        // 数据+消息

错误响应

// 使用Factory推荐
fac.Error(w, 1001, "用户不存在")              // 业务错误HTTP 200业务code非0
fac.SystemError(w, "服务器内部错误")          // 系统错误HTTP 500

// 或直接使用公共方法
commonhttp.Error(w, 1001, "用户不存在")
commonhttp.SystemError(w, "服务器内部错误")
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil)  // 自定义HTTP状态码仅公共方法

分页响应

// 使用Factory推荐
fac.SuccessPage(w, list, total, page, pageSize)
fac.SuccessPage(w, list, total, page, pageSize, "查询成功")

// 或直接使用公共方法
commonhttp.SuccessPage(w, list, total, page, pageSize)
commonhttp.SuccessPage(w, list, total, page, pageSize, "查询成功")

解析请求

解析JSON请求体

// 使用公共方法
var req struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

if err := commonhttp.ParseJSON(r, &req); err != nil {
    commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
    return
}

获取查询参数

// 使用公共方法
name := commonhttp.GetQuery(r, "name", "")
id := commonhttp.GetQueryInt(r, "id", 0)
userId := commonhttp.GetQueryInt64(r, "userId", 0)
isActive := commonhttp.GetQueryBool(r, "isActive", false)
price := commonhttp.GetQueryFloat64(r, "price", 0.0)

获取表单参数

// 使用公共方法
name := commonhttp.GetFormValue(r, "name", "")
age := commonhttp.GetFormInt(r, "age", 0)
userId := commonhttp.GetFormInt64(r, "userId", 0)
isActive := commonhttp.GetFormBool(r, "isActive", false)

获取请求头

// 使用公共方法
token := commonhttp.GetHeader(r, "Authorization", "")
contentType := commonhttp.GetHeader(r, "Content-Type", "application/json")

获取分页参数

方式1使用 PaginationRequest 结构(推荐)

// 定义请求结构(包含分页字段)
type ListUserRequest struct {
    Keyword string `json:"keyword"`
    commonhttp.PaginationRequest // 嵌入分页请求结构
}

// 从JSON请求体解析分页字段会自动解析
var req ListUserRequest
if err := commonhttp.ParseJSON(r, &req); err != nil {
    commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
    return
}

// 使用分页方法
page := req.GetPage()      // 获取页码默认1
size := req.GetSize()       // 获取每页数量默认20最大100优先使用page_size
offset := req.GetOffset()   // 计算偏移量

方式2从查询参数/form解析分页

// 使用公共方法
pagination := commonhttp.ParsePaginationRequest(r)
page := pagination.GetPage()
size := pagination.GetSize()
offset := pagination.GetOffset()

获取时区

// 使用公共方法
// 如果使用了middleware.Timezone中间件可以从context中获取时区信息
// 如果未设置,返回默认时区 AsiaShanghai
timezone := commonhttp.GetTimezone(r)

完整示例

使用Factory推荐

package main

import (
    "log"
    "net/http"
    "git.toowon.com/jimmy/go-common/factory"
    commonhttp "git.toowon.com/jimmy/go-common/http"
)

// 用户结构
type User struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// 用户列表接口
func GetUserList(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 获取分页参数(使用公共方法)
    pagination := commonhttp.ParsePaginationRequest(r)
    page := pagination.GetPage()
    pageSize := pagination.GetSize()
    
    // 获取查询参数(使用公共方法)
    keyword := commonhttp.GetQuery(r, "keyword", "")
    
    // 查询数据
    users, total := queryUsers(keyword, page, pageSize)
    
    // 返回分页响应使用factory方法
    fac.SuccessPage(w, users, total, page, pageSize)
}

// 创建用户接口
func CreateUser(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 解析请求体(使用公共方法)
    var req struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    
    if err := commonhttp.ParseJSON(r, &req); err != nil {
        fac.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
        return
    }
    
    // 参数验证
    if req.Name == "" {
        fac.Error(w, 1001, "用户名不能为空")
        return
    }
    
    // 创建用户
    user, err := createUser(req.Name, req.Email)
    if err != nil {
        fac.SystemError(w, "创建用户失败")
        return
    }
    
    // 返回成功响应使用factory方法
    fac.Success(w, user, "创建成功")
}

// 获取用户详情接口
func GetUser(w http.ResponseWriter, r *http.Request) {
    fac, _ := factory.NewFactoryFromFile("config.json")
    
    // 获取查询参数使用factory方法推荐
    id := fac.GetQueryInt64(r, "id", 0)
    
    if id == 0 {
        commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "用户ID不能为空", nil)
        return
    }
    
    // 查询用户
    user, err := getUserByID(id)
    if err != nil {
        fac.SystemError(w, "查询用户失败")
        return
    }
    
    if user == nil {
        fac.Error(w, 1002, "用户不存在")
        return
    }
    
    // 返回成功响应使用factory方法
    fac.Success(w, user)
}

func main() {
    http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            GetUserList(w, r)
        case http.MethodPost:
            CreateUser(w, r)
        default:
            commonhttp.WriteJSON(w, http.StatusMethodNotAllowed, 405, "方法不支持", nil)
        }
    })
    
    http.HandleFunc("/user", GetUser)
    
    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

API 参考

Factory HTTP响应结构体暴露给外部项目使用

Response

标准响应结构体,外部项目可以直接使用 factory.Response

字段:

  • Code: 业务状态码0表示成功
  • Message: 响应消息
  • Timestamp: 时间戳Unix时间戳
  • Data: 响应数据

示例:

response := factory.Response{
    Code:    0,
    Message: "success",
    Data:    userData,
}

PageData

分页数据结构体,外部项目可以直接使用 factory.PageData

字段:

  • List: 数据列表
  • Total: 总记录数
  • Page: 当前页码
  • PageSize: 每页大小

示例:

pageData := &factory.PageData{
    List:     users,
    Total:    100,
    Page:     1,
    PageSize: 20,
}
fac.Success(w, pageData)

PageResponse

分页响应结构体,外部项目可以直接使用 factory.PageResponse

字段:

  • Code: 业务状态码
  • Message: 响应消息
  • Timestamp: 时间戳
  • Data: 分页数据(*PageData

Factory HTTP响应方法推荐使用

(f *Factory) Success(w http.ResponseWriter, data interface{}, message ...string)

成功响应HTTP 200业务code 0。

参数:

  • data: 响应数据可以为nil
  • message: 响应消息(可选),如果为空则使用默认消息 "success"

示例:

fac.Success(w, data)                    // 只有数据,使用默认消息 "success"
fac.Success(w, data, "操作成功")        // 数据+消息

(f *Factory) Error(w http.ResponseWriter, code int, message string)

业务错误响应HTTP 200业务code非0。

示例:

fac.Error(w, 1001, "用户不存在")

(f *Factory) SystemError(w http.ResponseWriter, message string)

系统错误响应HTTP 500业务code 500。

示例:

fac.SystemError(w, "服务器内部错误")

(f *Factory) WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})

写入JSON响应自定义

参数:

  • httpCode: HTTP状态码
  • code: 业务状态码
  • message: 响应消息
  • data: 响应数据

说明:

  • 此方法不在 Factory 中,直接使用 commonhttp.WriteJSON()
  • 用于需要自定义HTTP状态码的场景如 400, 401, 403, 404 等)

示例:

commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil)
commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil)

(f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string)

分页成功响应。

参数:

  • list: 数据列表
  • total: 总记录数
  • page: 当前页码
  • pageSize: 每页大小
  • message: 响应消息(可选,如果为空则使用默认消息 "success"

示例:

fac.SuccessPage(w, users, total, page, pageSize)
fac.SuccessPage(w, users, total, page, pageSize, "查询成功")

HTTP公共方法直接使用

WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})

写入JSON响应自定义HTTP状态码和业务状态码

说明:

  • 此方法不在 Factory 中,直接使用 commonhttp.WriteJSON()
  • 用于需要自定义HTTP状态码的场景如 400, 401, 403, 404 等)

示例:

commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil)
commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil)

ParseJSON(r *http.Request, v interface{}) error

解析JSON请求体。

示例:

var req CreateUserRequest
if err := commonhttp.ParseJSON(r, &req); err != nil {
    // 处理错误
}

GetQuery(r *http.Request, key, defaultValue string) string

获取查询参数(字符串)。

GetQueryInt(r *http.Request, key string, defaultValue int) int

获取查询参数(整数)。

GetQueryInt64(r *http.Request, key string, defaultValue int64) int64

获取查询参数int64

GetQueryBool(r *http.Request, key string, defaultValue bool) bool

获取查询参数(布尔值)。

GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64

获取查询参数(浮点数)。

GetFormValue(r *http.Request, key, defaultValue string) string

获取表单值(字符串)。

GetFormInt(r *http.Request, key string, defaultValue int) int

获取表单值(整数)。

GetFormInt64(r *http.Request, key string, defaultValue int64) int64

获取表单值int64

GetFormBool(r *http.Request, key string, defaultValue bool) bool

获取表单值(布尔值)。

GetHeader(r *http.Request, key, defaultValue string) string

获取请求头。

ParsePaginationRequest(r *http.Request) *PaginationRequest

从请求中解析分页参数。

说明:

  • 支持从查询参数和form表单中解析
  • 优先级:查询参数 > form表单
  • 如果请求体是JSON格式且包含分页字段建议先使用ParseJSON解析完整请求体到包含PaginationRequest的结构体中

GetTimezone(r *http.Request) string

从请求的context中获取时区。

说明:

  • 如果使用了middleware.Timezone中间件可以从context中获取时区信息
  • 如果未设置,返回默认时区 AsiaShanghai

Success(w http.ResponseWriter, data interface{}, message ...string)

成功响应(公共方法)。

参数:

  • data: 响应数据可以为nil
  • message: 响应消息(可选),如果为空则使用默认消息 "success"

示例:

commonhttp.Success(w, data)                    // 只有数据
commonhttp.Success(w, data, "操作成功")        // 数据+消息

Error(w http.ResponseWriter, code int, message string)

错误响应(公共方法)。

SystemError(w http.ResponseWriter, message string)

系统错误响应(公共方法)。

WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})

写入JSON响应公共方法不在Factory中

说明:

  • 用于需要自定义HTTP状态码的场景
  • 直接使用 commonhttp.WriteJSON(),不在 Factory 中

SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string)

分页成功响应(公共方法)。

分页请求结构

PaginationRequest

分页请求结构支持从JSON和form中解析分页参数。

字段:

  • Page: 页码默认1
  • Size: 每页数量(兼容旧版本)
  • PageSize: 每页数量推荐使用优先于Size

方法:

  • GetPage() int: 获取页码如果未设置则返回默认值1
  • GetSize() int: 获取每页数量优先使用PageSize如果未设置则使用Size默认20最大100
  • GetOffset() int: 计算数据库查询的偏移量

ParsePaginationRequest(r *http.Request) *PaginationRequest

从请求中解析分页参数内部函数Handler内部使用

状态码说明

HTTP状态码

  • 200: 正常响应(包括业务错误)
  • 400: 请求参数错误
  • 401: 未授权
  • 403: 禁止访问
  • 404: 资源不存在
  • 500: 系统内部错误

业务状态码

  • 0: 成功
  • 非0: 业务错误(具体错误码由业务定义)

注意事项

  1. HTTP状态码与业务状态码分离

    • 业务错误如用户不存在、参数验证失败等返回HTTP 200业务code非0
    • 只有系统异常如数据库连接失败、程序panic等才返回HTTP 500
  2. 分页参数限制

    • page最小值为1
    • pageSize最小值为1最大值为100
  3. 响应格式统一

    • 所有响应都遵循标准结构
    • timestamp为Unix时间戳
  4. 错误处理

    • 使用Error方法返回业务错误HTTP 200业务code非0
    • 使用SystemError返回系统错误HTTP 500
    • 其他HTTP错误状态码400, 401, 403, 404等使用WriteJSON方法直接指定
  5. 推荐使用Factory

    • 使用 factory.Success() 等方法,代码更简洁
    • 直接使用 http 包的公共方法,保持低耦合
    • 不需要Handler结构减少不必要的封装

示例

完整示例请参考:

  • examples/http_handler_example.go - 使用Factory和公共方法
  • examples/http_pagination_example.go - 分页示例
  • examples/factory_blackbox_example.go - Factory黑盒模式示例