第39章:实战项目一 —— RESTful API 用户管理系统
本章目标
学完本章后,你将能够:
- 搭建一个完整的 Go Web 项目
- 使用 Gin + GORM 构建 RESTful API
- 实现用户注册、登录、增删改查
- 使用 Hook 实现密码加密
- 使用 Scope 封装通用查询
39.1 项目结构
user-management/
├── main.go # 入口
├── config/
│ └── config.go # 数据库配置
├── models/
│ └── user.go # 用户模型 + Hook
├── handlers/
│ └── user.go # HTTP 处理器
├── middleware/
│ └── error.go # 错误处理中间件
└── scopes/
└── paginate.go # 分页 Scope39.2 模型定义
go
package models
import (
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Username string `gorm:"type:varchar(50);uniqueIndex;not null" json:"username"`
Password string `gorm:"type:varchar(255);not null;->:false" json:"-"`
RealName string `gorm:"type:varchar(50)" json:"real_name"`
Email string `gorm:"type:varchar(255);uniqueIndex" json:"email"`
Phone string `gorm:"type:varchar(20)" json:"phone"`
Status string `gorm:"type:varchar(20);default:'active'" json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// 密码加密 Hook
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Password != "" {
hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hashed)
}
return nil
}
// 登录验证
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
return err == nil
}39.3 API 设计
go
package handlers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserHandler struct {
db *gorm.DB
}
// GET /api/users?page=1&page_size=10&status=active
func (h *UserHandler) List(c *gin.Context) {
var users []User
var total int64
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
status := c.Query("status")
query := h.db.Model(&User{})
if status != "" {
query = query.Where("status = ?", status)
}
query.Count(&total)
query.Scopes(Paginate(page, pageSize)).Order("id desc").Find(&users)
c.JSON(http.StatusOK, gin.H{
"data": users,
"meta": gin.H{
"total": total,
"page": page,
"page_size": pageSize,
},
})
}
// POST /api/users
func (h *UserHandler) Create(c *gin.Context) {
var input struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
RealName string `json:"real_name"`
Email string `json:"email" binding:"required,email"`
Phone string `json:"phone"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user := User{
Username: input.Username,
Password: input.Password, // Hook 自动加密
RealName: input.RealName,
Email: input.Email,
Phone: input.Phone,
}
if err := h.db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": user})
}
// GET /api/users/:id
func (h *UserHandler) Get(c *gin.Context) {
id := c.Param("id")
var user User
if err := h.db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
// PUT /api/users/:id
func (h *UserHandler) Update(c *gin.Context) {
id := c.Param("id")
var input map[string]interface{}
c.ShouldBindJSON(&input)
// 不要从 API 更新密码(密码更新走专门接口)
delete(input, "password")
if err := h.db.Model(&User{}).Where("id = ?", id).Updates(input).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "更新成功"})
}
// DELETE /api/users/:id
func (h *UserHandler) Delete(c *gin.Context) {
id := c.Param("id")
var user User
if err := h.db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
h.db.Delete(&user) // 软删除
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}39.4 路由注册
go
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
dsn := "root:123456@tcp(127.0.0.1:3306)/user_management?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
db.AutoMigrate(&User{})
handler := &UserHandler{db: db}
r := gin.Default()
api := r.Group("/api")
{
api.GET("/users", handler.List)
api.POST("/users", handler.Create)
api.GET("/users/:id", handler.Get)
api.PUT("/users/:id", handler.Update)
api.DELETE("/users/:id", handler.Delete)
}
r.Run(":8080")
}39.5 测试 API
bash
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"username":"zhangsan","password":"123456","email":"zs@example.com"}'
# 列表查询
curl http://localhost:8080/api/users?page=1&page_size=10
# 获取详情
curl http://localhost:8080/api/users/1
# 更新
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"real_name":"张三","phone":"13800138000"}'
# 删除
curl -X DELETE http://localhost:8080/api/users/1本章小结
本项目综合运用了:模型定义、Hook(密码加密)、CRUD、软删除、分页查询、Gin 框架集成。
练习题
- 重启项目,完成上面所有 API 的测试。
- 添加"用户登录"接口(POST /api/login),验证用户名和密码。
- 添加"按关键词搜索"功能,支持按用户名和邮箱模糊搜索。
- 添加"按创建时间范围"查询的 API 参数。