在Golang开发中,构建RESTful API是最常见的Web服务场景之一。不同于传统的单体Web应用,RESTful API专注于"资源"的操作,遵循无状态、统一接口等设计原则,适配前后端分离、微服务等主流架构。
本文将从路由设计、中间件、请求解析等基础环节入手,最终结合Gin框架(Golang最流行的Web框架之一),完整实现一个可复用、易维护的RESTful API,并集成接口文档、日志、错误统一等生产级必备功能,所有代码示例简洁可运行,关键工具标注官方引用,方便直接上手实践。
1. 路由设计
路由是API的"入口",负责将客户端的HTTP请求(GET/POST/PUT/DELETE等)映射到对应的处理函数。RESTful API的路由设计核心是"围绕资源命名",而非"围绕操作命名",同时需遵循简洁、语义化的原则。
1.1 路由设计原则
-
用名词表示资源(复数优先),如
/users(用户列表)、/posts(文章列表),而非/getUser、/addPost; -
用HTTP方法表示操作,GET(查询)、POST(新增)、PUT(全量更新)、DELETE(删除)、PATCH(部分更新);
-
用路径参数表示单个资源,如
/users/:id(指定ID的用户); -
用查询参数过滤资源,如
/users?page=1&size=10(分页查询用户); -
统一返回资源路径,新增资源后返回
Location响应头,指向新资源地址。
1.2 原生路由示例(net/http)
Golang标准库net/http自带基础路由功能,无需依赖第三方库,适合简单场景,核心是http.HandleFunc和http.Server。
go
package main
import (
"fmt"
"net/http"
)
// 处理用户列表查询(GET)
func userListHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "user list: [Alice, Bob]")
}
// 处理单个用户查询(GET + 路径参数)
func userDetailHandler(w http.ResponseWriter, r *http.Request) {
// 提取路径参数id(原生需手动解析)
id := r.URL.Path[len("/users/"):]
fmt.Fprintf(w, "user detail, id: %s", id)
}
func main() {
// 注册路由
http.HandleFunc("/users", userListHandler) // GET /users
http.HandleFunc("/users/", userDetailHandler) // GET /users/:id
// 启动服务,监听8080端口
fmt.Println("server start at :8080")
http.ListenAndServe(":8080", nil)
}
注意:原生路由不支持路由参数自动解析(如:id),需手动截取路径,且不支持路由分组,复杂场景下会显得繁琐。
标准库net/http官方文档:pkg.go.dev/net/http
1.3 路由分组思想
实际开发中,API会按资源类型分组(如用户、文章、订单),或按版本分组(如/v1/users、/v2/users),便于维护和扩展。原生路由需手动判断路径前缀,而后续介绍的Gin框架会提供便捷的分组方法。
2. 中间件
中间件(Middleware)是Web服务的"拦截器",用于在请求到达处理函数之前、响应返回客户端之前,执行统一的逻辑(如身份验证、日志记录、跨域处理等)。中间件支持链式调用,可复用性极强,是构建高可用API的核心组件。
2.1 中间件核心原理
Golang中,中间件本质是"包装Handler的函数",接收一个http.Handler作为参数,返回一个新的http.Handler,在新Handler中嵌入通用逻辑,再调用原Handler。
核心流程:客户端请求 → 中间件1 → 中间件2 → ... → 业务Handler → 中间件n → ... → 客户端响应
2.2 原生中间件示例(日志中间件)
实现一个简单的日志中间件,记录请求方法、路径、耗时:
go
package main
import (
"fmt"
"net/http"
"time"
)
// 日志中间件:包装http.Handler
func loggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 请求前:记录请求信息、开始时间
start := time.Now()
method := r.Method
path := r.URL.Path
fmt.Printf("request start: %s %s\n", method, path)
// 调用后续的Handler(业务逻辑)
next.ServeHTTP(w, r)
// 请求后:记录耗时
cost := time.Since(start)
fmt.Printf("request end: %s %s, cost: %v\n", method, path, cost)
})
}
// 业务Handler
func helloHandler(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond) // 模拟业务耗时
fmt.Fprintln(w, "hello middleware")
}
func main() {
// 注册路由,使用中间件包装业务Handler
http.Handle("/hello", loggerMiddleware(http.HandlerFunc(helloHandler)))
fmt.Println("server start at :8080")
http.ListenAndServe(":8080", nil)
}
运行后访问http://localhost:8080/hello,控制台会输出:
bash
request start: GET /hello
request end: GET /hello, cost: 101.234µs
2.3 常用中间件场景
-
身份验证:校验Token(如JWT),未登录则拦截请求;
-
跨域处理:设置
Access-Control-Allow-*响应头; -
请求限流:限制单个IP的请求频率,防止恶意攻击;
-
日志记录:记录请求、响应、错误等信息,便于排查问题;
-
异常捕获:捕获业务Handler中的 panic,避免服务崩溃。
3. 请求解析
客户端发送的请求数据,通常有3种形式:路径参数(如/users/:id)、查询参数(如/users?page=1)、请求体(如POST请求的JSON数据)。Golang需通过对应方式解析这些数据,才能用于业务逻辑。
3.1 路径参数解析(原生)
原生路由需手动截取路径,或使用第三方库(如gorilla/mux)简化解析,这里展示原生方式和gorilla/mux示例:
go
// 原生方式(手动截取)
func userDetailHandler(w http.ResponseWriter, r *http.Request) {
// 假设路径是 /users/123,截取 /users/ 后面的部分
id := r.URL.Path[len("/users/"):]
if id == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "id is required")
return
}
fmt.Fprintf(w, "user id: %s", id)
}
// 使用gorilla/mux(推荐,需先安装:go get github.com/gorilla/mux)
import "github.com/gorilla/mux"
func userDetailHandlerV2(w http.ResponseWriter, r *http.Request) {
// 自动解析路径参数 :id
vars := mux.Vars(r)
id := vars["id"]
fmt.Fprintf(w, "user id: %s", id)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users/{id}", userDetailHandlerV2) // 定义路径参数id
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
gorilla/mux官方文档:pkg.go.dev/github.com/...
3.2 查询参数解析
查询参数通过r.URL.Query()解析,返回url.Values类型(类似map[string][]string),可通过Get()方法获取单个值,Values()获取多个值。
go
func userListHandler(w http.ResponseWriter, r *http.Request) {
// 解析查询参数 page 和 size
page := r.URL.Query().Get("page") // 获取单个值
size := r.URL.Query().Get("size")
// 解析多个值(如 /users?tag=go&tag=java)
tags := r.URL.Query()["tag"]
// 默认值处理
if page == "" {
page = "1"
}
if size == "" {
size = "10"
}
fmt.Fprintf(w, "page: %s, size: %s, tags: %v", page, size, tags)
}
访问http://localhost:8080/users?page=2&size=20&tag=go&tag=java,响应结果:
text
page: 2, size: 20, tags: [go java]
3.3 请求体解析(JSON)
POST、PUT等请求,通常会将复杂数据放在请求体中,格式以JSON为主。解析JSON需先定义对应结构体,再使用encoding/json包的NewDecoder解析。
go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// 定义请求体对应的结构体(字段首字母大写,否则无法解析)
type CreateUserRequest struct {
Name string `json:"name"` // json标签:对应请求体中的key
Age int `json:"age"`
Email string `json:"email"`
}
// 处理POST请求,解析JSON请求体
func createUserHandler(w http.ResponseWriter, r *http.Request) {
// 校验请求方法
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintln(w, "method not allowed")
return
}
// 解析请求体(限制最大读取字节数,防止恶意请求)
var req CreateUserRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "invalid request body")
return
}
// 校验请求参数(简单校验)
if req.Name == "" || req.Email == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "name and email are required")
return
}
// 模拟新增用户
fmt.Fprintf(w, "create user success: %+v", req)
}
func main() {
http.HandleFunc("/users", createUserHandler)
fmt.Println("server start at :8080")
http.ListenAndServe(":8080", nil)
}
使用Postman发送POST请求,请求体为JSON:
json
{ "name": "Alice", "age": 20, "email": "alice@example.com" }
响应结果:create user success: {Name:Alice Age:20 Email:alice@example.com}
encoding/json官方文档:pkg.go.dev/encoding/js...
4. 响应封装
RESTful API的响应格式需统一,便于前端解析。通常包含3个核心字段:状态码(code)、提示信息(msg)、响应数据(data),避免直接返回原始数据或杂乱的字符串。
4.1 统一响应结构体
定义通用的响应结构体,结合JSON序列化,实现统一响应格式:
go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// 统一响应结构体
type Response struct {
Code int `json:"code"` // 业务状态码(非HTTP状态码)
Msg string `json:"msg"` // 提示信息
Data interface{} `json:"data"` // 响应数据(可自定义类型)
}
// 响应工具函数:简化响应编写
func ResponseJSON(w http.ResponseWriter, code int, msg string, data interface{}) {
// 设置响应头:返回JSON格式
w.Header().Set("Content-Type", "application/json;charset=utf-8")
// 序列化响应结构体
res, err := json.Marshal(Response{
Code: code,
Msg: msg,
Data: data,
})
if err != nil {
fmt.Fprintln(w, `{"code":500,"msg":"response serialize failed","data":null}`)
return
}
// 写入响应
w.Write(res)
}
// 业务Handler:使用统一响应
func userListHandler(w http.ResponseWriter, r *http.Request) {
// 模拟查询用户列表
users := []map[string]interface{}{
{"id": "1", "name": "Alice"},
{"id": "2", "name": "Bob"},
}
// 成功响应(code=200,msg=success,data=users)
ResponseJSON(w, 200, "success", users)
}
func main() {
http.HandleFunc("/users", userListHandler)
http.ListenAndServe(":8080", nil)
}
访问http://localhost:8080/users,响应JSON(格式统一):
json
{
"code": 200,
"msg": "success",
"data": [
{ "id": "1", "name": "Alice" },
{ "id": "2", "name": "Bob" }
]
}
4.2 响应状态码规范
注意区分「HTTP状态码」和「业务状态码」:
-
HTTP状态码:表示请求的网络状态(如200=请求成功、400=请求参数错误、500=服务器内部错误);
-
业务状态码:表示业务逻辑的执行结果(如200=成功、4001=用户不存在、4002=密码错误)。
推荐业务状态码规范(简化版):
-
200:成功;
-
4xx:客户端业务错误(如参数错误、权限不足);
-
5xx:服务器业务错误(如数据库异常、第三方接口调用失败)。
5. 错误统一
API开发中,错误场景繁多(参数错误、数据库错误、权限错误等),如果每个错误都单独处理、返回不同格式,会导致代码冗余、前端解析困难。统一错误处理的核心是"将所有错误封装为统一格式,区分错误类型,便于排查和前端适配"。
5.1 自定义错误类型
定义自定义错误结构体,包含错误码、错误信息,替代原生error,便于携带更多错误信息:
go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// 自定义错误类型
type APIError struct {
Code int `json:"code"` // 业务错误码
Message string `json:"message"` // 错误提示信息
}
// 实现error接口(必须,否则无法作为error类型使用)
func (e *APIError) Error() string {
return e.Message
}
// 错误工厂函数:快速创建自定义错误
func NewAPIError(code int, message string) *APIError {
return &APIError{
Code: code,
Message: message,
}
}
// 统一响应结构体(复用之前的)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 统一响应工具函数(增加错误处理)
func ResponseJSON(w http.ResponseWriter, err error, data interface{}) {
w.Header().Set("Content-Type", "application/json;charset=utf-8")
// 判断错误类型是否为自定义APIError
if apiErr, ok := err.(*APIError); ok {
json.NewEncoder(w).Encode(Response{
Code: apiErr.Code,
Msg: apiErr.Message,
Data: nil,
})
return
}
// 无错误:成功响应
if err == nil {
json.NewEncoder(w).Encode(Response{
Code: 200,
Msg: "success",
Data: data,
})
return
}
// 其他未知错误:默认500
json.NewEncoder(w).Encode(Response{
Code: 500,
Msg: "internal server error",
Data: nil,
})
}
// 业务Handler:使用统一错误处理
func userDetailHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Path[len("/users/"):]
// 模拟错误场景:id为空
if id == "" {
ResponseJSON(w, NewAPIError(400, "id is required"), nil)
return
}
// 模拟错误场景:id不存在
if id != "1" {
ResponseJSON(w, NewAPIError(404, "user not found"), nil)
return
}
// 成功场景
user := map[string]string{"id": id, "name": "Alice"}
ResponseJSON(w, nil, user)
}
func main() {
http.HandleFunc("/users/", userDetailHandler)
http.ListenAndServe(":8080", nil)
}
5.2 错误处理最佳实践
-
不要在Handler中直接打印错误、返回杂乱响应,全部通过统一工具函数处理;
-
自定义错误需区分"客户端错误"和"服务器错误",客户端错误(4xx)返回明确提示,服务器错误(5xx)不返回敏感信息(如数据库报错详情);
-
错误信息要简洁、易懂,便于前端提示用户(如"密码错误"而非"err: password mismatch");
-
可结合日志,将错误详情(如数据库报错)记录到日志中,便于排查问题。
6. 日志记录
日志是API运维、问题排查的"重要依据",需记录请求信息、响应信息、错误详情、耗时等关键内容。Golang标准库log功能简单,推荐使用第三方日志库zap(高性能、可配置)或logrus(灵活、易扩展)。
6.1 使用zap实现日志记录(推荐)
zap是Uber开源的高性能日志库,支持JSON格式、日志分级、文件输出等功能,适合生产环境使用。
go
package main
import (
"net/http"
"time"
"go.uber.org/zap"
)
// 初始化zap日志(全局单例,避免重复初始化)
var logger *zap.Logger
func init() {
// 开发环境:日志格式简洁,输出到控制台
logger, _ = zap.NewDevelopment()
// 生产环境:JSON格式,输出到文件(可配置滚动日志)
// logger, _ = zap.NewProduction(zap.Output(zapcore.AddSync(&lumberjack.Logger{
// Filename: "logs/api.log",
// MaxSize: 10, // 单个文件最大10MB
// MaxBackups: 5, // 最多保留5个备份文件
// MaxAge: 30, // 最多保留30天
// })))
defer logger.Sync() // 程序退出时刷新日志缓存
}
// 日志中间件:记录请求、响应、耗时、错误
func loggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 请求前:记录请求信息
start := time.Now()
method := r.Method
path := r.URL.Path
remoteAddr := r.RemoteAddr // 客户端IP
logger.Info("request start",
zap.String("method", method),
zap.String("path", path),
zap.String("remote_addr", remoteAddr),
)
// 调用业务Handler
next.ServeHTTP(w, r)
// 请求后:记录耗时
cost := time.Since(start)
logger.Info("request end",
zap.String("method", method),
zap.String("path", path),
zap.Duration("cost", cost),
)
})
}
// 业务Handler:模拟错误日志
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
logger.Error("method not allowed",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
)
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Write([]byte("hello zap logger"))
}
func main() {
r := http.NewServeMux()
r.HandleFunc("/hello", helloHandler)
// 使用日志中间件包装路由
server := &http.Server{
Addr: ":8080",
Handler: loggerMiddleware(r),
}
logger.Info("server start at :8080")
server.ListenAndServe()
}
安装zap:go get go.uber.org/zap
zap官方文档:pkg.go.dev/go.uber.org...
6.2 日志记录要点
-
日志分级:INFO(普通信息,如请求开始、服务启动)、WARN(警告信息,如参数不规范)、ERROR(错误信息,如业务失败)、FATAL(致命错误,如服务崩溃);
-
关键信息:必须记录请求方法、路径、客户端IP、耗时、错误详情(仅服务器端可见);
-
生产环境:日志输出到文件(而非控制台),配置滚动日志(避免单个文件过大),禁止输出敏感信息(如Token、密码);
-
日志格式:推荐JSON格式(便于日志分析工具解析),开发环境可使用简洁格式。
7. Gin框架
Gin是Golang最流行的Web框架之一,基于net/http开发,主打"高性能、轻量、易用",内置路由、中间件、请求解析、响应封装等功能,完美适配RESTful API开发,比原生net/http简洁得多,比gorilla/mux功能更全面。
7.1 Gin快速入门
第一步:安装Gin框架
bash
go get -u github.com/gin-gonic/gin
第二步:编写第一个Gin API
go
package main
import "github.com/gin-gonic/gin"
func main() {
// 1. 创建Gin引擎(默认模式:debug,生产环境用gin.ReleaseMode)
r := gin.Default() // 默认包含logger和recovery两个中间件(日志、异常捕获)
// 2. 注册路由(GET请求,路径/hello)
r.GET("/hello", func(c *gin.Context) {
// 3. 响应JSON(Gin内置JSON响应方法,无需手动序列化)
c.JSON(200, gin.H{ // gin.H 是map[string]interface{}的简写
"code": 200,
"msg": "success",
"data": "hello gin",
})
})
// 4. 启动服务(监听8080端口)
r.Run(":8080")
}
运行后访问http://localhost:8080/hello,即可得到JSON响应,Gin会自动处理日志、请求解析等基础逻辑。
Gin官方文档:gin-gonic.com/zh-cn/docs/
7.2 Gin核心功能实战
结合前面的知识点,使用Gin实现路由分组、请求解析、中间件、统一响应、错误处理的完整示例:
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// 全局变量:日志、Gin引擎
var (
logger *zap.Logger
r *gin.Engine
)
// 初始化函数:初始化日志、Gin引擎
func init() {
// 初始化zap日志(开发环境)
logger, _ = zap.NewDevelopment()
defer logger.Sync()
// 初始化Gin引擎(生产环境切换为gin.ReleaseMode)
gin.SetMode(gin.DebugMode)
r = gin.Default() // 内置logger、recovery中间件
// 注册全局中间件(所有路由都生效)
r.Use(loggerMiddleware())
// 路由分组:/v1 版本的API
v1 := r.Group("/v1")
{
// 用户相关路由
userGroup := v1.Group("/users")
{
userGroup.GET("", userListHandler) // GET /v1/users
userGroup.GET("/:id", userDetailHandler) // GET /v1/users/:id
userGroup.POST("", createUserHandler) // POST /v1/users
}
}
}
// 1. 日志中间件(Gin中间件格式:func(*gin.Context))
func loggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求前:记录请求信息
method := c.Request.Method
path := c.Request.URL.Path
remoteAddr := c.ClientIP()
logger.Info("request start",
zap.String("method", method),
zap.String("path", path),
zap.String("remote_addr", remoteAddr),
)
// 调用后续中间件/Handler
c.Next()
// 请求后:记录响应状态、耗时
statusCode := c.Writer.Status()
cost := c.Writer.Header().Get("X-Response-Time")
logger.Info("request end",
zap.String("method", method),
zap.String("path", path),
zap.Int("status_code", statusCode),
zap.String("cost", cost),
)
}
}
// 2. 统一响应结构体
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 3. 统一响应工具函数
func responseJSON(c *gin.Context, code int, msg string, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: code,
Msg: msg,
Data: data,
})
}
// 4. 自定义错误(复用之前的逻辑)
type APIError struct {
Code int
Message string
}
func (e *APIError) Error() string {
return e.Message
}
func NewAPIError(code int, msg string) *APIError {
return &APIError{Code: code, Message: msg}
}
// 5. 业务Handler
// GET /v1/users:用户列表
func userListHandler(c *gin.Context) {
// 解析查询参数(Gin内置解析,自动绑定到变量)
var page int
var size int
// 第二个参数是默认值,第三个参数是校验(这里不校验)
c.ShouldBindQuery(&page)
c.ShouldBindQuery(&size)
if page == 0 {
page = 1
}
if size == 0 {
size = 10
}
// 模拟数据
users := []map[string]interface{}{
{"id": "1", "name": "Alice", "age": 20},
{"id": "2", "name": "Bob", "age": 22},
}
// 统一响应
responseJSON(c, 200, "success", map[string]interface{}{
"page": page,
"size": size,
"total": 2,
"list": users,
})
}
// GET /v1/users/:id:用户详情
func userDetailHandler(c *gin.Context) {
// 解析路径参数(Gin内置,直接获取)
id := c.Param("id")
if id == "" {
responseJSON(c, 400, "id is required", nil)
return
}
// 模拟错误:用户不存在
if id != "1" {
logger.Error("user not found", zap.String("id", id))
responseJSON(c, 404, "user not found", nil)
return
}
// 模拟数据
user := map[string]interface{}{
"id": id,
"name": "Alice",
"age": 20,
}
responseJSON(c, 200, "success", user)
}
// POST /v1/users:创建用户
func createUserHandler(c *gin.Context) {
// 定义请求体结构体(Gin支持自动绑定JSON)
type CreateUserRequest struct {
Name string `json:"name" binding:"required"` // binding:required 表示必填
Age int `json:"age" binding:"required,gt=0"` // gt=0 表示必须大于0
Email string `json:"email" binding:"required,email"` // email 表示必须是邮箱格式
}
// 自动解析JSON请求体,并校验参数
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error("invalid request body", zap.Error(err))
responseJSON(c, 400, "invalid request body: "+err.Error(), nil)
return
}
// 模拟创建用户
responseJSON(c, 200, "create user success", map[string]string{
"id": "3",
"name": req.Name,
})
}
func main() {
// 启动服务
logger.Info("server start at :8080")
r.Run(":8080")
}
7.3 Gin关键特性说明
-
路由分组:通过
Group()方法实现路由分组,便于版本管理(如/v1、/v2)和权限控制; -
请求解析:内置
ShouldBindJSON()、ShouldBindQuery()、Param()等方法,自动解析请求体、查询参数、路径参数,支持参数校验(通过binding标签); -
中间件:支持全局中间件(
r.Use())、分组中间件(group.Use())、局部中间件(路由注册时指定),中间件函数格式为func(*gin.Context); -
异常捕获:
gin.Default()内置recovery中间件,可捕获Handler中的panic,避免服务崩溃,返回500响应; -
响应便捷:内置
c.JSON()、c.String()、c.HTML()等响应方法,无需手动处理响应头和序列化。
8. 接口文档
接口文档是前后端协作、API维护的关键,需清晰描述接口路径、请求方法、参数、响应格式、错误码等信息。手动编写文档效率低、易出错,推荐使用swaggo/swag(结合Gin),实现接口文档自动生成。
8.1 swaggo/swag 安装与配置
第一步:安装swag工具(用于生成文档)
bash
go install github.com/swaggo/swag/cmd/swag@latest
第二步:安装Gin适配的swag插件
bash
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
8.2 编写接口注释(自动生成文档的核心)
在主函数和Handler上编写规范的注释,swag会根据注释生成Swagger文档,注释格式需遵循swag规范:
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
"go.uber.org/zap"
// 导入生成的文档(注意:生成后才会有这个包,首次需先执行swag init)
_ "your-project-path/docs"
)
// @title Gin RESTful API 文档
// @version 1.0
// @description 基于Gin框架构建的RESTful API示例,包含用户管理相关接口
// @termsOfService http://example.com/terms/
// @contact.name API Support
// @contact.url http://example.com/support
// @contact.email support@example.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /v1
// @schemes http
func main() {
// 初始化日志、Gin引擎(省略,复用之前的代码)
logger, _ = zap.NewDevelopment()
defer logger.Sync()
gin.SetMode(gin.DebugMode)
r := gin.Default()
// 注册Swagger文档路由(访问 /swagger/index.html 查看文档)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// 路由分组(省略,复用之前的代码)
v1 := r.Group("/v1")
{
userGroup := v1.Group("/users")
{
userGroup.GET("", userListHandler)
userGroup.GET("/:id", userDetailHandler)
userGroup.POST("", createUserHandler)
}
}
r.Run(":8080")
}
// 用户列表接口
// @Summary 用户列表查询
// @Description 分页查询用户列表,支持page和size参数
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param page query int false "页码,默认1"
// @Param size query int false "每页条数,默认10"
// @Success 200 {object} Response{data=map[string]interface{}} "查询成功"
// @Failure 400 {object} Response "参数错误"
// @Failure 500 {object} Response "服务器内部错误"
// @Router /users [get]
func userListHandler(c *gin.Context) {
// 省略业务逻辑(复用之前的代码)
}
// 用户详情接口
// @Summary 用户详情查询
// @Description 根据ID查询单个用户信息
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param id path string true "用户ID"
// @Success 200 {object} Response{data=map[string]interface{}} "查询成功"
// @Failure 400 {object} Response "id不能为空"
// @Failure 404 {object} Response "用户不存在"
// @Failure 500 {object} Response "服务器内部错误"
// @Router /users/{id} [get]
func userDetailHandler(c *gin.Context) {
// 省略业务逻辑(复用之前的代码)
}
// 创建用户接口
// @Summary 新增用户
// @Description 新增用户,需传入name、age、email参数
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param user body CreateUserRequest true "用户信息"
// @Success 200 {object} Response{data=map[string]string} "创建成功"
// @Failure 400 {object} Response "参数错误(如邮箱格式不正确)"
// @Failure 500 {object} Response "服务器内部错误"
// @Router /users [post]
func createUserHandler(c *gin.Context) {
// 省略业务逻辑(复用之前的代码)
}
// 以下是结构体注释(用于文档显示参数和响应格式)
// @Description 用户创建请求参数
type CreateUserRequest struct {
Name string `json:"name" binding:"required" description:"用户名"`
Age int `json:"age" binding:"required,gt=0" description:"年龄,必须大于0"`
Email string `json:"email" binding:"required,email" description:"邮箱,格式需正确"`
}
// @Description 统一响应格式
type Response struct {
Code int `json:"code" description:"业务状态码,200表示成功"`
Msg string `json:"msg" description:"提示信息"`
Data interface{} `json:"data" description:"响应数据,成功时返回具体数据,失败时为null"`
}
8.3 生成并访问接口文档
第一步:在项目根目录执行命令,生成文档(生成后会出现docs文件夹)
bash
swag init
第二步:启动服务,访问http://localhost:8080/swagger/index.html,即可看到自动生成的Swagger文档,支持在线调试接口(填写参数、发送请求、查看响应)。
8.4 接口文档最佳实践
-
注释要完整:每个接口必须包含
@Summary(简介)、@Description(详细描述)、@Param(参数)、@Success(成功响应)、@Failure(失败响应)、@Router(接口路径); -
参数描述要清晰:结构体字段需添加
description标签,说明参数含义和约束(如"年龄,必须大于0"); -
及时更新:接口修改后,需重新执行
swag init生成新文档,避免文档与实际接口不一致; -
生产环境:可关闭Swagger文档(或添加权限控制),避免暴露接口细节。
总结
本章围绕Golang Web服务实战,从路由设计、中间件、请求解析、响应封装、错误统一、日志记录、Gin框架到接口文档,完整覆盖了RESTful API开发的核心环节。
核心要点:
-
路由设计遵循RESTful规范,围绕资源命名,使用HTTP方法表示操作;
-
中间件用于统一处理通用逻辑(日志、身份验证等),支持链式调用;
-
请求解析需区分路径参数、查询参数、请求体,Gin框架可简化解析流程;
-
响应和错误需统一格式,便于前端解析和问题排查;
-
日志是运维排查的关键,推荐使用zap实现高性能日志记录;
-
Gin框架是生产级API的首选,轻量、高性能,内置丰富功能;
-
接口文档推荐使用swaggo/swag自动生成,提升前后端协作效率。
结合本章代码示例,可快速搭建一个可复用、易维护的RESTful API,后续可在此基础上扩展数据库操作、缓存、权限控制等功能,适配更复杂的生产场景。