【Golang】gin框架入门

news2025/1/12 21:04:27

文章目录

  • gin框架入门
    • 认识gin
      • go流行的web框架
      • gin介绍
      • 快速入门
    • 路由
      • RESTful API规范
      • 请求方法
      • URI
      • 处理函数
      • 分组路由
    • 请求参数
      • GET请求参数
      • POST请求参数
      • 路径参数
      • 文件参数
    • 响应
      • 字符串方式
      • JSON方式
      • XML方式
      • 文件格式
      • 设置HTTP响应头
      • 重定向
      • YAML方式
    • 模板渲染
      • 基本使用
      • 多个模板渲染
      • 自定义模板函数
      • 静态文件处理
    • 会话
    • 中间件
      • 中间件使用
      • 自定义中间件

gin框架入门

认识gin

go流行的web框架

  • Gin

    号称最快的go语言web框架,目前是go官方的推荐框架(https://go.dev/doc/tutorial/)

  • iris

    性能比gin高一些,支持MVC,但这款框架评价不太好,使用上问题较多,近些年很少去选择使用

  • Beego

    国人开发,最早的go web框架之一,工具集比较完善,性能较差,据传言作者是php转行,所以框架带有浓厚的php特色,早期国内使用的多,目前少有人选择

  • fiber

    2020年发布的框架,发展迅速,建立在fasthttp之上,性能目前最高,受Express启发,比较简洁,上手较快,和gin类似

当然还有其他一些框架,但从star数上,以及流行程度上看,gin一骑绝尘,gin的好处在于其简洁,扩展性,稳定性以及性能都比较出色。

go的框架其实是可以理解为库,并不是用了某一个框架就不能用别的框架,可以选择性的使用各个库中的优秀组件,进行组合

gin介绍

特性:

  • 快速

    基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能

  • 支持中间件

    传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB

  • Crash处理

    Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!

  • JSON验证

    Gin 可以解析并验证请求的 JSON,例如检查所需值的存在

  • 路由组

    更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能

  • 错误管理

    Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送

  • 内置渲染

    Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API

  • 可扩展性

    新建一个中间件非常简单

快速入门

go版本需求:go1.13及以上

环境:windows 11

  • gin框架下载

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

    在这里插入图片描述

  • demo展示

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
    }
    

    在这里插入图片描述

    此时,通过postman发送请求测试是否联通

    在这里插入图片描述

路由

路由是URI到函数的映射。

一个URI含: http://localhost:8080/user/find?id=11

  • 协议,比如http,https等
  • ip端口或者域名,比如127.0.0.1:8080或者www.test.com
  • path,比如 /path
  • query,比如 ?query

同时访问的时候,还需要指明HTTP METHOD,比如

  • GET

    GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.

  • POST

    POST方法用于将实体提交到指定的资源,通常会导致在服务器上的状态变化

  • HEAD

    HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.

  • PUT

    PUT方法用请求有效载荷替换目标资源的所有当前表示

  • DELETE

    DELETE方法删除指定的资源

  • CONNECT

    CONNECT方法建立一个到由目标资源标识的服务器的隧道。

  • OPTIONS

    OPTIONS方法用于描述目标资源的通信选项。

  • TRACE

    TRACE方法沿着到目标资源的路径执行一个消息环回测试。

  • PATCH

    PATCH方法用于对资源应用部分修改。

使用的时候,应该尽量遵循其语义

RESTful API规范

RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。

比如:

  1. GET,表示读取服务器上的资源
  2. POST,表示在服务器上创建资源
  3. PUT,表示更新或者替换服务器上的资源
  4. DELETE,表示删除服务器上的资源
  5. PATCH,表示更新/修改资源的一部分

请求方法

比如

	r.GET("/get", func(ctx *gin.Context) {
		ctx.JSON(200, "get")
	})
	r.POST("/post", func(ctx *gin.Context) {
		ctx.JSON(200, "post")
	})
	r.DELETE("/delete", func(ctx *gin.Context) {
		ctx.JSON(200, "delete")
	})
	r.PUT("/put", func(ctx *gin.Context) {
		ctx.JSON(200, "put")
	})

如果想要支持所有:

r.Any("/any", func(ctx *gin.Context) {
		ctx.JSON(200, "any")
	})

如果想要支持其中的几种:

   r.GET("/hello", func(ctx *gin.Context) {
		//数组 map list 结构体
		ctx.JSON(200, gin.H{
			"name": "hello world",
		})
	})
	r.POST("/hello", func(ctx *gin.Context) {
		//数组 map list 结构体
		ctx.JSON(200, gin.H{
			"name": "hello world",
		})
	})

URI

URI书写的时候,我们不需要关心scheme和authority这两部分,我们主要通过path和query两部分的书写来进行资源的定位

  • 静态url,比如/hello/user/find

    r.POST("/user/find", func(ctx *gin.Context) {
    })
    
  • 路径参数,比如/user/find/:id

    r.POST("/user/find/:id", func(ctx *gin.Context) {
    		param := ctx.Param("id")
    		ctx.JSON(200, param)
    	})
    
  • 模糊匹配,比如/user/*path

    r.POST("/user/*path", func(ctx *gin.Context) {
    		param := ctx.Param("path")
    		ctx.JSON(200, param)
    })
    

处理函数

定义:

type HandlerFunc func(*Context)

通过上下文的参数,获取http的请求参数,响应http请求等。

分组路由

在进行开发的时候,我们往往要进行模块的划分,比如用户模块,以user开发,商品模块,以goods开头。

或者进行多版本开发,不同版本之间路径是一致的,这种时候,就可以用到分组路由了。

比如:

  ug := r.Group("/user")
	{
		ug.GET("find", func(ctx *gin.Context) {
			ctx.JSON(200, "user find")
		})
		ug.POST("save", func(ctx *gin.Context) {
			ctx.JSON(200, "user save")
		})
	}
	gg := r.Group("/goods")
	{
		gg.GET("find", func(ctx *gin.Context) {
			ctx.JSON(200, "goods find")
		})
		gg.POST("save", func(ctx *gin.Context) {
			ctx.JSON(200, "goods save")
		})
	}

请求参数

GET请求参数

使用Get请求传参时,类似于这样 http://localhost:8080/user/save?id=11&name=zhangsan

如何获取呢?

  • 1.1 普通参数

    request url: http://localhost:8080/user/save?id=11&name=zhangsan

    r.GET("/user/save", func(ctx *gin.Context) {
    		id := ctx.Query("id")
    		name := ctx.Query("name")
    		ctx.JSON(200, gin.H{
    			"id":   id,
    			"name": name,
    		})
    	})
    

    如果参数不存在,就会给一个默认值

    r.GET("/user/save", func(ctx *gin.Context) {
    		id := ctx.Query("id")
    		name := ctx.Query("name")
    		address := ctx.DefaultQuery("address", "北京")
    		ctx.JSON(200, gin.H{
    			"id":      id,
    			"name":    name,
    			"address": address,
    		})
    	})
    

    因此,为了正常处理业务,我们需要判断参数是否存在

    r.GET("/user/save", func(ctx *gin.Context) {
    		id, ok := ctx.GetQuery("id")
    		address, aok := ctx.GetQuery("address")
    		ctx.JSON(200, gin.H{
    			"id":      id,
    			"idok":    ok,
    			"address": address,
    			"aok":     aok,
    		})
    	})
    

    id是数值类型,上述获取的都是string类型,根据类型获取方式:

    type User struct {
    	Id   int64  `form:"id"`
    	Name string `form:"name"`
    }
    r.GET("/user/save", func(ctx *gin.Context) {
    		var user User
    		err := ctx.BindQuery(&user)
    		if err != nil {
    			log.Println(err)
    		}
    		ctx.JSON(200, user)
    })
    

    也可以通过这个方式:

    r.GET("/user/save", func(ctx *gin.Context) {
    		var user User
    		err := ctx.ShouldBindQuery(&user)
    		if err != nil {
    			log.Println(err)
    		}
    		ctx.JSON(200, user)
    	})
    
  • 参数是数组

    请求url:http://localhost:8080/user/save?address=Beijing&address=shanghai

    r.GET("/user/save", func(ctx *gin.Context) {
    		address := ctx.QueryArray("address")
    		ctx.JSON(200, address)
    	})
    
    r.GET("/user/save", func(ctx *gin.Context) {
    		address, ok := ctx.GetQueryArray("address")
    		fmt.Println(ok)
    		ctx.JSON(200, address)
    	})
    
    r.GET("/user/save", func(ctx *gin.Context) {
    		var user User
    		err := ctx.ShouldBindQuery(&user)
    		fmt.Println(err)
    		ctx.JSON(200, user)
    	})
    
  • map参数

    请求url:http://localhost:8080/user/save?addressMap[home]=Beijing&addressMap[company]=shanghai

    r.GET("/user/save", func(ctx *gin.Context) {
    		addressMap := ctx.QueryMap("addressMap")
    		ctx.JSON(200, addressMap)
    	})
    
    r.GET("/user/save", func(ctx *gin.Context) {
    		addressMap, _ := ctx.GetQueryMap("addressMap")
    		ctx.JSON(200, addressMap)
    	})
    

    map参数 bind并没有支持

POST请求参数

post请求一般是表单参数和json参数

  • 表单参数

    r.POST("/user/save", func(ctx *gin.Context) {
    		id := ctx.PostForm("id")
    		name := ctx.PostForm("name")
    		address := ctx.PostFormArray("address")
    		addressMap := ctx.PostFormMap("addressMap")
    		ctx.JSON(200, gin.H{
    			"id":         id,
    			"name":       name,
    			"address":    address,
    			"addressMap": addressMap,
    		})
    	})
    
    r.POST("/user/save", func(ctx *gin.Context) {
    		var user User
    		err := ctx.ShouldBind(&user)
    		addressMap, _ := ctx.GetPostFormMap("addressMap")
    		user.AddressMap = addressMap
    		fmt.Println(err)
    		ctx.JSON(200, user)
    	})
    
  • JSON参数

    {
        "id":1111,
        "name":"zhangsan",
        "address": [
            "beijing",
            "shanghai"
        ],
        "addressMap":{
            "home":"beijing"
        }
    }
    
    r.POST("/user/save", func(ctx *gin.Context) {
    		var user User
    		err := ctx.ShouldBindJSON(&user)
    		fmt.Println(err)
    		ctx.JSON(200, user)
    	})
    

路径参数

请求url:http://localhost:8080/user/save/111

r.POST("/user/save/:id", func(ctx *gin.Context) {
		ctx.JSON(200, ctx.Param("id"))
	})

文件参数

r.POST("/user/save", func(ctx *gin.Context) {
		form, err := ctx.MultipartForm()
		if err != nil {
			log.Println(err)
		}
		files := form.File
		for _, fileArray := range files {
			for _, v := range fileArray {
				ctx.SaveUploadedFile(v, "./"+v.Filename)
			}

		}
		ctx.JSON(200, form.Value)
	})

响应

字符串方式

r.GET("/user/save", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "this is a %s", "ms string response")
	})

JSON方式

r.GET("/user/save", func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, gin.H{
			"success": true,
		})
	})

XML方式

type XmlUser struct {
	Id   int64  `xml:"id"`
	Name string `xml:"name"`
}
r.GET("/user/save", func(ctx *gin.Context) {
		u := XmlUser{
			Id:   11,
			Name: "zhangsan",
		}
		ctx.XML(http.StatusOK, u)
	})

文件格式

r.GET("/user/save", func(ctx *gin.Context) {
		//ctx.File("./1.png")
		ctx.FileAttachment("./1.png", "2.png")
	})

设置HTTP响应头

r.GET("/user/save", func(ctx *gin.Context) {
		ctx.Header("test", "headertest")
	})

重定向

r.GET("/user/save", func(ctx *gin.Context) {
		ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
	})

YAML方式

r.GET("/user/save", func(ctx *gin.Context) {
		ctx.YAML(200, gin.H{"name": "ms", "age": 19})
})

模板渲染

模板是golang语言的一个标准库,使用场景很多,gin框架同样支持模板

基本使用

定义一个存放模板文件的templates文件夹

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>gin_templates</title>
</head>
<body>
{{.title}}
</body>
</html>

后端代码:

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	// 模板解析
	r.LoadHTMLFiles("templates/index.tmpl")

	r.GET("/index", func(c *gin.Context) {
		// HTML请求
		// 模板的渲染
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "hello 模板",
		})
	})

	r.Run(":9090") // 启动server
}

多个模板渲染

如果有多个模板,可以统一进行渲染

// 模板解析,解析templates目录下的所有模板文件
	r.LoadHTMLGlob("templates/**")

如果目录为templates/post/index.tmpltemplates/user/index.tmpl这种,可以

	// **/* 代表所有子目录下的所有文件
	router.LoadHTMLGlob("templates/**/*")

自定义模板函数

   // gin框架给模板添加自定义函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	})

	// 模板解析,解析templates目录下的所有模板文件
	r.LoadHTMLGlob("templates/**")

	r.GET("/index", func(c *gin.Context) {
		// HTML请求
		// 模板的渲染
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "<a href='http://baidu.com'>跳转到其他地方</a>",
		})
	})
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>gin_templates</title>
</head>
<body>
{{.title | safe}}
</body>
</html>Z

静态文件处理

如果在模板中引入静态文件,比如样式文件

index.css

body{
    background-color: aqua;
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>gin_templates</title>
    <link rel="stylesheet" href="/css/index.css">
</head>
<body>
{{.title}}
</body>
</html>
// 加载静态文件
r.Static("/css", "./static/css")

会话

会话控制涉及到cookie和session的使用

  • cookie

    1. HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
    2. Cookie就是解决HTTP协议无状态的方案之一
    3. Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
    4. Cookie由服务器创建,并发送给浏览器,最终由浏览器保存

    设置cookie

    func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
    

    参数说明:

    参数名类型说明
    namestringcookie名字
    valuestringcookie值
    maxAgeint有效时间,单位是秒,MaxAge=0 忽略MaxAge属性,MaxAge<0 相当于删除cookie, 通常可以设置-1代表删除,MaxAge>0 多少秒后cookie失效
    pathstringcookie路径
    domainstringcookie作用域
    secureboolSecure=true,那么这个cookie只能用https协议发送给服务器
    httpOnlybool设置HttpOnly=true的cookie不能被js获取到
    r.GET("/cookie", func(c *gin.Context) {
    		// 设置cookie
    		c.SetCookie("site_cookie", "cookievalue", 3600, "/", "localhost", false, true)
    	})
    

    获取cookie

    r.GET("/read", func(c *gin.Context) {
    		// 根据cookie名字读取cookie值
    		data, err := c.Cookie("site_cookie")
    		if err != nil {
    			// 直接返回cookie值
    			c.String(200,data)
    			return
    		}
    		c.String(200,"not found!")
    	})
    

    删除cookie

    通过将cookie的MaxAge设置为-1, 达到删除cookie的目的。

    r.GET("/del", func(c *gin.Context) {
    		// 设置cookie  MaxAge设置为-1,表示删除cookie
    		c.SetCookie("site_cookie", "cookievalue", -1, "/", "localhost", false, true)
    		c.String(200,"删除cookie")
    	})
    
  • session

    在Gin框架中,我们可以依赖gin-contrib/sessionsopen in new window中间件处理session

    安装session包

    go get github.com/gin-contrib/sessions
    
    package main
    
    import (
    	"fmt"
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/cookie"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	// 创建基于cookie的存储引擎,secret 参数是用于加密的密钥
    	store := cookie.NewStore([]byte("secret"))
    	// 设置session中间件,参数mysession,指的是session的名字,也是cookie的名字
    	// store是前面创建的存储引擎,我们可以替换成其他存储引擎
    	r.Use(sessions.Sessions("mysession", store))
    
    	r.GET("/hello", func(c *gin.Context) {
    		// 初始化session对象
    		session := sessions.Default(c)
    		// 通过session.Get读取session值
    		// session是键值对格式数据,因此需要通过key查询数据
    		if session.Get("hello") != "world" {
    			fmt.Println("没读到")
    			// 设置session数据
    			session.Set("hello", "world")
    			session.Save()
    		}
    		c.JSON(200, gin.H{"hello": session.Get("hello")})
    	})
    	r.Run(":8080")
    }
    

    多session

    package main
    
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/cookie"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	store := cookie.NewStore([]byte("secret"))
    	sessionNames := []string{"a", "b"}
    	r.Use(sessions.SessionsMany(sessionNames, store))
    
    	r.GET("/hello", func(c *gin.Context) {
    		sessionA := sessions.DefaultMany(c, "a")
    		sessionB := sessions.DefaultMany(c, "b")
    
    		if sessionA.Get("hello") != "world!" {
    			sessionA.Set("hello", "world!")
    			sessionA.Save()
    		}
    
    		if sessionB.Get("hello") != "world?" {
    			sessionB.Set("hello", "world?")
    			sessionB.Save()
    		}
    
    		c.JSON(200, gin.H{
    			"a": sessionA.Get("hello"),
    			"b": sessionB.Get("hello"),
    		})
    	})
    	r.Run(":8080")
    }
    

中间件

在Gin框架中,中间件(Middleware)指的是可以拦截http请求-响应生命周期的特殊函数,在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行

中间件的常见应用场景如下:

  • 请求限速
  • api接口签名处理
  • 权限校验
  • 统一错误处理

Gin支持设置全局中间件和针对路由分组设置中间件,设置全局中间件意思就是会拦截所有请求,针对分组路由设置中间件,意思就是仅对这个分组下的路由起作用

中间件使用

 r := gin.New()
	// 通过use设置全局中间件
	// 设置日志中间件,主要用于打印请求日志
	r.Use(gin.Logger())
	// 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉
	r.Use(gin.Recovery())
	r.GET("/test", func(ctx *gin.Context) {
		panic(errors.New("test error"))
	})
	r.Run(":8080")

自定义中间件

使用Use可以使用gin自带的中间件或者其他第三方中间件,也可以自己开发中间件

package main
// 导入gin包
import (
"github.com/gin-gonic/gin"
	"log"
	"time"
)

// 自定义个日志中间件
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
		c.Set("example", "12345")

		// 在这里处理请求到达控制器函数之前的逻辑
     
		// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。
		c.Next()

		// 在这里可以处理请求返回给用户之前的逻辑
		latency := time.Since(t)
		log.Print(latency)

		// 例如,查询请求状态吗
		status := c.Writer.Status()
		log.Println(status)
	}
}

func main() {
	r := gin.New()
	// 注册上面自定义的日志中间件
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
		// 查询我们之前在日志中间件,注入的键值数据
		example := c.MustGet("example").(string)
		// it would print: "12345"
		log.Println(example)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}
	panic(errors.New("test error"))
})
r.Run(":8080")

### 自定义中间件

使用Use可以使用gin自带的中间件或者其他第三方中间件,也可以自己开发中间件

```go
package main
// 导入gin包
import (
"github.com/gin-gonic/gin"
	"log"
	"time"
)

// 自定义个日志中间件
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
		c.Set("example", "12345")

		// 在这里处理请求到达控制器函数之前的逻辑
     
		// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。
		c.Next()

		// 在这里可以处理请求返回给用户之前的逻辑
		latency := time.Since(t)
		log.Print(latency)

		// 例如,查询请求状态吗
		status := c.Writer.Status()
		log.Println(status)
	}
}

func main() {
	r := gin.New()
	// 注册上面自定义的日志中间件
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
		// 查询我们之前在日志中间件,注入的键值数据
		example := c.MustGet("example").(string)
		// it would print: "12345"
		log.Println(example)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCache--缓存框架 ----苍穹外卖day7

目录 简介 ​快速入门 引入依赖 常用注解​ 使用步骤 1.开启缓存注解 2. Cacheable注解 简介 快速入门 引入依赖 常用注解 使用步骤 1.开启缓存注解 2. Cacheable注解 该注解仅用于查询操作&#xff0c…

零基础部署nginx mysql springboot

参考&#xff1a;写给开发人员看的Docker干货&#xff0c;零基础部署nginx mysql springboot 一、连接linux 阿里云 参考&#xff1a;部署到Linux 可能需要购买&#xff1a;购买链接 二、安装docker # 先切换到root用户下 sudo su# 更新apt-get&#xff0c;保证apt-get最新…

Shiro应用到Web Application

一、权限基础 a) 认证(你是谁&#xff1f;) 判断你(被认证者)是谁的过程。通常被认证者提供用户名和密码。 常见的认证包含如下几种&#xff1a; 匿名认证&#xff1a;允许访问资源&#xff0c;不做任何类型的安全检查。表单认证&#xff1a;访问资源之前&#xff0c;需要提…

SpringBoot vue云办公系统

SpringBoot vue云办公系统 系统功能 云办公系统 登录 员工资料管理: 搜索员工 添加编辑删除员工 导入导出excel 薪资管理: 工资账套管理 添加编辑删除工资账套 员工账套设置 系统管理: 基础信息设置 部门管理 职位管理 职称管理 权限组管理 操作员管理 开发环境和技术 开发语…

JavaEE-线程进阶

模拟实现一个定时器 运行结果如下&#xff1a; 上述模拟定时器的全部代码&#xff1a; import java.util.PriorityQueue;//创建一个类&#xff0c;用来描述定时器中的一个任务 class MyTimerTask implements Comparable<MyTimerTask> {//任务执行时间private long …

数据分析视角中的商业分析学习笔记

数据分析一大堆&#xff0c;结果却是大家早就知道的结论&#xff1f;是工具和方法出问题了吗&#xff1f;真正原因可能是你的思维有误区。 为什么分析的这么辛苦&#xff0c;得出的结论大家早知道&#xff0c;谁谁都不满意&#xff1f;核心原因有3个&#xff1a; 分析之前&am…

DHCPsnooping 配置实验(2)

DHCP报文泛洪攻击 限制接收到报文的速率 vlan 视图或者接口视图 dhcp request/ dhcp-rate dhcp snooping check dhcp-request enable dhcp snooping alarm dhcp-request enable dhcp snooping alarm dhcp-request threshold 1 超过则丢弃报文 查看[Huawei]dis dhcp statistic…

使用python-opencv检测图片中的人像

最简单的方法进行图片中的人像检测 使用python-opencv配合yolov3模型进行图片中的人像检测 1、安装python-opencv、numpy pip install opencv-python pip install numpy 2、下载yolo模型文件和配置文件&#xff1a; 下载地址&#xff1a; https://download.csdn.net/down…

如何使用 AI与人工智能的定义、研究价值、发展阶段

目录 一、什么是人工智能 二、人工智能的研究价值 三、人工智能的发展阶段 一、什么是人工智能 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一门研究如何使计算机能够模拟和执行人类智能活动的科学与技术。人工智能旨在开发智能代理&…

CLIP 论文逐段精读【论文精读】

00:06评价&#xfeff; 评价&#xff1a;工作clip呢自从去年2月底提出就立马火爆全场&#xff0c;他的方法出奇的简单&#xff0c;但是效果呢又出奇的好&#xff0c;很多结果和结论呢都让人瞠目结舌。比如呢作者说clip的这个迁移学习能力是非常强的&#xff0c;它预训好的这个模…

【C语言】利用数组处理批量数据(一维数组和二维数组)

前言:在前面学习的程序中使用的变量都属于基本类型&#xff0c;例如整型、字符型、浮点型数据&#xff0c;这些都是简单的数据类型。对于简单的问题&#xff0c;使用这些简单的数据类型就可以了。但是对于有些需要处理的数据&#xff0c;只用以上简单的数据类型是不够的&#x…

Obsidian插件推荐_231005

起因 十一在家整理 OB 笔记&#xff0c;发现两个超好用的插件&#xff1a;Linter & tag wrangler。 前一阵把 Obsidian 笔记用 Hexo 转换成静态网页发到 github.io 做自己 blog 网站。发现了笔记中的一些问题&#xff0c;比如 tag 过多、重复&#xff0c;markdown 格式不…

菲律宾电商市场潜力巨大,Temu迎来东南亚扩张良机!(测评补单)

从拼多多传出筹备出海扩张的消息以来&#xff0c;业界都认定其迟早要进军东南亚市场。在7月份Temu上线日本站后可以看出Temu这条扩张路线背后的商业逻辑是先占领高购买力国&#xff0c;再向中低购买力国扩张&#xff0c;所以亚洲首站选择了高购买力的日本。果然在完成日韩布局后…

零基础入门初学 Python 需要安装哪些软件?

Python是近年来备受热门的编程语言&#xff0c;其简明易读、开源免费、跨平台等特点&#xff0c;使得Python倍受喜爱&#xff0c;成为初学者及开发者心中的首选。 如果你是第一次接触Python&#xff0c;又不想繁琐地安装各种软件&#xff0c;可以尝试在线运行Python代码&#…

【RCRL充放电时间相关计算】

一. 基础知识 L、C元件称为“惯性元件”&#xff0c;即电感中的电流、电容器两端的电压&#xff0c;都有一定的“电惯性”&#xff0c;不能突然变化。充放电时间&#xff0c;不光与L、C的容量有关&#xff0c;还与充/放电电路中的电阻R有关。RC电路的时间常数&#xff1a;τRC…

【扩散模型】 DDPM和DDIM讲解

扩散模型DDPM和DDIM 扩散模型之DDPM介绍了经典扩散模型DDPM的原理和实现&#xff0c;那么生成一个样本的次数和训练次数需要一致&#xff0c;导致采样过程很缓慢。这篇文章我们将介绍另外一种扩散模型DDIM&#xff08;Denoising Diffusion Implicit Models&#xff09;&#x…

STM32CubeMX学习笔记-DAC接口使用(输出电压)

STM32CubeMX学习笔记-DAC接口使用&#xff08;输出电压&#xff09; 一、DAC简介二、DAC通道选择三、新建工程四、DAC14.1 参数配置4.2 生成代码 五、库函数六、修改main函数 原创链接 1 一、DAC简介 DAC(Digital-to-Analog Converter)&#xff0c;即数字/模拟转换模块&#x…

番外--常用文件目录类命令

------------- task00: 00&#xff1a;常用文件目录类命令1-18.&#xff08;pwd&#xff1b; cd&#xff1b;ls&#xff1b; more&#xff1b;less&#xff1b;head&#xff1b;tail&#xff1b; mkdir&#xff1b;rmdir&#xff1b;cp&#xff1b;mv&#xff1b;rm&#xff1b…

第八章 排序 三、希尔排序

目录 一、算法思想 二、例子 三、代码实现 五、验证 六、空间复杂度 七、时间复杂度 八、稳定性 一、算法思想 先追求表中元素部分有序&#xff0c;在逐渐逼近表中元素全部有序。 二、例子 1、我们要升序排列此表 2、取一个差值作为子表的划分的条件&#xff0c;希尔本…

SSM - Springboot - MyBatis-Plus 全栈体系(十九)

第四章 SpringMVC 二、SpringMVC 接收数据 1. 访问路径设置 RequestMapping 注解的作用就是将请求的 URL 地址和处理请求的方式&#xff08;handler 方法&#xff09;关联起来&#xff0c;建立映射关系。SpringMVC 接收到指定的请求&#xff0c;就会来找到在映射关系中对应的…