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:
- A repository contains one or more Go modules.
- Each module contains one or more Go packages.
- Each package consists of one or more Go source files in a single directory.
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:
- Add import statements to your
.gocode as needed. - Standard commands like
go buildorgo testwill automatically add new dependencies as needed to satisfy imports (updatinggo.modand downloading the new dependencies). - When needed, more specific versions of dependencies can be chosen with commands such as
go get foo@v1.2.3,go get foo@master(foo@defaultwith mercurial),go get foo@e3702bed2, or by editinggo.moddirectly.
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语句有以下特点
-
传入的模型指针(结构体指针)可以作为
- 指向需要查询的表,例如
Model(&user)或Model(&User{}) - 使用里面的数据作为查询条件或更改(可使用Where更改里面的数据),例如
Model(&user)或Update(&user) - 存储查询结果,例如
First(&user)
以上几点可能同时用到
- 指向需要查询的表,例如
增删改查
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 会按照下面的优先级。每个项目的优先级都高于它下面的项目:
- 显示调用
Set设置值 - 命令行参数(
flag) - 环境变量
- 配置文件
key/value存储- 默认值
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