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