目录
项目介绍
简介
项目地址:knoci/list: 基于Gin的待办清单小项目 (github.com)
一个仿照github/Q1mi/bubble 做的一个gin框架练习
技术
- gin 框架
- gorm 操作PostgreSQL
- ini 配置文件
项目结构
list
├── README.md
├── config
│ └── config.ini
├── controller
│ └── controller.go
├── dao
│ └── postgresql.go
├── go.mod
├── go.sum
├── main.go
├── models
│ └── todo.go
├── routers
│ └── routers.go
├── static
│ ├── css
│ │ ├── app.8eeeaf31.css
│ │ └── chunk-vendors.57db8905.css
│ ├── fonts
│ │ ├── element-icons.535877f5.woff
│ │ └── element-icons.732389de.ttf
│ └── js
│ ├── app.007f9690.js
│ └── chunk-vendors.ddcb6f91.js
└── templates
├── favicon.ico
└── index.html
项目分析
项目中有config,controllers,dao,models,routers,static,template这7个文件夹。
- config保存ini文件,配置连接数据库的参数(Port,User,Password...)
- controllers保存控制函数,实现器功能,处理web响应
- dao连接数据库
- models是各种数据结构模板定义
- routers负责路由分组和路由处理
- static存储静态资源
- template是前端模板
接下来我们从mian.go开始,逐步分析整个项目的运行。
package main
import (
"list/dao"
"list/models"
"list/routers"
//_ "gorm.io/driver/mysql"
_ "gorm.io/driver/postgres"
)
func main() {
//连接数据库
dao.Connect()
//模型绑定
dao.DB.AutoMigrate(&models.Todo{})
//启动router
routers.SetupRouter()
}
在main.go中我们导入了同文件夹下的dao,models,routers;随后运行第一个函数 dao.Connect(),接下来我们进入dao,来看看 dao.Connect() 函数实现了什么功能。
package dao
import (
"fmt"
"gopkg.in/ini.v1"
//"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
DB *gorm.DB
)
type MysqlConfig struct {
User string `ini:"user"`
Password string `ini:"password"`
Host string `ini:"host"`
Port string `ini:"port"`
DBname string `ini:"db"`
}
func LoadConfig() *MysqlConfig {
//development_通过结构体映射参数
c := new(MysqlConfig)
ini.MapTo(c, "config/config.ini")
fmt.Println(c)
return c
}
func Connect() {
c := LoadConfig()
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", c.Host, c.User, c.Password, c.DBname, c.Port)
/* mysql
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",c.User, c.Password, c.Host, c.Port, c.DBname)
dsn := "root:root1234@tcp(127.0.0.1:13306)/bubble?charset=utf8mb4&parseTime=True&loc=Local"
*/
var err error
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) // connect
// mysql DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err.Error())
}
fmt.Print("==========连接数据库成功==========\n")
}
在postgresql.go中导入了GORM,PG驱动,ini包,自定义了结构体Mysqlconfig,和一个 导包全局可访问的 gorm.DB类型的指针DB,封装了一个函数 LoadConfig() 通过ini.MapTo(c, "config/config.ini") 来接收配置文件。
在Connect函数中调用LoadConfig()获得配置,用Sprintf()把配置复制到dsn,然后通过gorm.Open方法连接到我们的数据库。
dao.Connect()完成后,我们继续回到main.go,用dao包内变量DB的内置方法 dao.DB.AutoMigrate(&models.Todo{}) ,实现了模型绑定,实际上就是按照models.Todo结构体在数据库中创建了一张表。
package models
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
Todo表中,每个待办事项有ID,Title,Status,并且有相应JOSN的tag。其中ID是用来标识事项的自增唯一值,Title是事务名,Status用0和1表示未完成和完成。
回到main.go,最后调用了routers.SetupRouter(),这是在本地routers包routers.go里的函数。在SetupRouter中,使用gin.Default()注册默认路由r;然后用r.Static()导入./static目录下静态文件指定为static;接着用r.LoadHTMLGlob()导入当前路径template/*的模板。
接着就是路由处理,指定了对"/"的GET请求回应controllers.ShowIndex函数,用r.Group()定义了路由组v1Group。
路由组中,指定了对"todo"的POST请求回应controllers.CreateTodo函数,对"/todo"的GET请求回应controllers.RetrieveTodo函数,对"/todo/:id"的PUT请求回应controllers.UpdateTodo函数,对"/todo/:id"的DELETE请求回应controllers.DeleteTodo函数。
最后用r.Run(:9090),在9090端口上监听并运行。
package routers
import (
"github.com/gin-gonic/gin"
"list/controllers"
)
func SetupRouter() {
r := gin.Default()
r.Static("/static", "static")
r.LoadHTMLGlob("template/*")
r.GET("/", controllers.ShowIndex)
v1Group := r.Group("v1")
{
//添加
v1Group.POST("todo", controllers.CreateTodo)
//查看
v1Group.GET("/todo", controllers.RetrieveTodo)
//修改
v1Group.PUT("/todo/:id", controllers.UpdateTodo)
//删除
v1Group.DELETE("/todo/:id", controllers.DeleteTodo)
}
r.Run(":9090")
}
接下来看看controller中的各个函数的功能,首先是ShowIndex,负责返回状态码200,展示index.html。
func ShowIndex(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
CreateTodo 用来创建一个待办事项。在接收到传来的数据后,定义一个models.Todo类型的todo结构体,然后用 c.ShouldBind(&todo) 自动的进行响应格式(这里是JSON)的参数绑定到todo,然后通过 dao.DB.Create(&todo) 把todo存入数据库DB,以JSON格式失败返回报错,成功返回todo。
func CreateTodo(c *gin.Context) {
//get data
var todo models.Todo
c.ShouldBind(&todo)
//add into database
err := dao.DB.Create(&todo).Error
//return
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, todo)
}
}
RetrieveTodo 用来获取所有待办事项,创建一个结构体数组todos,用 dao.DB.Find(&todos) 把所有的表数据给到todos,以JSON格式失败返回报错,成功返回todos。
func RetrieveTodo(c *gin.Context) {
var todos []models.Todo
if err := dao.DB.Find(&todos).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, todos)
}
}
UpdateTodo 用来更新指定的事项,用 c.Params.GET("id") 获得要修改事项名为"id"的指定url,定义todo结构体然后用 dao.DB.Where("id=?", id).First(&todo) 来查询数据库中第一个对应id的数据到todo,然后 c.BindJSON(&todo) 把方法请求体c以JSON绑定到todo,最后 dao.DB.Save(&todo) 来更新数据库。
func UpdateTodo(c *gin.Context) {
id, ok := c.Params.Get("id")
if !ok {
c.JSON(http.StatusOK, gin.H{"error": "id invalid"})
return
}
var todo models.Todo
if err := dao.DB.Where("id=?", id).First(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
c.BindJSON(&todo) //修改
if err := dao.DB.Save(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, todo)
}
}
DeletTodo 用来删除一个待办事项,还是通过 c.Params.GET("id") 获得要修改事项名为"id"的指定url,用 dao.DB.Where("id=?", id).Delete(models.Todo{}) 来删除数据库中对应id的数据,因为这里不接收请求体,没有定义局部变量结构体,所以直接传入model.Todo{}指定表格式。
总结
这个项目是Gin和GORM的非常非常简单的小项目,适合新手入门。