go gin web综合教程,包括 mysql redis log 路由

前言

在学习go许久,没看到网上有比较综合的gin web教程,很多都是最基础的教程,完全就是启动个服务返回参数,没有过多的结合实际开发。下面我结合一下我的经验,来写一篇深入的综合教程,包括数据库Mysql、redis结合。

项目结构

go gin web综合教程,包括 mysql redis log 路由_第1张图片
初学者一开始很迷茫,究竟项目结构应该是这么样的呢,我的结构仅仅供参考

包名 描述
app 应用相关的内容,比如request,response的定义
config 配置文件存的地方
const 常量
controller 控制器,需要路由结合
file 控制文件的操作,和日志相关
gredis redis的启动操作
logging 日志相关
model 数据库启动操作,和数据库相关的实体类
router 路由
service 服务逻辑内容
setting 配置相关代码
util 工具类

main文件

package main

import (
	"github.com/astaxie/beego/logs"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"gotest/src/gredis"
	"gotest/src/logging"
	"gotest/src/model"
	"gotest/src/router"
	"gotest/src/setting"
	"net/http"
)

func init() {
	logs.Info("配置文件设置 初始化配置")
	setting.Setup()
	logs.Info("数据库 初始化配置")
	model.SetUp()
	logs.Info("日志文件 初始化配置")
	logging.Setup()
	logs.Info("redis 初始化配置")
	gredis.Setup()
}

func main() {
	//debug,release,test
	gin.SetMode("debug")
	logs.Info("路由 初始化配置")
	routersInit := router.InitRouter()
	server := &http.Server{
		//访问端口
		Addr: ":8080",
		//路由处理器
		Handler: routersInit,
	}
	//开启服务
	server.ListenAndServe()
}

启动程序描述了这几样东西

  1. 他默认会先执行init()方法,这里面设置了几个启动初始化配置,配置文件、数据库、日志文件、redis,会按顺序启动初始化
  2. 在main方法中,初始路由配置
  3. 配置server,输入路由实例和配置的端口
  4. 启动服务

我下面按顺序来看看这些内容

配置文件初始化

在这里插入图片描述
他调用的是setting.Setup(),也就是setting包中的Setup方法,也就是下面这个

package setting

import (
	"github.com/go-ini/ini"
	"log"
	"time"
)

type App struct {
	JwtSecret string
	PageSize  int
	PrefixUrl string

	RuntimeRootPath string

	ImageSavePath  string
	ImageMaxSize   int
	ImageAllowExts []string

	ExportSavePath string
	QrCodeSavePath string
	FontSavePath   string

	LogSavePath string
	LogSaveName string
	LogFileExt  string
	TimeFormat  string
}

var AppSetting = &App{}

type Server struct {
	RunMode      string
	HttpPort     int
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

var ServerSetting = &Server{}

type Database struct {
	Type        string
	User        string
	Password    string
	Host        string
	Name        string
	TablePrefix string
}

var DatabaseSetting = &Database{}

type Redis struct {
	Host        string
	Password    string
	MaxIdle     int
	MaxActive   int
	IdleTimeout time.Duration
}

var RedisSetting = &Redis{}

var cfg *ini.File

// Setup initialize the configuration instance
func Setup() {
	var err error
	cfg, err = ini.Load("src/config/app.ini")
	if err != nil {
		log.Fatalf("setting.Setup, fail to parse 'config/app.ini': %v", err)
	}

	mapTo("app", AppSetting)
	mapTo("server", ServerSetting)
	mapTo("database", DatabaseSetting)
	mapTo("redis", RedisSetting)

	AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024
	ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second
	ServerSetting.WriteTimeout = ServerSetting.WriteTimeout * time.Second
	RedisSetting.IdleTimeout = RedisSetting.IdleTimeout * time.Second
}

// mapTo map section
func mapTo(section string, v interface{}) {
	err := cfg.Section(section).MapTo(v)
	if err != nil {
		log.Fatalf("Cfg.MapTo %s err: %v", section, err)
	}
}

配置文件放在config包中
在这里插入图片描述

[app]
PageSize = 10
JwtSecret = 233
PrefixUrl = http://127.0.0.1:8000

RuntimeRootPath = runtime/

ImageSavePath = upload/images/
# MB
ImageMaxSize = 5
ImageAllowExts = .jpg,.jpeg,.png

ExportSavePath = export/
QrCodeSavePath = qrcode/
FontSavePath = fonts/

LogSavePath = logs/
LogSaveName = log
LogFileExt = log
TimeFormat = 20060102

[server]
#debug or release
RunMode = debug
HttpPort = 8000
ReadTimeout = 60
WriteTimeout = 60

[database]
Type = mysql
User = root
Password = root
Host = 127.0.0.1:3306
Name = blog
TablePrefix = blog_

[redis]
Host = 127.0.0.1:6379
Password = 
MaxIdle = 30
MaxActive = 30
IdleTimeout = 200

Setup方法的步骤如下

  1. 读取src/config/app.ini文件
  2. 解析出AppSetting
  3. 解析出ServerSetting
  4. 解析出DatabaseSetting
  5. 解析出RedisSetting
  6. 分别赋值到全局变量,以供外部访问

配置文件初始化结束

数据库初始化配置

这里使用的是mysql,main方法调用的是model.SetUp()
在这里插入图片描述
内容如下:

package model

import (
	"fmt"
	"github.com/go-xorm/xorm"
	"gotest/src/setting"
)

type Model struct {
	ID         int `gorm:"primary_key" json:"id"`
	CreatedOn  int `json:"created_on"`
	ModifiedOn int `json:"modified_on"`
	DeletedOn  int `json:"deleted_on"`
}

var db *xorm.Engine

func SetUp() {
	var err error
	db, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8",
		setting.DatabaseSetting.User,
		setting.DatabaseSetting.Password,
		setting.DatabaseSetting.Host,
		setting.DatabaseSetting.Name))
	if err != nil {
		panic(err.Error())
	}
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(100)
}

日志文件初始化

相关的文件如下:
在这里插入图片描述
logging/log.go文件

package logging

import (
	"fmt"
	"gotest/src/file"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

type Level int

var (
	F *os.File

	DefaultPrefix      = ""
	DefaultCallerDepth = 2

	logger     *log.Logger
	logPrefix  = ""
	levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)

const (
	DEBUG Level = iota
	INFO
	WARNING
	ERROR
	FATAL
)

// Setup initialize the log instance
func Setup() {
	var err error
	filePath := getLogFilePath()
	fileName := getLogFileName()
	F, err = file.MustOpen(fileName, filePath)
	if err != nil {
		log.Fatalf("logging.Setup err: %v", err)
	}

	logger = log.New(F, DefaultPrefix, log.LstdFlags)
}

// Debug output logs at debug level
func Debug(v ...interface{}) {
	setPrefix(DEBUG)
	logger.Println(v)
}

// Info output logs at info level
func Info(v ...interface{}) {
	setPrefix(INFO)
	logger.Println(v)
}

// Warn output logs at warn level
func Warn(v ...interface{}) {
	setPrefix(WARNING)
	logger.Println(v)
}

// Error output logs at error level
func Error(v ...interface{}) {
	setPrefix(ERROR)
	logger.Println(v)
}

// Fatal output logs at fatal level
func Fatal(v ...interface{}) {
	setPrefix(FATAL)
	logger.Fatalln(v)
}

// setPrefix set the prefix of the log output
func setPrefix(level Level) {
	_, file, line, ok := runtime.Caller(DefaultCallerDepth)
	if ok {
		logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)
	} else {
		logPrefix = fmt.Sprintf("[%s]", levelFlags[level])
	}

	logger.SetPrefix(logPrefix)
}

logging/file.go文件:主要是定义日志文件的输出位置

package logging

import (
	"fmt"
	"gotest/src/setting"
	"time"
)

// getLogFilePath get the log file save path
func getLogFilePath() string {
	return fmt.Sprintf("%s%s", setting.AppSetting.RuntimeRootPath, setting.AppSetting.LogSavePath)
}

// getLogFileName get the save name of the log file
func getLogFileName() string {
	return fmt.Sprintf("%s%s.%s",
		setting.AppSetting.LogSaveName,
		time.Now().Format(setting.AppSetting.TimeFormat),
		setting.AppSetting.LogFileExt,
	)
}

文件相关的操作
file/file.go

package file

import (
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"os"
	"path"
)

// GetSize get the file size
func GetSize(f multipart.File) (int, error) {
	content, err := ioutil.ReadAll(f)

	return len(content), err
}

// GetExt get the file ext
func GetExt(fileName string) string {
	return path.Ext(fileName)
}

// CheckNotExist check if the file exists
func CheckNotExist(src string) bool {
	_, err := os.Stat(src)

	return os.IsNotExist(err)
}

// CheckPermission check if the file has permission
func CheckPermission(src string) bool {
	_, err := os.Stat(src)

	return os.IsPermission(err)
}

// IsNotExistMkDir create a directory if it does not exist
func IsNotExistMkDir(src string) error {
	if notExist := CheckNotExist(src); notExist == true {
		if err := MkDir(src); err != nil {
			return err
		}
	}

	return nil
}

// MkDir create a directory
func MkDir(src string) error {
	err := os.MkdirAll(src, os.ModePerm)
	if err != nil {
		return err
	}

	return nil
}

// Open a file according to a specific mode
func Open(name string, flag int, perm os.FileMode) (*os.File, error) {
	f, err := os.OpenFile(name, flag, perm)
	if err != nil {
		return nil, err
	}

	return f, nil
}

// MustOpen maximize trying to open the file
func MustOpen(fileName, filePath string) (*os.File, error) {
	dir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("os.Getwd err: %v", err)
	}

	src := dir + "/" + filePath
	perm := CheckPermission(src)
	if perm == true {
		return nil, fmt.Errorf("file.CheckPermission Permission denied src: %s", src)
	}

	err = IsNotExistMkDir(src)
	if err != nil {
		return nil, fmt.Errorf("file.IsNotExistMkDir src: %s, err: %v", src, err)
	}

	f, err := Open(src+fileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		return nil, fmt.Errorf("Fail to OpenFile :%v", err)
	}

	return f, nil
}

redis 初始化

执行的是main方法中的gredis.Setup()
目录中的这里:
在这里插入图片描述
gredis/redis.go

package gredis

import (
	"encoding/json"
	"gotest/src/setting"
	"time"

	"github.com/gomodule/redigo/redis"
)

var RedisConn *redis.Pool

// Setup Initialize the Redis instance
func Setup() error {
	RedisConn = &redis.Pool{
		MaxIdle:     setting.RedisSetting.MaxIdle,
		MaxActive:   setting.RedisSetting.MaxActive,
		IdleTimeout: setting.RedisSetting.IdleTimeout,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", setting.RedisSetting.Host)
			if err != nil {
				return nil, err
			}
			if setting.RedisSetting.Password != "" {
				if _, err := c.Do("AUTH", setting.RedisSetting.Password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	return nil
}

// Set a key/value
func Set(key string, data interface{}, time int) error {
	conn := RedisConn.Get()
	defer conn.Close()

	value, err := json.Marshal(data)
	if err != nil {
		return err
	}

	_, err = conn.Do("SET", key, value)
	if err != nil {
		return err
	}

	_, err = conn.Do("EXPIRE", key, time)
	if err != nil {
		return err
	}

	return nil
}

// Exists check a key
func Exists(key string) bool {
	conn := RedisConn.Get()
	defer conn.Close()

	exists, err := redis.Bool(conn.Do("EXISTS", key))
	if err != nil {
		return false
	}

	return exists
}

// Get get a key
func Get(key string) ([]byte, error) {
	conn := RedisConn.Get()
	defer conn.Close()

	reply, err := redis.Bytes(conn.Do("GET", key))
	if err != nil {
		return nil, err
	}

	return reply, nil
}

// Delete delete a kye
func Delete(key string) (bool, error) {
	conn := RedisConn.Get()
	defer conn.Close()

	return redis.Bool(conn.Do("DEL", key))
}

// LikeDeletes batch delete
func LikeDeletes(key string) error {
	conn := RedisConn.Get()
	defer conn.Close()

	keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
	if err != nil {
		return err
	}

	for _, key := range keys {
		_, err = Delete(key)
		if err != nil {
			return err
		}
	}

	return nil
}

路由初始化

执行的方法是main中的router.InitRouter()
在目录的位置如下:
在这里插入图片描述
router/router.go

package router

import (
	"github.com/gin-gonic/gin"
	"gotest/src/controller"
)

// InitRouter initialize routing information
func InitRouter() *gin.Engine {
	r := gin.New()
	r.Use(gin.Logger())
	r.Use(gin.Recovery())
	r.GET("/queryByDatabase", controller.TestQueryByDatabase)
	r.GET("/setRedis", controller.TestSetRedis)
	return r
}

编写表的实体类,用于类似dao层的处理

编写实体类以及操作

package model

type PersonTable struct {
	Id         int64  `xorm:"pk autoincr"`
	PersonName string `xorm:"varchar(24)"`
	PersonAge  int    `xorm:"int default 0"`
	PersonSex  int    `xorm:"notnull"`
}

func (a *PersonTable) GetAll() ([]PersonTable, error) {
	var persons []PersonTable
	db.Find(&persons)
	return persons, nil
}

对应的sql

CREATE TABLE `person_table` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `person_name` varchar(255) DEFAULT NULL,
  `person_age` int(11) DEFAULT NULL,
  `person_sex` int(11) DEFAULT NULL
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;

编写接口控制器controller

目录位置:
在这里插入图片描述
controller/testControlelr.go

package controller

import (
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego/logs"
	"github.com/gin-gonic/gin"
	"gotest/src/app"
	"gotest/src/const"
	"gotest/src/gredis"
	"gotest/src/logging"
	"gotest/src/model"
	"net/http"
)

func TestQueryByDatabase(c *gin.Context) {
	appG := app.Gin{C: c}
	personService := model.PersonTable{}
	persons, _ := personService.GetAll()
	fmt.Println(persons)
	appG.Response(http.StatusOK, e.SUCCESS, persons)

}

func TestSetRedis(c *gin.Context) {
	appG := app.Gin{C: c}
	key := c.Query("key")
	value := c.Query("value")
	logging.Info("key:", key, " value:", value)
	err := gredis.Set(key, value, 1000)
	if err != nil {
		logs.Error(err)
	}
	getValue, _ := gredis.Get(key)
	var sss string
	json.Unmarshal(getValue, &sss)
	logs.Info(sss)
	appG.Response(http.StatusOK, e.SUCCESS, sss)

}

里面还调用了2个包的内容
go gin web综合教程,包括 mysql redis log 路由_第2张图片
如下:
app/response.go

package app

import (
	"github.com/gin-gonic/gin"
	"gotest/src/const"
)

type Gin struct {
	C *gin.Context
}

type Response struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}

// Response setting gin.JSON
func (g *Gin) Response(httpCode, errCode int, data interface{}) {
	g.C.JSON(httpCode, Response{
		Code: errCode,
		Msg:  e.GetMsg(errCode),
		Data: data,
	})
	return
}

const/code.go

package e

const (
	SUCCESS        = 200
	ERROR          = 500
	INVALID_PARAMS = 400

	ERROR_EXIST_TAG       = 10001
	ERROR_EXIST_TAG_FAIL  = 10002
	ERROR_NOT_EXIST_TAG   = 10003
	ERROR_GET_TAGS_FAIL   = 10004
	ERROR_COUNT_TAG_FAIL  = 10005
	ERROR_ADD_TAG_FAIL    = 10006
	ERROR_EDIT_TAG_FAIL   = 10007
	ERROR_DELETE_TAG_FAIL = 10008
	ERROR_EXPORT_TAG_FAIL = 10009
	ERROR_IMPORT_TAG_FAIL = 10010

	ERROR_NOT_EXIST_ARTICLE        = 10011
	ERROR_CHECK_EXIST_ARTICLE_FAIL = 10012
	ERROR_ADD_ARTICLE_FAIL         = 10013
	ERROR_DELETE_ARTICLE_FAIL      = 10014
	ERROR_EDIT_ARTICLE_FAIL        = 10015
	ERROR_COUNT_ARTICLE_FAIL       = 10016
	ERROR_GET_ARTICLES_FAIL        = 10017
	ERROR_GET_ARTICLE_FAIL         = 10018
	ERROR_GEN_ARTICLE_POSTER_FAIL  = 10019

	ERROR_AUTH_CHECK_TOKEN_FAIL    = 20001
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
	ERROR_AUTH_TOKEN               = 20003
	ERROR_AUTH                     = 20004

	ERROR_UPLOAD_SAVE_IMAGE_FAIL    = 30001
	ERROR_UPLOAD_CHECK_IMAGE_FAIL   = 30002
	ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003
)

const/msg.go

package e

var MsgFlags = map[int]string{
	SUCCESS:                         "ok",
	ERROR:                           "fail",
	INVALID_PARAMS:                  "请求参数错误",
	ERROR_EXIST_TAG:                 "已存在该标签名称",
	ERROR_EXIST_TAG_FAIL:            "获取已存在标签失败",
	ERROR_NOT_EXIST_TAG:             "该标签不存在",
	ERROR_GET_TAGS_FAIL:             "获取所有标签失败",
	ERROR_COUNT_TAG_FAIL:            "统计标签失败",
	ERROR_ADD_TAG_FAIL:              "新增标签失败",
	ERROR_EDIT_TAG_FAIL:             "修改标签失败",
	ERROR_DELETE_TAG_FAIL:           "删除标签失败",
	ERROR_EXPORT_TAG_FAIL:           "导出标签失败",
	ERROR_IMPORT_TAG_FAIL:           "导入标签失败",
	ERROR_NOT_EXIST_ARTICLE:         "该文章不存在",
	ERROR_ADD_ARTICLE_FAIL:          "新增文章失败",
	ERROR_DELETE_ARTICLE_FAIL:       "删除文章失败",
	ERROR_CHECK_EXIST_ARTICLE_FAIL:  "检查文章是否存在失败",
	ERROR_EDIT_ARTICLE_FAIL:         "修改文章失败",
	ERROR_COUNT_ARTICLE_FAIL:        "统计文章失败",
	ERROR_GET_ARTICLES_FAIL:         "获取多个文章失败",
	ERROR_GET_ARTICLE_FAIL:          "获取单个文章失败",
	ERROR_GEN_ARTICLE_POSTER_FAIL:   "生成文章海报失败",
	ERROR_AUTH_CHECK_TOKEN_FAIL:     "Token鉴权失败",
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT:  "Token已超时",
	ERROR_AUTH_TOKEN:                "Token生成失败",
	ERROR_AUTH:                      "Token错误",
	ERROR_UPLOAD_SAVE_IMAGE_FAIL:    "保存图片失败",
	ERROR_UPLOAD_CHECK_IMAGE_FAIL:   "检查图片失败",
	ERROR_UPLOAD_CHECK_IMAGE_FORMAT: "校验图片错误,图片格式或大小有问题",
}

// GetMsg get error information based on Code
func GetMsg(code int) string {
	msg, ok := MsgFlags[code]
	if ok {
		return msg
	}

	return MsgFlags[ERROR]
}

启动

到这里,你可以启动你的go程序,如果你是idea可以直接按main方法的图标启动
go gin web综合教程,包括 mysql redis log 路由_第3张图片

验证结果

go gin web综合教程,包括 mysql redis log 路由_第4张图片

go gin web综合教程,包括 mysql redis log 路由_第5张图片

成功返回正确的数据,从mysql返回正确,从redis设置并返回值,返回正确

总结

我写的这个教程,应该可以满足普通的开发了,本教程结合mysql,redis,log,配置文件,项目结构,都贴合实际开发的场景,如果你想用go-gin来写一个服务,那应该没啥问题,大多服务不就是结合数据库的curd,然后处理后返回数据。如果你正处于迷茫阶段,希望这个教程可以帮助到你,去开启你的go-gin web旅程

年轻人,放手干吧,未来属于你们
just do it

你可能感兴趣的:(go,redis,mysql,golang)