这是我参与「第三届青训营 -后端场」笔记创作活动的第9篇笔记;
sync.Once是Go语言实现的一种对象,用来保证某种行为只会被执行一次。它只提供一个apl:
func (o *Once) Do(f func())
无论调用多少次Do,都只有第一次调用生效。
yaml文件方式连接mysql
需要引入相关yaml解析的依赖:
"gopkg.in/yaml.v2"
yaml文件
mysqldb:
dsn: "root:12345ssdlh@tcp(124.70.84.192:3306)/my_db01?charset=utf8mb4&parseTime=True&loc=Local"
pool:
# 最长空闲时间
connMaxIdleTime: 60
# 最大连接数
maxOpenConn: 23
# 最大空闲连接数
maxIdleConn: 1
# 连接的生命时长
connMaxLifetime: 236
配置包,将yaml文件与结构体对应
package config
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"sync"
"time"
)
type config struct {
Mysql mysqldb `yaml:"mysqldb"`
}
type mysqldb struct {
Myurl string `yaml:"dsn"`
Mypool pool `yaml:"pool"`
}
type pool struct {
ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime"`
MaxOpenConn int `yaml:"maxOpenConn"`
MaxIdleConn int `yaml:"maxIdleConn"`
ConnMaxLifetime time.Duration `yaml:"connMaxLifetime"`
}
var (
Config *config
configOnce *sync.Once
)
//读取文件
func readConfig() {
file, err := ioutil.ReadFile("config/config.yaml")
if err != nil {
fmt.Println(err)
}
if yaml.Unmarshal(file, Config) != nil {
fmt.Println(err)
}
}
func init() {
Config = &config{}
readConfig()
}
获取连接并创建连接池
package dao
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"goweb/config"
)
var DB *gorm.DB
func init() {
dbconf := config.Config
var err error
DB, err = gorm.Open(mysql.Open(dbconf.Mysql.Myurl), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
pool, err := DB.DB()
if err != nil {
fmt.Println(err)
}
pool.SetConnMaxLifetime(dbconf.Mysql.Mypool.ConnMaxLifetime)
pool.SetConnMaxIdleTime(dbconf.Mysql.Mypool.ConnMaxIdleTime)
pool.SetMaxIdleConns(dbconf.Mysql.Mypool.MaxIdleConn)
pool.SetMaxOpenConns(dbconf.Mysql.Mypool.MaxOpenConn)
}
main方法测试
package main
import (
"github.com/gin-gonic/gin"
"goweb/dao"
)
type User struct {
//`User`默认绑定的表名是`users`
Id int
Username string
Age int
Email string
AddTime int
}
func (User) TableName() string {
return "user" //结构体User绑定数据库表user
}
func main() {
r := gin.Default()
r.GET("/testyaml", func(c *gin.Context) {
userList := []User{}
dao.DB.Find(&userList)
c.JSON(200, gin.H{
"message": userList,
})
})
r.Run(":80")
}
测试成功:
使用连接池:首先获取一个链接然后放在连接池中,之后需要连接时再根据规定的数据判断是重新创建还是直接从池中拿;
遇到的坑
如果导入依赖时遇到导入失败:
则是因为设置了默认的
GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可以通过如下命令关闭:
go env -w GOSUMDB=off
token
JWT和TOKEN的关系
JWT就是TOKEN的一种载体,或者说JWT是一种标准,而Token是一个概念,而JWT就是这个概念执行的一种规范,通俗点说token就是一串字符串,jwt就是加上类型,信息等数据再加密包装一下成为一个新的token。 最直观的区别就是不使用JWT的token需要查询数据库来验证,而使用JWT可以不用对数据库进行操作,因为它本身包含了可自定义的信息
JWT token
一个JWT token包含3部分:
header:头部信息:指定类型和算法;Payload:荷载信息:存放Claims声明信息Signature:签名:把前两者对应的Json结构进行base64url编码之后的字符串拼接起来和密钥放一起加密后的签名组成方式为header.payload.signatureheader组成部分:typ:"JWT":是一种默认的标识,标识这条信息采用JWT规范;alh:"HS256":表示签名使用的加密算法,通常有ES256,ES512,RS256等等payload的结构:
payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的声明,这些声明被JWT标准称为claims(声明),它的每个属性键值对其实就是一个claim,JWT常用的有两种声明,一种是Reserved claims(保留声明),也就是JWT规定的标准声明。一种是Private claims(自定义声明),我们在这里定义要传递的信息,还有一种是public claims(公共声明),这个目前没用到。
格式: {"name": "value","id": "value2"}signature介绍:
签名,把header和payload对应的json结构进行base64url编码之后得到的字符串用点号拼接起来,然后根据header里面alg指定的签名算法生成出来的,然后添加自己设定的key进行加密签名;
实现:
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"time"
)
//用户信息类,作为生成token的参数
type UserClaims struct {
Id int
Username string
//上面是数据库里的信息,根据需要抽取部分信息
jwt.StandardClaims
//不写参数名相当于自定义结构体就是jwt.StandardClaims的子类
/*jwt-go提供标准claim,源码如下:
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}*/
}
var (
//自定义的token秘钥
secret = []byte("16849841325189456f487")
//该路由下不校验token
noVerify = []interface{}{"/login", "/ping"}
//token有效时间(纳秒)
effectTime = 2 * time.Hour
)
//生成token
func CreateToken(userclaim *UserClaims) string {
//获取当前时间+effectTime(token有效时间)并将其转换为unix时间格式
userclaim.ExpiresAt = time.Now().Add(effectTime).Unix()
//生成token
sign, err := jwt.NewWithClaims(jwt.SigningMethodHS256, userclaim).SignedString(secret)
//jwt.NewWithClaims(签名使用的加密算法, jwt-go提供的标准claim).SignedString(token秘钥)
if err != nil {
fmt.Println(err)
}
return sign
}
//解析Token
func parseToken(tokenstring string) *UserClaims {
token, err := jwt.ParseWithClaims(tokenstring, &UserClaims{},
func(token *jwt.Token) (interface{}, error) {
return secret, nil
}) //参数1:Token字符串;参数2:用户信息类实例的地址(作为生成token的参数)
//参数3:一个方法返回一个token密钥和一个nil
if err != nil {
fmt.Println(err)
}
claims, ok := token.Claims.(*UserClaims)
if !ok {
panic("token is valid")
}
return claims
}
//验证token
func JwtVerify(c *gin.Context) {
token := c.GetHeader("token")
if token == "" {
fmt.Println("token为空,验证失败")
}
//验证token,并将结果存储在请求中
c.Set("user", parseToken(token))
}