Gin实现论坛(一)

Gin实现论坛

一、用户建表

1、Model层创建User模型

type User struct{
	gorm.Model
	UserID string   `gorm:"not null"`
	UserName string `gorm:"not null"`
	PassWord string `gorm:"not null"`
	Name string `gorm:"not null"`
	Email string `gorm:"not null"`
	Gender int `gorm:"not null;default:1"`   //gender=0 is admin
}

2、Dao层创建表,可进行业务处理

//Create Init Tables  eg:User,Create User Can Add use ',' Split
	if err:=db.AutoMigrate(&Model.User{});err!=nil{
		zap.L().Error("AutoMigrate Err",zap.Error(err))
	}
	//If Not Have User Will Add Admin
	//if res:=db.Find(&Model.User{});res.RowsAffected==0&&res.Error==nil{
	//	db.Create(&Model.User{
	//		UserName: "root",
	//		PassWord: "root",
	//		Name:"admin",
	//		Email:"1102394156@qq.com",
	//	})
	//}

二、雪花算法go的实现

package main

import (
	"fmt"
	"github.com/bwmarrin/snowflake"
	"log"
	"time"
)

//轻量级的雪花算法实现
var node *snowflake.Node

func Init(startTime string, machineID int64) (err error) {
	var st time.Time
	st, err = time.Parse("2006-01-02", startTime)
	if err != nil {
		log.Fatal(err)
		return
	}
	snowflake.Epoch = st.UnixNano() / 1000000
	node, err = snowflake.NewNode(machineID)
	return
}

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

func main() {
	if err := Init("2021-07-13", 1); err != nil {
		fmt.Println("ERR Init")
		return
	}
	for i := 0; i < 10; i++ {
		id := GetId()
		fmt.Println(id)
	}

}

三、CLD分层逻辑

1、首先是router包下的函数定义路由(Login)到controller中

 r.POST("/Login",Controller.LoginHandler)

2、然后Controller进行参数获取,参数验证,业务处理,返回值

 func LoginHandler(c *gin.Context){
  	//Get Params And Check
    //c.GetString()...GetParams
	//Make Work
	Logic.Login()
	//Return Response
	c.JSON(http.StatusOK,gin.H{
		"msg":"ok",
	})
}

3、在处理业务逻辑Login的时候转换到logic层处理

package Logic
func Login(){
	//Juge User is have  (Use Dao)
    //Snowflake  Uuid
	//Save DB(Use Dao)
}

4、Logic层将会对数据库增删查改,所以需要操作Dao层

package mysql
func QueryUserByID(){
//select * from......
}

四、validator库参数校验

1、直接在结构体中使用bind参数校验参数,使用shouldbind的时候自动校验

RePassWord string `json:"re_password" binding:"required,eqfield=PassWord"`

2、将错误翻译为中文

(1)设置翻译器Translator

package Controller

import (
	"fmt"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	enTranslations "github.com/go-playground/validator/v10/translations/en"
	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

// 定义一个全局翻译器T
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
	// 修改gin框架中的Validator引擎属性,实现自定制
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

		zhT := zh.New() // 中文翻译器
		enT := en.New() // 英文翻译器

		// 第一个参数是备用(fallback)的语言环境
		// 后面的参数是应该支持的语言环境(支持多个)
		// uni := ut.New(zhT, zhT) 也是可以的
		uni := ut.New(enT, zhT, enT)

		// locale 通常取决于 http 请求头的 'Accept-Language'
		var ok bool
		// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
		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)
		}
		return
	}
	return
}

(2)主函数main初始化翻译器

	//Init Translate
	if err := Controller.InitTrans("zh"); err != nil {
		zap.L().Error("init trans failed",zap.Error(err))
		return
	}

(3)在bind的err中进行类型断言判断,如果是bind错误就输出翻译后的信息

   if err:=c.ShouldBindJSON(&newUser);err!=nil{
     	errs,ok:=err.(validator.ValidationErrors)
     	if !ok{
     		zap.L().Error("ShouldBind Err",zap.Error(err))
     		c.JSON(http.StatusOK,gin.H{
     			"msg":err.Error(),
			})
		}
		 c.JSON(http.StatusOK, gin.H{
			 "msg":errs.Translate(trans),
		 })
     	return
	 }

3、由于接受返回的json是前端,所以需要和前端的json标签相同,但是翻译器默认是与后端结构体的名字相同,所以在初始化InitTrans的时候注册jsontag方法

	// 注册一个获取json tag的自定义方法
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})

4、翻译器的获取的key值的结构是:结构体.字段(后端),但是前端接受数据是不需要后端的,所以需要进行切割

func removeTopStruct(fields map[string]string) map[string]string {
	res := map[string]string{}
	for field, err := range fields {
		res[field[strings.Index(field, ".")+1:]] = err
	}
	return res
}

调用方法:
 c.JSON(http.StatusOK, gin.H{
			 "msg":removeTopStruct(errs.Translate(trans)),
		 })

五、定义错误码与响应封装

1、定义错误码,制定错误码返回错误信息方法

package Model

type RespCode int64

const (
    CodeSuccess RespCode=1000+iota
    CodeInvalidParam
    CodeUserExitst
    CodeUserNotExitst
	CodeInvalidPassword
    CodeServerBusy
)

var CodeMsgMap =map[RespCode]string{
	CodeSuccess: "Success",
	CodeInvalidParam: "Param Err",
	CodeUserExitst: "User Exitst",
	CodeUserNotExitst: "Not have User",
	CodeInvalidPassword: "PassWord Err",
	CodeServerBusy: "Busy 404 Server",
}

func (r RespCode)Msg()string{
   msg,ok:=CodeMsgMap[r];
   if !ok{
    	msg=CodeMsgMap[CodeServerBusy]
	}
	return msg
}

2、定义响应结构并且绑定方法

package Controller

import (
	"GinProject/Model"
	"github.com/gin-gonic/gin"
	"net/http"
)

type Response struct {
	Code Model.RespCode     `json:"code"`
	Msg interface{} `json:"msg"`
	Data interface{} `json:"data"`
}

func ResponseErr(c *gin.Context,code Model.RespCode){
	c.JSON(http.StatusOK,&Response{
		Code:code,
		Msg: code.Msg(),
		Data: nil,
	})
}

func ResponseSuccess(c *gin.Context,data interface{}){
	c.JSON(http.StatusOK,&Response{
		Code:Model.CodeSuccess,
		Msg: Model.CodeSuccess.Msg(),
		Data: data,
	})
}

func ResponseErrWithMsg(c *gin.Context,code Model.RespCode,msg interface{}){
	c.JSON(http.StatusOK,&Response{
		Code:code,
		Msg: msg,
		Data: nil,
	})
}

六、用户注册与登录

1、注册

(1)使用bcrypt加密密码

//Get ErrHash
func HashAdd(passWord string)string{
	hash,err:=bcrypt.GenerateFromPassword([]byte(passWord),0)
	if err!=nil{
		zap.L().Error("bcrypt Err", zap.Error(err))
	}
	return string(hash)
}

(2)注册无非与数据库交互,自己梳理逻辑

2、登录

(1)加密密码比较方法

//HashCompare
func HashCompare(hash ,passWord string) bool{
     return bcrypt.CompareHashAndPassword([]byte(hash),[]byte(passWord))==nil
}

(2)登录逻辑自己梳理

七、用户认证JWT

1、session和cookie的问题

(1)储存的session资源大;

(2)创建session服务器和验证session的服务器可能不是同一台;

(3)跨域需要兼容性处理,难以防范CSRF攻击;

2、Token认证模式

(1)无状态,不需要在服务器储存Session,只需要解析客户端发来的Token;

(2)不需要Cookie,解决了CSRF问题;

(3)使用CORS解决跨域问题;

3、JWT

定义:Json Web Token 是基于Token的会话管理规则;

结构:分成三部分,头部,负载,签名;
在这里插入图片描述

头部:定义加密算法和类型,是一个JSON对象;
负载:规定了官方字段,比如签发人,过期时间等;(也可以自己增加字段);
签名:防止数据被篡改;

缺点:Token被盗的话,在有效期内能一直访问;

(1)使用jwt-go库获得设置Token

package Jwt

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"time"
)
//定义JWT的过期时间2小时
const TokenExpireDuration = time.Hour * 2
// Add Nacl
var MySecret = []byte("Cas")
// MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyClaims struct {
	UserId int64 `json:"userid"`
	Username string `json:"username"`
	jwt.StandardClaims
}
// GenToken 生成JWT
func GenToken(userid int64,username string) (string, error) {
	// 创建一个我们自己的声明
	c := MyClaims{
		userid ,
		username , // 自定义字段
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    "Cas",                               // 签发人
		},
	}
	// 使用指定的签名方法创建签名对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return token.SignedString(MySecret)
}
// ParseToken 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
	// 解析token
	var mc =new(MyClaims)
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) {
		return MySecret, nil
	})
	if err != nil {
		return nil, err
	}
	if token.Valid { // 校验token
		return mc, nil
	}
	return nil, errors.New("invalid token")
}

(2)在路由中增加中间件判断是否有合法的Token

  r.GET("/ping", middlewares.JWTAuthMiddleware(),func(c *gin.Context) {
        c.JSON(http.StatusOK,gin.H{
                   "msg":"pong",
           })
    })

(3)通过Token获得userid

func GetCurrentUser(c *gin.Context)(userid int64,err error){
	uid,ok:=c.Get("userid")
	if !ok{
		err=errors.New("User Not Login")
      	return
	}
	userid,ok=uid.(int64)
	if !ok{
		err=errors.New("User Not Login")
		return
	}
	return userid,nil
}

4、Refresh Token
在这里插入图片描述
大致流程:首先获得AccessToken和RefreshToken,然后判断AccessToken解析是否有值,如果没有值就使用RefreshToken来获取新的AccessToken

思考:同一账号只能登录一台设备;
在这里插入图片描述
方案:通过时间维度的不同,使用redis储存用户ID和Token的对应关系

如果看完对自己有所帮助,请点赞支持,谢谢大家

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于go语言的gin框架搭建的go-bbs项目,项目还处在开发阶段,但整体框架已搭建完毕,可git clone下来,删除多余部分,将此包作为一个脚手架使用。Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值