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

Go语言 Web框架Gin

Go语言 Web框架Gin

参考
https://docs.fengfengzhidao.com

https://www.liwenzhou.com/posts/Go/gin/#c-0-7-2

返回各种值

返回字符串

package mainimport ("net/http""github.com/gin-gonic/gin")func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "helloworld")})router.Run(":8080")}

返回json

package mainimport ("net/http""github.com/gin-gonic/gin")type Student struct {Name string `json:"name"`Age int `json:"age"`Number string `json: "number"`}func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {var student Student = Student{Name: "meowrain",Age: 20,Number: "10086",}c.JSON(http.StatusOK, student)})router.Run(":8080")}

返回map

package mainimport ("net/http""github.com/gin-gonic/gin")func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {userMap := map[string]any{"username": "meowrain","age": 20,"number": 10086,}c.JSON(http.StatusOK, userMap)})router.Run(":8080")}

返回原始json

package mainimport ("net/http""github.com/gin-gonic/gin")func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"username": "meowrain","age": 20,"number": 10086,})})router.Run(":8080")}

返回html并传递参数

package mainimport ("net/http""github.com/gin-gonic/gin")func _html(c *gin.Context) {type UserInfo struct {Username string `json:"username"`Age int `json:"age"`Password string `json:"-"`}user := UserInfo{Username: "meowrain",Age: 20,Password: "12345678",}c.HTML(http.StatusOK, "index.html", gin.H{"obj": user})}func main() {router := gin.Default()router.LoadHTMLGlob("template/*")router.GET("/", _html)router.Run(":8080")}
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h1>User Information</h1><p>Username: {{.obj.Username}}</p><p>Age: {{.obj.Age}}</p></body></html>

静态文件配置

router.Staticrouter.StaticFS都是用于处理静态文件的 Gin 框架路由处理方法,但它们有一些区别。

  1. router.Static:

    • 使用 router.Static 时,Gin 会简单地将请求的 URL 路径与提供的本地文件系统路径进行映射。通常,这适用于将 URL 路径直接映射到一个静态文件或目录。
    • 示例:router.Static("/static", "./static")/static 映射到当前工作目录下的 ./static 文件夹。
  2. router.StaticFS:

    • router.StaticFS 则允许你使用 http.FileSystem 对象,这可以提供更多的灵活性。你可以使用 http.Dir 创建 http.FileSystem,并将其传递给 router.StaticFS
    • 这允许你更灵活地处理静态文件,例如从不同的源(内存、数据库等)加载静态文件,而不仅限于本地文件系统。
    • 示例:router.StaticFS("/static", http.Dir("/path/to/static/files")) 使用本地文件系统路径创建一个 http.FileSystem 对象,然后将 /static 映射到这个文件系统。

总体而言,router.Static更简单,适用于基本的静态文件服务,而router.StaticFS提供了更多的灵活性,允许你自定义静态文件的加载方式。选择使用哪一个取决于你的具体需求。

package mainimport ("net/http""github.com/gin-gonic/gin")func _html(c *gin.Context) {type UserInfo struct {Username string `json:"username"`Age int `json:"age"`Password string `json:"-"`}user := UserInfo{Username: "meowrain",Age: 20,Password: "12345678",}c.HTML(http.StatusOK, "index.html", gin.H{"obj": user})}func main() {router := gin.Default()router.LoadHTMLGlob("template/*")router.Static("/static/", "./static")router.GET("/", _html)router.Run(":8080")}

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h1>User Information</h1><p>Username: {{.obj.Username}}</p><p>Age: {{.obj.Age}}</p><img src="/static/c68a16221f5bdf5486749d0993052981178827471.jpg" /></body></html>

重定向

package mainimport ("net/http""github.com/gin-gonic/gin")func _html(c *gin.Context) {type UserInfo struct {Username string `json:"username"`Age int `json:"age"`Password string `json:"-"`}user := UserInfo{Username: "meowrain",Age: 20,Password: "12345678",}c.HTML(http.StatusOK, "index.html", gin.H{"obj": user})}func _redirect(c *gin.Context) {c.Redirect(301, "https://www.baidu.com")}func main() {router := gin.Default()router.LoadHTMLGlob("template/*")router.Static("/static/", "./static")router.GET("/", _html)router.GET("/baidu", _redirect)router.Run(":8080")}

301和302的区别

HTTP状态码中的301和302分别表示重定向(Redirect)。它们之间的主要区别在于重定向的性质和原因:

  1. 301 Moved Permanently(永久重定向):

    • 当服务器返回状态码301时,它告诉客户端请求的资源已经被永久移动到新的位置。
    • 客户端收到301响应后,应该更新书签、链接等,将这个新的位置作为将来所有对该资源的请求的目标。
    • 搜索引擎在遇到301时,通常会更新索引,将原始URL替换为新的URL。
  2. 302 Found(临时重定向):

    • 当服务器返回状态码302时,它表示请求的资源暂时被移动到了另一个位置。
    • 客户端收到302响应后,可以在不更新书签和链接的情况下继续使用原始URL。
    • 搜索引擎在遇到302时,通常会保留原始URL在索引中,并不会立即更新为新的URL。

总体来说,使用301通常是在确定资源永久移动的情况下,而302通常用于暂时性的重定向,即资源可能在将来回到原始位置。选择使用哪种状态码取决于你希望客户端和搜索引擎如何处理被重定向的资源。

路由

默认路由

当访问路径不被匹配的时候返回默认路由内容

目录结构

image-20240308205926484

//main.go
package mainimport ("awesomeProject/pkg/controller""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")router.GET("/", func(c *gin.Context) {c.String(200, "helloworld")})router.NoRoute(controller.Default_route)router.Run(":80")
}
//server.go
package controllerimport ("github.com/gin-gonic/gin""net/http"
)func Default_route(c *gin.Context) {c.HTML(http.StatusNotFound, "404.html", nil)
}
<!--404.html-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>404 NOT FOUND</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>

效果

image-20240308210004320

路由组

参考:https://www.liwenzhou.com/posts/Go/gin/#c-0-7-2

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

//main.go
package mainimport ("awesomeProject/pkg/controller""github.com/gin-gonic/gin"
)func main() {router := gin.Default()userGroup := router.Group("/user"){userGroup.GET("/all", controller.GetUserList)userGroup.GET("/detail", controller.GetUserDetail)}router.LoadHTMLGlob("templates/*")router.NoRoute(controller.Default_route)router.Run(":80")
}
//controller/userController.go
package controllerimport (. "awesomeProject/pkg/entity""github.com/gin-gonic/gin""net/http""strconv"
)func GetUserList(c *gin.Context) {c.JSON(http.StatusOK, Response{Code: http.StatusOK,Data: UserList,Msg:  "返回成功",})
}
func GetUserDetail(c *gin.Context) {id := c.Query("id")for _, res := range UserList {if strconv.Itoa(res.ID) == id {c.JSON(http.StatusOK, Response{Code: http.StatusOK,Data: res,Msg:  "get successfully",})}}
}
//user.go
package entitytype User struct {ID   int    `json:"id"`Name string `json:"name"`Age  int    `json:"age"`
}type Response struct {Code int    `json:"code"`Data any    `json:"data"`Msg  string `json:"msg"`
}var UserList []User = []User{{ID:   1,Name: "meowrian",Age:  20,},{ID:   2,Name: "Mike",Age:  30,},{ID:   3,Name: "Amy",Age:  23,},{ID:   4,Name: "John",Age:  24,},
}
//server.go
package controllerimport ("github.com/gin-gonic/gin""net/http"
)func Default_route(c *gin.Context) {c.HTML(http.StatusNotFound, "404.html", nil)
}

image-20240308213055236

image-20240308213116279

路由组也是支持嵌套的

参数

查询参数

package mainimport ("net/http""github.com/gin-gonic/gin")func _query(c *gin.Context) {user := c.Query("user")c.HTML(http.StatusOK, "index.html", gin.H{"user": user,})}func main() {router := gin.Default()router.LoadHTMLGlob("template/*")router.Static("/static", "./static")router.GET("/", _query)router.Run(":8080")}

package main  import (  "fmt"  "net/http"  "github.com/gin-gonic/gin")  func _query(c *gin.Context) {  user, ok := c.GetQuery("user")  ids := c.QueryArray("id") //拿到多个相同的查询参数  maps := c.QueryMap("id")  fmt.Println(maps)  if ok {  c.HTML(http.StatusOK, "index.html", gin.H{  "user": user,  "id":   ids,  })  } else {  c.String(http.StatusOK, "No query!")  }  
}  func main() {  router := gin.Default()  router.LoadHTMLGlob("template/*")  router.Static("static", "./static")  router.GET("/", _query)  router.Run(":8080")  
}

请求为: http://127.0.0.1:8080/?user=good&id=1&id=2&id=3&id[good]=meowrain

动态参数

package main  import (  "fmt"  "github.com/gin-gonic/gin"    "net/http")  func _param(c *gin.Context) {  param := c.Param("user_id")  fmt.Println(param)  c.HTML(http.StatusOK, "index.html", gin.H{  "param": param,  })  }  
func main() {  router := gin.Default()  router.LoadHTMLGlob("template/*")  router.Static("static", "./static")  router.GET("/param/:user_id", _param)  router.Run(":8080")  
}

表单参数PostForm

package main  import (  "github.com/gin-gonic/gin"  "net/http")  func postForm(c *gin.Context) {  name := c.PostForm("name")  password := c.PostForm("password")  c.JSON(http.StatusOK, gin.H{  "name":     name,  "password": password,  })  
}  
func index(c *gin.Context) {  c.HTML(http.StatusOK, "index.html", gin.H{})  
}  
func main() {  router := gin.Default()  router.LoadHTMLGlob("template/*")  router.Static("static", "./static")  router.GET("/", index)  router.POST("/post", postForm)  router.Run(":8080")  
}
<!DOCTYPE html>  
<html lang="en">  
<head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Post Form Test</title>  
</head>  
<body>  
<h1>Post Form Test</h1>  
<form id="myForm" action="/post" method="post">  <label for="name">Name:</label>  <input type="text" id="name" name="name" required>  <br>    <label for="password">Password: </label>  <input type="password" id="password" name="password" required>  <br>    <button type="button" onclick="postData()">Submit</button>  
</form>  
<h3 id="response">Response: </h3>  
<script>  function postData() {  var form = document.getElementById("myForm");  var formData = new FormData(form);  var resp = document.getElementById("response");  fetch('http://127.0.0.1:8080/post', {  method: 'POST',  body: formData  })  .then(response => response.json())  .then(data => {  console.log('Success:', data);  resp.innerText = "Response: " + JSON.stringify(data)  })  .catch((error) => {  console.error('Error:', error);  resp.innerText = "Response Error: " + JSON.stringify(error)  });  }  
</script>  
</body>  
</html>

postFormArray函数

package main  import (  "github.com/gin-gonic/gin"  "net/http")  func postForm(c *gin.Context) {  name := c.PostForm("name")  password := c.PostForm("password")  respArr := c.PostFormArray("name")  c.JSON(http.StatusOK, gin.H{  "name":      name,  "password":  password,  "respArray": respArr,  })  
}  
func index(c *gin.Context) {  c.HTML(http.StatusOK, "index.html", gin.H{})  
}  
func main() {  router := gin.Default()  router.LoadHTMLGlob("template/*")  router.Static("static", "./static")  router.GET("/", index)  router.POST("/post", postForm)  router.Run(":8080")  
}

原始参数

/*  
原始参数  
*/  
package main  import (  "fmt"  "github.com/gin-gonic/gin")  func _raw(c *gin.Context) {  buf, err := c.GetRawData()  if err != nil {  fmt.Println("error:", err)  return  }  fmt.Println(string(buf))  
}  
func main() {  router := gin.Default()  router.POST("/", _raw)  router.Run(":8080")  
}

解析json数据

/*  
原始参数  
*/  
package main  import (  "encoding/json"  "fmt"    "github.com/gin-gonic/gin")  func bindJSON(c *gin.Context, obj any) error {  body, err := c.GetRawData()  contentType := c.GetHeader("Content-Type")  fmt.Println("ContentType:", contentType)  if err != nil {  fmt.Println("error:", err)  return err  }  switch contentType {  case "application/json":  err := json.Unmarshal(body, obj)  if err != nil {  fmt.Println(err.Error())  return err  }  }  return nil  
}  func raw(c *gin.Context) {  type User struct {  Name     string `json:"name"`  Age      int    `json:"age"`  Password string `json:"-"`  }  var user User  err := bindJSON(c, &user)  if err != nil {  fmt.Println("Error binding JSON:", err)  return  }  fmt.Println(user)  
}  func main() {  router := gin.Default()  router.POST("/", raw)  router.Run(":8080")  
}

四大请求方式

简单实现以下CRUD

package main  import (  "github.com/gin-gonic/gin"  "net/http"    "strconv")  type Article struct {  Id      int    `json:"id"`  Title   string `json:"title"`  Content string `json:"content"`  Author  string `json:"author"`  
}  type Response struct {  Code int    `json:"code"`  Data any    `json:"data"`  Msg  string `json:"msg"`  
}  var articleList []Article = []Article{  {  1,  "Go语言从入门到精通",  "Learn better",  "Mike Jason",  },  {  2,  "Java从入门到精通",  "Java is good",  "Jack Smith",  },  {  3,  "Javascript从入门到精通",  "Javascript is a nice programming language!",  "Amy Gorden",  },  {  4,  "Python从入门到精通",  "Python is a simple language!",  "Jack Buffer",  },  
}  /*简单增删改查*/  
func _getList(c *gin.Context) {  c.JSON(http.StatusOK, Response{Code: 200, Data: articleList, Msg: "获取成功"})  
}  
func _getDetail(c *gin.Context) {  id := c.Param("id")  flag := false  for _, res := range articleList {  if strconv.Itoa(res.Id) == id {  flag = true  c.JSON(http.StatusOK, Response{  Code: 200,  Data: res,  Msg:  "获取成功!",  })  }  }  if flag == false {  c.JSON(404, Response{  Code: 404,  Data: "Not Found the data",  Msg:  "获取失败,因为数据不存在",  })  }  
}  
func _create(c *gin.Context) {  id, _ := strconv.ParseInt(c.PostForm("id"), 10, 0)  title := c.PostForm("title")  content := c.PostForm("content")  author := c.PostForm("author")  var article Article = Article{  Id:      int(id),  Title:   title,  Content: content,  Author:  author,  }  articleList = append(articleList, article)  c.JSON(200, Response{Code: 200, Data: article, Msg: "添加成功!"})  
}  
func _delete(c *gin.Context) {  id := c.Param("id")  index := -1  for i, res := range articleList {  if strconv.Itoa(res.Id) == id {  index = i  break  }  }  if index != -1 {  articleList = append(articleList[:index], articleList[index+1:]...)  c.JSON(http.StatusOK, Response{Code: 200, Data: nil, Msg: "删除成功"})  } else {  c.JSON(http.StatusNotFound, Response{Code: 404, Data: "Not Found the data", Msg: "删除失败,数据不存在"})  }  
}  
func _update(c *gin.Context) {  id, _ := strconv.Atoi(c.Param("id"))  title := c.PostForm("title")  content := c.PostForm("content")  author := c.PostForm("author")  found := false  for i, res := range articleList {  if res.Id == id {  found = true  articleList[i] = Article{  id,  title,  content,  author,  }  break  }  }  if found {  c.JSON(http.StatusOK, Response{  Code: 200,  Data: nil,  Msg:  "更新成功",  })  return  } else {  c.JSON(http.StatusNotFound, Response{  Code: 404,  Data: "Not found the data",  Msg:  "更新失败,因为数据不存在",  })  }  }  func main() {  router := gin.Default()  router.GET("/articles", _getList)  router.GET("/articles/:id", _getDetail)  router.POST("/articles", _create)  router.PUT("/articles/:id", _update)  router.DELETE("/articles/:id", _delete)  router.Run(":8080")  }

文件上传

上传单个文件

package mainimport ("awesomeProject/pkg/controller""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")router.GET("/", func(c *gin.Context) {c.HTML(200, "upload.html", nil)})router.POST("/upload",controller.Upload_file)router.NoRoute(controller.Default_route)router.Run(":80")
}
package controllerimport ("fmt""github.com/gin-gonic/gin""log""net/http"
)func Default_route(c *gin.Context) {c.HTML(http.StatusNotFound, "404.html", nil)
}
//文件上传
func Upload_file(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("./tmp/%s", file.Filename)c.SaveUploadedFile(file, dst)c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("'%s' uploaded", file.Filename),})
}
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>上传文件示例</title><style>body {font-family: Arial, sans-serif;background-color: #f2f2f2;display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;}form {background-color: #fff;padding: 30px;border-radius: 5px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);text-align: center;}input[type="file"] {margin-bottom: 20px;}input[type="submit"] {background-color: #4CAF50;color: white;padding: 10px 20px;border: none;border-radius: 4px;cursor: pointer;}input[type="submit"]:hover {background-color: #45a049;}</style>
</head>
<body>
<form id="uploadForm"><input type="file" id="fileInput" name="f1"><input type="button" value="上传" onclick="uploadFile()">
</form><script>function uploadFile() {let fileInput = document.getElementById('fileInput');let file = fileInput.files[0];if (file) {let formData = new FormData();formData.append('f1', file);fetch('/upload', {method: 'POST',body: formData}).then(response => response.json()).then(result => {console.log(result);}).catch(error => {console.error('Error:', error);});} else {console.error('No file selected.');}}
</script>
</body>
</html>

image-20240308214643811

image-20240308220222511

上传多个文件

package mainimport ("awesomeProject/pkg/controller""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")router.GET("/", func(c *gin.Context) {c.HTML(200, "upload.html", nil)})router.POST("/upload", controller.UploadFiles)router.NoRoute(controller.Default_route)router.Run(":80")
}
package controllerimport ("fmt""github.com/gin-gonic/gin""log""net/http"
)func Default_route(c *gin.Context) {c.HTML(http.StatusNotFound, "404.html", nil)
}
func UploadFiles(c *gin.Context) {err := c.Request.ParseMultipartForm(100 << 20) // 100 MB limitif err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}form := c.Request.MultipartFormif form == nil || form.File == nil {c.JSON(http.StatusBadRequest, gin.H{"message": "No files provided in the request",})return}files := form.File["f1"]for _, file := range files {dst := fmt.Sprintf("./tmp/%s", file.Filename)if err := c.SaveUploadedFile(file, dst); err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": fmt.Sprintf("Failed to save file %s: %s", file.Filename, err.Error()),})return}log.Println(file.Filename)}c.JSON(http.StatusOK, gin.H{"message": "Files uploaded successfully",})
}
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>上传文件示例</title><style>body {font-family: Arial, sans-serif;background-color: #f2f2f2;display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;}form {background-color: #fff;padding: 30px;border-radius: 5px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);text-align: center;}input[type="file"] {margin-bottom: 20px;}input[type="submit"] {background-color: #4CAF50;color: white;padding: 10px 20px;border: none;border-radius: 4px;cursor: pointer;}input[type="submit"]:hover {background-color: #45a049;}</style>
</head>
<body>
<form id="uploadForm"><input type="file" id="fileInput" name="f1" multiple><input type="button" value="上传" onclick="uploadFile()">
</form><script>function uploadFile() {let fileInput = document.getElementById('fileInput');let formData = new FormData();for (const file of fileInput.files) {formData.append('f1', file);}fetch('/upload', {method: 'POST',body: formData}).then(response => response.json()).then(result => {console.log(result);}).catch(error => {console.error('Error:', error);});}
</script>
</body>
</html>

image-20240308220131880

image-20240308220155287

判断上传文件的类型

在Gin框架中,可以使用binding模块提供的FormFile函数来获取上传的文件,然后检查文件的MIME类型。具体步骤如下:

  1. 在处理函数中使用c.FormFile获取上传的文件:
file, err := c.FormFile("file")
if err != nil {c.String(http.StatusBadRequest, "获取文件失败")return
}
  1. 打开文件并读取文件头部的几个字节,以识别文件的MIME类型:
f, err := file.Open()
if err != nil {c.String(http.StatusInternalServerError, "打开文件失败")return
}
defer f.Close()buffer := make([]byte, 512)
_, err = f.Read(buffer)
if err != nil {c.String(http.StatusInternalServerError, "读取文件失败")return
}
  1. 使用http.DetectContentType函数检测文件的MIME类型:
contentType := http.DetectContentType(buffer)
  1. 判断文件类型是否允许:
allowedTypes := []string{"image/jpeg", "image/png", "application/pdf"}
allowed := false
for _, t := range allowedTypes {if t == contentType {allowed = truebreak}
}if !allowed {c.String(http.StatusBadRequest, "不支持的文件类型")return
}

完整的示例代码如下:

func uploadFile(c *gin.Context) {file, err := c.FormFile("file")if err != nil {c.String(http.StatusBadRequest, "获取文件失败")return}f, err := file.Open()if err != nil {c.String(http.StatusInternalServerError, "打开文件失败")return}defer f.Close()buffer := make([]byte, 512)_, err = f.Read(buffer)if err != nil {c.String(http.StatusInternalServerError, "读取文件失败")return}contentType := http.DetectContentType(buffer)allowedTypes := []string{"image/jpeg", "image/png", "application/pdf"}allowed := falsefor _, t := range allowedTypes {if t == contentType {allowed = truebreak}}if !allowed {c.String(http.StatusBadRequest, "不支持的文件类型")return}// 处理文件...
}

在上面的示例中,我们定义了一个允许的MIME类型列表allowedTypes,包括image/jpegimage/pngapplication/pdf。如果上传的文件类型不在允许列表中,就会返回错误响应。你可以根据需求修改允许的文件类型列表。

使用gin编写文件服务器

package controllerimport ("fmt""github.com/gin-gonic/gin""log""net/http""os"
)func Default_route(c *gin.Context) {c.HTML(http.StatusNotFound, "404.html", nil)
}
func UploadFiles(c *gin.Context) {err := c.Request.ParseMultipartForm(100 << 20) // 100 MB limitif err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}form := c.Request.MultipartFormif form == nil || form.File == nil {c.JSON(http.StatusBadRequest, gin.H{"message": "No files provided in the request",})return}files := form.File["f1"]for _, file := range files {dst := fmt.Sprintf("./tmp/%s", file.Filename)if err := c.SaveUploadedFile(file, dst); err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": fmt.Sprintf("Failed to save file %s: %s", file.Filename, err.Error()),})return}log.Println(file.Filename)}c.JSON(http.StatusOK, gin.H{"message": "Files uploaded successfully",})
}
func ListFiles(c *gin.Context) {// 读取 ./tmp 目录下的所有文件files, err := os.ReadDir("./tmp")if err != nil {c.String(http.StatusInternalServerError, err.Error())return}// 渲染模板c.HTML(http.StatusOK, "download.html", gin.H{"Files": files,})
}
package mainimport ("awesomeProject/pkg/controller""github.com/gin-gonic/gin"
)func main() {r := gin.Default()// 设置静态文件路径为 ./tmpr.Static("/tmp", "./tmp")// 设置模板目录r.LoadHTMLGlob("templates/*")// 定义路由r.GET("/", func(c *gin.Context) {c.HTML(200, "upload.html", nil)})r.POST("/upload", controller.UploadFiles)//文件列表服务器r.GET("/files", controller.ListFiles)// 启动HTTP服务器r.Run(":8080")
}
<!--download.html -->
<!DOCTYPE html>
<html>
<head><title>File List</title>
</head>
<body>
<h1>File List</h1>
<ul>{{ range .Files }}<li><a href="/tmp/{{ .Name }}">{{ .Name }}</a></li>{{ end }}
</ul>
</body>
</html>
<!--美化版-->
<!DOCTYPE html>
<html>
<head><title>File List</title><style>body {font-family: Arial, sans-serif;background-color: #f5f5f5;padding: 20px;}h1 {color: #333;text-align: center;}ul {list-style-type: none;margin: 0;padding: 0;display: flex;flex-wrap: wrap;justify-content: center;}li {background-color: #fff;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);margin: 10px;padding: 10px;border-radius: 5px;text-align: center;}a {text-decoration: none;color: #333;}a:hover {color: #666;}</style>
</head>
<body>
<h1>File List</h1>
<ul>{{ range .Files }}<li><a href="/tmp/{{ .Name }}">{{ .Name }}</a></li>{{ end }}
</ul>
</body>
</html>

image-20240308222026615

image-20240308222225060

image-20240308222527025

请求头相关

获取所有请求头

package main  import (  "fmt"  "github.com/gin-gonic/gin"    "net/http")  func main() {  router := gin.Default()  router.LoadHTMLGlob("template/*")  router.Static("static", "./static")  router.GET("/", func(c *gin.Context) {  c.HTML(http.StatusOK, "index.html", gin.H{  "header": c.Request.Header,  })  fmt.Println(c.Request.Header)  })  router.Run(":8080")  
}
<!DOCTYPE html>  
<html lang="en">  
<head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Post Form Test</title>  
</head>  
<body>  
<h1>Header Test</h1>  
<h3>Header: {{.header}}</h3>  
</body>  
</html>

绑定参数bind

绑定post发送的json数据转换为Student结构体的成员变量值,然后再把这个结构体转换为json对象

package main  import (  "fmt"  "github.com/gin-gonic/gin"    "net/http")  type Student struct {  Name string `json:"name"`  Age  int    `json:"age"`  
}  func main() {  router := gin.Default()  router.POST("/", func(c *gin.Context) {  var stu Student  err := c.BindJSON(&stu)  if err != nil {  fmt.Println("error: ", err)  c.JSON(http.StatusBadGateway, err)  return  }  c.JSON(http.StatusOK, stu)  })  router.Run(":8080")  
}

绑定查询参数

package main  import (  "fmt"  "github.com/gin-gonic/gin"    "net/http")  type Student struct {  Name string `json:"name" form:"name"`  Age  int    `json:"age" form:"age"`  
}  func main() {  router := gin.Default()  router.GET("/", func(c *gin.Context) {  var stu Student  err := c.BindQuery(&stu)  if err != nil {  fmt.Println("error: ", err)  c.JSON(http.StatusBadGateway, err)  return  }  c.JSON(http.StatusOK, stu)  })  router.Run(":8080")  
}

bind URI

package main  import (  "fmt"  "github.com/gin-gonic/gin"    "net/http")  type Student struct {  Name string `json:"name" form:"name" uri:"name"`  Age  int    `json:"age" form:"age" uri:"age"`  
}  func main() {  router := gin.Default()  router.GET("/uri/:name/:age", func(c *gin.Context) {  var stu Student  err := c.ShouldBindUri(&stu)  if err != nil {  fmt.Println("error: ", err)  c.JSON(http.StatusBadGateway, err)  return  }  c.JSON(http.StatusOK, stu)  })  router.Run(":8080")  
}

常用验证器

// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"  // 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值- 忽略字段,如:binding:"-"
package main  import (  "github.com/gin-gonic/gin"  "net/http")  type User struct {  Name        string `json:"name" binding:"required"`  Password    string `json:"password" binding:"eqfield=Re_Password"`  Re_Password string `json:"re_password"`  
}  
type Response struct {  Code int    `json:"code"`  Data any    `json:"data"`  Msg  string `json:"msg"`  
}  func main() {  router := gin.Default()  router.POST("/login", func(c *gin.Context) {  var user User  err := c.ShouldBindJSON(&user)  if err != nil {  c.JSON(http.StatusBadGateway, Response{  Code: http.StatusBadGateway,  Data: err.Error(),  Msg:  "bad response",  })  return  }  c.JSON(http.StatusOK, Response{  Code: http.StatusOK,  Data: user,  Msg:  "post successfully",  })  })  router.Run(":8080")  
}

密码相同

密码不同

我们看到报错对用户不是很友好,我们可以自定义验证的错误信息

TODO

gin内置验证器

// 枚举  只能是red 或green
oneof=red green // 字符串  
contains=fengfeng  // 包含fengfeng的字符串
excludes // 不包含
startswith  // 字符串前缀
endswith  // 字符串后缀// 数组
dive  // dive后面的验证就是针对数组中的每一个元素// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径// 日期验证  1月2号下午3点4分5秒在2006年
datetime=2006-01-02

Gin中间件

https://www.liwenzhou.com/posts/Go/gin/#c-0-8-3

Gin中的中间件必须是一个gin.HandlerFunc类型。

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

记录接口耗时的中间件

相关文章:

Go语言 Web框架Gin

Go语言 Web框架Gin 参考 https://docs.fengfengzhidao.com https://www.liwenzhou.com/posts/Go/gin/#c-0-7-2 返回各种值 返回字符串 package mainimport ("net/http""github.com/gin-gonic/gin")func main() {router : gin.Default()router.GET("…...

蓝桥杯-洛谷刷题-day5(C++)(为未完成)

1.P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 i.题目 ii.代码 #include <iostream> #include <string> using namespace std;int N, Na, Nb; //0-"剪刀", 1-"石头", 2-"布", 3-"蜥", 4-"斯"&#xff1…...

【Unity3D优化】使用ASTC压缩格式优化内存

在Unity3D手游开发中&#xff0c;合理选择纹理压缩格式对于优化内存占用、提高渲染效率至关重要。本文将记录近期在项目内进行的图片压缩格式优化过程&#xff0c;重点介绍从ETC2到ASTC 5x5的优化方案及其带来的收益。 1. 现状分析&#xff1a;从ETC2到ASTC 6x6 block 在项目…...

NO.13十六届蓝桥杯备战|条件操作符|三目操作符|逻辑操作符|!||||(C++)

条件操作符 条件操作符介绍 条件操作符也叫三⽬操作符&#xff0c;需要接受三个操作数的&#xff0c;形式如下&#xff1a; exp1 ? exp2 : exp3条件操作符的计算逻辑是&#xff1a;如果 exp1 为真&#xff0c; exp2 计算&#xff0c; exp2 计算的结果是整个表达式的结果&am…...

【uniapp-小程序】实现方法调用的全局tips弹窗

【uniapp-小程序】实现方法调用的全局tips弹窗 开发背景弹窗组件全局调用封装配置项入参全局注入使用 附带&#xff1a;如何在uniapp-H5项目中实现全局自定义弹窗组件定义定义vue插件引入 笑死&#xff0c;只有在想找工作的时候才会想更新博客。 开发背景 本来是个uniapp开发…...

springboot如何将lib和jar分离

遇到一个问题&#xff0c;就是每次maven package或者maven install后target中的jar很大&#xff0c;少的50几MB&#xff0c;大的100多兆 优化前&#xff1a; 优化后&#xff1a; 优化前 优化后压缩率77.2MB4.65MB93% 具体方案&#xff1a; pom.xml中 <build><…...

深入探索C语言中的字符串处理函数:strstr与strtok

在C语言的字符串处理领域&#xff0c; strstr 和 strtok 是两个非常重要的函数&#xff0c;它们各自承担着独特的功能&#xff0c;为开发者处理字符串提供了强大的支持。 一、strstr函数&#xff1a;字符串查找的利器 strstr 函数用于在一个字符串中查找另一个字符串的首次出现…...

Django学习笔记(第一天:Django基本知识简介与启动)

博主毕业已经工作一年多了&#xff0c;最基本的测试工作已经完全掌握。一方面为了解决当前公司没有自动化测试平台的痛点&#xff0c;另一方面为了向更高级的测试架构师转型&#xff0c;于是重温Django的知识&#xff0c;用于后期搭建测试自动化平台。 为什么不选择Java&#x…...

npm版本号标记

在 npm 中,版本号的标记遵循 语义化版本控制(Semantic Versioning, SemVer) 的规则,版本号通常由 主版本号(major)、次版本号(minor) 和 修订版本号(patch) 组成,格式为: <major>.<minor>.<patch>1. 版本号格式 主版本号(major):当你做了不兼…...

无人机雨季应急救灾技术详解

无人机在雨季应急救灾中发挥着至关重要的作用&#xff0c;其凭借机动灵活、反应迅速、高效安全等特点&#xff0c;为救灾工作提供了强有力的技术支撑。以下是对无人机雨季应急救灾技术的详细解析&#xff1a; 一、无人机在雨季应急救灾中的应用场景 1. 灾情侦查与监测 无人机…...

算法与数据结构(多数元素)

题目 思路 方法一&#xff1a;哈希表 因为要求出现次数最多的元素&#xff0c;所以我们可以使用哈希映射存储每个元素及其出现的次数。每次记录出现的次数若比最大次数大&#xff0c;则替换。 方法二&#xff1a;摩尔算法 摩尔的核心算法就是对抗&#xff0c;因为存在次数多…...

详解如何使用Pytest内置Fixture tmp_path 管理临时文件

关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 临时目录在测试中起着至关重要的作用&#xff0c;它为执行和验证代码提供了一个可控…...

量子计算的五大优势

量子计算的优势有哪些&#xff1f; 量子计算是一个快速发展的领域&#xff0c;有望彻底改变我们处理复杂计算问题的方式。那么&#xff0c;量子计算的优势是什么&#xff1f;与经典计算相比&#xff0c;量子计算又有哪些优势呢&#xff1f;当我们探索量子力学的世界以及量子系…...

行内元素和块级元素

行内元素和块级元素 1.行内元素1.1什么是行内元素1.2行内元素的特点1.3常见的行内元素 2.块级元素2.1什么是块级元素2.2块级元素的特点2.3常见的块级元素 3.行内元素和块级元素的区别 1.行内元素 1.1什么是行内元素 行内元素是指在网页中不会独占一行,而是与其他行内元素在同…...

java面试题-集合篇

Collection 1.Collection有哪些类&#xff1f; Java集合框架中的Collection接口是所有集合类的基础接口&#xff0c;定义了一些基本的集合操作&#xff0c;如添加元素、删除元素、判断是否包含某个元素等。常见的集合类包括List、Set和Queue。 List List接口定义了按照索引…...

二十九、vite项目集成webpack+vue2项目

一、开发 基座应用: 1、安装依赖 npm i @micro-zoe/micro-app@0.8.6 --save 2、在入口处引入(main.ts) import microApp from @micro-zoe/micro-appmicroApp.start()...

小程序之间实现互相跳转的逻辑

1:小程序之间可以实现互相跳转吗 可以实现互相跳转! 2:小程序跳转是否有限制 有限制!限制如下 2.1:需要用户触发跳转 从 2.3.0 版本开始,若用户未点击小程序页面任意位置,则开发者将无法调用此接口自动跳转至其他小程序。 2.2:需要用户确认跳转 从 2.3.0 版本开始…...

算法——数学建模的十大常用算法

数学建模的十大常用算法在数学建模竞赛和实际问题解决中起着至关重要的作用。以下是这些算法的具体信息、应用场景以及部分算法的C语言代码示例&#xff08;由于篇幅限制&#xff0c;这里只给出部分算法的简要代码或思路&#xff0c;实际应用中可能需要根据具体问题进行调整和扩…...

cookie、session、jwt、Oauth2.0、sso 分别有什么用

cookie、session、jwt都是 web 应用中的认证方式&#xff0c;最早只有 cookie&#xff0c;后面觉得所有数据存储在客户端不安全&#xff0c;就出现了 cookie-session&#xff0c;再后面有了 jwt。 cookie工作原理 cookie 数据存储在用户的本地。服务器完全根据 cookie 确定访…...

maven使用默认settings.xml配置时,Idea基于pom.xml更新依赖时报错,有些组件下载时连接超时

1、问题背景&#xff1a;maven使用默认settings.xml配置时&#xff0c;Idea基于pom.xml更新依赖时报错&#xff0c;有些组件下载时连接超时&#xff0c; 通过日志发下&#xff0c;去连接maven.org网站下载依赖&#xff0c;有时候肯定会超时。 2、解决办法&#xff1a;使用国外…...

信息收集-Web应用搭建架构指纹识别WAF判断蜜罐排除开发框架组件应用

知识点&#xff1a; 1、信息收集-Web应用-架构分析&指纹识别 2、信息收集-Web应用-架构分析&WAF&蜜罐 3、信息收集-Web应用-架构分析&框架组件识别 指纹识别 EHole_magic https://github.com/lemonlove7/EHole_magic 指纹识别 Wappalyzer https://github.com…...

蓝桥杯之图

图&#xff1a; 对于图来说&#xff0c;重点在于之后的最短路径算法&#xff0c;这边简单做一下了解即可...

ProxySQL构建PolarDB-X标准版高可用路由服务三节点集群

ProxySQL构建PolarDB-X标准版高可用路由服务三节点集群 一、PolarDB-X标准版主备集群搭建 三台机器上传 polardbx 包&#xff0c;包可以从官网https://openpolardb.com/download获取&#xff0c;这里提供离线rpm。 1、上传 polardbx 安装包 到 /opt目录下 rpm -ivh t-pol…...

【leetcode】双指针:移动零 and 复写零

文章目录 1.移动零2.复写零 1.移动零 class Solution { public:void moveZeroes(vector<int>& nums) {for (int cur 0, dest -1; cur < nums.size(); cur)if (nums[cur] ! 0)swap(nums[dest], nums[cur]);} };class Solution { public:void moveZeroes(vector&l…...

正则化(Regularization)和正则表达式(Regular Expression)区别

文章目录 1. **正则化&#xff08;Regularization&#xff09;**2. **正则表达式&#xff08;Regular Expression&#xff09;**关键区别为什么名字相近&#xff1f; 正则化&#xff08;Regularization&#xff09;和正则表达式&#xff08;Regular Expression&#xff09;不是…...

【C++】C++-教师信息管理系统(含源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 【C】C教师信息管理系统&#xff08;含源码&#x…...

MySql从入门到精通

第一部分 基础篇 1.概述 1.1 启动与停止MySql 启动 net start mysql80 停止 net stop mysql80 注意&#xff1a; mysql开机默认启动 1.2 客户端连接 方法一&#xff1a;使用MySQL提供的命令行客户端方法二&#xff1a;系统自带的命令行工具执行指令 mysql [-h 127.0.0.1] …...

27、深度学习-自学之路-NLP自然语言处理-做一个简单的项目识别一组电影评论,来判断电影评论是积极的,还是消极的。

一、如果我们要做这个项目&#xff0c;第一步我们要做的就是需要有对应的训练数据集。 这里提供两个数据集&#xff0c;一个是原始评论数据集《reviews.txt》&#xff0c;以及对应的评论是消极还是积极的数据集《labels.txt》&#xff0c;下面的程序就是找到这两个数据集&…...

微信小程序 - 组件和样式

组件和样式介绍 在开 Web 网站的时候&#xff1a; 页面的结构由 HTML 进行编写&#xff0c;例如&#xff1a;经常会用到 div、p、 span、img、a 等标签 页面的样式由 CSS 进行编写&#xff0c;例如&#xff1a;经常会采用 .class 、#id 、element 等选择器 但在小程序中不能…...

滤波总结 波形处理原理 如何对一个规律的波形进行滤波 显现出真正的波形 如何设计滤波

需要用到的软件:waveserialport vofa++ 1.波形想用MCU进行采集首先你要考虑到你的采集频率因为如果你的对象波形即你要采集的波形,他过于快速的话有一些MCU它是不能的比如说有一些它的主频才36兆72兆呢你如果遇到一个特别快的波形毫秒级别那他就检测不了 2.…...

【代码随想录】刷题记录(115)-岛屿数量(广搜)

题目描述&#xff1a; 题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周都是水域。你可以假设矩阵外均被水包围。 输入描…...

Git指南-从入门到精通

代码提交和同步命令 流程图如下&#xff1a; 第零步: 工作区与仓库保持一致第一步: 文件增删改&#xff0c;变为已修改状态第二步: git add &#xff0c;变为已暂存状态 bash $ git status $ git add --all # 当前项目下的所有更改 $ git add . # 当前目录下的所有更改 $ g…...

uvm错误记录4

如下所示&#xff0c;奇怪的是penable莫名其妙的出X。可问题&#xff0c;我发送激励了。 仔细定位发现&#xff0c;39行用的是vif中的penable, 问题是都是赋值&#xff0c;却出现同时赋值多次&#xff0c;这是因为nonblocking和blocking同时触发导致的&#xff0c;因此&#xf…...

用大模型学大模型03-数学基础 概率论

https://metaso.cn/s/Jp0OU9w 提示词:我正在学习深度学习相关的数学知识&#xff0c;学到了概率论与统计学部分&#xff0c;给我一个入门级的完整教程和实例。 好的&#xff0c;我现在需要回答用户关于深度学习所需的概率论与统计学入门教程和实例的问题。用户希望得到一个完整…...

数字电路-基础逻辑门实验

基础逻辑门是数字电路设计的核心元件&#xff0c;它们执行的是基本的逻辑运算。通过这些基本运算&#xff0c;可以构建出更为复杂的逻辑功能。常见的基础逻辑门包括与门&#xff08;AND&#xff09;、或门&#xff08;OR&#xff09;、非门&#xff08;NOT&#xff09;、异或门…...

基于springboot轨道交通安全评估系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 时代在飞速进步&#xff0c;每个行业都在努力发展现在先进技术&#xff0c;通过这些先进的技术来提高自己的水平和优势&#xff0c;轨道交通安全评估管理当然不能排除在外。轨道交通安全评估系统是在实际应用和软件工程的开发原理之上&#xff0c;运用Java语言以及Spring…...

多能互补综合能源系统,改变能源结构---安科瑞 吴雅芳

多能互补综合能源系统是一种通过整合多种能源的形势&#xff08;如电力、天然气、热能、冷能等&#xff09;和多种能源技术&#xff08;如可再生能源、储能技术、智能电网等&#xff09;&#xff0c;实现能源利用和配置调整的系统。其目标是通过多能互补和协同优化&#xff0c;…...

Python 量化

Python 量化是指利用 Python 编程语言以及相关的库和工具来进行金融市场数据分析、策略开发和交易执行的过程。 Python 由于其简洁、易学、强大的生态系统和丰富的金融库而成为量化交易的首选编程语言之一。 量化交易在金融领域得到广泛应用&#xff0c;它允许交易者通过系统…...

图数据库Neo4j面试内容整理-属性(Property)

在图数据库中,属性(Property)是用来描述节点(Node)和关系(Relationship)详细信息的键值对。属性可以附加到节点或关系上,用来存储具体的数据,如名字、年龄、时间戳、标签等。属性使得节点和关系不仅能够表示实体或交互,还能包含丰富的、与实体或交互相关的信息。 1. …...

uniapp - iconfont下载本地并且运用至项目上

1、项目中创建一个文件夹放置iconfont相关文件&#xff0c;例如src/assets/iconfont&#xff08;名称自己定义&#xff09; 2、在iconfont下载项目至本地 3、解压后把文件复制进1的文件夹中 4、修改src/assets/iconfont - iconfont.css里的font-face的src地址&#xff0c;修…...

leetcode 1594. 矩阵的最大非负积

题目如下 数据范围 示例 本题难就难在矩阵存在负数&#xff0c;我们可以先思考如果矩阵每个数都大于等于0那么很简单我们只需要维护左边和上面的最大值即可。那么如果遇到负数显然要得到最大值就要和左边和右边的最小值相乘。所以这里我们维护两个二维数组用于存从(0,0)开…...

Vue3 从入门到精通:全面掌握前端框架的进阶之路

一、Vue3 简介 Vue.js 是一款流行的 JavaScript 前端框架&#xff0c;用于构建用户界面。Vue3 作为 Vue.js 的重大升级版本&#xff0c;带来了诸多性能提升和新特性。它采用了 Proxy 实现数据响应式系统&#xff0c;优化了虚拟 DOM 算法&#xff0c;使得应用在运行时更加高效。…...

lightning.pytorch.callbacks内置的Callbacks介绍

PyTorch Lightning 提供了一些 内置回调 (Callback),可以在训练过程中自动执行 检查点保存、学习率调度、早停 等功能。通过使用 Trainer(callbacks=[...]) 来传入这些回调。 PyTorch Lightning 的 Callback 是一种强大的工具,允许用户在训练过程中插入自定义逻辑,而无需修…...

网络运维与网络安全技术分享

网络运维与网络安全介绍之二 在上阶段给大家基本介绍了网络运维与网络安全专业第一阶段的内容之后&#xff0c;接下来&#xff0c;我们就开始进入正式内容分享了&#xff01; 第一阶段&#xff1a;运维基础与网络系统管理之Windows系统的安装部署以及常见Windows应用技巧。 在这…...

基于巨控GRM242Q-4D4I4QHE模块的农村供水自动化监控技术方案

一、系统架构设计 拓扑结构&#xff1a; 传感器层&#xff08;液位/压力/流量&#xff09;→ 巨控GRM242Q模块 → 4G网络 → 云平台 → 手机/PC监控端硬件配置&#xff1a; 核心设备&#xff1a;GRM242Q-4D4I4QHE模块&#xff08;4DI/4DO/4AI/1485&#xff09;传感器&#xff1…...

Java 单元测试框架之 Mockito 详细介绍

本文是博主在学习如何高效创建单元测试时的知识记录&#xff0c;文中项目代码是基于 SpringBoot 项目&#xff0c;测试组件使用的 JUnit 5&#xff0c;单元测试组件使用的 Mockito 。虽然现在都是在使用 AI 助手帮助生成单元测试和代码辅助修改&#xff0c;但我们不能被工具挡住…...

对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,比较其各自的优势 , 基于 openEuler 构建 LVS-DR 群集。

对比 LVS 负载均衡群集的 NAT 模式和 DR 模式&#xff0c;比较其各自的优势 NAT模式的优势&#xff1a; 可以隐藏后端服务器的IP地址&#xff0c;提高了系统的安全性。 支持多个后端服务器共享同一个IP地址&#xff0c;提高了系统的可扩展性。 可以在负载均衡器和后端服务…...

mapbox V3 新特性,添加下雪效果

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;mapbox 从入门到精通 文章目录 一、&#x1f340;前言1.1 ☘️mapboxgl.Map 地图对象…...

谈谈云计算、DeepSeek和哪吒

我不会硬蹭热点&#xff0c;去分析自己不擅长的跨专业内容&#xff0c;本文谈DeepSeek和哪吒&#xff0c;都是以这两个热点为引子&#xff0c;最终仍然在分析的云计算。 这只是个散文随笔&#xff0c;没有严谨的上下游关联关系&#xff0c;想到哪里就写到哪里。 “人心中的成见…...

深入HBase——引入

引入 前面我们通过深入HDFS到深入MapReduce &#xff0c;从设计和落地&#xff0c;去深入了解了大数据最底层的基石——存储与计算是如何实现的。 这个专栏则开始来看大数据的三驾马车中最后一个。 通过前面我们对于GFS和MapReduce论文实现的了解&#xff0c;我们知道GFS在数…...