跳转至

第八章:项目实战

8.1 项目概述

构建一个 RESTful API 服务,包含用户管理功能。

项目结构

myapp/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   │   └── user.go
│   ├── model/
│   │   └── user.go
│   ├── repository/
│   │   └── user.go
│   └── service/
│       └── user.go
├── pkg/
│   └── config/
│       └── config.go
├── go.mod
├── go.sum
└── Makefile

8.2 项目初始化

# 创建项目
mkdir myapp && cd myapp
go mod init github.com/myorg/myapp

# 安装依赖
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/postgres

8.3 配置管理

// pkg/config/config.go
package config

import (
    "os"
    "strconv"
)

type Config struct {
    ServerPort string
    DBHost     string
    DBPort     int
    DBUser     string
    DBPassword string
    DBName     string
}

func Load() *Config {
    port, _ := strconv.Atoi(getEnv("DB_PORT", "5432"))

    return &Config{
        ServerPort: getEnv("SERVER_PORT", "8080"),
        DBHost:     getEnv("DB_HOST", "localhost"),
        DBPort:     port,
        DBUser:     getEnv("DB_USER", "postgres"),
        DBPassword: getEnv("DB_PASSWORD", "postgres"),
        DBName:     getEnv("DB_NAME", "myapp"),
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

8.4 数据模型

// internal/model/user.go
package model

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

type User struct {
    ID        uint           `json:"id" gorm:"primaryKey"`
    Name      string         `json:"name" gorm:"not null"`
    Email     string         `json:"email" gorm:"unique;not null"`
    Password  string         `json:"-" gorm:"not null"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}

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

8.5 数据访问层

// internal/repository/user.go
package repository

import (
    "github.com/myorg/myapp/internal/model"
    "gorm.io/gorm"
)

type UserRepository struct {
    db *gorm.DB
}

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

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

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

func (r *UserRepository) FindAll(page, pageSize int) ([]model.User, int64, error) {
    var users []model.User
    var total int64

    r.db.Model(&model.User{}).Count(&total)
    err := r.db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users).Error

    return users, total, err
}

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

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

8.6 业务逻辑层

// internal/service/user.go
package service

import (
    "errors"
    "github.com/myorg/myapp/internal/model"
    "github.com/myorg/myapp/internal/repository"
)

type UserService struct {
    repo *repository.UserRepository
}

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

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

type UpdateUserRequest struct {
    Name     string `json:"name"`
    Email    string `json:"email" binding:"email"`
    Password string `json:"password" binding:"min=6"`
}

func (s *UserService) Create(req *CreateUserRequest) (*model.User, error) {
    user := &model.User{
        Name:     req.Name,
        Email:    req.Email,
        Password: req.Password, // 实际项目需要加密
    }

    if err := s.repo.Create(user); err != nil {
        return nil, err
    }

    return user, nil
}

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

func (s *UserService) List(page, pageSize int) ([]model.User, int64, error) {
    if page < 1 {
        page = 1
    }
    if pageSize < 1 || pageSize > 100 {
        pageSize = 10
    }

    return s.repo.FindAll(page, pageSize)
}

func (s *UserService) Update(id uint, req *UpdateUserRequest) (*model.User, error) {
    user, err := s.repo.FindByID(id)
    if err != nil {
        return nil, errors.New("用户不存在")
    }

    if req.Name != "" {
        user.Name = req.Name
    }
    if req.Email != "" {
        user.Email = req.Email
    }
    if req.Password != "" {
        user.Password = req.Password
    }

    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)
}

8.7 HTTP 处理层

// internal/handler/user.go
package handler

import (
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/myorg/myapp/internal/service"
)

type UserHandler struct {
    service *service.UserService
}

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

func (h *UserHandler) RegisterRoutes(r *gin.RouterGroup) {
    users := r.Group("/users")
    {
        users.POST("", h.Create)
        users.GET("", h.List)
        users.GET("/:id", h.GetByID)
        users.PUT("/:id", h.Update)
        users.DELETE("/:id", h.Delete)
    }
}

func (h *UserHandler) Create(c *gin.Context) {
    var req service.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.service.Create(&req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, user)
}

func (h *UserHandler) GetByID(c *gin.Context) {
    id, err := strconv.ParseUint(c.Param("id"), 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
        return
    }

    user, err := h.service.GetByID(uint(id))
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
        return
    }

    c.JSON(http.StatusOK, user)
}

func (h *UserHandler) List(c *gin.Context) {
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))

    users, total, err := h.service.List(page, pageSize)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "data": users,
        "pagination": gin.H{
            "page":       page,
            "page_size":  pageSize,
            "total":      total,
            "total_page": (total + int64(pageSize) - 1) / int64(pageSize),
        },
    })
}

func (h *UserHandler) Update(c *gin.Context) {
    id, _ := strconv.ParseUint(c.Param("id"), 10, 32)

    var req service.UpdateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.service.Update(uint(id), &req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, user)
}

func (h *UserHandler) Delete(c *gin.Context) {
    id, _ := strconv.ParseUint(c.Param("id"), 10, 32)

    if err := h.service.Delete(uint(id)); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.Status(http.StatusNoContent)
}

8.8 主程序

// cmd/server/main.go
package main

import (
    "fmt"
    "log"

    "github.com/gin-gonic/gin"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"

    "github.com/myorg/myapp/internal/handler"
    "github.com/myorg/myapp/internal/model"
    "github.com/myorg/myapp/internal/repository"
    "github.com/myorg/myapp/internal/service"
    "github.com/myorg/myapp/pkg/config"
)

func main() {
    // 加载配置
    cfg := config.Load()

    // 连接数据库
    dsn := fmt.Sprintf(
        "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPassword, cfg.DBName,
    )
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("数据库连接失败:", err)
    }

    // 自动迁移
    db.AutoMigrate(&model.User{})

    // 初始化依赖
    userRepo := repository.NewUserRepository(db)
    userService := service.NewUserService(userRepo)
    userHandler := handler.NewUserHandler(userService)

    // 创建路由
    r := gin.Default()

    // 注册路由
    api := r.Group("/api/v1")
    userHandler.RegisterRoutes(api)

    // 启动服务器
    fmt.Println("服务器启动在 :" + cfg.ServerPort)
    r.Run(":" + cfg.ServerPort)
}

8.9 运行项目

# 运行
go run cmd/server/main.go

# 构建
go build -o bin/server cmd/server/main.go

# 测试 API
curl -X POST http://localhost:8080/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"name":"张三","email":"zhangsan@example.com","password":"123456"}'

curl http://localhost:8080/api/v1/users
curl http://localhost:8080/api/v1/users/1

小结

  1. 使用分层架构组织代码
  2. GORM 是流行的 Go ORM 库
  3. Gin 是高性能的 Web 框架
  4. 依赖注入提高代码可测试性
  5. 配置管理使用环境变量

参考资料