优化已有的 Go 程序|青训营

52 阅读3分钟

本文根据github练习项目改编而来,主要是用来学习后端开发流程,用gorml连接mysql数据据,并用gin框架进行业务处理,麻雀虽小但是五脏俱全。通过这次的练习,学会了后端代码的分层结构,让代码结构更加清晰,利于后期的维护。 image.png

一、配置数据库

  1. 创建数据库

CREATE DATABASE todos DEFAULT CHARSET=utf8mb4;

  1. 查看数据库

    desc todos

  2. 连接数据库

    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()
    }
    

    二、编写业务

    1. 绑定数据结构

      type Todo struct {
          ID     int    `json:"id`
          Title  string `json:"title"`
          Status bool   `json:"status"`
      }
      
    2. 编写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()
      
    3. 编写业务

      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 = dev
      

      3. 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()
    }
    
    1. 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
    }
    
    1. 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)
    }
    
    1. 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)
    	}
    }