项目简介

  • 目的:这个demo旨在快速了解如何用gin+gorm实现简单的CRUD操作。
  • 功能:实现简单的个人信息录入。
  • 技术:
    • gin:golang的轻量级web框架
    • gorm:golang的ORM框架
  • 测试工具:postman
  • Ps:本demo实现分层,所有的代码都在main.go中。

准备工作

导入gin、gorm、MySQL驱动的包,在命令行运行 go mod tidy,下载对应包。

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"strconv"
	"time"
)

第一步:数据库连接

首先,新建一个dsn(data source name)字符串,具体格式可以按照下面来。

DSN:即数据源名称。通常包含Driver、username、password、address、port、数据库名、其它选项。

调用gorm的Open接口,连接数据库。第一个参数是驱动的dialector,第二个参数是配置,可以在里面配置连接池等。具体请看官方文档。

dsn := "root:123456@tcp(127.0.0.1:3306)/crud?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
	fmt.Println("fail to connect mysql .")
}

第二步:创建结构体,将结构体迁移到数据库

gorm.Model表示使用gorm定义的默认属性,如ID、deleted_at、created_at等字段。
在结构体标签中配置上数据库的信息、JSON的序列化信息、参数绑定信息。

type List struct {  
    gorm.Model  
    Name    string `gorm:"type:varchar(20);not null'" json:"name" binding:"required"`  
    State   string `gorm:"type:varchar(20);not null'" json:"state" binding:"required"`  
    Phone   string `gorm:"type:varchar(20);not null'" json:"phone" binding:"required"`  
    Email   string `gorm:"type:varchar(40);not null'" json:"email" binding:"required"`  
    Address string `gorm:"type:varchar(200);not null'" json:"address" binding:"required"`  
}

定义返回的JSON数据格式:

{
	"msg": "信息",
	"code": 200,//业务状态码 定义200成功,400失败,
	"data": //数据
}
//分页
{
	"msg": "信息",
	"code": 200,//业务状态码 定义200成功,400失败,
	"data": {
		"list": ,
		"total":,
		"pageNum":,
		"pageSize":,
	}
}

AutoMigrate 是 GORM 中的一个功能,用于自动创建或更新数据库表结构,使其与定义的 Go 结构体模型保持同步。这个功能非常方便,可以减少手动管理数据库表结构的工作量。

db.AutoMigrate(&List{})

第三步:创建引擎

engine := gin.Default()
//这里创建添加路由和处理函数
engine.run(":3001") //启动服务,在3001端口监听

gin.default会创建一个默认的 Gin 引擎实例。

Gin 的引擎(Engine)是处理 HTTP 请求和路由的核心组件。gin.Default() 创建的引擎实例具有一些默认的设置,包括:

  1. 日志输出: 默认情况下,gin.Default() 会将请求的信息以及响应状态写入标准输出,方便调试和日志记录。
  2. 恢复中间件: Gin 会自动加载一个恢复中间件,用于在发生 panic 时恢复程序,防止服务器崩溃。这对于保障程序的稳定性很有帮助。
  3. 请求和响应处理: 引擎会自动处理请求和响应,将请求数据解析为参数、JSON 或者表单数据,并将响应数据编码成 JSON 或者 HTML。

第四步:接口设计——添加路由和处理函数

  • engine.GET(),第一个参数接收路由地址,第二个是一个处理函数。
  • 处理函数:
    • 参数:需要有 *gin.Context上下文作为参数。
    • 返回值:使用C.JSON返回数据。第一个参数是状态码,第二个参数是数据。
    • 数据一般用 gin.H{}结构体封装,本质上是 map[string]interface{}
  • 如何接收参数:
    • 接收JSON数据:c.ShouldBindJSON(&data)
    • RESTful传参:c.Param("name")
    • GET传参:c.Query("pageSize")
  • 接口定义:这里接口全部定义为RESTful风格,URL是 /user/data,根据POST/PUT/GET/DELETE请求,分别执行增加、修改、获取、删除操作。
    • 新增接口--POST--/user/data,传JSON
    • 查询接口--GET--/user/data/:name,条件查询-按照name
    • 分页查询--GET--/user/data,参数:pageSizepageNum
    • 修改接口--PUT--/user/data/:id,传JSON
    • 删除接口--DELETE--/user/data/:id
      • 注意:gorm默认用的是软删除。

第五步:执行SQL语句并返回数据

调用gorm的增删改查接口,进行查询。具体接口看gorm官方文档。

分页查询:

  • 公式:pageOffset := (pageNum - 1) * pageSize
  • 查询:首先计算总记录数,然后根据分页信息(每页记录数和页码)来获取相应的数据,从而实现在应用中进行分页显示数据。db.Model(data).Count(&total).Limit(pageSize).Offset(pageOffset).Find(&data)

处理函数的逻辑:

  1. 接受参数
  2. 查询数据库/修改数据
  3. 返回数据

具体代码如下:

	engine.POST("/user/data", func(c *gin.Context) {
		var data List
		err2 := c.ShouldBindJSON(&data)
		if err2 != nil {
			fmt.Println("binding error:", err2)
			c.JSON(200, gin.H{
				"msg":  "数据绑定失败",
				"data": gin.H{},
				"code": 400,
			})
		} else {
			//数据库操作
			res := db.Create(&data)
			if res.Error != nil {

			}
			c.JSON(200, gin.H{
				"msg":  "添加成功",
				"data": data,
				"code": 200,
			})
		}

	})

	//R

	//条件查询
	engine.GET("/user/data/:name", func(c *gin.Context) {
		name := c.Param("name")
		var data []List
		// 查询数据库
		db.Where("name=?", name).Find(&data)
		//判断是否查询成功
		if len(data) == 0 {
			c.JSON(200, gin.H{
				"msg":  "查询不到数据",
				"code": 400,
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "查询成功",
				"code": 200,
				"data": data,
			})
		}
	})

	//分页查询 and 全部查询
	engine.GET("/user/data", func(c *gin.Context) {
		var data []List

		pageSize, _ := strconv.Atoi(c.Query("pageSize"))
		pageNum, _ := strconv.Atoi(c.Query("pageNum"))

		//判断是否需要分页
		if pageSize == 0 {
			pageSize = -1
		}
		if pageNum == 0 {
			pageNum = -1
		}

		pageOffset := (pageNum - 1) * pageSize

		//返回总数
		var total int64
		db.Model(data).Count(&total).Limit(pageSize).Offset(pageOffset).Find(&data)
		if len(data) == 0 {
			c.JSON(200, gin.H{
				"msg":  "没有查到数据",
				"code": 400,
				"data": gin.H{
					"list":     data,
					"total":    total,
					"pageNum":  pageNum,
					"pageSize": pageSize,
				},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "查询成功",
				"code": 200,
				"data": gin.H{
					"list":     data,
					"total":    total,
					"pageNum":  pageNum,
					"pageSize": pageSize,
				},
			})
		}

	})

	//U
	engine.PUT("/user/data/:id", func(c *gin.Context) {
		var data List
		id := c.Param("id")
		db.Select("id").Where("id=?", id).Find(&data)
		if data.ID == 0 {
			c.JSON(200, gin.H{
				"msg":  "用户ID不存在",
				"code": 400,
			})
		} else {
			err2 := c.ShouldBindJSON(&data)
			if err2 != nil {
				c.JSON(200, gin.H{
					"msg":  "更新失败",
					"code": 400,
				})
			} else {
				db.Where("id=?", id).Updates(&data)
				c.JSON(200, gin.H{
					"msg":  "更新成功",
					"code": 200,
				})
			}
		}
	})
	//D
	engine.DELETE("/user/data/:id", func(c *gin.Context) {
		var data []List
		//接收参数,非键值对形式
		id := c.Param("id")
		c.Query(id)
		//查找
		db.Where("id=?", id).Find(&data)
		if len(data) == 0 {
			c.JSON(200, gin.H{
				"msg":  "删除失败,未找到此用户",
				"data": gin.H{},
				"code": 400,
			})
		} else {
			db.Where("id=?", id).Delete(&data)
			c.JSON(200, gin.H{
				"msg":  "删除成功",
				"code": 200,
			})
		}
	})

结语

到这里,我们就成功用golang开发了简单的支持CRUD的后端服务了。可以看出用Go开发一个服务端非常简单,这也体现了Golang的设计哲学:简单和清晰。不过这份代码有非常多不足的点:如代码不规范,未实现分层和封装等,在更加深入学习go和go框架后,我也会继续更新go web系列文章。