第八章:项目实战¶
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
小结¶
- 使用分层架构组织代码
- GORM 是流行的 Go ORM 库
- Gin 是高性能的 Web 框架
- 依赖注入提高代码可测试性
- 配置管理使用环境变量