Go语言Web入门之浅谈Gin框架
Gin框架
- Gin简介
- 第一个Gin示例Helloworld
- RESTful API
- Gin返回数据的几种格式
- Gin 获取参数
- HTTP重定向
- Gin路由&路由组
- Gin框架当中的中间件
Gin简介
Gin 是一个用 Go (Golang) 编写的 web 框架。它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter,速度提高了近 40 倍。Gin在GitHub上已经有47k的star,它和Golang的语法一样简洁明了,使得初学者得以迅速入门。只需要在终端上输入以下命令就可以将使用gin框架了
go get -u github.com/gin-gonic/gin
第一个Gin示例Helloworld
import ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()//默认引擎router.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello World")})router.Run(":8000")//启动服务
}
下面解释一下这个上面这份代码的意思
- router:=gin.Default():这是默认的服务器。使用gin的Default方法创建一个路由Handler
- 然后通过Http方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到了gin.Context的上下文环境中。
- 最后启动路由的Run方法监听端口。还可以用http.ListenAndServe(“:8080”, router),或者自定义Http服务器配置。
启动方式有如下两种:
// 启动方式一
router.Run(":8000")
// 启动方式二
http.ListenAndServe(":8000", router)
注意这个 :8080的意思其实是这个127.0.0.1:8080这一点大家需要注意了。当然我们也可以将这个**“:8080"改为这个"0.0.0.0:8080”**.
package mainimport ("github.com/gin-gonic/gin""net/http"
)func Index(context *gin.Context) {context.String(http.StatusOK, "Hello ksy!")
}
func main() {// 创建一个默认的路由router := gin.Default()// 绑定路由规则和路由函数,访问/index的路由,将由对应的函数去处理router.GET("/index", Index)// 启动监听,gin会把web服务运行在本机的0.0.0.0:8080端口上router.Run("0.0.0.0:8080")// 用原生http服务的方式, router.Run本质就是http.ListenAndServe的进一步封装http.ListenAndServe(":8080", router)
}
在这里博主在说一下这个Index这个函数的参数是固定写死的就是确定的他的类型必须是这个gin.Context。上面的这个http.StatusOk代表的是这个状态码在这里代表的是这个200*
下面我们将上面这个程序跑起来,然后打开浏览器访问/index这个路径我看一下这个效果
RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
- GET用来获取资源
- POST用来新建资源
- PUT用来更新资源
- DELETE用来删除资源。
Gin框架支持开发RESTful API的开发。
func main() {r := gin.Default()r.GET("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "GET",})})r.POST("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "POST",})})r.PUT("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "PUT",})})r.DELETE("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "DELETE",})})
}
其中这个c.JSON代表的是返回的是一个json格式的数据,而这个gin.H其实是一个这个Map。我们可以查看其定义。
// H is a shortcut for map[string]interface{}
type H map[string]any
当然我们也可以不使用这个gin框架给我提供的使用我们自己定义的。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/json", func(c *gin.Context) {//方法1使用map,方法二使用结构体data := map[string]interface{}{"name": "小王子","message": "ksy","age": 18,}var msg struct { //注意这个结构体里面的字段必须大写,因为golang当中包的访问性Name string `json:"name"`Age int `json:"age"`Message string `json:"message"`}msg.Message = "ni hao ksy"msg.Age = 20msg.Name = "ksy"//获取使用gin.Hc.JSON(http.StatusOK, data)c.JSON(http.StatusOK, msg)})r.Run(":9090")
}
在这里我们需要注意的是这个结构体在进行序列化的时候这个首字母需要大写,赋值会序列化失败这是因为这个golang包的可见性决定的。
那会不会有这样一个场景了,就是某个路径Get,Post等方法都可以访问这个路径时我们又该如何来写了,或者是这个当用户返回这个服务器上的路径不存在时我们不希望返回这个404NotFound,我们希望返回我们这个我们自定义的东西时又该如何写了。gin框架已经提供给了我们这个处理的函数了下面我们一起来看看吧。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//可以接受任何请求r.Any("usr", func(c *gin.Context) {switch c.Request.Method {//判断到底是那种请求case http.MethodGet:c.JSON(http.StatusOK, gin.H{"method": "get"})case http.MethodPost:c.JSON(http.StatusOK, gin.H{"method": "post"})}})//定义没有时执行的函数也就是用户访问的路径不存在时会调用这个函数r.NoRoute(func(c *gin.Context) {c.JSON(http.StatusNotFound, gin.H{"msg": "ksy.com",})})r.Run(":8081")
}
我们可以使用这个Any方法进而在其回调方法当中获取其请求的方试通过一个简单的switch,case进行不同的处理将数据返回。还有就是这个NoRoute方法就是当用户这个输入的路径不存在时会执行这个回调。下面我们来演示一下这个效果。
当这个请求路径不存在时。
Gin返回数据的几种格式
在gin框架当中我们可以指定这个返回数据的格式。在前面的例子当中我们这个String,json类型的数据我们都返回了下面我们一起看看这个剩下的格式吧
1.返回字符串类型的数据
router.GET("/index", func(c *gin.Context) {c.String(http.StatusOK, "hello world")
})
2.返回json类型数据
c.JSON(http.StatusOK, gin.H{"method": "get"})
3.返回xml数据格式
router.GET("/xml", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
})
4.返回yaml数据格式
router.GET("/yaml", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
})
5.返回html数据格式
先要使用 **LoadHTMLGlob()或者LoadHTMLFiles()**方法来加载模板文件。
router.LoadHTMLGlob("gin框架/templates/*")
//router.LoadHTMLFiles("templates/index.html", "templates/index2.html")
//定义路由
router.GET("/html", func(c *gin.Context) {//根据完整文件名渲染模板,并传递参数c.HTML(http.StatusOK, "index.html", gin.H{"title": "hello world",})
})
在模板中使用这个title,需要使用{{ .title }}
不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "posts/index.html", gin.H{"title": "Posts",})c.HTML(http.StatusOK, "users/index.html", gin.H{"title": "Users",})})
6.返回文件响应
// 在golang总,没有相对文件的路径,它只有相对项目的路径
// 网页请求这个静态目录的前缀, 第二个参数是一个目录,注意,前缀不要重复
router.StaticFS("/static", http.Dir("static/static"))
// 配置单个文件, 网页请求的路由,文件的路径
router.StaticFile("/titian.png", "static/titian.png")
Gin 获取参数
1.查询参数 Query也就是这个获取querystring参数,querystring指的是URL中?后面携带的参数,例如:/user/search?username=匡思源&address=沙河。 获取请求的querystring参数的方法如下:
package mainimport ("github.com/gin-gonic/gin""net/http"
)//queryString 通常用在Get方法func main() {r := gin.Default()r.GET("/get", func(c *gin.Context) {//Get请求URL?后面是querystring参数 key=val形势多个用&连接//获取json那边发请求携带的queryString//Name := c.Query("name")//Name:=c.DefaultQuery("name","ddd")//如果能查到用查到的否则用设置的默认值Name, _ := c.GetQuery("name") //返回值bool取不到返回falsec.JSON(http.StatusOK, gin.H{"name": Name,})})r.Run(":8089")
}
在这里获取这个这个请求参数querystring,这个gin提供了多种函数:
- GetQuery:如果参数不存在第二个返回值为false
- DefaultQuery:如果参数不存在返回默认设置的值,存在用传进来的值
- Query:获取参数不存在为""
下面我们将这个程序运行起来看一看这个效果如何
剩下的各位铁子可以自行下去演示这个效果,在这里博主就不一一演示这个效果了。
2.获取form参数
当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:
package mainimport ("github.com/gin-gonic/gin""net/http"
)/*获取form表单的提交参数
*/func main() {r := gin.Default()r.LoadHTMLFiles("./login.html") //加载某版文件r.GET("/login", func(c *gin.Context) {c.HTML(http.StatusOK, "login.html", nil)})r.POST("/login", func(c *gin.Context) {//获取form表单提交的数据// username:=c.PostForm("username")// password:=c.PostForm("password")password := c.DefaultPostForm("password", "****")username := c.DefaultPostForm("username", "somebody")//c.GetPostForm("username")两个返回值有一个为这个是否存在c.JSON(http.StatusOK, gin.H{"username": username,"password": password,})})r.Run("127.0.0.1:9091")
}
和这个QueryString是一样的这个gin提供了多种获取函数:
- PostForm:获取用户传过来的表单数据
- DefaultPostForm:如果用户没传使用设置的默认值
- GetPostForm:第二个返回值可以判断用户是否传递了这个参数
下面我们可以使用这个PostMan来进行这个测试
3.获取json数据
当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:
package mainimport ("encoding/json""github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.POST("/json", func(c *gin.Context) {// 注意:下面为了举例子方便,暂时忽略了错误处理b, _ := c.GetRawData() // 从c.Request.Body读取请求数据// 定义map或结构体var m map[string]interface{}// 反序列化_ = json.Unmarshal(b, &m)c.JSON(http.StatusOK, m)})r.Run(":8081")
}
在这里我们同样的使用这个Postman来进行测试
4.获取获取path参数
请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。
package mainimport ("github.com/gin-gonic/gin""net/http"
)//获取请求path(URI)参数返回的都是字符串类型
func main() {r := gin.Default()r.GET("/:name/:age", func(c *gin.Context) {//获取路径参数name := c.Param("name")age := c.Param("age")c.JSON(http.StatusOK, gin.H{"name": name,"age": age,})})r.GET("/blog/:year/:month", func(c *gin.Context) {year := c.Param("year")month := c.Param("month")c.JSON(http.StatusOK, gin.H{"year": year,"month": month,})})r.Run(":9092")
}
这个在这里就不演示了,非常的简单各位铁子可以自行演示即可。
5.参数绑定(非常重要)
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
// Binding from JSON
type Login struct {User string `form:"user" json:"user" binding:"required"`Password string `form:"password" json:"password" binding:"required"`
}func main() {router := gin.Default()// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})router.POST("/loginJSON", func(c *gin.Context) {var login Loginif err := c.ShouldBind(&login); err == nil {fmt.Printf("login info:%#v\n", login)c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 绑定form表单示例 (user=q1mi&password=123456)router.POST("/loginForm", func(c *gin.Context) {var login Login// ShouldBind()会根据请求的Content-Type自行选择绑定器if err := c.ShouldBind(&login); err == nil {c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)router.GET("/loginForm", func(c *gin.Context) {var login Login// ShouldBind()会根据请求的Content-Type自行选择绑定器if err := c.ShouldBind(&login); err == nil {c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// Listen and serve on 0.0.0.0:8080router.Run(":8080")
}
ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
如果是 GET 请求,只使用 Form 绑定引擎(query)。
如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)功能非常的强大各位铁子需要这个重点看一下这个.
HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。下面我们一起来看看这个如何进行重定向
package mainimport ("github.com/gin-gonic/gin""net/http"
)//重定向未登录进行重定向
func main() {r := gin.Default()r.GET("/a", func(c *gin.Context) {//重定向//c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") //永久重定向c.Request.URL.Path = "/b"r.HandleContext(c) //继续处理})//路由重定向r.GET("/b", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "b",})})r.Run(":9093")
}
注意上面注释那个是永久重定向当用户第二次请求时不会再这个返回它了,而是直接访问重定向的网址。而下面那个时使用这个路由重定向,使用HandleContext。
Gin路由&路由组
我们之前的例子使用的是这个普通路由,就是类似于这个下面这样
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
此外,还有一个可以匹配所有请求方法的Any方法如下:
r.Any("/test", func(c *gin.Context) {...})
之前也已经写过了,还有一个为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
r.NoRoute(func(c *gin.Context) {c.HTML(http.StatusNotFound, "views/404.html", nil)})
下面我们看看这个路由组吧,有时候这个路由都有这个公共的前缀,但是又有很多个函数此时我们就可以将其抽离出来。下面我们看看这个路由组的写法吧
func main() {r := gin.Default()userGroup := r.Group("/user"){userGroup.GET("/index", func(c *gin.Context) {...})userGroup.GET("/login", func(c *gin.Context) {...})userGroup.POST("/login", func(c *gin.Context) {...})}shopGroup := r.Group("/shop"){shopGroup.GET("/index", func(c *gin.Context) {...})shopGroup.GET("/cart", func(c *gin.Context) {...})shopGroup.POST("/checkout", func(c *gin.Context) {...})}r.Run(":8900")
}
当然这个路由组也支持这个嵌套
shopGroup := r.Group("/shop"){shopGroup.GET("/index", func(c *gin.Context) {...})shopGroup.GET("/cart", func(c *gin.Context) {...})shopGroup.POST("/checkout", func(c *gin.Context) {...})// 嵌套路由组xx := shopGroup.Group("xx")xx.GET("/oo", func(c *gin.Context) {...})}
通常我们将路由分组用在划分业务逻辑或划分API版本时。
Gin框架当中的中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
Gin中的中间件必须是一个gin.HandlerFunc类型。 这点非常的重要这个是固定不变的
下面我们就写一个小小的Demo来看看这个中间件的写法
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)//定义一个中间件
func httpfunc(c *gin.Context) {start := time.Now()c.Next() //调用后续的处理函数//c.Abort()//阻止调用后面的函数cost := time.Since(start)fmt.Printf("一共花费了%dms", int(cost))
}
func main() {r := gin.Default()r.Use(httpfunc) //全局注册中间件函数httpfunc,所有的函数都会只会这个全局的中间件函数r.GET("/index", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"msg": "index",})})r.Run(":9098")
}
首先这个客户段的请求进来会执行这个httpFunc函数,这个是安装这个顺序决定的,c.Next()代表的是这个继续执行,而如果我们使用这个c.Abort()那么下面这个函数将不会执行也就是说他的意思就是停止的意思。
Demo2
我们有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。
type bodyLogWriter struct {gin.ResponseWriter // 嵌入gin框架ResponseWriterbody *bytes.Buffer // 我们记录用的response
}// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {w.body.Write(b) // 我们记录一份return w.ResponseWriter.Write(b) // 真正写入响应
}// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}c.Writer = blw // 使用我们自定义的类型替换默认的c.Next() // 执行业务逻辑fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
当然这个我们还可以为这个路由组定义这个中间件
shopGroup := r.Group("/shop", StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}
当然还有另外一种写法
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}
Demo3 中间件传递数据
使用Set设置一个key-value,在后续中间件中使用Get接收数据。对应代码如下
package mainimport ("fmt""github.com/gin-gonic/gin"
)type User struct {Name stringAge int
}func m10(c *gin.Context) {fmt.Println("m1 ...in")c.Set("name", User{"ranran", 21})c.Next()fmt.Println("m1 ...out")
}func main() {router := gin.Default()router.Use(m10)router.GET("/", func(c *gin.Context) {fmt.Println("index ...in")name, _ := c.Get("name")user := name.(User)fmt.Println(user.Name, user.Age)c.JSON(200, gin.H{"msg": "index"})})router.Run(":8080")}
value的类型是any类型,所有我们可以用它传任意类型,在接收的时候做好断言即可
中间件的使用还是这个非常好的,当前端发送请求过来之后我们可以定义中间件先检查这个参数的合法性如果不合法我们可以直接Abort停止,如果合法我们可以执行这个配置文件的初始化最后再这个到具体的业务逻辑。
最后说一下这个中间件再使用过程当中的几个注意事项:
1.gin.Default()默认使用了Logger和Recovery中间件,其中:Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
2.gin中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。因为传递指针会修改上一层的内容导致意想不到的事情发生
相关文章:

Go语言Web入门之浅谈Gin框架
Gin框架Gin简介第一个Gin示例HelloworldRESTful APIGin返回数据的几种格式Gin 获取参数HTTP重定向Gin路由&路由组Gin框架当中的中间件Gin简介 Gin 是一个用 Go (Golang) 编写的 web 框架。它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter&a…...

《MySQL学习》 MySQL优化器选择如何选择索引
一.优化器的选择逻辑 建表语句 CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,PRIMARY KEY (id),KEY a (a),KEY b (b) ) ENGINEInnoDB;往表中插入10W条数据 delimiter ;; create procedure idata() begindeclare i in…...

uniapp 悬浮窗(应用内、无需授权) Ba-FloatWindow2
简介(下载地址) Ba-FloatWindow2 是一款应用内并且无需授权的悬浮窗插件。支持多种拖动;自定义位置、大小;支持动态修改。 支持自动定义起始位置支持自定义悬浮窗大小支持贴边显示支持多种拖动方效果:不可拖动、任意…...

MMKV与mmap:全方位解析
概述 MMKV 是基于 mmap 内存映射的移动端通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。近期已移植…...
【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度)
【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度) 【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度)【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度ÿ…...

一起学 pixijs(3):Sprite
大家好,我是前端西瓜哥。今天来学习 pixijs 的 Sprite。 Sprite pixijs 的 Sprite 类用于将一些纹理(Texture)渲染到屏幕上。 Sprite 直译为 “精灵”,是游戏开发中常见的术语,就是将一个角色的多个动作放到一个图片…...
深入讲解Kubernetes架构-垃圾收集
垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。 垃圾收集允许系统清理如下资源:终止的 Pod已完成的 Job不再存在属主引用的对象未使用的容器和容器镜像动态制备的、StorageClass 回收策略为 Delete 的 PV 卷…...

Flink03: 集群安装部署
Flink支持多种安装部署方式 StandaloneON YARNMesos、Kubernetes、AWS… 这些安装方式我们主要讲一下standalone和on yarn。 如果是一个独立环境的话,可能会用到standalone集群模式。 在生产环境下一般还是用on yarn 这种模式比较多,因为这样可以综合利…...

OCR项目实战(一):手写汉语拼音识别(Pytorch版)
✨写在前面:强烈推荐给大家一个优秀的人工智能学习网站,内容包括人工智能基础、机器学习、深度学习神经网络等,详细介绍各部分概念及实战教程,非常适合人工智能领域初学者及研究者学习。➡️点击跳转到网站。 📝OCR专栏…...

【js】export default也在影响项目性能呢
这里写目录标题介绍先说结论分析解决介绍 无意间看到一个关于export与exprot default对比的话题, 于是对二者关于性能方面,有了想法,二者的区别,仅仅是在于写法吗? 于是,有了下面的测试。 先说结论 太长…...
《软件安全》 彭国军 阅读总结
对于本书,小编本意是对其讲述的内容,分点进行笔记的整理,后来学习以后,发现,这本书应该不算是一本技术提升类的书籍,更像是一本领域拓展和知识科普类书籍,所讲知识广泛,但是较少实践…...
深入讲解Kubernetes架构-节点与控制面之间的通信
本文列举控制面节点(确切说是 API 服务器)和 Kubernetes 集群之间的通信路径。 目的是为了让用户能够自定义他们的安装,以实现对网络配置的加固, 使得集群能够在不可信的网络上(或者在一个云服务商完全公开的 IP 上&am…...

120个IT冷知识,看完就不愁做选择题了
目录 IT冷知识 01-10 1.冰淇淋馅料 2.蠕虫起源 3.Linux和红帽子 4."间谍软件"诞生 5.游戏主机的灵魂 6.Linux之父 7.NetBSD的口号 8.安卓起源 9.不是第七代的 Win 7 10.域名金字塔 11~20 11.神奇魔盒 12. 第一个Ubuntu 正式版本 13.巾帼英雄 14.密码…...

Java之动态规划之机器人移动
目录 0.动态规划问题 一.不同路径 1.题目描述 2.问题分析 3.代码实现 二.不同路径 II 1.题目描述 2.问题分析 3.代码实现 三.机器人双向走路 1.题目描述 2.问题分析 3.代码实现 0.动态规划问题 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问…...

seata源码-全局事务提交 服务端源码
前面的博客中,我们介绍了,发起全局事务时,是如何进行全局事务提交的,这篇博客,主要记录,在seata分布式事务中,全局事务提交的时候,服务端是如何进行处理的 发起全局事务提交操作 事…...

C++ 模板
文章目录一、泛型编程二、 函数模板三、类模板一、泛型编程 泛型编程:编写与类型无关的通用代码,代码复用的一种方法 在 C 中,我们可以通过函数重载实现通用的交换函数 Swap ,但是有一些缺点 重载函数只有类型不同,…...

JWT安全漏洞以及常见攻击方式
前言 随着web应用的日渐复杂化,某些场景下,仅使用Cookie、Session等常见的身份鉴别方式无法满足业务的需要,JWT也就应运而生,JWT可以有效的解决分布式场景下的身份鉴别问题,并且会规避掉一些安全问题,如CO…...
华为OD机试题 - 最小施肥机能效(JavaScript)
最近更新的博客 华为OD机试题 - 任务总执行时长(JavaScript) 华为OD机试题 - 开放日活动(JavaScript) 华为OD机试 - 最近的点 | 备考思路,刷题要点,答疑 【新解法】 华为OD机试题 - 最小步骤数(JavaScript) 华为OD机试题 - 任务混部(JavaScript) 华为OD机试题 - N 进…...

Python(1)变量的命名规则
目录 1.变量的命名原则 3.内置函数尽量不要做变量 4.删除变量和垃圾回收机制 5.结语 参考资料 1.变量的命名原则 ①由英文字母、_(下划线)、或中文开头 ②变量名称只能由英文字母、数字、下画线或中文字所组成。 ③英文字母大小写不相同 实例: 爱_aiA1 print(…...

Shiro1.9学习笔记
文章目录一、Shiro概述1、Shiro简介1.1 介绍1.2 Shiro特点2、Shiro与SpringSecurity的对比3、Shiro基本功能4、Shiro原理4.1 Shiro 架构(外部)4.2 shiro架构(内部)二、Shiro基本使用1、环境准备2、登录认证2.1 登录认证概念2.2 登录认证基本流程2.3 登录认证实例2.4 身份认证源…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...