1:雪花算法(分布式id生成器)

时间因子毫秒级别,机器id

(long整型的64bit位结构)

mysql 采用雪花算法 替换自增id 雪花算法workid_go

雪花算法的主要目标是在分布式环境下生成全局唯一的ID,以避免ID冲突问题。以下是雪花算法的优缺点:

优点:

  1. 唯一性: 雪花算法生成的ID在同一时刻、同一数据中心、同一机器上都是唯一的,且具有较高的唯一性。
  2. 分布式: 算法支持分布式环境,每个节点都能够独立生成ID,无需中心化的ID生成服务。
  3. 有序性: 生成的ID是趋势递增的,因为雪花算法使用了时间戳作为其中的一部分,所以在一定程度上保持了ID的有序性。
  4. 简单高效: 算法相对简单,执行效率高,适用于高并发场景。
  5. 可读性: 雪花算法生成的ID通常是一个64位的整数,其中包含了时间戳等信息,便于人类阅读和理解。

缺点:

  1. 时钟回拨问题: 如果系统时钟发生回拨,有可能导致生成的ID不是严格递增的,这可能引发一些问题。为了解决这个问题,需要进行额外的时钟同步处理。
  2. 依赖系统时钟: 雪花算法依赖系统时钟,如果系统时钟不稳定,可能会影响ID的生成。
  3. ID大小: 生成的ID是64位的整数,相对于一些其他算法(如UUID)来说,占用的存储空间相对较大。
  4. 不适用于短时间内大量生成ID的场景: 在极短时间内大量生成ID可能会导致序列号部分耗尽,需要等待下一个时间戳。

总体而言,雪花算法在大多数分布式系统中表现良好,尤其适用于需要生成全局唯一ID并保持一定有序性的场景。然而,在一些特殊情况下,如时钟回拨等问题,可能需要额外的处理来保证其稳定性。

2:请求参数绑定

结构体参数统一格式

例如:


//ParamSignUp注册参数定义 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` }


参数绑定使用


p := new(models.ParamSignUp) err := ctx.ShouldBindJSON(p)


Validator 是用于验证数据的工具,它可以确保数据符合一定的规范、格式或业务逻辑。在参数绑定后,你可以使用 Validator 来验证数据的有效性。

3:validator简单的验证器与中英文包翻译


err.(validator.ValidationErrors)//验证错误


if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		//注册一个获取json tag的自定义方法
		//将解析字段的 JSON tag 并返回对应的名称,以便在校验失败时提供更友好的错误提示。
		v.RegisterTagNameFunc(func(field reflect.StructField) string {
			name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})
		zhT := zh.New() //中文翻译包
		enT := en.New()
		uni := ut.New(enT, zhT, enT) //创建一个通用的翻译器
		var ok bool
		trans, ok = uni.GetTranslator(locale) //获取指定语言环境的翻译器
		if !ok {
			return fmt.Errorf("uni GetTranslator(%s) failed", locale)
		}
		//注册翻译器
		switch locale {
		case "en":
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		case "zh":
			err = zhTranslations.RegisterDefaultTranslations(v, trans)
		default:
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		}


// removeTopStruct 去除提示信息中的结构体名称,校验失败时提供更友好的错误提示 func removeTopStruct(flieds map[string]string) map[string]string { res := map[string]string{} for flied, err := range flieds { res[flied[strings.Index(flied, ".")+1:]] = err } return res }


5:jwt基于token的认证

JWT分为三个部分,header(头部) payload (负载) signature (签名);头部和负载部,是用base64URL转成字符串的,签名是用头部指定的算法转成字符串的 ,所以jwt数据通常有两个小数点

JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,token也可直接被用于认证,也可被加密。JWT原理就是,服务器认证后,生成一个json格式的对象 ,发送给客户端,然后每次客户端发送请求都需要带着token

Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息。

流程:

这个token在每次请求时传递给服务端,它应该被保存在请求头里

// 定义JWT的过期时间,这里以24小时为例
const TokenExpireDuration = time.Hour * 24

// CustomSecret 用于加盐的字符串
var CustomSecret = []byte("今晚的月色好美啊")

type CustomClaims struct {
	UserId               int64  `json:"user_id"`
	Username             string `json:"username"`
	jwt.RegisteredClaims        //内嵌标准的声明
}

// GenToken 生成JWT
func GenToken(userId int64, username string) (aToken, rToken string, err error) {
	// 创建一个我们自己的声明
	claims := CustomClaims{
		userId,
		username, // 自定义字段
		jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),
			Issuer:    "my-project", // 签发人
		},
	}
	//获取aToken,加密并获得完整字符串 使用指定的secret签名
	aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(CustomSecret)
	//rToken 不需要存任何自定义数据
	rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
		ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
		Issuer:    "my-project", // 签发人
	}).SignedString(CustomSecret)
	return
}

// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
	var mc = new(CustomClaims)
	// 如果是自定义Claim结构体则需要使用 ParseWithClaims 方法
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) {
		// 直接使用标准的Claim则可以直接使用Parse方法
		//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		return CustomSecret, nil
	})
	if err != nil {
		return nil, err
	}
	// 对token对象中的Claim进行类型断言
	if token.Valid { // 校验token
		return mc, nil
	}
	return nil, errors.New("invalid token")
}

// refreshToken 刷新 accessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
	// refreshToken 无效返回
	_, err = jwt.Parse(rToken, func(token *jwt.Token) (i interface{}, err error) {
		// 直接使用标准的Claim则可以直接使用Parse方法
		//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		return CustomSecret, nil
	})
	if err != nil {
		return
	}
	//从旧的access token中解析出claims数据
	var claims CustomClaims
	_, err = jwt.ParseWithClaims(aToken, &claims, func(token *jwt.Token) (i interface{}, err error) {
		// 直接使用标准的Claim则可以直接使用Parse方法
		//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		return CustomSecret, nil
	})

	v, _ := err.(*jwt.ValidationError)
	//accessToken过期 refreshToken没有过期  创建一个新的 accessToken
	if v.Errors == jwt.ValidationErrorExpired {
		return GenToken(claims.UserId, claims.Username)
	}
	return
}

6:swagger接口文档

(1):写注释(main方法和接口函数之前)

注意:注册路由前需要添加


// 注册 swagger api 相关路由 r.GET("/swagger/*any", gs.WrapHandler(swaggerFiles.Handler))


(2):安装swagger

go get -u github.com/swaggo/swag/cmd/swag

(3):swag init

成功之后会生成docx文件夹

mysql 采用雪花算法 替换自增id 雪花算法workid_mysql 采用雪花算法 替换自增id_02

(4):访问http://127.0.0.1:8080/swagger/index.html

进入swagger UI页面接口测试

7:测试函数

名字以路由接口名_test.go,例如登录测试:login_test.go

func TestLoginHandler(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.Default()
	url := "/api/v1/login"
	r.POST(url, LoginHandler)
	body := `{
		"username": "zhangsan",
		"password": "123456"
	}`
	req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader([]byte(body)))
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
}
  • 第一个部分是 1 个 bit:0,符号位
  • 第二个部分是 41 个 bit:表示的是时间戳。系统控制
  • 第三个部分是(5 个 bit:表示的是机房 ID,10001) +  (5 个 bit:表示的是机器 ID,1 1001)。
  • 第四个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。
//设置唯一ID生成器的起始时间和机器ID。
func Init(startTime string, machineId int64) (err error) {
	var st time.Time
	st, err = time.Parse("2006-01-02", startTime)
	if err != nil {
		return
	}
	//将解析后的起始时间转换为毫秒级别的时间戳,并将其赋值给 sf.Epoch
	sf.Epoch = st.UnixNano() / 1000000
	node, err = sf.NewNode(machineId)
	return
}

func GenID() int64 {
	return node.Generate().Int64()
}

优点:

  • 唯一性: 雪花算法生成的ID在同一时刻、同一数据中心、同一机器上都是唯一的,且具有较高的唯一性。
  • 分布式: 算法支持分布式环境,每个节点都能够独立生成ID,无需中心化的ID生成服务。
  • 有序性: 生成的ID是趋势递增的,因为雪花算法使用了时间戳作为其中的一部分,所以在一定程度上保持了ID的有序性。
  • 简单高效: 算法相对简单,执行效率高,适用于高并发场景。
  • 时钟回拨问题: 如果系统时钟发生回拨,有可能导致生成的ID不是严格递增的,这可能引发一些问题。为了解决这个问题,需要进行额外的时钟同步处理。
  • 依赖系统时钟: 雪花算法依赖系统时钟,如果系统时钟不稳定,可能会影响ID的生成。
  • ID大小: 生成的ID是64位的整数,相对于一些其他算法(如UUID)来说,占用的存储空间相对较大。
  • 不适用于短时间内大量生成ID的场景: 在极短时间内大量生成ID可能会导致序列号部分耗尽,需要等待下一个时间戳。
  • 可读性: 雪花算法生成的ID通常是一个64位的整数,其中包含了时间戳等信息,便于人类阅读和理解。
  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据