Go语言Web入门之浅谈Gin框架

news2024/11/16 1:18:11

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 main

import (
	"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 main

import (
	"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 = 20
		msg.Name = "ksy"
		//获取使用gin.H
		c.JSON(http.StatusOK, data)
		c.JSON(http.StatusOK, msg)
	})
	r.Run(":9090")
}

在这里我们需要注意的是这个结构体在进行序列化的时候这个首字母需要大写,赋值会序列化失败这是因为这个golang包的可见性决定的。
那会不会有这样一个场景了,就是某个路径Get,Post等方法都可以访问这个路径时我们又该如何来写了,或者是这个当用户返回这个服务器上的路径不存在时我们不希望返回这个404NotFound,我们希望返回我们这个我们自定义的东西时又该如何写了。gin框架已经提供给了我们这个处理的函数了下面我们一起来看看吧。

package main

import (
	"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 main

import (
	"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取不到返回false
		c.JSON(http.StatusOK, gin.H{
			"name": Name,
		})
	})
	r.Run(":8089")
}

在这里获取这个这个请求参数querystring,这个gin提供了多种函数:

  • GetQuery:如果参数不存在第二个返回值为false
  • DefaultQuery:如果参数不存在返回默认设置的值,存在用传进来的值
  • Query:获取参数不存在为""
    下面我们将这个程序运行起来看一看这个效果如何
    在这里插入图片描述
    剩下的各位铁子可以自行下去演示这个效果,在这里博主就不一一演示这个效果了。

2.获取form参数
当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

package main

import (
	"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 main

import (
	"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 main

import (
	"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 Login

		if 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:8080
	router.Run(":8080")
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
如果是 GET 请求,只使用 Form 绑定引擎(query)。
如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)功能非常的强大各位铁子需要这个重点看一下这个.

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。下面我们一起来看看这个如何进行重定向

package main

import (
	"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 main

import (
	"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框架ResponseWriter
	body               *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 main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

type User struct {
  Name string
  Age  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())。因为传递指针会修改上一层的内容导致意想不到的事情发生

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

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

相关文章

MySQl----- 单表查询

表名:worker-- 表中字段均为中文,比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker ( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL, 工资 float(8,2) NOT NULL, 政治面貌 varchar…

【Spark分布式内存计算框架——Spark SQL】11. External DataSource(中)parquet 数据

6.3 parquet 数据 SparkSQL模块中默认读取数据文件格式就是parquet列式存储数据,通过参数【spark.sql.sources.default】设置,默认值为【parquet】。 范例演示代码:直接load加载parquet数据和指定parquet格式加载数据 import org.apache.s…

事物发展的不同阶段会有不同的状态

之前讨论过一个话题,有人问“股票交易稳定盈利很难么?” 我的回答:不难,难在之前。 这几天我又想到经常看到论坛里有人pk观点,最后甩出一句话:“证明你说得对,你先赚一个亿再说。否则&#xf…

写代码犹如写文章: “大师级程序员把系统当故事来讲,而不是当做程序来写” | 如何架构设计复杂业务系统? 如何写复杂业务代码?...

“大师级程序员把系统当故事来讲,而不是当做程序来写”写代码犹如写文章好的代码应该如好文章一样表达思想,被人读懂。中心思想: 突出明确程序是开发者用编程语言写成的一本书,首先应该是记录开发者对业务需求分析、系统分析,最终…

并发编程底层原理

并发编程 文章目录并发编程线程知识点回顾多线程并行和并发什么是并发编程?并发编程的根本原因?Java内存模型(JMM)并发编程的核心问题-可见性、有序性、原子性可见性有序性原子性并发问题总结volatile关键字volatile的底层原理如何…

K8s学习(二)Kubernetest的资源管理及五大资源介绍

文章目录前言1.kubernetes的资源管理系统资源查看2.资源管理方式3.资源管理实战3.1 Namespace3.2 Pod3.3 Label3.4 Deployment3.5 Service3.5.1创建集群内部可访问的Service3.5.2创建集群外部可访问的Service前言 本文是k8s学习系列文章,前后串起来是一个完整的课程…

一招鉴别真假ChatGPT,并简要介绍ChatGPT、GPT、GPT2和GPT3模型之间的区别和联系

以下内容除红色字体部分之外,其他均来源于ChatGPT自动撰写。 ChatGPT是基于GPT模型的对话生成模型,旨在通过对话模拟实现自然语言交互。它是为了改善人机对话体验而设计的,主要应用于聊天机器人、智能客服等场景。 与GPT模型相比,…

全栈之路-前端篇 | 第一讲.基础前置知识【浏览器内核与网络知识】学习笔记

欢迎关注「全栈工程师修炼指南」公众号 点击 👇 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习! 涉及 企业运维、网络安全、应用开发、物联网、人工智能、大数据 学习知识 “ 花开堪折直须折,莫待无花…

内大-oj练习题(1期)

用于存储内大oj练习题 1. 排序题2. 实数输出3. 字符串比较大小4. 1055 找最小放表头,找最大放表尾5. 通过反转实现数据移动6. 破圈报数7. 通话记录8. 用栈实现进制转换9. 判断升序10. 金额的中文大写11. 生日组成的素数12. 判断是否属于一个子网13 统计字符个数14. 求前n项和1…

LeetCode02.07面试题 链表相交 带有输入和输出的链表相交

题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意, 函…

Git的基本操作

文章目录1.git的工作流程2.git的工作环境3.git的基本操作(1)git init(2)git status(3)git add(4)git commit4.版本控制(1)git reflog与git log(2)再增加两个版本(3)git reset --hard 版本号(4)两个指针4.分支管理(1)对分支的理解(2)git branch和git branch -v(3)git checkout 分…

基于matlab的斜视模式下SAR建模

一、前言此示例说明如何使用线性 FM (LFM) 波形对基于聚光灯的合成孔径雷达 (SAR) 系统进行建模。在斜视模式下,SAR平台根据需要从宽侧斜视一定角度向前或向后看。斜视模式有助于对位于当前雷达平台位置前面的区域进行…

mysql EXPLAIN关键字

EXPLAIN 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。通过EXPLAIN,我们可以分析出以下结果: 表的读取顺序数据读取操作的操作类型哪些索引可以使用哪些索…

一文吃透SQL性能优化,阿里47条军规

目录1、先了解MySQL的执行过程2、数据库常见规范3、所有表必须使用Innodb存储引擎4、每个Innodb表必须有个主键5、数据库和表的字符集统一使用UTF86、查询SQL尽量不要使用select *,而是具体字段7、避免在where子句中使用 or 来连接条件8、尽量使用数值替代字符串类型…

【数据挖掘实战】——中医证型的关联规则挖掘

目录 一、背景和挖掘目标 1、问题背景 2、传统方法的缺陷 3、原始数据情况 4、挖掘目标 二、分析方法和过程 1、初步分析 2、总体过程 第1步:数据获取 第2步:数据预处理 第3步:构建模型 三、思考和总结 项目地址:Data…

YOLOv6-目标检测论文解读

文章目录摘要问题算法网络设计BackboneNeckHead标签分配SimOTA(YOLOX提出):TAL(Task alignment learning,TOOD提出)损失函数分类损失框回归损失目标损失行业有用改进自蒸馏图像灰度边界填充量化及部署实验消…

测试1:测试相关概念

1.测试相关概念 1.1.测试概念 1.1.1.需求 符合正式文档规定的条件和权能,包括用户需求和软件需求 它们之间的的转换是:沟通 用户需求和软件需求的区别: 能否指导开发人员开发,测试人员编写测试用例 1.1.2.缺陷Bug 与正确的…

补充前端面试题(三)

图片懒加载<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, in…

「数据仓库」怎么选择现代数据仓库?

构建自己的数据仓库时要考虑的基本因素我们用过很多数据仓库。当我们的客户问我们&#xff0c;对于他们成长中的公司来说&#xff0c;最好的数据仓库是什么时&#xff0c;我们会根据他们的具体需求来考虑答案。通常&#xff0c;他们需要几乎实时的数据&#xff0c;价格低廉&…

[SSD科普之1] PCIE接口详解及应用模式

PCI-Express(peripheral component interconnect express)是一种高速串行计算机扩展总线标准&#xff0c;它原来的名称为“3GIO”&#xff0c;是由英特尔在2001年提出的&#xff0c;旨在替代旧的PCI&#xff0c;PCI-X和AGP总线标准。一、PCI-E x1/x4/x8/x16插槽模式PCI-E有 x1/…