Files
cs-note/hhs/某公司文档/docs/Go语言web开发/02-gin框架基础.md
T
2026-05-24 12:43:51 +08:00

1375 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gin框架基础
# Gin框架简介
Gin是一个用Go语言编写的Web框架,它基于httprouter,提供了一种简单、快速、轻量级的方式来构建Web应用和API服务。Gin框架以其高性能和低内存占用而闻名,是目前Go语言中最受欢迎的Web框架之一。
## 1.1 Gin框架的特点
- **高性能**:基于Radix树路由匹配算法,速度极快
- **轻量级**:核心库代码量少,依赖少
- **中间件支持**:提供丰富的中间件机制
- **优雅的API**:简洁易用的接口设计
- **JSON验证**:能够方便地验证和解析JSON请求
- **路由分组**:支持路由分组,便于管理API版本和权限控制
- **错误管理**:内置错误处理机制
- **内置渲染**:支持JSON、XML、HTML等多种响应格式
## 1.2 与其他Go Web框架的比较
|框架|特点|适用场景|
|---|---|---|
|Gin|高性能、轻量级、简洁API|高性能API服务、微服务|
|Echo|高性能、可扩展、极简主义|全栈Web应用|
|Fiber|类Express设计、基于fasthttp|高性能Web服务|
|Gorilla|模块化设计、灵活性高|复杂Web应用|
# 第一个Gin应用
## 3.1 Hello World示例
创建`main.go`文件:
go复制代码
```go
package main
import "github.com/gin-gonic/gin"
func main() {
// 创建默认的路由引擎
r := gin.Default()
// 定义一个GET请求处理
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Gin!")
})
// 启动服务器
r.Run(":8080") // 监听并在0.0.0.0:8080上启动服务
}
```
运行应用:
bash复制代码
```bash
go run main.go
```
访问`http://localhost:8080`即可看到"Hello, Gin!"
# 路由(Route)
Gin提供了强大而灵活的路由机制,支持各种HTTP方法和路径匹配。
## 4.1 基本路由
go复制代码
```go
// GET方法
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello GET")
})
// POST方法
r.POST("/submit", func(c *gin.Context) {
c.String(200, "Hello POST")
})
// PUT方法
r.PUT("/update", func(c *gin.Context) {
c.String(200, "Hello PUT")
})
// DELETE方法
r.DELETE("/remove", func(c *gin.Context) {
c.String(200, "Hello DELETE")
})
// 匹配所有HTTP方法
r.Any("/anything", func(c *gin.Context) {
c.String(200, "Hello Any Method")
})
```
## 4.2 路径参数
go复制代码
```go
// 获取路径参数
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(200, "Hello %s", name)
})
// 使用*通配符
r.GET("/file/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(200, "File path: %s", filepath)
})
```
## 4.3 查询参数
go复制代码
```go
// 获取查询参数
r.GET("/search", func(c *gin.Context) {
query := c.Query("q") // 获取参数,没有则返回空字符串
page := c.DefaultQuery("page", "1") // 获取参数,没有则使用默认值
c.JSON(200, gin.H{
"query": query,
"page": page,
})
})
```
## 4.4 路由分组
go复制代码
```go
// 创建v1版本的API组
v1 := r.Group("/v1")
{
v1.GET("/users", getUsers)
v1.POST("/users", createUser)
v1.GET("/users/:id", getUserByID)
}
// 创建v2版本的API组
v2 := r.Group("/v2")
{
v2.GET("/users", getUsersV2)
v2.POST("/users", createUserV2)
}
```
# 中间件(Middleware)
中间件是Gin框架的核心特性之一,允许在请求处理过程中执行代码、修改请求和响应,以及终止请求处理流程。
## 5.1 内置中间件
Gin提供了多种内置中间件:
go复制代码
```go
// 默认使用Logger和Recovery中间件
r := gin.Default()
// 不使用任何中间件
r := gin.New()
// 手动添加Logger中间件
r.Use(gin.Logger())
// 手动添加Recovery中间件(从任何panic恢复)
r.Use(gin.Recovery())
```
## 5.2 自定义中间件
创建自定义中间件:
go复制代码
```go
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 中间件逻辑(请求前)
token := c.GetHeader("Authorization")
if token != "valid-token" {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
// 处理请求
c.Next()
// 中间件逻辑(请求后)
// 可以访问响应状态等
}
}
// 全局使用中间件
r.Use(AuthMiddleware())
// 对特定路由组使用中间件
authorized := r.Group("/admin")
authorized.Use(AuthMiddleware())
{
authorized.GET("/secrets", getSecrets)
}
// 对单个路由使用中间件
r.GET("/sensitive", AuthMiddleware(), getSensitiveData)
```
## 5.3 中间件间数据传递
在Gin中,中间件之间或中间件与处理函数之间可以通过Context上下文共享数据。Gin提供了一系列方法来设置和获取Context中的值:
go复制代码
```go
func DataMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 在中间件中设置值
c.Set("userId", 1001)
c.Set("username", "张三")
// 继续处理请求
c.Next()
// 请求完成后的处理
// 可以在这里记录响应时间等信息
}
}
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// 从上一个中间件获取值
userId, exists := c.Get("userId")
if !exists {
c.AbortWithStatusJSON(401, gin.H{"error": "未授权访问"})
return
}
// 可以根据userId进行进一步的权限检查
c.Set("role", "admin") // 设置新的值供后续中间件或处理函数使用
c.Next()
}
}
// 在路由处理函数中使用
r.GET("/user/profile", DataMiddleware(), AuthRequired(), func(c *gin.Context) {
// 获取中间件设置的值
userId := c.GetInt("userId")
username := c.GetString("username")
role := c.GetString("role")
// 获取特定类型的值,并进行类型断言
userIdInt, ok := userId.(int)
if !ok {
c.JSON(500, gin.H{"error": "类型断言失败"})
return
}
// 使用MustGet,如果值不存在会panic
// 在确定值一定存在的情况下使用
role := c.MustGet("role").(string)
c.JSON(200, gin.H{
"userId": userIdInt,
"username": username,
"role": role,
})
})
```
## 5.3.1 主要方法说明
1. **c.Set(key string, value interface{})**
- 设置键值对到当前请求的上下文中
- 可以在中间件或处理函数中使用
- 设置的值仅在当前请求周期内有效
2. **c.Get(key string) (value interface{}, exists bool)**
- 从当前请求的上下文中获取值
- 返回值和是否存在的标志
- 需要进行类型断言才能使用具体类型的值
3. **c.MustGet(key string) interface{}**
- 从当前请求的上下文中获取值
- 如果值不存在,会触发panic
- 仅在确定值一定存在的情况下使用
4. **c.GetString(key string) (s string)**
- 从上下文中获取字符串值
- 如果值不存在或不是字符串类型,返回空字符串
5. **c.GetBool(key string) (b bool)**
- 从上下文中获取布尔值
- 如果值不存在或不是布尔类型,返回false
6. **c.GetInt(key string) (i int)**
- 从上下文中获取整数值
- 如果值不存在或不是整数类型,返回0
7. **c.GetInt64(key string) (i64 int64)**
- 从上下文中获取64位整数值
- 如果值不存在或不是64位整数类型,返回0
8. **c.GetFloat64(key string) (f64 float64)**
- 从上下文中获取64位浮点数值
- 如果值不存在或不是64位浮点数类型,返回0
9. **c.GetTime(key string) (t time.Time)**
- 从上下文中获取时间值
- 如果值不存在或不是时间类型,返回零值时间
10. **c.GetDuration(key string) (d time.Duration)**
- 从上下文中获取时间间隔值
- 如果值不存在或不是时间间隔类型,返回0
11. **c.GetStringSlice(key string) (ss []string)**
- 从上下文中获取字符串切片
- 如果值不存在或不是字符串切片类型,返回nil
12. **c.GetStringMap(key string) (sm map[string]interface{})**
- 从上下文中获取字符串映射
- 如果值不存在或不是字符串映射类型,返回nil
13. **c.GetStringMapString(key string) (sms map[string]string)**
- 从上下文中获取字符串到字符串的映射
- 如果值不存在或不是字符串到字符串的映射类型,返回nil
## 5.3.2 实际应用场景
1. **用户认证**:在认证中间件中验证用户身份,然后将用户ID和角色设置到上下文中,供后续处理函数使用。
2. **日志记录**:在请求开始时记录时间戳,在请求结束时计算处理时间并记录日志。
3. **错误处理**:在中间件中捕获处理函数中的错误,并统一处理。
4. **权限控制**:基于用户角色进行访问控制。
# 六、请求数据处理
## 6.1 表单数据
```go
r.POST("/form", func(c *gin.Context) {
// 获取表单参数
username := c.PostForm("username")
password := c.DefaultPostForm("password", "default_password")
c.JSON(200, gin.H{
"username": username,
"password": password,
})
})
```
## 6.2 JSON数据
```go
type LoginForm struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
r.POST("/login", func(c *gin.Context) {
var form LoginForm
// 绑定JSON数据到结构体
if err := c.ShouldBindJSON(&form); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 处理登录逻辑...
c.JSON(200, gin.H{
"message": "登录成功",
"username": form.Username,
})
})
```
## 6.3 文件上传
```go
r.POST("/upload", func(c *gin.Context) {
// 单文件上传
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 保存文件
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"message": "文件上传成功",
"filename": file.Filename,
})
})
r.POST("/multi-upload", func(c *gin.Context) {
// 多文件上传
form, err := c.MultipartForm()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
files := form.File["files"]
filenames := []string{}
for _, file := range files {
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
filenames = append(filenames, file.Filename)
}
c.JSON(200, gin.H{
"message": "多文件上传成功",
"filenames": filenames,
})
})
```
# 七、响应数据处理
## 7.1 JSON响应
```go
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "这是一个JSON响应",
"status": "success",
"data": gin.H{
"name": "张三",
"age": 25,
},
})
})
```
## 7.2 XML响应
```go
r.GET("/xml", func(c *gin.Context) {
c.XML(200, gin.H{
"message": "这是一个XML响应",
"status": "success",
})
})
```
## 7.3 HTML响应
首先需要加载模板:
```go
r.LoadHTMLGlob("templates/*")
r.GET("/html", func(c *gin.Context) {
c.HTML(200, "index.tmpl", gin.H{
"title": "Gin框架",
"message": "欢迎学习Gin框架",
})
})
```
创建模板文件`templates/index.tmpl`:
```html
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>{{.message}}</h1>
</body>
</html>
```
## 7.4 文件下载
go复制代码
```go
//下载文件
r.GET("/download", func(c *gin.Context) {
c.File("./files/sample.pdf")
})
// 下载文件并设置文件名
r.GET("/download-attachment", func(c *gin.Context) {
c.FileAttachment("./files/sample.pdf", "用户手册.pdf")
})
```
## 7.5 HTTP Header和Cookie设置
在Web开发中,设置HTTP响应头和Cookie是常见的需求。Gin提供了简单易用的方法来处理这些操作。
### 7.5.1 设置HTTP Header
go复制代码
```go
// 设置单个响应头
r.GET("/header", func(c *gin.Context) {
c.Header("X-Custom-Header", "自定义头部值")
c.Header("Content-Type", "application/json; charset=utf-8")
c.JSON(200, gin.H{
"message": "成功设置自定义Header",
})
})
// 设置多个响应头
r.GET("/headers", func(c *gin.Context) {
// 设置CORS相关头部
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 设置缓存控制
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
c.JSON(200, gin.H{
"message": "设置了多个HTTP头部",
})
})
// 设置安全相关头部
r.GET("/secure", func(c *gin.Context) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.JSON(200, gin.H{
"message": "设置了安全相关的HTTP头部",
})
})
```
### 7.5.2 Cookie操作
```go
// 设置Cookie
r.GET("/set-cookie", func(c *gin.Context) {
// 基本Cookie设置
// SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
c.SetCookie("username", "张三", 3600, "/", "localhost", false, true)
// 设置Session Cookie (浏览器关闭时过期)
c.SetCookie("session_id", "abc123def456", 0, "/", "", false, true)
c.JSON(200, gin.H{
"message": "Cookie已设置",
})
})
// 读取Cookie
r.GET("/get-cookie", func(c *gin.Context) {
username, err := c.Cookie("username")
if err != nil {
c.JSON(400, gin.H{
"error": "无法获取Cookie: " + err.Error(),
})
return
}
sessionId, err := c.Cookie("session_id")
if err != nil {
sessionId = "未设置"
}
c.JSON(200, gin.H{
"username": username,
"session_id": sessionId,
})
})
// 删除Cookie
r.GET("/delete-cookie", func(c *gin.Context) {
// 通过设置过期时间为-1来删除Cookie
c.SetCookie("username", "", -1, "/", "localhost", false, true)
c.SetCookie("session_id", "", -1, "/", "", false, true)
c.JSON(200, gin.H{
"message": "Cookie已删除",
})
})
```
### 7.5.3 Cookie参数详解
go复制代码
```go
// Cookie参数详细说明
r.GET("/cookie-details", func(c *gin.Context) {
// SetCookie参数说明:
// name: Cookie名称
// value: Cookie值
// maxAge: 过期时间(秒),0表示会话Cookie,-1表示立即过期(删除Cookie)
// path: Cookie路径,"/"表示整个网站可用
// domain: Cookie域名,空字符串表示当前域名
// secure: 是否只在HTTPS连接中发送Cookie
// httpOnly: 是否禁止JavaScript访问Cookie(提高安全性)
// 示例:设置一个安全的登录Cookie
c.SetCookie(
"auth_token", // Cookie名称
"secure_token_value", // Cookie值
86400, // 过期时间:24小时 (24*60*60)
"/", // 路径:整个网站
"yourdomain.com", // 域名
true, // 仅HTTPS
true, // HttpOnly
)
c.JSON(200, gin.H{
"message": "已设置安全的认证Cookie",
})
})
```
### 7.5.4 实际应用场景
```go
// 用户登录设置Cookie
r.POST("/login", func(c *gin.Context) {
var loginData struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
if err := c.ShouldBindJSON(&loginData); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 这里应该验证用户名和密码
if loginData.Username == "admin" && loginData.Password == "password" {
// 登录成功,设置认证Cookie
c.SetCookie("user_id", "1001", 86400, "/", "", false, true)
c.SetCookie("username", loginData.Username, 86400, "/", "", false, false)
// 设置自定义响应头
c.Header("X-Login-Time", time.Now().Format(time.RFC3339))
c.JSON(200, gin.H{
"message": "登录成功",
"username": loginData.Username,
})
} else {
// 登录失败,设置错误响应头
c.Header("X-Login-Failed", "true")
c.JSON(401, gin.H{
"error": "用户名或密码错误",
})
}
})
// 用户注销清除Cookie
r.POST("/logout", func(c *gin.Context) {
// 删除认证相关的Cookie
c.SetCookie("user_id", "", -1, "/", "", false, true)
c.SetCookie("username", "", -1, "/", "", false, false)
// 设置注销时间响应头
c.Header("X-Logout-Time", time.Now().Format(time.RFC3339))
c.JSON(200, gin.H{
"message": "注销成功",
})
})
// 需要认证的路由
r.GET("/profile", func(c *gin.Context) {
// 检查认证Cookie
userID, err := c.Cookie("user_id")
if err != nil {
c.Header("X-Auth-Required", "true")
c.JSON(401, gin.H{
"error": "需要登录",
})
return
}
username, _ := c.Cookie("username")
c.JSON(200, gin.H{
"user_id": userID,
"username": username,
"message": "用户信息获取成功",
})
})
```
### 7.5.5 中间件中设置Header和Cookie
go复制代码
```go
// 创建设置通用响应头的中间件
func CommonHeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 设置API版本
c.Header("X-API-Version", "v1.0.0")
// 设置服务器信息
c.Header("X-Powered-By", "Gin Framework")
// 设置响应时间
c.Header("X-Response-Time", time.Now().Format(time.RFC3339))
c.Next()
}
}
// 创建CORS中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// 在路由中使用中间件
func setupRouter() *gin.Engine {
r := gin.Default()
// 全局应用中间件
r.Use(CommonHeadersMiddleware())
r.Use(CORSMiddleware())
// 定义路由...
return r
}
```
# 八、模板渲染
## 8.1 基本用法
go复制代码
```go
// 加载模板
r.LoadHTMLGlob("templates/*")
// 或者
r.LoadHTMLFiles("templates/index.tmpl", "templates/user.tmpl")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.tmpl", gin.H{
"title": "Gin框架模板示例",
})
})
```
## 8.2 模板语法
模板文件`templates/users.tmpl`:
html复制代码
```html
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>用户列表</h1>
<ul>
{{range .users}}
<li>{{.Name}} - {{.Age}}岁</li>
{{end}}
</ul>
{{if .showAdmins}}
<h2>管理员</h2>
<ul>
{{range .admins}}
<li>{{.}}</li>
{{end}}
</ul>
{{else}}
<p>没有显示管理员信息</p>
{{end}}
</body>
</html>
```
对应的处理函数:
go复制代码
```go
r.GET("/users", func(c *gin.Context) {
users := []struct{
Name string
Age int
}{
{"张三", 25},
{"李四", 30},
{"王五", 28},
}
c.HTML(200, "users.tmpl", gin.H{
"title": "用户列表",
"users": users,
"showAdmins": true,
"admins": []string{"admin1", "admin2"},
})
})
```
## 8.3 静态文件服务
提供静态文件(CSS、JS、图片等):
go复制代码
```go
// 单个静态目录
r.Static("/assets", "./static")
// 单个文件
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
// 多个静态目录
r.StaticFS("/more_static", http.Dir("./public"))
```
# 九、数据绑定和验证
## 9.1 数据绑定
Gin提供了多种数据绑定方法:
go复制代码
```go
type User struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
Age int `form:"age" json:"age" binding:"required,gte=18"`
Email string `form:"email" json:"email" binding:"required,email"`
}
// 绑定JSON
r.POST("/json-binding", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
// 绑定表单
r.POST("/form-binding", func(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
// 绑定URI参数
r.GET("/uri-binding/:name/:age", func(c *gin.Context) {
type Person struct {
Name string `uri:"name" binding:"required"`
Age int `uri:"age" binding:"required,gte=18"`
}
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, person)
})
```
## 9.2 自定义验证器
go复制代码
```go
// 自定义验证器
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn"`
}
func bookableDate(fl validator.FieldLevel) bool {
date, ok := fl.Field().Interface().(time.Time)
if !ok {
return false
}
today := time.Now()
if date.Unix() < today.Unix() {
return false
}
return true
}
func setupValidators() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
}
```
# 十、项目实战:待办事项API
让我们创建一个简单的待办事项(Todo)API来综合应用Gin框架的各种功能。
## 10.1 定义模型
```go
// models/todo.go
package models
import (
"time"
)
type Todo struct {
ID uint `json:"id"`
Title string `json:"title" binding:"required"`
Description string `json:"description"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"created_at"`
}
var todos = []Todo{
{ID: 1, Title: "学习Gin框架", Description: "掌握Gin框架的基础知识", Completed: false, CreatedAt: time.Now()},
{ID: 2, Title: "完成项目", Description: "使用Gin框架完成项目开发", Completed: false, CreatedAt: time.Now()},
}
// 获取所有待办事项
func GetAllTodos() []Todo {
return todos
}
// 根据ID获取待办事项
func GetTodoByID(id uint) (Todo, bool) {
for _, todo := range todos {
if todo.ID == id {
return todo, true
}
}
return Todo{}, false
}
// 创建新的待办事项
func CreateTodo(todo Todo) Todo {
todo.ID = uint(len(todos) + 1)
todo.CreatedAt = time.Now()
todos = append(todos, todo)
return todo
}
// 更新待办事项
func UpdateTodo(id uint, updatedTodo Todo) (Todo, bool) {
for i, todo := range todos {
if todo.ID == id {
updatedTodo.ID = id
updatedTodo.CreatedAt = todo.CreatedAt
todos[i] = updatedTodo
return updatedTodo, true
}
}
return Todo{}, false
}
// 删除待办事项
func DeleteTodo(id uint) bool {
for i, todo := range todos {
if todo.ID == id {
todos = append(todos[:i], todos[i+1:]...)
return true
}
}
return false
}
```
## 10.2 实现控制器
```go
// controllers/todo_controller.go
package controllers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gin-demo/models"
)
// 获取所有待办事项
func GetTodos(c *gin.Context) {
todos := models.GetAllTodos()
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": todos,
})
}
// 获取单个待办事项
func GetTodo(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": "无效的ID",
})
return
}
todo, found := models.GetTodoByID(uint(id))
if !found {
c.JSON(http.StatusNotFound, gin.H{
"status": "error",
"message": "待办事项不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": todo,
})
}
// 创建待办事项
func CreateTodo(c *gin.Context) {
var todo models.Todo
if err := c.ShouldBindJSON(&todo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
newTodo := models.CreateTodo(todo)
c.JSON(http.StatusCreated, gin.H{
"status": "success",
"data": newTodo,
})
}
// 更新待办事项
func UpdateTodo(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": "无效的ID",
})
return
}
var todo models.Todo
if err := c.ShouldBindJSON(&todo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
updatedTodo, found := models.UpdateTodo(uint(id), todo)
if !found {
c.JSON(http.StatusNotFound, gin.H{
"status": "error",
"message": "待办事项不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": updatedTodo,
})
}
// 删除待办事项
func DeleteTodo(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": "无效的ID",
})
return
}
found := models.DeleteTodo(uint(id))
if !found {
c.JSON(http.StatusNotFound, gin.H{
"status": "error",
"message": "待办事项不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"message": "待办事项已删除",
})
}
```
## 10.3 定义路由
go复制代码
```go
// main.go
package main
import (
"github.com/gin-gonic/gin"
"gin-demo/controllers"
)
func setupRouter() *gin.Engine {
r := gin.Default()
// 日志中间件
r.Use(gin.Logger())
// API路由
api := r.Group("/api")
{
todos := api.Group("/todos")
{
todos.GET("", controllers.GetTodos)
todos.GET("/:id", controllers.GetTodo)
todos.POST("", controllers.CreateTodo)
todos.PUT("/:id", controllers.UpdateTodo)
todos.DELETE("/:id", controllers.DeleteTodo)
}
}
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
```
## 10.4 测试API
可以使用Postman或curl测试API:
bash复制代码
```bash
# 获取所有待办事项
curl http://localhost:8080/api/todos
# 获取单个待办事项
curl http://localhost:8080/api/todos/1
# 创建待办事项
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":"测试Gin API","description":"使用curl测试Gin API功能"}'
# 更新待办事项
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"title":"学习Gin框架","description":"掌握Gin框架的基础知识","completed":true}'
# 删除待办事项
curl -X DELETE http://localhost:8080/api/todos/3
```
# 十一、部署与最佳实践
## 11.1 生产环境部署
go复制代码
```go
// 设置生产模式
gin.SetMode(gin.ReleaseMode)
// 创建自定义HTTP服务器
s := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MB
}
// 启动服务器
s.ListenAndServe()
```
## 11.2 优雅关闭
go复制代码
```go
// 优雅关闭服务器
func main() {
router := setupRouter()
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// 在一个新的goroutine中启动服务器
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("服务器强制关闭:", err)
}
log.Println("服务器已优雅关闭")
}
```
## 11.3 性能优化建议
1. **使用合适的并发模型**:Go的goroutine很轻量,但过多会消耗资源
2. **优化数据库查询**:使用索引、连接池
3. **实现缓存机制**:使用Redis等缓存热门数据
4. **启用GZIP压缩**:减少传输数据量
5. **使用适当的日志级别**:生产环境中减少不必要的日志
6. **定期进行性能测试**:使用工具如Apache Bench或Vegeta
# 十二、总结
Gin是一个功能强大且性能卓越的Go Web框架,适合构建各种类型的Web应用和API服务。本教程涵盖了Gin的基础知识和实际应用,包括路由、中间件、请求处理、响应生成等方面。
通过学习和实践,您应该能够使用Gin框架开发出高效、可靠的Web应用程序。继续深入学习和探索Gin的更多高级特性,将有助于您成为一名出色的Go Web开发者。
# 十三、课堂练习
## (一)、项目概述
开发简单的学生信息管理系统的接口,用于对学生的基本信息进行增删改查操作,以此熟悉和加深 Gin 框架的基础使用,包括路由设置、请求处理、数据绑定等知识。
## (二)、功能需求
### 学生信息结构体定义
定义一个 Student 结构体,包含以下字段:
- ID:学生 ID,整数类型
- Name:学生姓名,字符串类型
- Age:学生年龄,整数类型
- Grade:学生年级,字符串类型
### 路由与请求处理
1. **创建学生信息(POST 请求)**
- **路由**:`/students`
- **功能**:接收包含学生信息的 JSON 数据,将其绑定到 Student 结构体,在控制台打印学生信息,并返回创建成功的消息。
2. **获取所有学生信息(GET 请求)**
- **路由**:`/students`
- **功能**:模拟从数据库或数据存储中获取所有学生信息(可以简单用切片存储数据),将学生信息以 JSON 格式返回。如果没有学生信息,返回一个空的学生信息列表。
3. **获取单个学生信息(GET 请求)**
- **路由**:`/students/:id`
- **功能**:根据 URL 中的 id 参数获取对应的学生信息。如果找到,返回该学生信息;如果未找到,返回错误消息。
4. **更新学生信息(PUT 请求)**
- **路由**:`/students/:id`
- **功能**:根据 URL 中的 `id` 参数找到对应的学生信息,接收包含更新后学生信息的 JSON 数据,将其绑定到 `Student` 结构体,更新学生信息(在控制台打印更新后的信息),并返回更新成功的消息。如果未找到指定 `id` 的学生信息,返回错误消息。
5. **删除学生信息(DELETE 请求)**
- **路由**:`/students/:id`
- **功能**:根据 URL 中的 `id` 参数找到对应的学生信息并删除(可以简单在内存中模拟删除,如从切片中移除该学生信息)。如果删除成功,返回删除成功的消息;如果未找到指定 `id` 的学生信息,返回错误消息。
## (三)、项目结构要求
1. **`main.go`**:作为项目入口,初始化 Gin 引擎,加载所有路由。
2. **`models` 目录**:在该目录下创建 `student.go` 文件,定义 `Student` 结构体。
3. **`routes` 目录**:在该目录下创建 `student.go` 文件,定义所有与学生信息相关的路由。
4. **`controllers` 目录**:在该目录下创建 `student.go` 文件,编写处理学生信息相关请求的具体逻辑。
## (四)、其他要求
1. 目录: `week03/practice/gin/`
2. 合理处理请求过程中的错误,如数据绑定失败、找不到资源等情况,并返回合适的 HTTP 状态码和错误信息。
3. 在控制台打印关键的操作信息,如接收到的请求、处理结果等,方便调试和观察系统运行情况。
## (五)进阶部分
1. **版本划分**:将上述实现的接口定义为 v1 版本。在此基础上,开发 v2 和 v3 版本接口。
2. **v2 版本**:使用 SQLite 数据库,通过原始 SQL 语句实现学生信息的增删改查功能。路由以 /v2 开头,与 v1 版本对应功能的路由类似,如 /v2/students 等。需要处理好数据库连接、SQL 语句执行及错误处理等操作。
3. **v3 版本**:使用 Mysql 数据库,但借助 GORM 库实现学生信息的增删改查功能。路由以 /v3 开头,例如 /v3/students 等。要完成 GORM 的配置、模型定义及相关数据库操作方法的调用,同时做好错误处理。
4. **v4 版本**:在 GORM 操作 MySQL 实现学生信息增删改查基础上,在获取学生信息(包括获取单个与所有学生信息)时使用 Redis 缓存,为缓存数据设过期时间(如 5 分钟),增改操作后清理相关缓存以保证数据一致性 。
5. **项目结构调整**:在 routes 和 controllers 目录下分别创建 v2 和 v3 子目录,存放对应版本的路由和请求处理逻辑代码。在 db 目录下创建数据库连接相关代码,供 v2 和 v3 版本使用。
6. **错误处理与日志**:各版本均需合理处理请求过程中的各类错误,返回合适的 HTTP 状态码和错误信息,并在控制台打印关键操作信息以便调试。
# 参考资源
- [Gin官方文档](https://gin-gonic.com/docs/)
- [Gin GitHub仓库](https://github.com/gin-gonic/gin)
- [Go官方文档](https://golang.org/doc/)
- [Go Web编程](https://astaxie.gitbooks.io/build-web-application-with-golang/content/zh/)