gin+gorm的小项目
写在开头
-
这是一个基于Q1mi的bubble项目提供,编写后端实现清单内容的增删改查。这是一个
Gin+Gorm
结合的小项目,目的只是为了理清在Gin+Gorm
结合下的项目结构与编写。 -
github项目提供者相关讲解视频如下,想看具体讲解可以点击链接
https://www.bilibili.com/video/BV1gJ411p7xC/?spm_id_from=333.999.0.0&vd_source=84fc27804252448ba51ef3b6abfd5d36
- 笔者在原作者的基础上,改进了项目结构,并将编写过程中遇到的困难以及解决方法一并提供,可以给读者帮助解决困难。
项目展示效果
项目运行结果如下:
然后点击删除 睡觉
选项
更新事项表示完成:
项目编写
项目结构
笔者的项目结构(左边)VS 原项目结构(右边)
:
这里可以看出,笔者在项目层次中新添了很多包,并命名也与原本项目不同,比如config
包=原dao
包。这是因为笔者认为更规范,更适合大众理解中的结构层次。不过,层次不重要,只是为了之后公司中更好合作,自己编写无所谓,编码能力才是最重要的。
编写过程讲解
笔者从让人利于理解的层次结构来顺序地书写过程啊~
下载前端代码
首先因为我们这项目主打的是联系gin+gorm
项目编写流程,主要瞄准于后端,要想实现文章开头的想过,所以要有前端逻辑。在这个项目中,我们首先先将Q1mi的bubble项目中的static
和template
文件夹下载到我们的项目文件中,或者从笔者上传的资源【免费】Gin+Gorm练手小项目-bubble前后端代码合集资源-CSDN文库下载。因为我们不管前端,除了个别代码,我们基本实现了前后端分离的思想。
理清前后端传递数据格式
操作 | 传递的路径 | 方法 | 前端传递数据 | 正常后端返回数据 |
---|---|---|---|---|
查看所有的记录 | localhost:9090/v1/todo | GET | nil | todos切片 |
增加记录 | localhost:9090/v1/todo | POST | Todo结构体 | {“code”: 200, “msg”: “success”, “data”: todo,} |
删除记录 | localhost:9090/v1/todo/:id | DELETE | 路由的id | {id: “delete”,} |
更新记录状态 | localhost:9090/v1/todo/:id | PUT | 路由的id,Todo结构体 | 都可 |
编写config包
首先一个项目要想运行,必须写好配置文件,我们可以新建一个config
包,然后在该包的目录下,新建文件mysql.go
,意味着该文件是为了与自己的数据库mysql连接。
package config
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
//全局变量DB
var DB *gorm.DB
func InitMySql() (err error) {
//注意这里要修改自己数据库的账户密码
dsn := "root:wst113929@tcp(127.0.0.1:3306)/database_learn?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
return err
}
注意:InitMySql
函数名要大写。函数名的大小写决定了该函数的可见性(visibility)。函数名以大写字母开头的函数是可导出的(exported),也称为公开函数(public functions),可以被其他包引用和调用。而以小写字母开头的函数是不可导出的(unexported),只能在当前包内部使用。
叙写models包
Model
层:在规范的情况下,表示我们在数据库存储的数据类型,通常用struct
表示。
package models
type Todo struct {
Id int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
注意:属性名的大写!!!
默认情况下的id是主键,这是gorm的知识,相关内容可以在入手Gorm,小白能看懂的基础使用_法耶会输出的博客-CSDN博客内容了解。
实现dao层
dao
: Data Access Object(数据访问对象)的缩写。在软件开发中,DAO 是一种设计模式,用于封装对数据源(如数据库、文件系统等)的访问和操作。
package daos
import (
"bubble/config"
"bubble/models"
)
//Todo 的 CURD
// CTodo 创建一个TODO记录
func CTodo(todo *models.Todo) (err error) {
if err := config.DB.Create(&todo).Error; err != nil {
return err
}
return
}
// DTodo 删除一个TODO记录 根据前端传来的路径中id删除
func DTodo(id string) (err error) {
if err := config.DB.Where("id=?", id).Delete(&models.Todo{}).Error; err != nil {
return err
}
return
}
// UTodo :更新操作,返回要更新的todo 这里是为了查找前端是否传递的正确
func UTodo(id string) (todo *models.Todo, err error) {
if err := config.DB.Where("id=?", id).First(&todo).Error; err != nil {
return nil, err
}
return
}
// STodo:储存更新的数据
func STodo(todo *models.Todo) (err error) {
if err := config.DB.Save(&todo).Error; err != nil {
return err
}
return
}
// GTodo 查询所有的记录展示
func GTodo() (todolist []*models.Todo, err error) {
err = config.DB.Find(&todolist).Error
return
}
总之,在这一层就是根据需求,来编写访问数据库,获取相关数据,属于gorm知识。
- 如更新操作,这里提供了
UTodo
和STodo
两个函数,是因为后端设计理念是:
在后端开发中,确实不能完全信任前端传递的数据,而是需要对数据进行验证和处理。这是因为前端的数据可以被篡改、伪造或以其他方式被恶意操作。
所以,必须先在UTodo
中查询是否存在,然后使用STodo
操作,保存数据库。会有两个函数。
完成controller层
Controller
(控制器)层是负责处理来自客户端的请求,并协调其他组件(如服务、模型等)来完成请求的处理和响应。
package controllers
import (
"bubble/daos"
"bubble/models"
"net/http"
"github.com/gin-gonic/gin"
)
// 就是返回执行的函数,类似于SpringBoot+mybatis的controller层
func IndexHandler(c *gin.Context) {
//在router包中会使用查找html所在的方式
c.HTML(http.StatusOK, "index.html", nil)
}
// 创建记录
func CreatTodo(c *gin.Context) {
var todo models.Todo
//与前端传来的的数据进行绑定
err := c.BindJSON(&todo)
if err != nil {
return
}
if err = daos.CTodo(&todo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err,
})
} else {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": todo,
})
}
}
// 删除记录
func DeleteTodo(c *gin.Context) {
//从路径中获取id的值然后实行删除
id := c.Param("id")
if err := daos.DTodo(id); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err,
})
} else {
c.JSON(http.StatusOK, gin.H{
//这里为什么会返回这个,要斟酌一下
id: "delete",
})
}
}
// 修改记录的状态
func UpdateTodo(c *gin.Context) {
//也是根据路径传递的id来进行筛选要更改的值
id := c.Param("id")
todo, err := daos.UTodo(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
}
//绑定前端传来的数据
err = c.BindJSON(&todo)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
}
err = daos.STodo(todo)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
}
}
// 得到所有数据
func GetTodo(c *gin.Context) {
if todos, err := daos.GTodo(); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
} else {
c.JSON(http.StatusOK, todos)
}
}
该层就是调用dao层的函数,然后进行返回 c *gin.Context
类型的函数,以便之后函数调用
编写router层
router
:路由层,改成编写每个路由执行的函数。属于gin
的知识范畴。
package routers
import (
"bubble/controllers"
"github.com/gin-gonic/gin"
)
func SetUpRouter() *gin.Engine {
r := gin.Default()
//告诉gin到static找今天文件
r.Static("/static", "static")
//告诉gin在templates找html文件
r.LoadHTMLGlob("template/*")
r.GET("/", controllers.IndexHandler)
v1Group := r.Group("/v1")
{
//增加数据,post请求
v1Group.POST("/todo", controllers.CreatTodo)
//获取数据,get请求
v1Group.GET("todo/", controllers.GetTodo)
//删除数据,delete请求
v1Group.DELETE("todo/:id", controllers.DeleteTodo)
//更新数据,put请求
v1Group.PUT("todo/:id", controllers.UpdateTodo)
}
return r
}
该层如果懂gin知识,就简单~
main.go
在已经完成了其他包文件的开发,主要业务逻辑已经在其他包中实现的情况,此时main.go
文件就是调用,运行。
package main
import (
"bubble/config"
"bubble/models"
"bubble/routers"
)
func main() {
err := config.InitMySql()
if err != nil {
//初始化连接失败
panic(err)
}
//进行迁移
err = config.DB.AutoMigrate(&models.Todo{})
if err != nil {
//迁移失败
panic(err)
}
r := routers.SetUpRouter()
err = r.Run(":9090")
if err != nil {
//运行失败
panic(err)
}
}
然后再terminal中输入
go run main.go
然后在浏览器输入127.0.0.1:9090
就可以看到自己的界面
编写过程完结撒花~
出错处理
笔者在最先运行的时候,增加删除更新等操作均可以完成,甚至在前端界面也有信息提示,表示已完成,数据库也可以看到信息,但是界面始终不显示数据库信息。like this:
然后就返回goland看到terminal一大片爆红
点击图中我出错的文件位置,看到在 err = config.DB.Find(todolist).Error
我只是写了todolist
,但是应该find
函数参数应该是 指向切片的指针,而不是仅仅的切片。
更改正确的是:
最后重新在terminal中运行,就可以得到正确的界面啦
在编写的过程中还碰到了其他错误,这里就不一一叙述啦,反正gin.defelut()
函数默认提供例子记录功能,所以可以在终端terminal中看到自己出错的信息,根据错误修改代码。多搜,多看,多写!