Go RESTful API 最佳实践:构建高质量 Web 服务
什么是 RESTful API?
REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在 2000 年提出。RESTful API 遵循以下核心原则:
- 资源导向:一切皆资源,通过 URL 标识
- 统一接口:使用标准的 HTTP 方法(GET、POST、PUT、DELETE)
- 无状态:每个请求独立,服务器不保存客户端状态
- 可缓存:响应可以被客户端或中间层缓存
- 分层系统:客户端不需要知道是否直接连接到服务器
项目结构
一个良好的项目结构是高质量 API 的基础:
api-project/
├── cmd/
│ └── api/
│ └── main.go # 应用程序入口
├── internal/
│ ├── config/ # 配置管理
│ ├── handler/ # HTTP 处理器
│ ├── middleware/ # 中间件
│ ├── model/ # 数据模型
│ ├── repository/ # 数据访问层
│ ├── service/ # 业务逻辑层
│ └── router/ # 路由配置
├── pkg/
│ ├── response/ # 响应封装
│ ├── validator/ # 验证器
│ └── logger/ # 日志工具
├── docs/ # API 文档
├── migrations/ # 数据库迁移
├── go.mod
├── go.sum
└── README.md路由设计
使用 Gin 框架
go
package router
import (
"github.com/gin-gonic/gin"
"api/internal/handler"
"api/internal/middleware"
)
func SetupRouter() *gin.Engine {
r := gin.New()
// 全局中间件
r.Use(middleware.Logger())
r.Use(middleware.Recovery())
r.Use(middleware.CORS())
// API 版本
v1 := r.Group("/api/v1")
{
// 用户相关
users := v1.Group("/users")
{
users.GET("", handler.ListUsers)
users.POST("", handler.CreateUser)
users.GET("/:id", handler.GetUser)
users.PUT("/:id", handler.UpdateUser)
users.DELETE("/:id", handler.DeleteUser)
}
// 文章相关
articles := v1.Group("/articles")
{
articles.GET("", handler.ListArticles)
articles.POST("", handler.CreateArticle)
articles.GET("/:id", handler.GetArticle)
articles.PUT("/:id", handler.UpdateArticle)
articles.DELETE("/:id", handler.DeleteArticle)
}
}
return r
}RESTful URL 设计规范
| 操作 | HTTP 方法 | URL | 说明 |
|---|---|---|---|
| 列表 | GET | /api/v1/users | 获取用户列表 |
| 创建 | POST | /api/v1/users | 创建新用户 |
| 详情 | GET | /api/v1/users/:id | 获取单个用户 |
| 更新 | PUT/PATCH | /api/v1/users/:id | 更新用户信息 |
| 删除 | DELETE | /api/v1/users/:id | 删除用户 |
| 搜索 | GET | /api/v1/users?keyword=xxx | 搜索用户 |
请求处理
统一的请求验证
go
package validator
import (
"github.com/go-playground/validator/v10"
"github.com/gin-gonic/gin/binding"
)
var validate *validator.Validate
func init() {
validate = validator.New()
binding.Validator = &defaultValidator{validate}
}
type defaultValidator struct {
*validator.Validate
}
// 自定义验证规则示例
func RegisterCustomValidations() {
validate.RegisterValidation("phone", validatePhone)
}
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
// 手机号验证逻辑
return len(phone) == 11
}请求结构体定义
go
package model
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=50"`
Email string `json:"email" binding:"required,email"`
Phone string `json:"phone" binding:"omitempty,phone"`
Age int `json:"age" binding:"omitempty,min=0,max=150"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
Username string `json:"username" binding:"omitempty,min=3,max=50"`
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone" binding:"omitempty,phone"`
}
// ListUsersRequest 用户列表请求
type ListUsersRequest struct {
Page int `form:"page" binding:"omitempty,min=1"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100"`
Keyword string `form:"keyword" binding:"omitempty,max=100"`
}响应设计
统一响应格式
go
package response
import (
"github.com/gin-gonic/gin"
"net/http"
)
// Response 统一响应结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// PageData 分页数据结构
type PageData struct {
List interface{} `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// Success 成功响应
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: data,
})
}
// SuccessWithPage 成功响应(带分页)
func SuccessWithPage(c *gin.Context, list interface{}, total int64, page, pageSize int) {
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: PageData{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
},
})
}
// Error 错误响应
func Error(c *gin.Context, code int, message string) {
c.JSON(code, Response{
Code: code,
Message: message,
})
}
// BadRequest 400 错误
func BadRequest(c *gin.Context, message string) {
Error(c, http.StatusBadRequest, message)
}
// NotFound 404 错误
func NotFound(c *gin.Context, message string) {
Error(c, http.StatusNotFound, message)
}
// InternalServerError 500 错误
func InternalServerError(c *gin.Context, message string) {
Error(c, http.StatusInternalServerError, message)
}处理器示例
go
package handler
import (
"github.com/gin-gonic/gin"
"api/internal/model"
"api/internal/service"
"api/pkg/response"
"strconv"
)
type UserHandler struct {
userService *service.UserService
}
func NewUserHandler() *UserHandler {
return &UserHandler{
userService: service.NewUserService(),
}
}
// ListUsers 获取用户列表
func (h *UserHandler) ListUsers(c *gin.Context) {
var req model.ListUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 设置默认值
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 10
}
users, total, err := h.userService.List(c.Request.Context(), &req)
if err != nil {
response.InternalServerError(c, err.Error())
return
}
response.SuccessWithPage(c, users, total, req.Page, req.PageSize)
}
// CreateUser 创建用户
func (h *UserHandler) CreateUser(c *gin.Context) {
var req model.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
user, err := h.userService.Create(c.Request.Context(), &req)
if err != nil {
response.InternalServerError(c, err.Error())
return
}
response.Success(c, user)
}
// GetUser 获取用户详情
func (h *UserHandler) GetUser(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "invalid user id")
return
}
user, err := h.userService.GetByID(c.Request.Context(), id)
if err != nil {
response.NotFound(c, "user not found")
return
}
response.Success(c, user)
}
// UpdateUser 更新用户
func (h *UserHandler) UpdateUser(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "invalid user id")
return
}
var req model.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
user, err := h.userService.Update(c.Request.Context(), id, &req)
if err != nil {
response.InternalServerError(c, err.Error())
return
}
response.Success(c, user)
}
// DeleteUser 删除用户
func (h *UserHandler) DeleteUser(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "invalid user id")
return
}
if err := h.userService.Delete(c.Request.Context(), id); err != nil {
response.InternalServerError(c, err.Error())
return
}
response.Success(c, nil)
}错误处理
自定义错误类型
go
package errors
import (
"errors"
"fmt"
)
// AppError 应用错误
type AppError struct {
Code int
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error {
return e.Err
}
// 预定义错误
var (
ErrNotFound = NewAppError(404, "resource not found")
ErrBadRequest = NewAppError(400, "bad request")
ErrUnauthorized = NewAppError(401, "unauthorized")
ErrForbidden = NewAppError(403, "forbidden")
ErrInternal = NewAppError(500, "internal server error")
)
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
func Wrap(err error, code int, message string) *AppError {
return &AppError{Code: code, Message: message, Err: err}
}
func IsNotFound(err error) bool {
var appErr *AppError
if errors.As(err, &appErr) {
return appErr.Code == 404
}
return false
}中间件
日志中间件
go
package middleware
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// 处理请求
c.Next()
// 记录日志
latency := time.Since(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
if raw != "" {
path = path + "?" + raw
}
log.Printf("[%s] %s %s %d %v",
clientIP,
method,
path,
statusCode,
latency,
)
}
}认证中间件
go
package middleware
import (
"github.com/gin-gonic/gin"
"api/pkg/jwt"
"api/pkg/response"
"strings"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
response.Error(c, 401, "authorization header required")
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
response.Error(c, 401, "invalid authorization header format")
c.Abort()
return
}
claims, err := jwt.ParseToken(parts[1])
if err != nil {
response.Error(c, 401, "invalid token")
c.Abort()
return
}
// 将用户信息存入上下文
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Next()
}
}CORS 中间件
go
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}API 文档
使用 Swagger
go
package main
// @title Example API
// @version 1.0
// @description This is a sample server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
r := router.SetupRouter()
r.Run(":8080")
}测试
单元测试
go
package handler
import (
"bytes"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestCreateUser(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
handler := NewUserHandler()
r.POST("/users", handler.CreateUser)
// 构造请求
reqBody := map[string]interface{}{
"username": "testuser",
"email": "test@example.com",
}
jsonBody, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}性能优化
连接池配置
go
package config
import (
"database/sql"
"time"
_ "github.com/go-sql-driver/mysql"
)
func NewDB() (*sql.DB, error) {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
return nil, err
}
// 连接池配置
db.SetMaxOpenConns(25) // 最大打开连接数
db.SetMaxIdleConns(10) // 最大空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大生命周期
return db, nil
}部署
Dockerfile
dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o api cmd/api/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/api .
EXPOSE 8080
CMD ["./api"]总结
构建高质量的 RESTful API 需要关注以下关键点:
- 良好的项目结构:分层清晰,职责单一
- 统一的接口规范:RESTful 设计,版本控制
- 完善的错误处理:自定义错误类型,统一响应格式
- 合理的中间件设计:日志、认证、CORS 等
- 完善的文档:使用 Swagger 自动生成
- 充分的测试:单元测试、集成测试
- 性能优化:连接池、缓存、异步处理
相关文章推荐:
- Gin 框架实战指南 - Web 框架详解
- Go JWT 认证与授权 - 安全认证实现
- Go 中间件设计模式 - 中间件高级用法

