Gin框架系列教程(4)- Router 路由封装
今天我们研究一下gin中的路由应该怎么配置:首先我们先测试一个简单的路由,也就是默认路由:package main import ( "github.com/gin-gonic/gin" ) func main(c *gin.Context) { r := gin.Default() r.GET("/", func(c *gin.Context) { c.String(200, "hello world") }) r.run('9000') 这个就是首页路由,很简短。那么gin框架中怎么封装路由呢,就是把多个路由按照一定规则进行拆分封装。下面代码展示:一、主入口文件初始化路由r := gin.Default() // 路由初始化 routers.Routers(r)二、新建routers文件夹,以及user,member路由文件夹,路由分组,三个示例文件(routers.go,member.go,user.go)routers.go//router.go package routers import ( "gin_alingfeng/routes/memberRouter" "gin_alingfeng/routes/userRouter" "github.com/gin-gonic/gin" ) func Routers(r *gin.Engine) { //api分组路由 api := r.Group("/api") memberRouter.MemberApiRouter(api) userRouter.UserApiRouter(api) //直接路由 memberRouter.MemberRouter(r) userRouter.UserRouter(r) }member.gopackage memberRouter import ( "gin_alingfeng/controllers/userController" "gin_alingfeng/middleware" "github.com/gin-gonic/gin" ) func MemberApiRouter(api *gin.RouterGroup) { api.GET("/member", func(c *gin.Context) { c.String(200, "hello world member") }) } func MemberRouter(r *gin.Engine) { r.GET("/member2", middleware.Cors(), middleware.Logs, userController.User) } user.gopackage memberRouter import ( "gin_alingfeng/controllers/userController" "gin_alingfeng/middleware" "github.com/gin-gonic/gin" ) func MemberApiRouter(api *gin.RouterGroup) { api.GET("/member", func(c *gin.Context) { c.String(200, "hello world member") }) } func MemberRouter(r *gin.Engine) { r.GET("/member2", middleware.Cors(), middleware.Logs, userController.User) } 结构如下:
查看详情点赞1评论收藏1浏览2582024-02-06 09:43:03Gin框架系列教程(5)- 项目集成swagger及注解简单应用
Swagger介绍:Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。安装Swagger:从 Go 1.17 开始,go get不推荐使用安装可执行文件。go install可以改用:go install github.com/swaggo/swag/cmd/swag@latest swag init //是否安装成功 swag -v项目使用:导入import ( _ "gin_alingfeng/docs" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" )主程序入口,以及swagger路由:// @host 127.0.0.1:9000 // @schemes http // @Title 接口文档 // @version v1.0 // @description 所有接口的方法,描述,参数,响应,文档,测试,仅供参考,有问题请及时联系管理员。 func main() { r := gin.Default() // 路由初始化 routers.Routers(r) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) // 数据库初始化 models.Init() //启动 r.Run(":9000") }api方法注解:(参数,可以单独写,也可以用结构体一起放进去)// @Summary 新增用户 // @Description 新增用户 // @Tags User // @Accept json // @Param name body string true "姓名" example(不超过50字) // @Param email body string false "邮箱" example(不超过50字) // @Param account body string true "账号" example(不超过50字) // @Param data query modules.User true "这里是介绍" // @Produce json // @Success 200 {string} string "{"success":200,"msg":"新增成功"}" // @Failure 500 {string} Add 失败后返回值 // @Router /api/v1/user/add [post] func Add(c *gin.Context) { c.String(200, "user add") }注意:每次修改了注释都需要重新init一下:swag init //其他用法 swag init -dir ./cmd/ -output docs/api/ --pd true --dir main.go文件所在目录 --output 输出目录 --pd 从依赖关系中解析文件 默认false用法描述:// @Tags 每个API操作的标签列表,以逗号分隔,如分组 // @Summary 该操作的简短摘要,如api名称 // @Router 以空格分隔的路径定义。 格式:path,[httpMethod] // @Accept 请求体接收的格式,值为Mime类型 // @param 请求参数用空格分隔的参数。 param name,param type,data type,is mandatory?,comment,attribute(optional) 1.参数名,2.参数类型,3.参数数据类型,4.是否必须,5.参数描述,6.其他属性 其他属性详解: 最大最小值 minimum(0.01),maximum(100) 最大最小长度 minLength(1),maxLength(200) 枚举值 Enums(A, B),Enums(1, 2),Enums(1.1, 2.2) 示例 example(A) 默认值 default(A)-选填 // @Produce 返回体返回的格式,值为Mime类型 // @Success 以空格分隔的成功响应。 return code,{param type},data type,comment 1.返回状态码,2.返回参数类型,3.返回数据,4.参数描述 // @failure 以空格分隔的故障响应。 return code,{param type},data type,comment 1. 返回状态码,2.返回参数类型,3.返回数据,4.参数描述 // Response 定义业务处理响应类型 type Success_Response struct { Code RespCode `json:"code" example:"0"` Msg string `json:"msg" example:"操作成功"` Data RespData `json:"data" example:""` } // Response 定义业务处理响应类型 type failure_Response struct { Code RespCode `json:"code" example:"1"` Msg string `json:"msg" example:"操作失败"` Data RespData `json:"data example:""` } type类型:json: application/json, x-www-form-urlencoded: application/x-www-form-urlencoded, xml: text/xml, plain: text/plain, html: text/html, mpfd: multipart/form-data, json-api: application/vnd.api+json, json-stream: application/x-json-stream, octet-stream: application/octet-stream, png: image/png, jpeg: image/jpeg, gif: image/gif,数据类型:● string (string)● integer (int, uint, uint32, uint64)● number (float32)● boolean (bool)● user defined struct参数类型:● query● path● header● body● formData最后:访问地址:构建您的应用程序,然后访问http://localhost:8080/swagger/index.html,您将看到您的 Swagger UI。
查看详情点赞1评论收藏浏览872024-02-06 15:50:59Gin框架系列教程(6)- 连接Mysql,集成Gorm库
本文主要是了解一下Gin框架如何链接MySQL并集成Gorm库的。当然ORM库有很多,虽然gorm库被很多人吐槽,但是并不能否认它很强大,很完善,用的人最多。当然你也可以用其他的库。介绍:Object-Relationl Mapping,即对象关系映射,这里的Relationl指的是关系型数据库,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了,Golang写的,GitHub上活跃度很高的orm库。特点:全功能ORM(几乎)关联(包含一个,包含多个,属于,多对多,多种包含)Callbacks(创建/保存/更新/删除/查找之前/之后)预加载(急加载)事务复合主键SQL Builder自动迁移日志可扩展,编写基于GORM回调的插件每个功能都有测试开发人员友好安装:go get gorm.io/driver/mysql go get gorm.io/gorm链接MySQL,迁移数据库,我这里用的是viper解析yaml文档,配置数据库的信息(就直接展示代码):链接并配置:config.yamlmysql: db_username: "root" db_password: "root" db_database: "demo_database" db_host: "127.0.0.1" db_port: "3306" logs: log_level: "debug"base.gopackage models import ( "time" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB var err error func Init() { //读取配置 viper.SetConfigFile("config/config.yaml") errConfi := viper.ReadInConfig() if errConfi != nil { panic(errConfi) } db_username := viper.GetString("mysql.db_username") db_password := viper.GetString("mysql.db_password") db_database := viper.GetString("mysql.db_database") db_host := viper.GetString("mysql.db_host") db_port := viper.GetString("mysql.db_port") DB, err = gorm.Open(mysql.New(mysql.Config{ DSN: db_username + ":" + db_password + "@tcp(" + db_host + ":" + db_port + ")/" + db_database + "?charset=utf8mb4&parseTime=True&loc=Local", // DSN data source name DefaultStringSize: 256, // string 类型字段的默认长度 DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 }), &gorm.Config{}) if err != nil { panic(err) } //迁移数据库,上线之后去掉 DB.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{}, &Article{}, &ArticleCategory{}, &Role{}, &Menus{}) sqlDB, _ := DB.DB() // SetMaxIdleConns 设置空闲连接池中连接的最大数量 sqlDB.SetMaxIdleConns(100) // SetMaxOpenConns 设置打开数据库连接的最大数量。 sqlDB.SetMaxOpenConns(50) // SetConnMaxLifetime 设置了连接可复用的最大时间。 sqlDB.SetConnMaxLifetime(time.Hour) }模型下的一个User结构体,用于迁移数据库等:package models import ( "gorm.io/gorm" ) type User struct { gorm.Model Mame string `json:"name" form:"name" gorm:"column:name;comment:姓名;type:varchar(10);"` //姓名 Account string `json:"account" form:"account" gorm:"column:account;comment:账号;type:varchar(10);"` //账号 Password string `json:"password" form:"password" gorm:"column:password;comment:密码;type:varchar(32);"` //密码 Email string `json:"email" form:"email" gorm:"column:email;comment:邮箱;type:varchar(300);"` //邮箱 Image string `json:"image" form:"image" gorm:"column:image;comment:头像;type:varchar(300);"` //头像 IsEnabled int64 `json:"is_enabled" form:"is_enabled" gorm:"column:is_enabled;comment:是否启用;type:smallint;default:1"` //是否启用 } func (User) TableName() string { return "users" }最后注册进程序主入口中:// 数据库初始化 models.Init()结构如下:
查看详情点赞1评论收藏1浏览2842024-02-07 14:37:18Gin框架系列教程(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:44JS屏蔽鼠标右键审查元素,禁用键盘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:41Gin框架系列教程(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:52GBASESTUDIO数据库管理工具下载地址
点赞1评论收藏浏览942024-04-08 11:41:38Gin框架系列教程(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:56Gin框架系列教程(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:07go-zero微服务框架 proto 案例
以文章管理为例syntax = "proto3"; package article; // protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成 option go_package = "./article"; message IdRequest { string id = 1; } message getArticleByArticleCodeReq { string articleCode = 1; } // 文章 message addArticleReq { string title = 1; int64 sectionId = 2; int64 columnId = 3; int64 memberId = 4; string content = 5; string picUrl = 6; int64 isAdvert = 7; string linkUrl = 8; int64 createTime = 9; int64 updateTime = 10; string articleCode = 11; } message articleResp { int64 code = 1; string msg = 2; } message getArticleListReq { int64 pageSize = 1; int64 page = 2; string title = 3; int64 memberId = 4; int64 sectionId = 5; int64 columnId = 6; int64 isChecked = 7; int64 isAdvert = 8; string column = 9; string type = 10; int64 num = 11; string articleCode = 12; } message articleList { int64 id = 1; string title = 2; string keyword = 3; string description = 4; int64 createTime = 5; int64 updateTime = 6; int64 memberId = 7; int64 sectionId = 8; int64 columnId = 9; int64 commentNum = 10; int64 likeNum = 11; int64 viewNum = 12; int64 collectionNum = 13; int64 isChecked = 14; string content = 15; int64 isAdvert = 16; string linkUrl = 17; string picUrl = 18; int64 weight = 19; int64 mid = 20; string memberCode = 21; string nickName = 22; string image = 23; string articleCode =24; } message getArticleListResp { repeated articleList articleList = 1; int64 code = 2; string msg = 3; int64 total = 4; } message getArticleDetailResp { articleList articleList = 1; int64 code = 2; string msg = 3; } // 评论 message addCommentReq { int64 articleId = 2; int64 reviewer = 3; string content = 4; int64 appraisee = 5; int64 isChecked = 6; int64 likeNum = 7; int64 createTime = 8; int64 updateTime = 9; int64 pid = 10; } message commentResp { int64 code = 1; string msg = 2; } message getCommentListReq { int64 pageSize = 1; int64 page = 2; int64 articleId = 3; int64 isChecked = 4; int64 likeNum = 5; int64 createTime = 6; int64 pid = 7; } message commentList { int64 id = 1; int64 articleId = 2; int64 reviewer = 3; string content = 4; int64 appraisee = 5; int64 isChecked = 6; int64 likeNum = 7; int64 pid = 8; int64 createTime = 9; int64 updateTime = 10; } message getCommentListResp { repeated commentList commentList = 1; int64 code = 2; string msg = 3; int64 total = 4; } service Article { // 根据文章编号获取文章 rpc getArticleByArticleCode(getArticleByArticleCodeReq) returns(getArticleDetailResp); // 添加文章 rpc addArticle(addArticleReq) returns(articleResp); // 更新文章 rpc updateArticle(articleList) returns(articleResp); // 获取文章列表 rpc getArticleList(getArticleListReq) returns(getArticleListResp); // 添加评论 rpc addComment(addCommentReq) returns(commentResp); // 更新评论 rpc updateComment(commentList) returns(commentResp); // 获取评论列表 rpc getCommentList(getCommentListReq) returns(getCommentListResp); }
查看详情点赞评论收藏浏览462023-02-27 18:20:54