本文根据github练习项目改编而来,主要是用来学习后端开发流程,用gorml连接mysql数据据,并用gin框架进行业务处理,麻雀虽小但是五脏俱全。通过这次的练习,学会了后端代码的分层结构,让代码结构更加清晰,利于后期的维护。
一、配置数据库
-
创建数据库
CREATE DATABASE todos DEFAULT CHARSET=utf8mb4;
-
查看数据库
desc todos -
连接数据库
func initMYSQL() (err error) { dsn := "root:fighting123.@tcp(127.0.0.1:3306)/dev?charset=utf8mb4&parseTime=True&loc=Local" DB, err = gorm.Open("mysql", dsn) if err != nil { return } return DB.DB().Ping() }二、编写业务
-
绑定数据结构
type Todo struct { ID int `json:"id` Title string `json:"title"` Status bool `json:"status"` } -
编写main函数
err := initMYSQL() if err != nil { panic(err) } defer DB.Close() DB.AutoMigrate(&Todo{}) r := gin.Default() r.Static("/static", "static") r.LoadHTMLGlob("templates/*") r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }) r.Run() -
编写业务
v1Group := r.Group("v1") { //添加 v1Group.POST("/todo", func(c *gin.Context) { var todo Todo c.BindJSON(&todo) if err = DB.Create(&todo).Error; err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todo) } }) //查看 v1Group.GET("/todo", func(c *gin.Context) { var todoList []Todo if err = DB.Find(&todoList).Error; err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todoList) } }) //修改 v1Group.PUT("/todo/:id", func(c *gin.Context) { id, _ := c.Params.Get("id") var todo Todo if err = DB.Where("id=?", id).First(&todo).Error; err != nil { c.JSON(http.StatusOK, gin.H{"error": "无效id"}) return } c.BindJSON(&todo) if err = DB.Save(&todo).Error; err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todo) } }) //删除 v1Group.DELETE("/todo/:id", func(c *gin.Context) { id, _ := c.Params.Get("id") if err := DB.Where("id=?", id).Delete(Todo{}).Debug().Error; err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, gin.H{id: "deleted"}) } }) }三、项目优化
1. 项目分层
url --> controller --> logic --> model
请求来了 --> 控制器 --> 业务逻辑 --> 模型层的增删改查
2. 数据库配置文件
port = 8080 release = false [mysql] user = root password = fighting123. host = 127.0.0.1 port = 3306 db = dev3. models层
定义数据结构
package models import ( "bubble/dao" ) // Todo Model type Todo struct { ID int `json:"id"` Title string `json:"title"` Status bool `json:"status"` } /* Todo这个Model的增删改查操作都放在这里 */ // CreateATodo 创建todo func CreateATodo(todo *Todo) (err error) { err = dao.DB.Create(&todo).Error return } func GetAllTodo() (todoList []*Todo, err error) { if err = dao.DB.Find(&todoList).Error; err != nil { return nil, err } return } func GetATodo(id string) (todo *Todo, err error) { todo = new(Todo) if err = dao.DB.Debug().Where("id=?", id).First(todo).Error; err != nil { return nil, err } return } func UpdateATodo(todo *Todo) (err error) { err = dao.DB.Save(todo).Error return } func DeleteATodo(id string) (err error) { err = dao.DB.Where("id=?", id).Delete(&Todo{}).Error return }
4. dao层
具体处理后端数据
package dao import ( "bubble/setting" "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) // 👉🏻 https://gorm.io/zh_CN/docs/ var ( DB *gorm.DB ) func InitMySQL(cfg *setting.MySQLConfig) (err error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) DB, err = gorm.Open("mysql", dsn) if err != nil { return } return DB.DB().Ping() } func Close() { DB.Close() }- controller 控制器
package controller import ( "bubble/models" "net/http" "github.com/gin-gonic/gin" ) /* url --> controller --> logic --> model 请求来了 --> 控制器 --> 业务逻辑 --> 模型层的增删改查 */ func IndexHandler(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) } func CreateTodo(c *gin.Context) { // 前端页面填写待办事项 点击提交 会发请求到这里 // 1. 从请求中把数据拿出来 var todo models.Todo c.BindJSON(&todo) // 2. 存入数据库 err := models.CreateATodo(&todo) if err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todo) //c.JSON(http.StatusOK, gin.H{ // "code": 2000, // "msg": "success", // "data": todo, //}) } } func GetTodoList(c *gin.Context) { // 查询todo这个表里的所有数据 todoList, err := models.GetAllTodo() if err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todoList) } } func UpdateATodo(c *gin.Context) { id, ok := c.Params.Get("id") if !ok { c.JSON(http.StatusOK, gin.H{"error": "无效的id"}) return } todo, err := models.GetATodo(id) if err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) return } c.BindJSON(&todo) if err = models.UpdateATodo(todo); err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, todo) } } func DeleteATodo(c *gin.Context) { id, ok := c.Params.Get("id") if !ok { c.JSON(http.StatusOK, gin.H{"error": "无效的id"}) return } if err := models.DeleteATodo(id); err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, gin.H{id: "deleted"}) } }5. 路由层
控制路由地址
package routers import ( "bubble/controller" "bubble/setting" "github.com/gin-gonic/gin" ) func SetupRouter() *gin.Engine { if setting.Conf.Release { gin.SetMode(gin.ReleaseMode) } r := gin.Default() // 告诉gin框架模板文件引用的静态文件去哪里找 r.Static("/static", "static") // 告诉gin框架去哪里找模板文件 r.LoadHTMLGlob("templates/*") r.GET("/", controller.IndexHandler) // v1 v1Group := r.Group("v1") { // 待办事项 // 添加 v1Group.POST("/todo", controller.CreateTodo) // 查看所有的待办事项 v1Group.GET("/todo", controller.GetTodoList) // 修改某一个待办事项 v1Group.PUT("/todo/:id", controller.UpdateATodo) // 删除某一个待办事项 v1Group.DELETE("/todo/:id", controller.DeleteATodo) } return r }- setting 主要是针对数据库的配置以及其他项目数据配置
package setting import ( "gopkg.in/ini.v1" ) var Conf = new(AppConfig) // AppConfig 应用程序配置 type AppConfig struct { Release bool `ini:"release"` Port int `ini:"port"` *MySQLConfig `ini:"mysql"` } // MySQLConfig 数据库配置 type MySQLConfig struct { User string `ini:"user"` Password string `ini:"password"` DB string `ini:"db"` Host string `ini:"host"` Port int `ini:"port"` } func Init(file string) error { return ini.MapTo(Conf, file) }- main 程序函数入口,比原始的版本精炼了很多。
package main import ( "bubble/dao" "bubble/models" "bubble/routers" "bubble/setting" "fmt" "os" ) const defaultConfFile = "./conf/config.ini" func main() { confFile := defaultConfFile if len(os.Args) > 2 { fmt.Println("use specified conf file: ", os.Args[1]) confFile = os.Args[1] } else { fmt.Println("no configuration file was specified, use ./conf/config.ini") } // 加载配置文件 if err := setting.Init(confFile); err != nil { fmt.Printf("load config from file failed, err:%v\n", err) return } // 创建数据库 // sql: CREATE DATABASE bubble; // 连接数据库 err := dao.InitMySQL(setting.Conf.MySQLConfig) if err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer dao.Close() // 程序退出关闭数据库连接 // 模型绑定 dao.DB.AutoMigrate(&models.Todo{}) // 注册路由 r := routers.SetupRouter() if err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)); err != nil { fmt.Printf("server startup failed, err:%v\n", err) } } -