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

GoLong的学习之路(十六)基础工具之Gin框架

Gin框架介绍及使用,这张不用看内容就知道非常重要,重要到什么地步呢?重要到开发java不会Spring全家桶这种概念。

上几篇文章写的是如何构建骨架,经脉。这一章是将血肉注入。

文章目录

  • Gin框架
    • RESTful API
  • Gin渲染
    • HTML渲染
    • 静态文件处理
    • 使用模板继承
    • 补充文件路径处理
    • Json渲染
    • XML渲染
    • YMAL渲染
    • protobuf渲染
  • 获取参数
    • 获取querystring参数
    • 获取form参数
    • 获取JSON参数
    • 获取path参数
    • 参数绑定
  • 文件上传
    • 单个文件上传
    • 多个文件上传
  • 重定向
    • Http重定向
    • 路由重定向
  • Gin路由
    • 普通路由
    • 路由组
      • 路由嵌套
  • Gin中间件
    • 记录接口耗时的中间件
    • 记录响应体的中间件
    • 跨域中间件
    • 注册中间件
      • 全局路由注册
      • 为某个路由单独注册
      • 为路由组注册中间件
    • 中间件的注意事项
      • 默认的中间件
      • gin中间件中使用goroutine
  • 启动多个服务

Gin框架

这个框架是世界上最流行的Web框架。Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。

如何搭建框架?其实非常简单。

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

在import中导入这个依赖就好了,不过刚开始导入是报错的。但是没有关系,只要鼠标放在那个位置下载一下就好。编译器会自动下载。

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(Representational State Transfer)与技术无关,代表的是一种软件架构风格,中文翻译为“表征状态转移”或“表现层状态转化”。

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

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

测试的化需要我们用Postman来作为客户端的测试工具。

Gin渲染

这个渲染在整个框架中呈现支撑作用。

HTML渲染

什么是页面渲染呢?其实就是服务器对页面进行传输数据,跳转等操作的手段。

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行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}}
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.Run(":8080")
}

静态文件处理

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

不能与 服务器 端做动态交互的文件都是静态文件

  • 如:图片,css,js, 音频,视频,html 文件 (部分)
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.tmpl和index.tmpl继承了base.tmpl。

在这里插入图片描述
具体怎么用还是个大学问。得后面在写文章了。

补充文件路径处理

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

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

Json渲染

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是Google公司提出的一种轻便高效的结构化数据存储格式,常用于结构化数据的序列化,具有语言无关、平台无关、可扩展性特性,常用于通讯协议、服务端数据交换场景。

获取参数

前端上传数据对于后端来说,是需要获取的其参数的。

获取querystring参数

querystring指的是URL中?后面携带的参数/user/search?username=李四&address=北京。

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

获取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")
}

获取JSON参数

当前端请求的数据通过JSON提交时

例如:
向/json发送一个JSON格式的POST请求:

注意:下面为了举例子方便,暂时忽略了错误处理

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

获取path参数

请求的参数通过URL路径传递.。

路由器引擎指的就是主控处理单元,功能差不多相当于电脑的CPU。

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识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。

context.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)。

文件上传

单个文件上传

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

多个文件上传

主要逻辑是通过for循环一个一个传输。

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

匹配所有请求方法

r.Any("/test", func(c *gin.Context) {...})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

我就说为什么有些404页面如此好看。

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

路由的原理: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)}
}

记录响应体的中间件

有时候我们需要直接查看客户端的响应数据。


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()) // 事后按需记录返回的响应
}

跨域中间件

推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。

注意: 该中间件需要注册在业务处理函数前面

简单版

func main() {router := gin.Default()// same as// config := cors.DefaultConfig()// config.AllowAllOrigins = true// router.Use(cors.New(config))router.Use(cors.Default())router.Run()
}

复杂版

package mainimport ("time""github.com/gin-contrib/cors""github.com/gin-gonic/gin"
)func main() {router := gin.Default()// CORS for https://foo.com and https://github.com origins, allowing:// - PUT and PATCH methods// - Origin header// - Credentials share// - Preflight requests cached for 12 hoursrouter.Use(cors.New(cors.Config{AllowOrigins:     []string{"https://foo.com"},  // 允许跨域发来请求的网站AllowMethods:     []string{"GET", "POST", "PUT", "DELETE",  "OPTIONS"},  // 允许的请求方法AllowHeaders:     []string{"Origin", "Authorization", "Content-Type"},ExposeHeaders:    []string{"Content-Length"},AllowCredentials: true,AllowOriginFunc: func(origin string) bool {  // 自定义过滤源站的方法return origin == "https://github.com"},MaxAge: 12 * time.Hour,}))router.Run()
}

注册中间件

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

全局路由注册

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", StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}

中间件的注意事项

默认的中间件

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

什么是goroutine?

goroutine是Go语言提供的一种用户态线程,有时我们也称之为协程。 但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部实现了这些goroutine之间的内存共享。 执行goroutine只需极少的栈内存 (大概是4~5KB),当然会根据相应的数据伸缩。

其实吧,大部分组件有API不需我们去实现。只需要去调用就好。

启动多个服务

我们可以在多个端口启动服务。这个就有点意思了。

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

相关文章:

GoLong的学习之路(十六)基础工具之Gin框架

Gin框架介绍及使用&#xff0c;这张不用看内容就知道非常重要&#xff0c;重要到什么地步呢&#xff1f;重要到开发java不会Spring全家桶这种概念。 上几篇文章写的是如何构建骨架&#xff0c;经脉。这一章是将血肉注入。 文章目录 Gin框架RESTful API Gin渲染HTML渲染静态文件…...

VMware打开centos黑屏解决方法汇总

VMware打开centos黑屏解决方法汇总 前言&#xff1a;一. VMware打开centos黑屏解决方法汇总一 .情况情况一&#xff1a;情况二情况三 二. 解决方法最简单的方法&#xff1a;一. 以管理员权限在命令行执行1. 管理员身份运行cmd2. 输入“netsh winsock reset”,回车3. 重启电脑即…...

5G物联网关相较有线网关有哪些独特优势

5G为产业物联网应用带来了质的飞跃&#xff0c;5G技术实现更高速率、更低延迟和更大带宽&#xff0c;使得物联网能够接入更多数量的设备&#xff0c;实现更稳定、高效的连接和数据传输&#xff0c;在提高生产效率的同时&#xff0c;也进一步促进了物联网的应用发展和升级。 针对…...

【数据结构】顺序表的学习

前言:在之前我们学习了C语言的各种各样的语法&#xff0c;因此我们今天开始学习数据结构这一个模块&#xff0c;因此我们就从第一个部分来开始学习"顺序表"。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:C程序设计谭浩强版本…...

在NISQ小型计算机上执行大型并行量子计算的可能性

简介 Steve White提出了密度矩阵重整化群&#xff08;DMRG&#xff09;的基本思想&#xff0c;即纠缠是一种有价值的资源&#xff0c;可以用来精确或近似地描述大量子系统。后来&#xff0c;这一思想被理解为优化矩阵积状态&#xff08;MPS&#xff09;的算法&#xff0c;支持…...

考虑时空相关性的风电功率预测误差MATLAB代码

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 风电功率预测置信区间误差分析拟合 1.风电功率预测误差--时空相关性 展示第一一个时间段的风电功率预测与实际风电功率值的比较。填充区域表示预测的不确定性&#xff0c;显示了95%置信区间内预测可能的范围…...

ASP.NET WebApi 极简依赖注入

文章目录 环境服务类启动项注入使用依赖注入的优点 环境 .NET Core 7.0ASP.NET CoreVisual Studio 2022 服务类 public class T_TempService {public T_TempService(){}public void Test(){}}启动项注入 #region 依赖注入 builder.Services.AddTransient<T_TempService&g…...

解决proteus仿真stm32,IIC通讯,IIC DEBUG无法显示从机应答信号的问题(问题情况为在8位数据后应答位显示?)

1、错误现象 错误现象如下&#xff0c;在IIC数据传输8位数据后&#xff0c;IIC DEBUG的应答位无法显示应答位 2、错误原因 我们打开信号传输的示波器&#xff0c;直接去查看IIC从机校验位的数据波形&#xff0c;可以看到从机示波器显示的的波形为半高ACK&#xff0c;那错误原…...

PHP判断闰年

闰年的规则 1.能被4整除且不能被100整除 &#xff08;普通闰年&#xff09; 2.能被400整除&#xff0c;公历年份是整百数的&#xff0c;必须是400的倍数才是闰年&#xff08;世纪闰年&#xff09; 代码 function isLeapYear($year) {if($year%40 && $year%100!0){r…...

证照之星XE专业版下载专业证件照制作工具

值得肯定的是智能背景替换功能&#xff0c;轻松解决背景处理这一世界难题。不得不提及的是新增打印字体设置&#xff0c;包含字体选择、字号大小、字体颜色等。不同领域的应用证明了万能制作&#xff0c;系统支持自定义证照规格&#xff0c;并预设了17种常用的证件照规格。人所…...

VR全景图片如何制作?揭秘VR全景图片制作全流程

引言&#xff1a; VR全景图片是一种以全景视角为基础的图片制作技术&#xff0c;能够呈现出更为真实、立体的视觉体验。通过VR全景图片&#xff0c;观众可以360环顾四周&#xff0c;仿佛身临其境&#xff0c;提供了一种全新的感官体验&#xff0c;那么如何制作出令人满意的全景…...

vue element el-table-column 循环示例代码

如果你想循环生成多个el-table-column&#xff0c;可以使用v-for指令。以下是一个示例&#xff1a; <template><el-table :data"tableData"><el-table-column v-for"column in columns" :key"column.prop" :label"column.l…...

R语言生物群落(生态)数据统计分析与绘图实践技术应用

R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#xff0c;涉及众多统计分析方法。以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线&#xff0c;通过多个来自经典…...

有了 GPT,还需要付费咨询吗?

之前写过一篇文章《在创业公司&#xff0c;我靠它续命 …》&#xff0c;提到现在写代码基本靠 GPT。现在这种状况不仅没有改变&#xff0c;反而依赖更深。公司立项开发产品的 Linux 版本&#xff0c;全靠我一个人。我之前虽然一直使用 Linux 开发环境&#xff0c;对 Linux 系统…...

如何搭建一台服务器?

一.准备工作 1. 确定服务器类型&#xff1a;根据需求选择适合的服务器类型&#xff0c;如网站服务器、数据库服务器、文件服务器等。 2. 选择操作系统&#xff1a;根据服务器类型选择合适的操作系统&#xff0c;如Linux&#xff08;如Ubuntu、CentOS&#xff09;、Windows Se…...

[转载]C++序列化框架介绍和对比

Google Protocol Buffers Protocol buffers 是一种语言中立&#xff0c;平台无关&#xff0c;可扩展的序列化数据的格式&#xff0c;可用于通信协议&#xff0c;数据存储等。 Protocol buffers 在序列化数据方面&#xff0c;它是灵活的&#xff0c;高效的。相比于 XML 来说&…...

分类预测 | Matlab实现KOA-CNN-BiLSTM-selfAttention多特征分类预测(自注意力机制)

分类预测 | Matlab实现KOA-CNN-BiLSTM-selfAttention多特征分类预测&#xff08;自注意力机制&#xff09; 目录 分类预测 | Matlab实现KOA-CNN-BiLSTM-selfAttention多特征分类预测&#xff08;自注意力机制&#xff09;分类效果基本描述程序设计参考资料 分类效果 基本描述 1…...

浮点数和定点数(上):怎么用有限的Bit表示尽可能多的信息?

目录 背景 浮点数的不精确性 定点数的表示 浮点数的表示 小结 背景 在我们日常的程序开发中&#xff0c;不只会用到整数。更多情况下&#xff0c;我们用到的都是实数。比如&#xff0c;我们开发一个电商 App&#xff0c;商品的价格常常会是 9 块 9&#xff1b;再比如&…...

一文详解汽车电子LIN总线

0.摘要 汽车电子LIN总线不同于CAN总线。 LIN总线基本上是CAN总线的廉价补充&#xff0c;相比于CAN总线&#xff0c;它提供较低的可靠性和性能。同时LIN总线也是一个应用非常广泛的网络协议&#xff0c;并且越来越受欢迎。 再一次&#xff0c;我们准备了一个关于LIN总线的简要…...

论文阅读——GPT3

来自论文&#xff1a;Language Models are Few-Shot Learners Arxiv&#xff1a;https://arxiv.org/abs/2005.14165v2 记录下一些概念等。&#xff0c;没有太多细节。 预训练LM尽管任务无关&#xff0c;但是要达到好的效果仍然需要在特定数据集或任务上微调。因此需要消除这个…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...