jwt 中间件
前面介绍了jwt的基本使用,但是那种写法比较不方便 你要是每个路由 都要增加这个特性,要复制粘贴很多代码,这里我们可以使用中间件来优化
func JWTAuthMiddleWare() func(context *gin.Context) {
return func(context *gin.Context) {
token := context.Request.Header.Get("auth-token")
// 注意abort的写法 只要发现 token不合法 则直接abort 不会传到真正的路由那里
if token == "" {
controllers.ResponseError(context, controllers.CodeTokenIsEmpty)
context.Abort()
return
}
parseToken, err := jwt.ParseToken(token)
if err != nil {
controllers.ResponseError(context, controllers.CodeTokenInvalid)
context.Abort()
return
}
// 当token合法的时候 我们可以把对应的关键信息 取出来放到context里面,则对应的路由就可以直接通过get的方法 取出关键信息了
zap.L().Debug("token parse", zap.String("username", parseToken.UserName))
context.Set("username", parseToken.UserName)
context.Set("userId", parseToken.UserId)
context.Next()
}
}
这样可以直接用 ,会方便不少
//验证jwt机制
r.GET("/ping", JWTAuthMiddleWare(), func(context *gin.Context) {
// 这里post man 模拟的 将token auth-token
zap.L().Debug("ping", zap.String("ping-username", context.GetString("username")))
controllers.ResponseSuccess(context, "pong")
})
也可以增加一个方法 来统一的取值
package controllers
import (
"errors"
"go_web_app/middleware"
"github.com/gin-gonic/gin"
)
var (
ErrorNotLogin = errors.New("no login")
)
func getCurrentUserId(c *gin.Context) (int64, error) {
userId, ok := c.Get(middleware.ContextUserIdKey)
if !ok {
return 0, ErrorNotLogin
}
return userId.(int64), nil
}
func getCurrentUserName(c *gin.Context) (string, error) {
userName, ok := c.Get(middleware.ContextUserIdKey)
if !ok {
return "", ErrorNotLogin
}
return userName.(string), nil
}
代码中的循环引用问题
上述的代码在编译的时候 会有一些问题
middleware这个里面有引用controller的东西
controller下面 又有引用middleware下面的东西
这里就是循环引用了 你调用我 我调用你。
那要修改起来其实也简单 只要挪个位置就行
把我们的key 挪到controller包下 即可
package controllers
import (
"errors"
"github.com/gin-gonic/gin"
)
const ContextUserNameKey = "username"
const ContextUserIdKey = "userid"
var (
ErrorNotLogin = errors.New("no login")
)
func getCurrentUserId(c *gin.Context) (int64, error) {
userId, ok := c.Get(ContextUserIdKey)
if !ok {
return 0, ErrorNotLogin
}
return userId.(int64), nil
}
func getCurrentUserName(c *gin.Context) (string, error) {
userName, ok := c.Get(ContextUserIdKey)
if !ok {
return "", ErrorNotLogin
}
return userName.(string), nil
}
makefile
我自己是在mac上开发的,然而你要发布到 linux上 你得编译成linux下的可执行文件,每次敲命令是有点麻烦的。 用makefile 可以大大降低这个成本
# 告诉makefile 不需要去当前目录找这些同名的文件 只要makefile 文件中 找即可
.PHONY: all build run gotool clean help
# 定义一个变量 项目的名称
BINARY="bbs_server"
# 定义2个目标
all: gotool build
# 我自己的开发机器是mac 所以最终要在linux上部署 需要build出来 linux下的可执行文件
build:
CGO_ENABled=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY}
# 不加@ 则在make的时候 会打印出来对应的命令
run:
@go run ./main.go config.yaml
# 格式化代码 并检查
gotool:
go fmt ./
go vet ./
# 删除对应的文件
clean:
@if [ -f ${BINARY} ]; then rm ${BINARY} ; fi
help:
@echo "make - 格式化 Go代码 并编译成二进制文件"
@echo "make build - 编译 Go代码 并编译成linux平台下的二进制文件"
@echo "make run - 直接运行"
@echo "make clean 移除二进制文件"
@echo "make gotool 运行go工具 fmt 和vet "
我们这里比较简单,主要是为了演示用。自己的项目可能比这个要复杂的多,makefile 作用就越大
air
通常而言 我们在开发的时候 希望我们保存代码以后 程序能够自动编译并且运行,这在前端领域或者是python领域很常见,在go中也有类似的方案
在项目的根目录中 新建一个.air.conf 的文件
# 表示当前路径
root="."
# 临时文件目录
tmp_dir="tmp"
[build]
# 编译的命令
cmd="go build -o ./tmp/main"
# 最终可执行文件的名字
bin="/tmp/main"
# 执行程序的命令
full_bin="./tmp/main ./config.yaml"
# 监听哪些扩展名的文件
include_ext=["go","yaml","tpl","html"]
# 忽略文件扩展名或目录
exclude_dir=["assets","tmp"]
# 编译延迟 防止构建频繁
delay=1000
# 构建错误时 停止运行旧的二进制文件
stop_on_error=true
# air 的日志名
log="air_errors.log"
# 显示日志时间
time=true
[color]
main="magenta"
watcher="cyan"
build="yellow"
runner="green"
[misc]
# 退出时删除tmp目录
clean_on_exit=true
在项目的根目录中 输入 air 命令
至此,我们就可以完成 修改任意文件以后 程序自动build的 功能了,这可以极大的提高我们的开发效率