Golang-常见中间件

-目录-

SemVer

https://semver.org/lang/zh-CN/

semantic versioning 语义化版本信息,根据版号判断信息

v(major).(minor).(patch)

v0.1.0, v1.2.3, or v1.5.0-rc.1

Go Module

https://github.com/golang/go/wiki/Modules

Summarizing the relationship between repositories, modules, and packages:

go.mod

go.mod是在一系列源码的根目录上,定义了一个module.

A module is defined by a tree of Go source files with a go.mod file in the tree’s root directory. Module source code may be located outside of GOPATH. There are four directives: module, require, replace, exclude.

Your typical day-to-day workflow can be:

Sentry

https://docs.sentry.io/platforms/go/

在程序中报告messges,errors,panics。方便定位问题

func main() {
	err := sentry.Init(sentry.ClientOptions{
		Dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
	})
	if err != nil {
		log.Fatalf("sentry.Init: %s", err)
	}
	defer sentry.Flush(2 * time.Second)

	sentry.CaptureMessage("It works!")
}

Gorm

https://www.tizi365.com/archives/6.html

对象-关系映射(Object-Relational Mapping,简称ORM)。实现程序对象到关系数据库数据的映射。

gorm负责将对模型的读写操作翻译成sql语句,然后gorm再把数据库执行sql语句后返回的结果转化为我们定义的模型对象。

Gorm里的sql语句有以下特点

增删改查

db.Create(&u)
db.Where("username = ?", "tizi365").First(&u)
db.Model(&User{}).Where("username = ?", "tizi365").Update("password", "654321")
db.Where("username = ?", "tizi365").Delete(&User{})

连接池

避免重复建立数据库连接带来的性能消耗

//定义一个工具包,用来管理gorm数据库连接池的初始化工作。
package tools

//定义全局的db对象,我们执行数据库操作主要通过他实现。
var _db *gorm.DB

//包初始化函数,golang特性,每个包初始化的时候会自动执行init函数,这里用来初始化gorm。
func init() {
    ...忽略dsn配置,请参考上面例子...
    
    // 声明err变量,下面不能使用:=赋值运算符,否则_db变量会当成局部变量,导致外部无法访问_db变量
    var err error
    //连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
    _db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
		panic("连接数据库失败, error=" + err.Error())
	}
     
    sqlDB, _ := db.DB()

    //设置数据库连接池参数
    sqlDB.SetMaxOpenConns(100)   //设置数据库连接池最大连接数
    sqlDB.SetMaxIdleConns(20)   //连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭。
}

//获取gorm db对象,其他包需要执行数据库查询的时候,只要通过tools.getDB()获取db对象即可。
//不用担心协程并发使用同样的db对象会共用同一个连接,db对象在调用他的方法的时候会从数据库连接池中获取新的连接
func GetDB() *gorm.DB {
	return _db
}

事务处理

保证原子性操作

db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
  }

  // 返回 nil 提交事务
  return nil
})

Viper

https://www.liwenzhou.com/posts/Go/viper_tutorial/

https://zhuanlan.zhihu.com/p/144323180

读取,编辑,写入各种格式的配置文件

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

func main() {
	//设置文件
	//全路径 根据路径名称直接读取配置文件
	//viper.SetConfigFile("./config/con.yaml")
	//名字+类型+路径 设置名字(无扩展名),设置配置类型,设置配置文件路径(可设置多个进行搜索)
	viper.SetConfigType("yaml")
	viper.SetConfigName("con")
	viper.AddConfigPath("./config")
	//读取文件
	err := viper.ReadInConfig()
	if err != nil {
		panic(err)
	}
	//读取值
	fmt.Println(viper.Get("port"), viper.Get("name"), viper.Get("likes"))
	//9999 map[a:1 b:2] [big small]
	//设置值,但不会改变原有文件
	viper.Set("port", "8080")
	viper.Set("name", map[string]string{"a": "大", "b": "小"})
	viper.Set("likes", []string{"coding", "reading"})
	//保存值到新的文件
	err = viper.WriteConfigAs("config/con2.yaml")
	if err != nil {
		panic(err)
	}
	//likes:
	//- coding
	//- reading
	//name:
	//  a: 大
	//  b: 小
	//port: "8080"
	//通过设置默认值创建新的文件
	viper2 := viper.New()
	viper2.SetDefault("port", "2020")
	viper2.SetDefault("name", "jack")
	viper2.SetDefault("likes", []string{"swimming", "singing"})
	err = viper2.WriteConfigAs("config/con3.yaml")
	//likes:
	//- swimming
	//- singing
	//name: jack
	//port: "2020"
}

Viper 会按照下面的优先级。每个项目的优先级都高于它下面的项目:

  1. 显示调用Set设置值
  2. 命令行参数(flag)
  3. 环境变量
  4. 配置文件
  5. key/value 存储
  6. 默认值

Logrus

提供日志输出,快速入门

package main

import (
	"github.com/sirupsen/logrus"
	"log"
	"os"
)

func main() {
	//默认是info级别显示,这里设置TraceLevel都显示
	logrus.SetLevel(logrus.TraceLevel)

	//设置日志格式
	logrus.SetFormatter(&logrus.JSONFormatter{})

	//logrus.Trace("trace msg")
	//logrus.Debug("debug msg")
	logrus.Info("info msg")
	//logrus.Warn("warn msg")
	//logrus.Error("error msg")
	//logrus.Fatal("fatal msg")
	//logrus.Panic("panic msg")
	//只会对下面的第一条生效
	logger := logrus.WithField("module", "main")
	logger.Trace("trace msg")

	writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755)
	if err != nil {
		log.Fatalf("create file log.txt failed: %v", err)
	}

	//会进行覆写
	logrus.SetOutput(writer3)
	logrus.Info("info msg")
}

Protobuf

https://colobu.com/2019/10/03/protobuf-ultimate-tutorial-in-go/

是一个跨平台的序列化库

序列化(serialization、marshalling)的过程是指将数据结构或者对象的状态转换成可以存储(比如文件、内存)或者传输的格式(比如网络)。反向操作就是反序列化(deserialization、unmarshalling)的过程

syntax = "proto3";
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

输出

type SearchRequest struct {
	Query                string   `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
	PageNumber           int32    `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"`
	ResultPerPage        int32    `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}
func (m *SearchRequest) Reset()         { *m = SearchRequest{} }
func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
func (*SearchRequest) ProtoMessage()    {}
func (*SearchRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_5ffd045dd4d042c1, []int{0}
}
func init() {
	proto.RegisterType((*SearchRequest)(nil), "abc.SearchRequest")
}

Token和SessionId的区别

都是一串字符串,但是sessionId只是一个标识ID,里面没有包含额外信息。需要服务器存储,然后判断是哪个用户然后是否过期。而Token不仅是一个标识ID,里面还包含了过期时间,用户ID。那么这样不需要服务器存储了,但是需要服务器根据Secret进行解密运算。可以说是一个典型的时间换空间的例子。

Redis

进入:

redis-cli -a password

也可以进入后再auth+密码。

使用:

    查看所有的key列表:                   keys *
    设置键值:                          SET key_name value
    查看指定key:                       GET key_name
    删除键及对应的值:                   DEl key_name
    修改 key 的名称:                    RENAME key_name newkey_name

    判断key是否存在:                    EXISTS key_name
    查看key对应的value类型:            TYPE key_name
    
    删除全部							F
© 2019 - 2023 · YuYoung's Blog