前言
上一篇:从零开始基于go-zero的go web项目实战-01项目初始化
从零开始基于go-zero搭建go web项目实战-02集成JWT和cobra命令行工具
源码仓库地址 源码 https://gitee.com/li_zheng/treasure-box
JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用间传递声明式信息。它是一种基于JSON的轻量级的身份验证和授权机制,用于在客户端和服务器之间安全地传输信息。《JSON Web Tokens》
go-zero中默认引入了golang-jwt
依赖,以中间件的形式,可以给请求配置jwt校验,需要开发人员自己编写生成token的逻辑和接口,接下来从用户登录、验证、token生成、接口校验进行讲解。
实体声明
定义一个TokenClaims 结构体,承载token信息
type TokenClaims struct {
UserId int64
UserName string
UserType string
Role []string
Token string
Ext map[string]any
jwt.RegisteredClaims
}
定义一个UserLoginReq 结构体,保存用户名和密码
type UserLoginReq struct {
Username string `form:"username"`
Password string `form:"password"`
}
编写生成token的工具方法
编写一个工具方法,用于通过密钥和时间生成token
package util
import (
"github.com/golang-jwt/jwt/v4"
"github.com/zachary/tbox/internal/types"
"time"
)
// GetJwtToken
// @secretKey: JWT 加解密密钥
// @seconds: 过期时间,单位秒
// @token: 数据载体
func GetJwtToken(secretKey string, seconds int64, token *types.TokenClaims) (string, error) {
token.RegisteredClaims = jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(seconds))),
Issuer: "zachary",
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
}
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, token)
mySigningKey := []byte(secretKey)
signedString, err := claims.SignedString(mySigningKey)
return signedString, err
}
声明一个post的login接口进行登录校验
登录业务逻辑编写(临时登录后续进行优化)
// handler.go
package login
import (
"github.com/zachary/tbox/internal/svc"
"github.com/zachary/tbox/internal/types"
"github.com/zachary/tbox/internal/util"
"github.com/zachary/tbox/internal/web"
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
)
var tempUser = types.UserLoginReq{
Username: "admin",
Password: "admin",
}
func TempUpLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UserLoginReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
if req.Username == tempUser.Username && req.Password == tempUser.Password {
// 验证成功,生成测试数据
token := types.TokenClaims{
UserId: 1000,
UserName: tempUser.Username,
UserType: "WEB",
Role: []string{"admin"},
Ext: map[string]any{"ex1": "val1", "ext2": "val2"},
}
secret := svcCtx.Config.Auth.AccessSecret
expire := svcCtx.Config.Auth.AccessExpire
jwtToken, err := util.GetJwtToken(secret, expire, &token)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
token.Token = jwtToken
httpx.OkJsonCtx(r.Context(), w, web.Success(token))
} else {
httpx.WriteJsonCtx(r.Context(), w, http.StatusUnauthorized, web.Fail("登录失败,用户名或密码错误!"))
}
}
}
声明接口,并加入Server中
// routes.go
func GetNoAuthRoutes(serverCtx *svc.ServiceContext) []rest.Route {
return []rest.Route{
{
Method: http.MethodPost,
Path: "/token/",
Handler: TempUpLoginHandler(serverCtx),
},
}
}
// root_routes.go
server.AddRoutes(login.GetNoAuthRoutes(serverCtx), rest.WithPrefix("/login"))
编写一个无权访问回调函数
编写一个无权访问回调函数 UnauthorizedCallback,处理无权访问时,接口返回值
package web
import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
)
// UnauthorizedCallback 无权访问回调
func UnauthorizedCallback(w http.ResponseWriter, r *http.Request, err error) {
if err != nil {
logx.Errorf("authentication error: %v", err)
}
httpx.OkJson(w, Fail("authentication error"))
}
创建Server时候,加入回调函数
server = rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(web.UnauthorizedCallback))
代码位置
测试
请求头无token
登录操作
携带token访问
cobra
cobra是一种创建强大的现代CLI应用程序的库。cobra用于许多GO项目,如Kubernetes,Hugo和Github Cli等。cobra遵循commands, arguments & flags结构。其中commands代表行为,arguments代表数值,flags代表对行为的改变。
引入依赖
本项目版本:v1.7.0
go get -u github.com/spf13/cobra@latest
编写root根命令
root命令是所有子命令的根
var rootCmd = &cobra.Command{
// 使用提示:Use、Short、Long
Use: "tbox",
Short: "treasure box",
Long: "treasure box platform",
//命令遇到错误时不显示使用方法
SilenceUsage: false,
Args: func(cmd *cobra.Command, args []string) error {
//if len(args) < 1 {
// return errors.New("至少需要一个参数")
//}
return nil
},
// 命令运行之前会回调
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
// 命令运行之前会回调
PreRun: func(cmd *cobra.Command, args []string) {
},
// 命令运行的逻辑
Run: func(cmd *cobra.Command, args []string) {
tip()
},
}
// 打印简单的提示信息
func tip() {
fmt.Println("欢迎使用tbox平台系统, -h 参数查看帮助文档")
}
func initCmd() {
// 添加版本控制的子命令
rootCmd.AddCommand(newVersionCommand())
// 添加start服务的子命令
rootCmd.AddCommand(newRunCommand())
}
// main方法里调用,触发程序执行
func Execute() {
// 初始化子命令
initCmd()
// 执行命令
if err := rootCmd.Execute(); err != nil {
logx.Errorf("Execute error:%v\n", err)
}
}
编写版本信息version子命令
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/core/color"
)
func newVersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "version info",
Long: "View current version!",
// 别名
Aliases: []string{"V", "v"},
Run: func(cmd *cobra.Command, args []string) {
// 打印版本信息,颜色包裹实现彩色展示
fmt.Println(color.WithColor("1.0.0", color.FgGreen))
},
}
}
编写启动start子命令
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/zachary/tbox/internal/config"
"github.com/zachary/tbox/internal/svc"
"github.com/zachary/tbox/internal/util"
"github.com/zachary/tbox/internal/web"
"github.com/zachary/tbox/internal/web/handler"
"github.com/zeromicro/go-zero/core/color"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
"log"
"os"
"os/signal"
"syscall"
"time"
)
func newRunCommand() *cobra.Command {
var server *rest.Server
runC := &cobra.Command{
Use: "start",
Short: "Run server",
Long: "Start http server!",
// 给出一个使用例子
Example: "tbox start -f etc/tbox-api.yaml",
PreRun: func(cmd *cobra.Command, args []string) {
// 运行之前加载配置等信息
server = preRun(cmd)
},
Run: func(cmd *cobra.Command, args []string) {
// 运行服务
runServer(server)
},
}
// 指定运行参数:-f 指定配置文件
runC.Flags().StringP("file", "f", "etc/tbox-api.yaml", "config path")
return runC
}
func preRun(cmd *cobra.Command) (server *rest.Server) {
var c config.Config
configFile := cmd.Flag("file").Value.String()
fmt.Println(color.WithColor("loading config file:", color.FgGreen), color.WithColor(configFile, color.FgMagenta))
conf.MustLoad(configFile, &c)
server = rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(web.UnauthorizedCallback))
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
return
}
func runServer(server *rest.Server) {
c := svc.GetServiceContext().Config
fmt.Printf(color.WithColor("Starting server at %s:%d...\n", color.FgGreen), c.Host, c.Port)
// 等待中断信号以优雅地关闭服务器
// kill (no param) default send syscanll.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
//开启新的goroutine启动
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(color.WithColor("Server start failed:", color.FgRed), err)
time.Sleep(time.Millisecond * 200)
quit <- syscall.SIGTERM
}
}()
server.Start()
}()
time.Sleep(time.Millisecond * 500)
fmt.Println(color.WithColor("Server run at:", color.FgGreen))
fmt.Printf("- %s: http://localhost:%d/ \r\n", color.WithColor("Local", color.BgGreen), c.Port)
fmt.Printf("- %s: http://%s:%d/ \r\n", color.WithColor("Network", color.BgGreen), util.GetLocalHost(), c.Port)
fmt.Printf("%s Enter Control + C Shutdown Server \r\n", util.NowDateTimeStr())
<-quit
fmt.Printf("%s Shutdown Server ... \r\n", util.NowDateTimeStr())
log.Println("Server exiting")
server.Stop()
}
使用测试
编译项目
go build .
测试命令
PS D:\dev\GolandProjects\treasure-box\treasure-box> .\tbox.exe
欢迎使用tbox平台系统, -h 参数查看帮助文档
# 查看-h信息
PS D:\dev\GolandProjects\treasure-box\treasure-box> .\tbox.exe -h
treasure box platform
Usage:
tbox [flags]
tbox [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
start Run server
version version info
Flags:
-h, --help help for tbox
Use "tbox [command] --help" for more information about a command.
# 查看版本信息
PS D:\dev\GolandProjects\treasure-box\treasure-box> .\tbox.exe version
1.0.0
#查看start帮助文档
PS D:\dev\GolandProjects\treasure-box\treasure-box> .\tbox start -h
Start http server!
Usage:
tbox start [flags]
Examples:
tbox start -f etc/tbox-api.yaml
Flags:
-f, --file string config path (default "etc/tbox-api.yaml")
-h, --help help for start
# 启动服务
PS D:\dev\GolandProjects\treasure-box\treasure-box> .\tbox start
loading config file: etc/tbox-api.yaml
Starting server at 0.0.0.0:8888...
Server run at:
- Local: http://localhost:8888/
- Network: http://30.236.0.84:8888/
2023-07-24 13:12:13 Enter Control + C Shutdown Server
代码位置
源码仓库地址 源码 https://gitee.com/li_zheng/treasure-box
下一篇
- 引入redis,分布式缓存中间件
- 引入gorm,持久化层框架