RESTful API实战 #

一、项目概述 #

1.1 项目结构 #

text
myapi/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── handlers/
│   │   └── user.go
│   ├── models/
│   │   └── user.go
│   ├── repositories/
│   │   └── user.go
│   └── services/
│       └── user.go
├── pkg/
│   ├── response/
│   │   └── response.go
│   └── middleware/
│       └── auth.go
├── config/
│   └── config.yaml
├── go.mod
└── go.sum

1.2 API设计 #

方法 路径 说明
GET /api/v1/users 获取用户列表
GET /api/v1/users/:id 获取单个用户
POST /api/v1/users 创建用户
PUT /api/v1/users/:id 更新用户
DELETE /api/v1/users/:id 删除用户

二、配置管理 #

2.1 配置文件 #

config/config.yaml

yaml
app:
  name: myapi
  port: 8080
  mode: debug

database:
  driver: mysql
  host: localhost
  port: 3306
  name: myapi
  user: root
  password: secret
  max_open_conns: 25
  max_idle_conns: 5

jwt:
  secret: your-secret-key
  expire: 24h

2.2 配置加载 #

internal/config/config.go

go
package config

import (
    "time"
    "github.com/spf13/viper"
)

type Config struct {
    App      AppConfig
    Database DatabaseConfig
    JWT      JWTConfig
}

type AppConfig struct {
    Name string
    Port string
    Mode string
}

type DatabaseConfig struct {
    Driver       string
    Host         string
    Port         int
    Name         string
    User         string
    Password     string
    MaxOpenConns int
    MaxIdleConns int
}

type JWTConfig struct {
    Secret string
    Expire time.Duration
}

func Load() *Config {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("./config")
    
    if err := viper.ReadInConfig(); err != nil {
        panic(err)
    }
    
    return &Config{
        App: AppConfig{
            Name: viper.GetString("app.name"),
            Port: viper.GetString("app.port"),
            Mode: viper.GetString("app.mode"),
        },
        Database: DatabaseConfig{
            Driver:       viper.GetString("database.driver"),
            Host:         viper.GetString("database.host"),
            Port:         viper.GetInt("database.port"),
            Name:         viper.GetString("database.name"),
            User:         viper.GetString("database.user"),
            Password:     viper.GetString("database.password"),
            MaxOpenConns: viper.GetInt("database.max_open_conns"),
            MaxIdleConns: viper.GetInt("database.max_idle_conns"),
        },
        JWT: JWTConfig{
            Secret: viper.GetString("jwt.secret"),
            Expire: viper.GetDuration("jwt.expire"),
        },
    }
}

三、模型定义 #

internal/models/user.go

go
package models

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    Name      string         `gorm:"size:100;not null" json:"name"`
    Email     string         `gorm:"size:100;uniqueIndex;not null" json:"email"`
    Password  string         `gorm:"size:255;not null" json:"-"`
    Status    string         `gorm:"size:20;default:'active'" json:"status"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

func (User) TableName() string {
    return "users"
}

四、响应封装 #

pkg/response/response.go

go
package response

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

type PageData struct {
    List  interface{} `json:"list"`
    Total int64       `json:"total"`
    Page  int         `json:"page"`
    Size  int         `json:"size"`
}

func Success(c echo.Context, data interface{}) error {
    return c.JSON(http.StatusOK, Response{
        Code:    0,
        Message: "success",
        Data:    data,
    })
}

func SuccessWithPage(c echo.Context, list interface{}, total int64, page, size int) error {
    return c.JSON(http.StatusOK, Response{
        Code:    0,
        Message: "success",
        Data: PageData{
            List:  list,
            Total: total,
            Page:  page,
            Size:  size,
        },
    })
}

func Error(c echo.Context, code int, message string) error {
    return c.JSON(code, Response{
        Code:    code,
        Message: message,
    })
}

func BadRequest(c echo.Context, message string) error {
    return Error(c, http.StatusBadRequest, message)
}

func NotFound(c echo.Context, message string) error {
    return Error(c, http.StatusNotFound, message)
}

func Unauthorized(c echo.Context, message string) error {
    return Error(c, http.StatusUnauthorized, message)
}

func Forbidden(c echo.Context, message string) error {
    return Error(c, http.StatusForbidden, message)
}

func InternalError(c echo.Context, message string) error {
    return Error(c, http.StatusInternalServerError, message)
}

五、数据访问层 #

internal/repositories/user.go

go
package repositories

import (
    "myapi/internal/models"
    "gorm.io/gorm"
)

type UserRepository struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) Create(user *models.User) error {
    return r.db.Create(user).Error
}

func (r *UserRepository) Update(user *models.User) error {
    return r.db.Save(user).Error
}

func (r *UserRepository) Delete(id uint) error {
    return r.db.Delete(&models.User{}, id).Error
}

func (r *UserRepository) FindByID(id uint) (*models.User, error) {
    var user models.User
    err := r.db.First(&user, id).Error
    return &user, err
}

func (r *UserRepository) FindByEmail(email string) (*models.User, error) {
    var user models.User
    err := r.db.Where("email = ?", email).First(&user).Error
    return &user, err
}

func (r *UserRepository) FindAll(page, size int) ([]models.User, int64, error) {
    var users []models.User
    var total int64
    
    r.db.Model(&models.User{}).Count(&total)
    
    offset := (page - 1) * size
    err := r.db.Offset(offset).Limit(size).Find(&users).Error
    
    return users, total, err
}

func (r *UserRepository) ExistsByEmail(email string) bool {
    var count int64
    r.db.Model(&models.User{}).Where("email = ?", email).Count(&count)
    return count > 0
}

六、业务逻辑层 #

internal/services/user.go

go
package services

import (
    "errors"
    "myapi/internal/models"
    "myapi/internal/repositories"
    "golang.org/x/crypto/bcrypt"
)

type UserService struct {
    repo *repositories.UserRepository
}

func NewUserService(repo *repositories.UserRepository) *UserService {
    return &UserService{repo: repo}
}

type CreateUserInput struct {
    Name     string `json:"name" validate:"required"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=6"`
}

type UpdateUserInput struct {
    Name   string `json:"name"`
    Status string `json:"status"`
}

func (s *UserService) Create(input *CreateUserInput) (*models.User, error) {
    if s.repo.ExistsByEmail(input.Email) {
        return nil, errors.New("邮箱已被使用")
    }
    
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }
    
    user := &models.User{
        Name:     input.Name,
        Email:    input.Email,
        Password: string(hashedPassword),
        Status:   "active",
    }
    
    if err := s.repo.Create(user); err != nil {
        return nil, err
    }
    
    return user, nil
}

func (s *UserService) Update(id uint, input *UpdateUserInput) (*models.User, error) {
    user, err := s.repo.FindByID(id)
    if err != nil {
        return nil, errors.New("用户不存在")
    }
    
    if input.Name != "" {
        user.Name = input.Name
    }
    if input.Status != "" {
        user.Status = input.Status
    }
    
    if err := s.repo.Update(user); err != nil {
        return nil, err
    }
    
    return user, nil
}

func (s *UserService) Delete(id uint) error {
    return s.repo.Delete(id)
}

func (s *UserService) GetByID(id uint) (*models.User, error) {
    return s.repo.FindByID(id)
}

func (s *UserService) GetList(page, size int) ([]models.User, int64, error) {
    return s.repo.FindAll(page, size)
}

七、处理器 #

internal/handlers/user.go

go
package handlers

import (
    "net/http"
    "strconv"
    "myapi/internal/services"
    "myapi/pkg/response"
    "github.com/labstack/echo/v4"
)

type UserHandler struct {
    service *services.UserService
}

func NewUserHandler(service *services.UserService) *UserHandler {
    return &UserHandler{service: service}
}

func (h *UserHandler) List(c echo.Context) error {
    page, _ := strconv.Atoi(c.QueryParam("page"))
    if page < 1 {
        page = 1
    }
    
    size, _ := strconv.Atoi(c.QueryParam("size"))
    if size < 1 || size > 100 {
        size = 10
    }
    
    users, total, err := h.service.GetList(page, size)
    if err != nil {
        return response.InternalError(c, "获取用户列表失败")
    }
    
    return response.SuccessWithPage(c, users, total, page, size)
}

func (h *UserHandler) Get(c echo.Context) error {
    id, err := strconv.ParseUint(c.Param("id"), 10, 64)
    if err != nil {
        return response.BadRequest(c, "无效的用户ID")
    }
    
    user, err := h.service.GetByID(uint(id))
    if err != nil {
        return response.NotFound(c, "用户不存在")
    }
    
    return response.Success(c, user)
}

func (h *UserHandler) Create(c echo.Context) error {
    input := new(services.CreateUserInput)
    if err := c.Bind(input); err != nil {
        return response.BadRequest(c, "参数格式错误")
    }
    
    if err := c.Validate(input); err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    user, err := h.service.Create(input)
    if err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    return c.JSON(http.StatusCreated, response.Response{
        Code:    0,
        Message: "创建成功",
        Data:    user,
    })
}

func (h *UserHandler) Update(c echo.Context) error {
    id, err := strconv.ParseUint(c.Param("id"), 10, 64)
    if err != nil {
        return response.BadRequest(c, "无效的用户ID")
    }
    
    input := new(services.UpdateUserInput)
    if err := c.Bind(input); err != nil {
        return response.BadRequest(c, "参数格式错误")
    }
    
    user, err := h.service.Update(uint(id), input)
    if err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    return response.Success(c, user)
}

func (h *UserHandler) Delete(c echo.Context) error {
    id, err := strconv.ParseUint(c.Param("id"), 10, 64)
    if err != nil {
        return response.BadRequest(c, "无效的用户ID")
    }
    
    if err := h.service.Delete(uint(id)); err != nil {
        return response.InternalError(c, "删除失败")
    }
    
    return c.NoContent(http.StatusNoContent)
}

八、主程序 #

cmd/server/main.go

go
package main

import (
    "fmt"
    "myapi/internal/config"
    "myapi/internal/handlers"
    "myapi/internal/models"
    "myapi/internal/repositories"
    "myapi/internal/services"
    "myapi/pkg/response"
    "github.com/go-playground/validator/v10"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type CustomValidator struct {
    validator *validator.Validate
}

func (cv *CustomValidator) Validate(i interface{}) error {
    return cv.validator.Struct(i)
}

func main() {
    cfg := config.Load()
    
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        cfg.Database.User,
        cfg.Database.Password,
        cfg.Database.Host,
        cfg.Database.Port,
        cfg.Database.Name,
    )
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    db.AutoMigrate(&models.User{})
    
    e := echo.New()
    
    e.Validator = &CustomValidator{validator: validator.New()}
    
    e.Use(middleware.Recover())
    e.Use(middleware.Logger())
    e.Use(middleware.CORS())
    e.Use(middleware.Gzip())
    
    e.HTTPErrorHandler = func(err error, c echo.Context) {
        if he, ok := err.(*echo.HTTPError); ok {
            response.Error(c, he.Code, fmt.Sprintf("%v", he.Message))
            return
        }
        response.InternalError(c, "服务器内部错误")
    }
    
    userRepo := repositories.NewUserRepository(db)
    userService := services.NewUserService(userRepo)
    userHandler := handlers.NewUserHandler(userService)
    
    api := e.Group("/api/v1")
    
    users := api.Group("/users")
    {
        users.GET("", userHandler.List)
        users.GET("/:id", userHandler.Get)
        users.POST("", userHandler.Create)
        users.PUT("/:id", userHandler.Update)
        users.DELETE("/:id", userHandler.Delete)
    }
    
    e.Logger.Fatal(e.Start(":" + cfg.App.Port))
}

九、总结 #

RESTful API实战要点:

要点 说明
项目结构 分层架构
配置管理 Viper读取配置
模型定义 GORM模型
响应封装 统一响应格式
数据访问 Repository模式
业务逻辑 Service层
处理器 Handler层

准备好学习用户认证系统了吗?让我们进入下一章!

最后更新:2026-03-28