Gin框架系列教程(11)- gin 集成Redis,简单使用
我们在项目中经常会用到redis缓存数据,gin框架中使用redis非常简单。我们封装一下,我用的是go-redispackage redis_client import ( "context" "time" "github.com/redis/go-redis/v9" ) var redisClient *redis.Client var ctx = context.Background() // 设置reids值 func Set(key string, value string, time time.Duration) error { err := redisClient.Set(ctx, key, value, time).Err() if err != nil { return err } return nil } // 获取reids值 func Get(key string) (string, error) { value, err := redisClient.Get(ctx, key).Result() if err != nil { return "", err } return value, err } // 删除rediskey func Del(key string) error { err := redisClient.Del(ctx, key).Err() if err != nil { return err } return nil } func RedisInit() { client := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 0, }) timeout, goBack := context.WithTimeout(context.Background(), time.Second*3) defer goBack() _, err := client.Ping(timeout).Result() if err != nil { panic("redis初始化失败! " + err.Error()) } redisClient = client } func init() { RedisInit() } 我们调用方法就可以使用了redis_client.Set("lxl", "aaaa", 10*time.Minute) value, _ := redis_client.Get("lxl") fmt.Println(value)我们就可以看到结果:
查看详情点赞评论收藏浏览962024-05-25 14:35:48PHP中的HTTP请求(GET请求,POST请求),及response响应返回
不说了,直接记录代码,下次直接用。get请求:/** * GET请求 * @param $url * @return mixed */ public function curlGet($url){ $ch1 = curl_init(); $timeout = 0; curl_setopt($ch1, CURLOPT_URL, $url); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch1, CURLOPT_ENCODING, ''); curl_setopt($ch1, CURLOPT_MAXREDIRS, 10); curl_setopt($ch1, CURLOPT_HTTPHEADER, array()); curl_setopt($ch1, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch1, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch1, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch1, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch1, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch1, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); $access_txt = curl_exec($ch1); curl_close($ch1); return json_decode($access_txt, true); }post请求:/** * 发送http post请求 * @param $url * @param $data * @param array $header * @param bool $is_json * @return mixed|string */ function httpPost($url, $data, $header = [],$is_json = true) { if (empty($header)) { $header = array( "Accept: application/json", "Content-Type:application/json;charset=utf-8", ); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)'); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); if (curl_errno($ch)) { $error_message = curl_error($ch); @file_put_contents(storage_path('logs/error.log'), var_export($error_message, true) . PHP_EOL, FILE_APPEND); } curl_close($ch); if($is_json){ return json_decode($res, true); } return trim($res); }response响应返回(记录日常用的下次直接用了):/** * 响应 * @param null $msg * @param int $code * @param array $data * @param int $result_code * @param string $type * @return void */ public function response($msg = null,$code = self::SUCCESS,$data = [],$result_code = 200,$type = 'application/json') { $result = [ 'msg' => $msg, 'code' => $code, 'data' => $data, ]; $this->header['Content-Type'] = $type; $response = response($result,$result_code,$this->header); throw new HttpResponseException($response); } /** * @param array $data * @param string $message * @param int $status * @return string */ protected function success($data = [], $message = 'success', $status = 200) { $array = compact('status', 'message', 'data'); return json_encode($array, JSON_UNESCAPED_UNICODE); } /** * @param int $status * @param string $message * @param array $data * @return string */ protected function error($message = 'error', $status = 400, $data = []) { $array = compact('status', 'message', 'data'); return json_encode($array, JSON_UNESCAPED_UNICODE); }
查看详情点赞评论收藏浏览502024-05-20 12:05:27PHP 利用ZipArchive类库实现压缩&解压文件夹
最近有一个小的功能需求,就是需要通过访问接口根据token访问或下载远程的资源压缩包,所以需要实现资源文件夹的压缩及解压,用了很多库最后发现这个类库是比较方便的。zip文件夹压缩/** * 压缩文件夹 */ public function createZipFile(){ $folderPath = public_path("target"); $zipFilePath = public_path().'/target.zip'; $zip = new ZipArchive(); if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) { // 递归添加文件夹下的所有文件和子文件夹 $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($folderPath), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($files as $name => $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($folderPath) + 1); $zip->addFile($filePath, $relativePath); } } $zip->close(); echo '文件夹压缩成功'; } else { echo '无法打开或创建压缩文件'; } }解压zip压缩包 /** * 网站html解压 * @param $url * @return string */ public function websiteHtmlHandle($url) { $pathInfo = pathinfo($url); $extension = $pathInfo['extension']; //只允许解压zip格式文件 if (in_array($extension, ["zip"])) { try { $targetFile = $this->downLoadFile($url); $zip = new ZipArchive(); if ($zip->open($targetFile) === TRUE) { $outputFolder = public_path(); if (!is_dir($outputFolder)) { mkdir($outputFolder, 0777, true); } // 解压缩文件,保留原文件结构 $zip->extractTo($outputFolder); $zip->close(); $this->deleteDirectory($targetFile); } else { return $this->error('解压失败!'); // 处理打开压缩文件失败的情况 } } catch (\Exception $e) { return $this->error($e->getMessage()); } }else{ return $this->error('不允许解压改格式压缩包!'); } return $this->success(); }看看操作结果:功能没问题。
查看详情点赞评论收藏浏览502024-05-14 11:20:37Gin框架系列教程(10)- gin IP限流+令牌桶限流
我们经常有一些场景需要用到限流,比如,我们登录的时候会限制IP时间段内登录的次数,或者防止并发量突然增高时,服务器无法承受,保证了QPS的上限值。限流的方法有很多,下面我们来实现一下gin IP限流+令牌桶限流,参考网上多种方法测试代码如下:中间件:package middleware import ( "admin/services/commonService/response" "fmt" "net/http" "sync" "time" "github.com/gin-gonic/gin" "github.com/juju/ratelimit" ) type RequestInfo struct { LastAccessTime time.Time // 上次访问时间 RequestNum int // 请求计数 } var ( requestInfoMap = make(map[string]*RequestInfo) // IP到请求信息的映射 mutex = &sync.Mutex{} // 用于保护requestInfoMap的互斥锁 maxRequests = 2 // 允许的最大请求数 timeWindow = 1 * time.Second // 时间窗口 ) // IP限流器 func IpLimit(c *gin.Context) { ip := c.ClientIP() mutex.Lock() defer mutex.Unlock() // 检查IP是否在map中 info, exists := requestInfoMap[ip] // 如果IP不存在,初始化并添加到map中 if !exists { requestInfoMap[ip] = &RequestInfo{LastAccessTime: time.Now(), RequestNum: 1} return } // 如果IP存在,检查时间窗口 if time.Since(info.LastAccessTime) > timeWindow { // 如果超过时间窗口,重置请求计数 info.RequestNum = 1 info.LastAccessTime = time.Now() return } info.RequestNum++ // 如果在时间窗口内,增加请求计数 // 如果请求计数超过限制,禁止访问 if info.RequestNum > maxRequests { errMsg := fmt.Errorf("too many requests") response.Error(c, http.StatusTooManyRequests, errMsg, "请求过于频繁,请稍后再试!") c.Abort() return } // 更新最后访问时间 info.LastAccessTime = time.Now() c.Next() } // ratelimit限流器 func RateLimit(time time.Duration, originNum, pushNum int64) gin.HandlerFunc { bucket := ratelimit.NewBucketWithQuantum(time, originNum, pushNum) return func(c *gin.Context) { if bucket.TakeAvailable(1) < 1 { errMsg := fmt.Errorf("too many requests") response.Error(c, http.StatusTooManyRequests, errMsg, "请求过于频繁,请稍后再试!") c.Abort() return } c.Next() } } 路由中使用中间件:api.POST("/member/login", middleware.IpLimit, memberApi.MemberLogin) //登录 api.POST("/member/login", middleware.RateLimit(time.Second, 10, 10), memberApi.MemberLogin) //登录结果:经过测试上面两种方式都能达到限流的目的。
查看详情点赞1评论收藏1浏览1422024-04-24 17:13:07Gin框架系列教程(9)- gin+grpc,实现微服务数据交互
什么是rpc?1、定义:远程过程调用协议。和http请求类似,只不过协议不同,rpc使用的大多数为TCP,http请求使用的为HTTP协议。可以简单的理解为一个节点调用另外一个节点。2.组成:客户端:Client,服务调用方。客户端存根:Client Stub,存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端网络传输模块:Network Service,底层传输,可以是 TCP 或 HTTP服务端存根:Server Stub,接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。服务端等:server,服务的真正提供者。3.执行过程:Client 客户端通过本地调用的方式调用服务。客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。网络传输服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理服务端(Server)本地服务业务处理。处理结果返回给服务端存根(Server Stub)。服务端存根(Server Stub)序列化结果。服务端存根(Server Stub)将结果通过网络发送至消费方。客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。客户端得到最终结果。什么是grpc1、定义:是一个高性能、开源、通用的RPC框架。gRPC中采用的是HTTP2协议谷歌的产品gRPC默认使用protoBuf2.组成:类似rpc3.执行过程:类似rpc安装GRPC + protoc-gen-go使用以下命令安装protoc-gen-go插件,它用于将protocol buffer文件生成Go代码这里不做过多介绍,网上有很多教程。go get -u google.golang.org/grpc go get -u github.com/golang/protobuf/protoc-gen-go安装完成,检查是否安装成功。实际操作:我们接下来定义一个user服务,和一个article服务,然后用gin服务同时来连接两个服务,调用不同数据返回给接口。我们先定义两个proto文件,然后生成,创建服务端,然后用客户端连接并获取数据。这是大概思路。下面我们来简单实现一下代码:1、定义proto文件并生成定义user.protosyntax = "proto3"; package userBuf; option go_package = "./userBuf"; message UserAddReq { string name = 1; int64 age = 2; string sex = 3; } message UserAddResp { string msg = 1; string code = 2; } service UserService { rpc UserAdd (UserAddReq) returns (UserAddResp); }定义article.protosyntax = "proto3"; package articleBuf; option go_package = "./articleBuf"; message ArticleAddReq { string name = 1; int64 age = 2; string sex = 3; } message ArticleAddResp { string msg = 1; string code = 2; } service ArticleService { rpc ArticleAdd (ArticleAddReq) returns (ArticleAddResp); }生成protoBuf文件,命令:protoc --go_out=. --go-grpc_out=. user.proto protoc --go_out=. --go-grpc_out=. article.proto2、创建grpc服务端user.gopackage main import ( "context" "fmt" "gprc_demo/user/userBuf" "net" "google.golang.org/grpc" ) type UserServer struct { userBuf.UnimplementedUserServiceServer } func (s UserServer) UserAdd(ctx context.Context, in *userBuf.UserAddReq) (*userBuf.UserAddResp, error) { var aa userBuf.UserAddResp aa.Msg = in.Name aa.Code = "200" fmt.Println("user 进来了") return &aa, nil } func main() { // 创建grpc服务 s := grpc.NewServer() userBuf.RegisterUserServiceServer(s, UserServer{}) listen, err := net.Listen("tcp", "127.0.0.1:6001") fmt.Println("监听6001端口。。。") if err != nil { fmt.Println("网络错误") } s.Serve(listen) // grpc服务启动 } article.gopackage main import ( "context" "fmt" "gprc_demo/article/articleBuf" "net" "google.golang.org/grpc" ) type ArticleServer struct { articleBuf.UnimplementedArticleServiceServer } func (s ArticleServer) ArticleAdd(ctx context.Context, in *articleBuf.ArticleAddReq) (*articleBuf.ArticleAddResp, error) { var aa articleBuf.ArticleAddResp aa.Msg = in.Name aa.Code = "200" fmt.Println("article 进来了") return &aa, nil } func main() { // 创建grpc服务 s := grpc.NewServer() articleBuf.RegisterArticleServiceServer(s, ArticleServer{}) listen, err := net.Listen("tcp", "127.0.0.1:6002") fmt.Println("监听6002端口。。。") if err != nil { fmt.Println("网络错误") } s.Serve(listen) // grpc服务启动 } 3、创建Gin服务端,调用数据package main import ( "gprc_demo/article/articleBuf" "gprc_demo/user/userBuf" "net/http" "github.com/gin-gonic/gin" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func main() { r := gin.Default() // 创建grpc连接,grpc客户端 connUser, err := grpc.Dial("127.0.0.1:6001", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { panic(err) } defer connUser.Close() userClient := userBuf.NewUserServiceClient(connUser) connArticle, err := grpc.Dial("127.0.0.1:6002", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { panic(err) } defer connArticle.Close() articleClient := articleBuf.NewArticleServiceClient(connArticle) // 定义一个Gin路由 r.POST("/hello", func(c *gin.Context) { name := "你好" name1 := "小明" // 调用gRPC服务 userReq := &userBuf.UserAddReq{Name: name} userResp, userErr := userClient.UserAdd(c, userReq) articleRreq := &articleBuf.ArticleAddReq{Name: name1} articleResp, articleErr := articleClient.ArticleAdd(c, articleRreq) if userErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": userErr}) return } if articleErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error2": articleErr}) return } c.JSON(http.StatusOK, gin.H{ "code": 200, "message": userResp.Msg + "," + articleResp.Msg, }) }) // 启动Gin服务器 if err := r.Run(":6000"); err != nil { panic(err) } } 最终我们的文件结构如图:上面我们同时连接user服务和article服务,分别传入“你好”,“小明”,两个数据,然后调用服务返回的数据返回给接口,我们看下效果。好了,数据测试没问题,当然我们用的是ip+端口直连,我们也可以用edcd,consul等工具来管理服务。
查看详情点赞1评论收藏浏览1722024-04-23 17:39:56GBASESTUDIO数据库管理工具下载地址
点赞1评论收藏浏览942024-04-08 11:41:38Gin框架系列教程(8)- Gin图片&文件上传(单文件)
今天我们用Gin框架做一个单图片上传功能,多图上传同理就行。之后有时间可以测试一下。直接上代码:后端文件上传工具package utils import ( "errors" "os" "path" "strconv" "time" "github.com/gin-gonic/gin" ) func UploadFile(c *gin.Context) (string, error) { // 读取file文件 file, err := c.FormFile("file") if err != nil { return "", nil } //文件大小校验,限制300KB大小 size := file.Size if size/1024 > 300 { return "", errors.New("超过允许上传文件大小,最大300KB") } // 文件后缀校验 extName := path.Ext(file.Filename) allowExtMap := map[string]bool{ ".jpg": true, ".png": true, ".gif": true, ".jpeg": true, } if _, ok := allowExtMap[extName]; !ok { return "", errors.New("不允许上传此类文件") } // 创建图片文件目录 day := DateStr() dir := "./public/upload/" + day err = os.MkdirAll(dir, 0666) if err != nil { return "", err } //执行上传,毫秒级时间名称 now := time.Now() unixNano := now.UnixNano() / int64(time.Millisecond) createFileName := strconv.FormatInt(unixNano, 10) + extName pathDir := path.Join(dir, createFileName) c.SaveUploadedFile(file, pathDir) outerPath := "/" + pathDir return outerPath, nil } // 组装年月日字符串当做文件路径 func DateStr() string { template := "20060102" return time.Now().Format(template) } 后端调用:// @Summary 文件上传 // @Description 用于文件上传 // @Tags UploadFile // @Accept json // @Produce json // @Success 200 {string} string "{"success":200,"msg":"上传成功"}" // @Failure 500 {string} UploadFile 失败后返回值 // @Router /api/v1/upload [post] func UploadFile(c *gin.Context) { path, err := utils.UploadFile(c) if err != nil { response.Error(c, http.StatusInternalServerError, err, "上传失败") } else { response.Success(c, path, "上传成功") } }好了,测试图片上传成功。
查看详情点赞1评论收藏浏览2492024-03-25 15:19:52JS屏蔽鼠标右键审查元素,禁用键盘F12功能,或者其他键盘功能(详情如下)
屏蔽鼠标右键点击审查元素HTML方法:body标签上面加属性<body ondragstart="window.event.returnValue=false" oncontextmenu="window.event.returnValue=false" onselectstart="event.returnValue=false"> </body>JS方法:document.onkeydown = function(){ if(window.event && window.event.keyCode == 123) { event.keyCode=0; event.returnValue=false; } if(window.event && window.event.keyCode == 13) { window.event.keyCode = 505; } if(window.event && window.event.keyCode == 8) { window.event.returnValue=false; } }禁用键盘F12事件,及其其他键盘事件(F1-F12,Ctrl+s,Ctrl+P)等:document.onkeydown = function () { if (window.event && window.event.keyCode == 123) { event.keyCode = 0; event.returnValue = false; } if (window.event && window.event.keyCode == 13) { window.event.keyCode = 505; } // 禁止通过F12打开控制台 let e = event || window.event || arguments.callee.caller.arguments[0]; //禁用F1-F12 if (event.keyCode === 112) { event.preventDefault() event.returnValue = false } if (event.keyCode === 113) { event.preventDefault() event.returnValue = false } if (event.keyCode === 114) { event.preventDefault() event.returnValue = false } if (event.keyCode === 115) { event.preventDefault() event.returnValue = false } if (event.keyCode === 116) { // event.preventDefault() // event.returnValue = false } if (event.keyCode === 117) { event.preventDefault() event.returnValue = false } if (event.keyCode === 118) { event.preventDefault() event.returnValue = false } if (event.keyCode === 119) { event.preventDefault() event.returnValue = false } if (event.keyCode === 120) { event.preventDefault() event.returnValue = false } if (event.keyCode === 121) { event.preventDefault() event.returnValue = false } if (event.keyCode === 122) { // event.preventDefault() // event.returnValue = false } if (event.keyCode === 123) { event.preventDefault() event.returnValue = false } }
查看详情点赞1评论收藏浏览1182024-02-29 14:27:41JS 实现网页复内容复制,携带作者信息
有时候我们站长想用户复制我们的页面内容,携带作者信息。下面是我们的JS实现代码:// 内容复制功能取消 $(function () { var Sys = {}; var ua = navigator.userAgent.toLowerCase(); if (window.ActiveXObject) { document.body.oncopy = function () { event.returnValue = false; var t = document.selection.createRange().text; var s = "-----------------------尊重作者,转载请标明出处:(alingfeng.cn),原文链接:" + location.href; clipboardData.setData("Text", t + "" + s); }; } else { function addLink() { var body_element = document.getElementsByTagName("body")[0]; var selection; selection = window.getSelection(); var pagelink = "-----------------------尊重作者,转载请标明出处:(alingfeng.cn),原文链接:" + location.href; var copytext = selection + pagelink; var newdiv = document.createElement("div"); newdiv.style.position = "absolute"; newdiv.style.left = "-99999px"; body_element.appendChild(newdiv); newdiv.innerHTML = copytext; selection.selectAllChildren(newdiv); window.setTimeout(function () { body_element.removeChild(newdiv); }, 0); } document.oncopy = addLink; } });这样我们复制内容的时候就会携带设置的内容了。
查看详情点赞评论收藏浏览962024-02-29 13:51:41Gin框架系列教程(7)- Gin集成JWT权限认证
不管是我们什么后台,系统,都会接触了用户登录Token认证,今天我们需要gin集成jwt功能。如今有很多将身份验证内置到API中的方法 -JSON Web令牌只是其中之一。JSON Web令牌(JWT)作为令牌系统而不是在每次请求时都发送用户名和密码,因此比其他方法(如基本身份验证)具有固有的优势。要了解更多信息,请直接进入jwt.io上的介绍,然后再直接学习。通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输,可以避免被别人篡改,以及在用户认证授权方面相对于传统Session减小了开销。接下来我们使用jwt:直接上代码package utils import ( "errors" "fmt" "time" "github.com/dgrijalva/jwt-go/v4" ) var JwtSecret string = "xxxxxxxxxxxxxx" type Users struct { Account string `json:"account"` Password string `json:"password"` } type CustomClaims struct { Users jwt.StandardClaims } var MySecret = []byte(JwtSecret) // 创建 Token func GenToken(user Users) (string, error) { claim := CustomClaims{ user, jwt.StandardClaims{ ExpiresAt: jwt.At(time.Now().Add(time.Minute * 120)), //2小时后过期 Issuer: "xxxxxxxx", //签发人 }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) return token.SignedString(MySecret) } // 解析 token func ParseToken(tokenStr string) (*CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { fmt.Println(" token parse err:", err) return nil, err } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") } // 刷新 Token func RefreshToken(tokenStr string) (string, error) { jwt.TimeFunc = func() time.Time { return time.Unix(0, 0) } token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { return "", err } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { jwt.TimeFunc = time.Now claims.StandardClaims.ExpiresAt = jwt.At(time.Now().Add(time.Minute * 120)) return GenToken(claims.Users) } return "", errors.New("Cloudn't handle this token") } 上面这段是我们用户登录成功创建token。然后我们需要用一个jwt中间件来验证用户权限:func Jwt() gin.HandlerFunc { return func(c *gin.Context) { // var users utils.Users // users.Account = "admin" // users.Password = "12346" // str, err1 := utils.GenToken(users) // fmt.Println("str", str) // fmt.Println("err1", err1) tokenString := c.GetHeader("Authorization") fmt.Println("getHeaderToken", tokenString) if tokenString == "" { c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "权限不足"}) c.Abort() return } claims, err := utils.ParseToken(tokenString) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "err": err, "msg": "权限不足"}) c.Abort() return } fmt.Println(claims) c.Next() } }好了,我们来测试一下,结果是没问题的。
查看详情点赞1评论收藏1浏览2312024-02-24 11:12:44