当前位置: 首页 > news >正文

2.Gin内容介绍

目录

参考

主要内容

关于Web

创建项目

为什么要用框架

Gin框架介绍

Gin框架安装与使用

安装

第一个Gin示例:

RESTful API

Gin渲染

HTML渲染

自定义模板函数

静态文件处理

使用模板继承

补充文件路径处理

JSON渲染

XML渲染

YMAL渲染

protobuf渲染

获取参数

获取querystring参数

获取form参数

获取path参数

参数绑定

文件上传

单个文件上传

多个文件上传

重定向

HTTP重定向

路由重定向

Gin路由

普通路由

路由组

路由原理

Gin中间件

定义中间件

注册中间件

中间件注意事项

运行多个服务


参考

参考博客和文档:

  • Gin框架介绍及使用
  • Gin中文文档

主要内容

本教程主要从下面几个方面来进行讲解

  • Gin框架基本使用
  • GORM基本使用
  • Web开发项目实战

关于Web

  • Web是基于HTTP协议进行交互的应用网络
  • Web就是通过使用浏览器/APP访问的各种资源

 

一个请求对应一个响应,以淘宝网为例,我们输入一个url,就会返回一个页面

 

创建项目

首先我们使用Goland创建一个Go项目

 创建完成后,打开命令窗口,输入下面的命令,创建一个依赖管理

go mod init gin_demo

然后打开setting页面,勾选这个选项【不勾选会导致go.mod依赖爆红】

我们创建一个main.go文件,然后使用go代码实现一个请求和响应

package mainimport ("fmt""net/http"
)// http.ResponseWriter:代表响应,传递到前端的
// *http.Request:表示请求,从前端传递过来的
func sayHello(w http.ResponseWriter, r *http.Request) {_, _ = fmt.Fprintln(w, "hello Golang!");
}func main() {http.HandleFunc("/hello", sayHello)err := http.ListenAndServe(":9090", nil)if err != nil {fmt.Println("http server failed, err:%v \n", err)return}
}

在浏览器访问如下地址

http://localhost:9090/hello

就能打开我们的hello golang页面了

 

我们可以给文字添加色彩

// http.ResponseWriter:代表响应,传递到前端的
// *http.Request:表示请求,从前端传递过来的
func sayHello(w http.ResponseWriter, r *http.Request) {_, _ = fmt.Fprintln(w, "<h1 style='color:red'>hello Golang!<h1>");
}

然后重启后,在刷新

 

我们还可以把里面的字符串放在一个文件里,我们定义一个 hello.html文件

<html><title>hello golang</title><body><h1 style='color:red'>hello Golang!</h1><h1>hello gin!</h1><img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1600011052622&di=9aeee5de695a40c8d469f0c3980c2d48&imgtype=0&src=http%3A%2F%2Fa4.att.hudong.com%2F22%2F59%2F19300001325156131228593878903.jpg"></body>
</html>

然后修改刚刚的main.go,使用 ioutil解析文件

package mainimport ("fmt""io/ioutil""net/http"
)// http.ResponseWriter:代表响应,传递到前端的
// *http.Request:表示请求,从前端传递过来的
func sayHello(w http.ResponseWriter, r *http.Request) {html, _ := ioutil.ReadFile("./template/hello.html")_, _ = fmt.Fprintln(w, string(html));
}func main() {http.HandleFunc("/hello", sayHello)err := http.ListenAndServe(":9090", nil)if err != nil {fmt.Println("http server failed, err:%v \n", err)return}
}

最后刷新我们的页面,就出来这样的效果了,这就是我们通过golang开发的一个Web页面

 

为什么要用框架

我们通过上面的http包,就能够实现一个web的开发,那为什么还要用gin呢?

其实框架的好处,就是别人帮我们搭建了一个舞台,同时提供了很多现成的轮子,让我们专注于业务的开发,同时让开发效率更高。

Gin框架介绍

Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin

Go世界里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。

Gin框架安装与使用

安装

下载并安装Gin:

go get -u github.com/gin-gonic/gin

第一个Gin示例:

package mainimport ("github.com/gin-gonic/gin"
)func main() {// 创建一个默认的路由引擎r := gin.Default()// GET:请求方式;/hello:请求的路径// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数r.GET("/hello", func(c *gin.Context) {// c.JSON:返回JSON格式的数据c.JSON(200, gin.H{"message": "Hello world!",})})// 启动HTTP服务,默认在0.0.0.0:8080启动服务r.Run()
}

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。

RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

推荐阅读阮一峰 理解RESTful架构

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法URL含义
GET/book查询书籍信息
POST/create_book创建书籍记录
POST/update_book更新书籍信息
POST/delete_book删除书籍信息

同样的需求我们按照RESTful API设计如下:

请求方法URL含义
GET/book查询书籍信息
POST/book创建书籍记录
PUT/book更新书籍信息
DELETE/book删除书籍信息

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",})})// 启动HTTP服务,默认在0.0.0.0:8080启动服务r.Run()
}

开发RESTful API的时候我们通常使用Postman来作为客户端的测试工具。

Gin渲染

HTML渲染

我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

{{define "posts/index.html"}}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>posts/index</title>
</head>
<body>{{.title}}
</body>
</html>
{{end}}
users/index.html文件的内容如下:{{define "users/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>users/index</title>
</head>
<body>{{.title}}
</body>
</html>
{{end}}

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。

func main() {r := gin.Default()r.LoadHTMLGlob("templates/**/*")//r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")r.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "posts/index.html", gin.H{"title": "posts/index",})})r.GET("users/index", func(c *gin.Context) {c.HTML(http.StatusOK, "users/index.html", gin.H{"title": "users/index",})})r.Run(":8080")
}

自定义模板函数

定义一个不转义相应内容的safe模板函数如下:

func main() {router := gin.Default()router.SetFuncMap(template.FuncMap{"safe": func(str string) template.HTML{return template.HTML(str)},})router.LoadHTMLFiles("./index.tmpl")router.GET("/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.tmpl", "<a href='https://liwenzhou.com'>李文周的博客</a>")})router.Run(":8080")
}

index.tmpl中使用定义好的safe模板函数:

<!DOCTYPE html>
<html lang="zh-CN">
<head><title>修改模板引擎的标识符</title>
</head>
<body>
<div>{{ . | safe }}</div>
</body>
</html>

为了让index.tmpl文件有语法显示,我们还需要配置一下

然后我们加入 *.tmpl,保存即可

静态文件处理

当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.Static方法即可。

func main() {r := gin.Default()r.Static("/static", "./static")r.LoadHTMLGlob("templates/**/*")// ...r.Run(":8080")
}

使用模板继承

Gin框架默认都是使用单模板,如果需要使用block template功能,可以通过"github.com/gin-contrib/multitemplate"库实现,具体示例如下:

首先,假设我们项目目录下的templates文件夹下有以下模板文件,其中home.tmplindex.tmpl继承了base.tmpl

templates
├── includes
│   ├── home.tmpl
│   └── index.tmpl
├── layouts
│   └── base.tmpl
└── scripts.tmpl

然后我们定义一个loadTemplates函数如下:

func loadTemplates(templatesDir string) multitemplate.Renderer {r := multitemplate.NewRenderer()layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")if err != nil {panic(err.Error())}includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl")if err != nil {panic(err.Error())}// 为layouts/和includes/目录生成 templates mapfor _, include := range includes {layoutCopy := make([]string, len(layouts))copy(layoutCopy, layouts)files := append(layoutCopy, include)r.AddFromFiles(filepath.Base(include), files...)}return r
}

我们在main函数中

func indexFunc(c *gin.Context){c.HTML(http.StatusOK, "index.tmpl", nil)
}func homeFunc(c *gin.Context){c.HTML(http.StatusOK, "home.tmpl", nil)
}func main(){r := gin.Default()r.HTMLRender = loadTemplates("./templates")r.GET("/index", indexFunc)r.GET("/home", homeFunc)r.Run()
}

补充文件路径处理

关于模板文件和静态文件的路径,我们需要根据公司/项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。

func getCurrentPath() string {if ex, err := os.Executable(); err == nil {return filepath.Dir(ex)}return "./"
}

JSON渲染

func main() {r := gin.Default()// gin.H 是map[string]interface{}的缩写r.GET("/someJSON", func(c *gin.Context) {// 方式一:自己拼接JSONc.JSON(http.StatusOK, gin.H{"message": "Hello world!"})})r.GET("/moreJSON", func(c *gin.Context) {// 方法二:使用结构体var msg struct {Name    string `json:"user"`Message stringAge     int}msg.Name = "小王子"msg.Message = "Hello world!"msg.Age = 18c.JSON(http.StatusOK, msg)})r.Run(":8080")
}

XML渲染

注意需要使用具名的结构体类型。

func main() {r := gin.Default()// gin.H 是map[string]interface{}的缩写r.GET("/someXML", func(c *gin.Context) {// 方式一:自己拼接JSONc.XML(http.StatusOK, gin.H{"message": "Hello world!"})})r.GET("/moreXML", func(c *gin.Context) {// 方法二:使用结构体type MessageRecord struct {Name    stringMessage stringAge     int}var msg MessageRecordmsg.Name = "小王子"msg.Message = "Hello world!"msg.Age = 18c.XML(http.StatusOK, msg)})r.Run(":8080")
}

YMAL渲染

r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})

protobuf渲染

r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具体定义写在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps:  reps,}// 请注意,数据在响应中变为二进制数据// 将输出被 protoexample.Test protobuf 序列化了的数据c.ProtoBuf(http.StatusOK, data)
})

获取参数

获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

func main() {//Default返回一个默认的路由引擎r := gin.Default()r.GET("/user/search", func(c *gin.Context) {// 可以添加默认值username := c.DefaultQuery("username", "小王子")//username := c.Query("username")address := c.Query("address")//输出json结果给调用方c.JSON(http.StatusOK, gin.H{"message":  "ok","username": username,"address":  address,})})r.Run()
}

我们输入对应的URL,就能获取到对应的参数了

http://localhost:9090/web?username=小王子&address=沙河

获取form参数

请求的数据通过form表单来提交,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

func main() {//Default返回一个默认的路由引擎r := gin.Default()r.POST("/user/search", func(c *gin.Context) {// DefaultPostForm取不到值时会返回指定的默认值//username := c.DefaultPostForm("username", "小王子")username := c.PostForm("username")address := c.PostForm("address")//输出json结果给调用方c.JSON(http.StatusOK, gin.H{"message":  "ok","username": username,"address":  address,})})r.Run(":8080")
}

获取path参数

请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

func main() {//Default返回一个默认的路由引擎r := gin.Default()r.GET("/user/search/:username/:address", func(c *gin.Context) {username := c.Param("username")address := c.Param("address")//输出json结果给调用方c.JSON(http.StatusOK, gin.H{"message":  "ok","username": username,"address":  address,})})r.Run(":8080")
}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单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会按照下面的顺序解析请求中的数据完成绑定:

  1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  2. 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Formform-data)。

文件上传

单个文件上传

文件上传前端页面代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head><title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="f1"><input type="submit" value="上传">
</form>
</body>
</html>

后端gin框架部分代码:

func main() {router := gin.Default()// 处理multipart forms提交文件时默认的内存限制是32 MiB// 可以通过下面的方式修改// router.MaxMultipartMemory = 8 << 20  // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// 单个文件file, err := c.FormFile("f1")if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}log.Println(file.Filename)dst := fmt.Sprintf("C:/tmp/%s", file.Filename)// 上传文件到指定的目录c.SaveUploadedFile(file, dst)c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("'%s' uploaded!", file.Filename),})})router.Run()
}

多个文件上传

func main() {router := gin.Default()// 处理multipart forms提交文件时默认的内存限制是32 MiB// 可以通过下面的方式修改// router.MaxMultipartMemory = 8 << 20  // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// Multipart formform, _ := c.MultipartForm()files := form.File["file"]for index, file := range files {log.Println(file.Filename)dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)// 上传文件到指定的目录c.SaveUploadedFile(file, dst)}c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%d files uploaded!", len(files)),})})router.Run()
}

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/test", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

路由重定向,使用HandleContext

r.GET("/test", func(c *gin.Context) {// 指定重定向的URLc.Request.URL.Path = "/test2"r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

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)})

路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

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()
}

路由组也是支持嵌套的,例如:

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框架中的路由使用的是httprouter这个库。

其基本原理就是构造一个路由地址的前缀树。

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值// 调用该请求的剩余处理程序c.Next()// 不调用该请求的剩余处理程序// c.Abort()// 计算耗时cost := time.Since(start)log.Println(cost)}
}

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

 

中间的这个通过 Abort() 可以阻止执行

 

为全局路由注册

func main() {// 新建一个没有任何默认中间件的路由r := gin.New()// 注册一个全局中间件r.Use(StatCost())r.GET("/test", func(c *gin.Context) {name := c.MustGet("name").(string) // 从上下文取值log.Println(name)c.JSON(http.StatusOK, gin.H{"message": "Hello world!",})})r.Run()
}

为某个路由单独注册

// 给/test2路由单独注册中间件(可注册多个)r.GET("/test2", StatCost(), func(c *gin.Context) {name := c.MustGet("name").(string) // 从上下文取值log.Println(name)c.JSON(http.StatusOK, gin.H{"message": "Hello world!",})})

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:shopGroup := r.Group("/shop", StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}
写法2:shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

运行多个服务

我们可以在多个端口启动服务,例如:

package mainimport ("log""net/http""time""github.com/gin-gonic/gin""golang.org/x/sync/errgroup"
)var (g errgroup.Group
)func router01() http.Handler {e := gin.New()e.Use(gin.Recovery())e.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK,gin.H{"code":  http.StatusOK,"error": "Welcome server 01",},)})return e
}func router02() http.Handler {e := gin.New()e.Use(gin.Recovery())e.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK,gin.H{"code":  http.StatusOK,"error": "Welcome server 02",},)})return e
}func main() {server01 := &http.Server{Addr:         ":8080",Handler:      router01(),ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,}server02 := &http.Server{Addr:         ":8081",Handler:      router02(),ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,}// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务g.Go(func() error {return server01.ListenAndServe()})g.Go(func() error {return server02.ListenAndServe()})if err := g.Wait(); err != nil {log.Fatal(err)}
}

相关文章:

2.Gin内容介绍

目录 参考 主要内容 关于Web 创建项目 为什么要用框架 Gin框架介绍 Gin框架安装与使用 安装 第一个Gin示例&#xff1a; RESTful API Gin渲染 HTML渲染 自定义模板函数 静态文件处理 使用模板继承 补充文件路径处理 JSON渲染 XML渲染 YMAL渲染 protobuf渲染…...

python--matplotlib(3)

前言 Matplotlib画图工具的官网地址是 http://matplotlib.org/ Python环境下实现Matlab制图功能的第三方库&#xff0c;需要numpy库的支持&#xff0c;支持用户方便设计出二维、三维数据的图形显示&#xff0c;制作的图形达到出版级的标准。 其他matplotlib文章 python--matpl…...

从源码中探究React中的虚拟DOM

引文 通过本文你将了解到 什么是虚拟DOM&#xff1f;虚拟DOM有什么优势&#xff1f;React的虚拟Dom是如何实现的&#xff1f;React是如何将虚拟Dom转变为真实Dom&#xff1f; 一、概念 虚拟DOM实际上是一种用来模拟DOM结构的javascript对象。当页面变化的时候通过一种算法来…...

容器架构概述

文章目录1. 介绍容器历史2. 描述 Linux 容器架构3. Podman 管理容器1. 介绍容器历史 近年来&#xff0c;容器迅速流行起来。然而&#xff0c;容器背后的技术已经存在了相对较长的时间。2001年&#xff0c;Linux引入了一个名为VServer的项目。VServer 是第一次尝试在高度隔离的…...

掌握MySQL分库分表(四)分库分表中间件Sharding-Jdbc,真实表、逻辑表、绑定表、广播表,常见分片策略

文章目录什么是ShardingSphere-JDBC&#xff1f;Sharding-Jdbc常见概念术语数据节点Node真实表逻辑表绑定表广播表数据库表分片&#xff08;水平库、表&#xff09;分片键 (PartitionKey)行表达式分片策略 InlineShardingStrategy&#xff08;必备&#xff09;标准分片策略Stan…...

2022-06-16_555时基的迷人历史和先天缺陷!

https://www.eet-china.com/news/magazine220608.html 555时基的迷人历史和先天缺陷&#xff01; 发布于2022-06-16 03:39:12 LARRY STABILE 流行数十年的555时基&#xff0c;业内不知晓的工程师应该寥寥无几&#xff01;几乎所有的数字电路教材中&#xff0c;都有该芯片的身影…...

SpringBoot 基础知识汇总

一、环境准备Java&#xff1a;Spring Boot 3.0.2 需要 Java 17&#xff0c;并且与 Java 19 兼容Maven&#xff1a;Apache Maven 3.5 或更高版本兼容二、启动器以下应用程序启动器由 Spring Boot 在该组下提供&#xff1a;org.springframework.boot表 1.Spring 引导应用程序启动…...

centos7下用kvm启动Fedora36 Cloud镜像

环境 os:centos7 Arch: aarch64 安装qemu-kvm yum install qemu-kvm kvm virt-install libvirt systemctl start libvirtd.service创建镜像 下载aarch64架构的Fedora36镜像 wget https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/36/Cloud/aarch64/images/Fedora-Cl…...

修复 K8s SSL/TLS 漏洞(CVE-2016-2183)指南

作者&#xff1a;老 Z&#xff0c;中电信数智科技有限公司山东分公司运维架构师&#xff0c;云原生爱好者&#xff0c;目前专注于云原生运维&#xff0c;云原生领域技术栈涉及 Kubernetes、KubeSphere、DevOps、OpenStack、Ansible 等。 前言 测试服务器配置 主机名IPCPU内存系…...

uniapp 引入彩色symbol和 指令权限

uniapp 引入iconfont图标库彩色symbol 1&#xff0c;先去阿里巴巴矢量图标库登录 然后点击下载至本地 2.下载本地&#xff0c;然后解压文件夹 3.打开终端cmd命令窗口 npm安装全局包npm i -g iconfont-tools 4.终端切换到上面解压的文件夹里面&#xff0c;运行iconfont-too…...

【C语言】初识结构体

Yan-英杰 悟已往之不谏 知来者之可追 目录 一、结构体的声明 二、结构体变量的定义和初始化 三、结构体成员的访问 四、结构体传参 一、结构体的声明 1.结构的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。2.结构的…...

前端将base64图片转换成file文件

1、base64转成file具体代码 // base64图片转file的方法&#xff08;base64图片, 设置生成file的文件名&#xff09;function base64ToFile(base64, fileName) {// 将base64按照 , 进行分割 将前缀 与后续内容分隔开let data base64.split(,);// 利用正则表达式 从前缀中获取图…...

OAK相机跑各种yolo模型的检测帧率和深度帧率

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…...

存储拆分后,如何解决唯一主键问题?

在单库单表时&#xff0c;业务 ID 可以依赖数据库的自增主键实现&#xff0c;现在我们把存储拆分到了多处&#xff0c;如果还是用数据库的自增主键&#xff0c;就会出现主键重复的情况。 所以我们不得不面对的一个选择&#xff0c;就是ID生成器&#xff0c;使用一个唯一的字符…...

仿射变换学习

affine_trans_&#xff08;iamge,region,xld)&#xff1a;仿射变换-作用到iamge&#xff0c;region、xld等都可以 vector_angle_to_rigid()&#xff1a;得到一个刚性仿射变换矩阵 orientation_region()&#xff1a;得到指定区域的弧度&#xff08;与x轴正方向的弧度&#xff09…...

基于java的爬虫框架webmagic基本使用

简单记录一下java项目实现网页爬取数据的基本使用. 需要引入的依赖 <dependency><groupId>us.codecraft</groupId><artifactId>webmagic-core</artifactId><version>0.7.3</version></dependency><dependency><grou…...

Python每日一练(20230221)

目录 1. 不同路径 II 2. 字符串转换整数 (atoi) 3. 字符串相乘 1. 不同路径 II 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为“Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中…...

【Linux】vim拒绝服务安全漏洞修复

根据国家信息安全漏洞共享平台于2023年2月19日发布的安全漏洞通知&#xff0c;Linux系统自带的vim编辑器存在两个高危安全漏洞&#xff08;CNVD-2023-09166、CNVD-2023-09647&#xff09;&#xff0c;攻击者可以利用该漏洞发起拒绝服务攻击&#xff0c;并可能运行&#xff08;恶…...

moveit 2源码编译

文章目录前言下载编译过程创建开发环境拉取源代码更新rosdep下载安装moveit 2依赖包编译源码输出结果总结前言 本文用来记录moveit 2从源码编译的全流程。 本机环境&#xff1a; 系统&#xff1a;debian 11 ros版本&#xff1a;ros2 humble 处理器&#xff1a;intel i7 内存&a…...

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A模块(4)

目录 二、竞赛注意事项 &#xff08;本模块20分&#xff09; 一、项目和任务描述&#xff1a; 二、服务器环境说明 三、具体任务&#xff08;每个任务得分以电子答题卡为准&#xff09; A-1任务一 登录安全加固&#xff08;Windows&#xff09; 1.密码策略 a.更改或创建…...

Flask 基础与实战概述

一、Flask 基础知识 什么是 Flask? Flask 是一个基于 Python 的轻量级 Web 框架(微框架)。 特点:核心代码简洁,给予开发者更多选择空间。 与 Django 对比: Django 创建空项目生成多个文件,Flask 仅需一个文件即可实现简单应用(如 "Hello, World!")。 Flask …...

立志成为一名优秀测试开发工程师(第十一天)—Postman动态参数/变量、文件上传、断言策略、批量执行及CSV/JSON数据驱动测试

目录 一、Postman接口关联与正则表达式应用 1.正则表达式解析 2.提取鉴权码。 二、Postman内置动态参数以及自定义动态参数 1.常见内置动态参数&#xff1a; 2.自定义动态参数&#xff1a; 3.“编辑”接口练习 三、图片上传 1.文件的上传 2.上传后内容的验证 四、po…...

数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档(20250607)

数据库管理334期 2024-06-07 数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档&#xff08;20240607&#xff09;1 环境与安装介质2 操作标准系统配置2.1 关闭防火墙2.2 关闭SELinux2.3 关闭avahi-daemon2.4 时间同步配置 3 存储服务器配置3.1 配置本地yum源3.2 安装…...

NLP学习路线图(二十七):Transformer编码器/解码器

一、Transformer概览&#xff1a;抛弃循环&#xff0c;拥抱注意力 传统RNN及其变体&#xff08;如LSTM、GRU&#xff09;处理序列数据时存在顺序依赖的瓶颈&#xff1a;必须逐个处理序列元素&#xff0c;难以并行计算&#xff0c;且对长程依赖建模能力较弱。Transformer的革命…...

pygame开发的坦克大战

使用Python和Pygame开发的精美坦克大战游戏。这个游戏包含玩家控制的坦克、敌方坦克、各种障碍物、爆炸效果和完整的游戏机制。 游戏说明 这个坦克大战游戏包含以下功能&#xff1a; 游戏特点 玩家控制&#xff1a;使用方向键移动坦克&#xff0c;空格键射击 敌人AI&#x…...

三维GIS开发cesium智慧地铁教程(4)城市白模加载与样式控制

一、添加3D瓦片 <!-- 核心依赖引入 --> <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"><!-- 模型数据路径 --> u…...

产品经理课程(九)

从需求到功能设计 &#xff08;一&#xff09;复习 产品规划&#xff1a;产品定位、阶段性计划 产品定位&#xff1a;产品画布&#xff08;9个步骤&#xff1b;最重要的是先解决什么问题&#xff09; &#xff08;Roadmap&#xff09;目标要素&#xff1a;时间、事项、里程碑…...

Chrome安装代理插件ZeroOmega(保姆级别)

目录 本文直接讲解一下怎么本地安装ZeroOmega一、下载文件在GitHub直接下ZeroOmega 的文件&#xff08;下最新版即可&#xff09; 二、安装插件打开 Chrome 浏览器&#xff0c;访问 chrome://extensions/ 页面&#xff08;扩展程序管理页面&#xff09;&#xff0c;并打开开发者…...

Vue 3 弹出式计算器组件(源码 + 教程)

&#x1f9ee; Vue 3 弹出式计算器组件&#xff08;源码 教程&#xff09; &#x1f4cc; 建议收藏 点赞 关注&#xff0c;本组件支持加减乘除、双向绑定、计算过程展示&#xff0c;适用于表单辅助输入场景。 &#x1f527; 一、完整源码&#xff08;复制即用&#xff09; …...

极限复习c++

一、核心语法必背 1. 指针 vs 引用&#xff08;简答题高频&#xff09; 区别指针引用定义存储地址的变量&#xff0c;可改指向变量的别名&#xff0c;绑定后不可改初始化可空&#xff08;nullptr&#xff09;、延迟初始化必须初始化&#xff0c;不能引用空值访问需解引用&…...