Gin Framework 完整指南
目录
1. 基础介绍
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"log"
)
func main() {
// 创建默认的 gin 路由引擎
// 包含了 Logger 和 Recovery 中间件
r := gin.Default()
// 定义一个简单的 GET 路由
r.GET("/ping", func(c *gin.Context) {
// 返回 JSON 响应
c.JSON(http.StatusOK, gin.H{
"message": "pong",
"status": "success",
})
})
// 定义一个处理错误的路由
r.GET("/error", func(c *gin.Context) {
// 使用 Error 方法添加错误
c.Error(fmt.Errorf("这是一个错误"))
// 返回错误响应
c.JSON(http.StatusInternalServerError, gin.H{
"error": "发生了一个错误",
})
})
// 启动服务器,默认监听 0.0.0.0:8080
// 可以通过环境变量 PORT 更改端口
if err := r.Run(":8080"); err != nil {
log.Fatal("启动服务器失败: ", err)
}
}
2. 安装和配置
package main
import (
"github.com/gin-gonic/gin"
"io"
"os"
"time"
)
func main() {
// 设置为发布模式
// 可选值: debug, release, test
gin.SetMode(gin.ReleaseMode)
// 创建一个不带中间件的路由引擎
r := gin.New()
// 创建日志文件
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
// 配置全局中间件
r.Use(gin.Logger()) // 日志中间件
r.Use(gin.Recovery()) // 恢复中间件,用于处理 panic
// 配置信任代理
r.SetTrustedProxies([]string{"192.168.1.2", "192.168.1.3"})
// 配置路由超时
r.GET("/timeout", func(c *gin.Context) {
// 创建一个带超时的上下文
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
defer cancel()
// 将超时上下文设置到请求中
c.Request = c.Request.WithContext(ctx)
// 模拟耗时操作
select {
case <-time.After(5 * time.Second):
c.JSON(http.StatusOK, gin.H{"message": "完成"})
case <-ctx.Done():
c.JSON(http.StatusGatewayTimeout, gin.H{"error": "请求超时"})
}
})
// 配置自定义 HTTP 配置
s := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
// 启动服务器
if err := s.ListenAndServe(); err != nil {
log.Fatal("启动服务器失败: ", err)
}
}
3. 路由管理
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"log"
)
// 用户结构体
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}
func main() {
r := gin.Default()
// 1. 基本路由
// GET 请求
r.GET("/users", func(c *gin.Context) {
c.JSON(200, []User{
{ID: "1", Name: "张三"},
{ID: "2", Name: "李四"},
})
})
// POST 请求
r.POST("/users", 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(201, user)
})
// 2. 路由分组
// API v1 版本分组
v1 := r.Group("/v1")
{
// 添加中间件到分组
v1.Use(AuthMiddleware())
// 分组下的路由
v1.GET("/users", listUsers)
v1.POST("/users", createUser)
v1.GET("/users/:id", getUser)
v1.PUT("/users/:id", updateUser)
v1.DELETE("/users/:id", deleteUser)
}
// 3. 路由参数
// 必选参数
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
// 可选参数
r.GET("/users/*action", func(c *gin.Context) {
action := c.Param("action")
c.JSON(200, gin.H{"action": action})
})
// 查询参数
r.GET("/search", func(c *gin.Context) {
// 获取查询参数
query := c.Query("q") // 获取参数 q
page := c.DefaultQuery("page", "1") // 获取参数 page,默认值为 1
limit := c.DefaultQuery("limit", "10") // 获取参数 limit,默认值为 10
c.JSON(200, gin.H{
"query": query,
"page": page,
"limit": limit,
})
})
// 4. 路由重定向
// 内部重定向
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
})
// HTTP 重定向
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.google.com")
})
// 5. 路由组合
// 支持多种 HTTP 方法
r.Any("/any", func(c *gin.Context) {
c.JSON(200, gin.H{
"method": c.Request.Method,
})
})
// 没有路由匹配时的处理
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"message": "Page not found"})
})
if err := r.Run(":8080"); err != nil {
log.Fatal("启动服务器失败: ", err)
}
}
// 中间件函数
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查认证逻辑
auth := c.GetHeader("Authorization")
if auth == "" {
c.JSON(401, gin.H{"error": "未授权"})
c.Abort()
return
}
c.Next()
}
}
// 路由处理函数
func listUsers(c *gin.Context) {
users := []User{
{ID: "1", Name: "张三"},
{ID: "2", Name: "李四"},
}
c.JSON(200, users)
}
func createUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 这里应该有数据库操作
c.JSON(201, user)
}
func getUser(c *gin.Context) {
id := c.Param("id")
user := User{ID: id, Name: "张三"}
c.JSON(200, user)
}
func updateUser(c *gin.Context) {
id := c.Param("id")
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
user.ID = id
c.JSON(200, user)
}
func deleteUser(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"message": "用户" + id + "已删除"})
}
4. 请求处理
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"log"
"mime/multipart"
)
// 用户注册请求结构体
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=32"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
}
// 用户信息结构体
type UserInfo struct {
Name string `form:"name"`
Age int `form:"age"`
Avatar *multipart.FileHeader `form:"avatar"`
Hobbies []string `form:"hobbies[]"`
}
func main() {
r := gin.Default()
// 1. 处理 JSON 请求
r.POST("/json", func(c *gin.Context) {
var req RegisterRequest
// 绑定 JSON 数据
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"username": req.Username,
"email": req.Email,
})
})
// 2. 处理表单数据
r.POST("/form", func(c *gin.Context) {
var userInfo UserInfo
// 绑定表单数据
if err := c.ShouldBind(&userInfo); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 处理文件上传
if userInfo.Avatar != nil {
// 保存文件
if err := c.SaveUploadedFile(userInfo.Avatar, "uploads/"+userInfo.Avatar.Filename); err != nil {
c.JSON(500, gin.H{"error": "文件上传失败"})
return
}
}
c.JSON(200, userInfo)
})
// 3. 处理 URL 查询参数
r.GET("/search", func(c *gin.Context) {
// 获取查询参数
query := c.Query("q") // 必选参数
page := c.DefaultQuery("page", "1") // 可选参数带默认值
if exists := c.QueryArray("tags"); exists != nil {
// 处理数组参数
tags := c.QueryArray("tags")
c.JSON(200, gin.H{
"query": query,
"page": page,
"tags": tags,
})
return
}
c.JSON(200, gin.H{
"query": query,
"page": page,
})
})
// 4. 处理路径参数
r.GET("/users/:id/*action", func(c *gin.Context) {
id := c.Param("id")
action := c.Param("action")
c.JSON(200, gin.H{
"id": id,
"action": action,
})
})
// 5. 处理请求头
r.GET("/headers", func(c *gin.Context) {
// 获取特定请求头
userAgent := c.GetHeader("User-Agent")
// 获取所有请求头
headers := c.Request.Header
c.JSON(200, gin.H{
"user_agent": userAgent,
"headers": headers,
})
})
// 6. 处理 Cookie
r.GET("/cookies", func(c *gin.Context) {
// 获取 cookie
cookie, err := c.Cookie("session_id")
if err != nil {
// 设置 cookie
c.SetCookie("session_id", "123456", 3600, "/", "localhost", false, true)
cookie = "123456"
}
c.JSON(200, gin.H{
"cookie": cookie,
})
})
// 7. 处理原始数据
r.POST("/raw", func(c *gin.Context) {
// 读取原始数据
data, err := c.GetRawData()
if err != nil {
c.JSON(400, gin.H{"error": "读取数据失败"})
return
}
c.JSON(200, gin.H{
"data": string(data),
})
})
if err := r.Run(":8080"); err != nil {
log.Fatal("启动服务器失败: ", err)
}
}
5. 响应处理
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"path/filepath"
)
// 响应数据结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
// 用户数据结构
type User struct {
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Email string `json:"email" xml:"email"`
}
func main() {
r := gin.Default()
// 1. JSON 响应
r.GET("/json", func(c *gin.Context) {
// 使用自定义响应结构
c.JSON(http.StatusOK, Response{
Code: 200,
Message: "success",
Data: User{
ID: "1",
Name: "张三",
Email: "[email protected]",
},
})
})
// 2. XML 响应
r.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, User{
ID: "1",
Name: "张三",
Email: "[email protected]",
})
})
// 3. YAML 响应
r.GET("/yaml", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{
"name": "张三",
"age": 20,
"hobbies": []string{
"reading",
"music",
},
})
})
// 4. HTML 响应
// 加载模板文件
r.LoadHTMLGlob("templates/*")
r.GET("/html", func(c *gin.Context) {
// 渲染模板
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Gin HTML 示例",
"content": "这是一个 HTML 页面",
})
})
// 5. 文件下载
r.GET("/download", func(c *gin.Context) {
// 文件路径
filePath := "files/example.pdf"
// 获取文件名
filename := filepath.Base(filePath)
// 设置响应头,使浏览器下载文件
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Header("Content-Type", "application/octet-stream")
// 发送文件
c.File(filePath)
})
// 6. 流式响应
r.GET("/stream", func(c *gin.Context) {
// 设置响应头
c.Header("Transfer-Encoding", "chunked")
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
// 创建通道
messageChan := make(chan string)
// 在 goroutine 中发送数据
go func() {
defer close(messageChan)
for i := 0; i < 5; i++ {
messageChan <- fmt.Sprintf("message %d", i)
time.Sleep(time.Second)
}
}()
// 发送数据到客户端
c.Stream(func(w io.Writer) bool {
if msg, ok := <-messageChan; ok {
c.SSEvent("message", msg)
return true
}
return false
})
})
// 7. 自定义响应
r.GET("/custom", func(c *gin.Context) {
// 设置自定义响应头
c.Header("X-Custom-Header", "custom value")
// 设置 Cookie
c.SetCookie("session", "123456", 3600, "/", "localhost", false, true)
// 自定义状态码和响应
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": gin.H{
"message": "这是自定义响应",
"timestamp": time.Now().Unix(),
},
})
})
// 8. 错误响应处理
r.GET("/error", func(c *gin.Context) {
// 根据不同情况返回不同的错误响应
err := someFunction()
if err != nil {
switch err.(type) {
case *NotFoundError:
c.JSON(http.StatusNotFound, Response{
Code: 404,
Message: err.Error(),
})
case *ValidationError:
c.JSON(http.StatusBadRequest, Response{
Code: 400,
Message: err.Error(),
})
default:
c.JSON(http.StatusInternalServerError, Response{
Code: 500,
Message: "服务器内部错误",
})
}
return
}
c.JSON(http.StatusOK, Response{
Code: 200,
Message: "success",
})
})
// 9. 重定向响应
r.GET("/redirect", func(c *gin.Context) {
// 临时重定向
c.Redirect(http.StatusTemporaryRedirect, "/target")
})
// 10. 异步响应
r.GET("/async", func(c *gin.Context) {
// 复制上下文
cCp := c.Copy()
// 异步处理
go func() {
// 使用复制的上下文执行异步操作
time.Sleep(5 * time.Second)
log.Printf("异步操作完成: %s", cCp.Request.URL.Path)
}()
// 立即返回响应
c.JSON(http.StatusOK, gin.H{
"message": "异步操作已开始",
})
})
if err := r.Run(":8080"); err != nil {
log.Fatal("启动服务器失败: ", err)
}
}
// 自定义错误类型
type NotFoundError struct {
Message string
}
func (e *NotFoundError) Error() string {
return e.Message
}
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
// 模拟业务函数
func someFunction() error {
// 模拟业务逻辑
return &ValidationError{Message: "验证失败"}
}
6. 中间件
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
"fmt"
)
// 自定义日志格式
type LogFormatterParams struct {
StatusCode int
Latency time.Duration
ClientIP string
Method string
Path string
ErrorMessage string
}
func main() {
r := gin.New() // 创建不带中间件的引擎
// 1. 全局中间件
// 自定义日志中间件
r.Use(LoggerMiddleware())
// 恢复中间件
r.Use(gin.Recovery())
// CORS中间件
r.Use(CORSMiddleware())
// 2. 路由组中间件
authorized := r.Group("/auth")
authorized.Use(AuthMiddleware())
{
authorized.GET("/user", func(c *gin.Context) {
// 从中间件获取用户信息
user, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到用户信息"})
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
})
}
// 3. 单个路由中间件
r.GET("/admin", AdminAuthRequired(), func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "管理员页面"})
})
// 4. 中间件链
r.GET("/chain",
RateLimiter(),
AuthMiddleware(),
func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "通过所有中间件"})
},
)
r.Run(":8080")
}
// 日志中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 开始时间
startTime := time.Now()
// 处理请求
c.Next()
// 结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
// 请求方式
reqMethod := c.Request.Method
// 请求路由
reqUri := c.Request.RequestURI
// 状态码
statusCode := c.Writer.Status()
// 请求IP
clientIP := c.ClientIP()
// 日志格式
log.Printf("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
// 如果有错误,记录错误信息
for _, err := range c.Errors.Errors() {
log.Printf("Error: %v", err)
}
}
}
// CORS中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// 认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取认证token
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证token"})
c.Abort()
return
}
// 验证token(这里简化处理)
if token != "valid-token" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的token"})
c.Abort()
return
}
// 设置用户信息到上下文
c.Set("user", gin.H{"id": 1, "name": "测试用户"})
c.Next()
}
}
// 管理员认证中间件
func AdminAuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取用户角色
role := c.GetHeader("User-Role")
if role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
c.Abort()
return
}
c.Next()
}
}
// 限流中间件
func RateLimiter() gin.HandlerFunc {
// 创建令牌桶
limiter := time.NewTicker(time.Second / 10) // 每秒10个请求
return func(c *gin.Context) {
select {
case <-limiter.C:
c.Next()
default:
c.JSON(http.StatusTooManyRequests, gin.H{"error": "请求过于频繁"})
c.Abort()
}
}
}
// 错误处理中间件
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 记录错误日志
log.Printf("Panic: %v", err)
// 返回500错误
c.JSON(http.StatusInternalServerError, gin.H{
"error": "服务器内部错误",
})
}
}()
c.Next()
// 处理请求过程中的错误
if len(c.Errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"errors": c.Errors.Errors(),
})
}
}
}
// 请求追踪中间件
func RequestTracer() gin.HandlerFunc {
return func(c *gin.Context) {
// 生成请求ID
requestID := fmt.Sprintf("%d", time.Now().UnixNano())
// 设置请求ID到header
c.Writer.Header().Set("X-Request-ID", requestID)
// 设置到上下文
c.Set("RequestID", requestID)
// 记录请求开始
log.Printf("Request started: %s %s", requestID, c.Request.URL.Path)
c.Next()
// 记录请求结束
log.Printf("Request completed: %s %s %d", requestID, c.Request.URL.Path, c.Writer.Status())
}
}
7. 参数验证
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"net/http"
"time"
"regexp"
)
// 用户注册请求
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=32"`
Password string `json:"password" binding:"required,min=6,max=32"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=18,lte=130"`
Phone string `json:"phone" binding:"required,phone"`
Birthday string `json:"birthday" binding:"required,datetime=2006-01-02"`
Gender string `json:"gender" binding:"required,oneof=male female other"`
Agreement bool `json:"agreement" binding:"required,agreed"`
}
// 产品信息
type Product struct {
ID string `json:"id" binding:"required,uuid"`
Name string `json:"name" binding:"required,min=2"`
Price float64 `json:"price" binding:"required,gt=0"`
Stock int `json:"stock" binding:"required,gte=0"`
CreatedAt time.Time `json:"created_at" binding:"required,ltefield=UpdatedAt"`
UpdatedAt time.Time `json:"updated_at" binding:"required"`
}
func main() {
r := gin.Default()
// 注册自定义验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 手机号验证器
v.RegisterValidation("phone", phoneValidator)
// 同意协议验证器
v.RegisterValidation("agreed", agreedValidator)
}
// 1. 基本验证
r.POST("/register", func(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": translateValidationError(err),
})
return
}
c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
})
// 2. 嵌套结构验证
r.POST("/products", func(c *gin.Context) {
var product Product
if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": translateValidationError(err),
})
return
}
c.JSON(http.StatusOK, product)
})
// 3. 查询参数验证
r.GET("/search", validateQueryParams(), func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "查询参数验证通过",
})
})
r.Run(":8080")
}
// 手机号验证器
func phoneValidator(fl validator.FieldLevel) bool {
phone := fl.Field().String()
// 简单的手机号验证规则
match, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return match
}
// 协议同意验证器
func agreedValidator(fl validator.FieldLevel) bool {
return fl.Field().Bool()
}
// 查询参数验证中间件
func validateQueryParams() gin.HandlerFunc {
return func(c *gin.Context) {
// 验证页码
page := c.DefaultQuery("page", "1")
if _, err := strconv.Atoi(page); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "页码必须是数字"})
c.Abort()
return
}
// 验证每页数量
limit := c.DefaultQuery("limit", "10")
if limitNum, err := strconv.Atoi(limit); err != nil || limitNum <= 0 || limitNum > 100 {
c.JSON(http.StatusBadRequest, gin.H{"error": "每页数量必须在1-100之间"})
c.Abort()
return
}
c.Next()
}
}
// 验证错误翻译
func translateValidationError(err error) map[string]string {
errors := make(map[string]string)
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, e := range validationErrors {
// 这里可以根据需要翻译成中文错误信息
switch e.Tag() {
case "required":
errors[e.Field()] = "此字段是必需的"
case "email":
errors[e.Field()] = "请输入有效的电子邮件地址"
case "min":
errors[e.Field()] = "长度不能小于" + e.Param()
case "max":
errors[e.Field()] = "长度不能大于" + e.Param()
case "phone":
errors[e.Field()] = "请输入有效的手机号码"
default:
errors[e.Field()] = "验证失败"
}
}
return errors
}
errors["general"] = err.Error()
return errors
}
8. 文件处理
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"path/filepath"
"fmt"
"os"
"io"
"strings"
"mime/multipart"
"crypto/md5"
"encoding/hex"
)
// 文件上传配置
const (
MaxFileSize = 10 << 20 // 10MB
UploadDir = "./uploads"
AllowedExts = ".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx"
)
// 文件信息结构体
type FileInfo struct {
Filename string `json:"filename"`
Size int64 `json:"size"`
ContentType string `json:"content_type"`
MD5 string `json:"md5"`
Path string `json:"path"`
}
func main() {
r := gin.Default()
// 设置文件上传大小限制
r.MaxMultipartMemory = MaxFileSize
// 创建上传目录
if err := os.MkdirAll(UploadDir, 0755); err != nil {
panic(err)
}
// 1. 单文件上传
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"})
return
}
// 验证文件
if err := validateFile(file); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 生成文件信息
fileInfo, err := saveFile(file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
return
}
c.JSON(http.StatusOK, fileInfo)
})
// 2. 多文件上传
r.POST("/uploads", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "获取文件失败"})
return
}
files := form.File["files"]
fileInfos := make([]FileInfo, 0)
for _, file := range files {
// 验证文件
if err := validateFile(file); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 保存文件
fileInfo, err := saveFile(file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
return
}
fileInfos = append(fileInfos, fileInfo)
}
c.JSON(http.StatusOK, fileInfos)
})
// 3. 文件下载
r.GET("/download/:filename", func(c *gin.Context) {
filename := c.Param("filename")
filepath := filepath.Join(UploadDir, filename)
// 检查文件是否存在
if _, err := os.Stat(filepath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
return
}
// 设置下载头
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Header("Content-Type", "application/octet-stream")
c.File(filepath)
})
// 4. 文件预览
r.GET("/preview/:filename", func(c *gin.Context) {
filename := c.Param("filename")
filepath := filepath.Join(UploadDir, filename)
// 检查文件是否存在
if _, err := os.Stat(filepath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
return
}
// 获取文件类型
contentType := getContentType(filename)
c.Header("Content-Type", contentType)
c.File(filepath)
})
// 5. 文件删除
r.DELETE("/files/:filename", func(c *gin.Context) {
filename := c.Param("filename")
filepath := filepath.Join(UploadDir, filename)
// 检查文件是否存在
if _, err := os.Stat(filepath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
return
}
// 删除文件
if err := os.Remove(filepath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "文件删除失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "文件删除成功"})
})
r.Run(":8080")
}
// 验证文件
func validateFile(file *multipart.FileHeader) error {
// 检查文件大小
if file.Size > MaxFileSize {
return fmt.Errorf("文件大小超过限制")
}
// 检查文件扩展名
ext := strings.ToLower(filepath.Ext(file.Filename))
if !strings.Contains(AllowedExts, ext) {
return fmt.Errorf("不支持的文件类型")
}
return nil
}
// 保存文件
func saveFile(file *multipart.FileHeader) (FileInfo, error) {
src, err := file.Open()
if err != nil {
return FileInfo{}, err
}
defer src.Close()
// 计算MD5
hash := md5.New()
if _, err := io.Copy(hash, src); err != nil {
return FileInfo{}, err
}
md5sum := hex.EncodeToString(hash.Sum(nil))
// 重新打开文件
src.Seek(0, 0)
// 生成唯一文件名
ext := filepath.Ext(file.Filename)
newFilename := fmt.Sprintf("%s%s", md5sum, ext)
dst := filepath.Join(UploadDir, newFilename)
// 创建目标文件
out, err := os.Create(dst)
if err != nil {
return FileInfo{}, err
}
defer out.Close()
// 复制文件内容
src.Seek(0, 0)
if _, err = io.Copy(out, src); err != nil {
return FileInfo{}, err
}
return FileInfo{
Filename: file.Filename,
Size: file.Size,
ContentType: file.Header.Get("Content-Type"),
MD5: md5sum,
Path: dst,
}, nil
}
// 获取文件Content-Type
func getContentType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
switch ext {
case ".jpg", ".jpeg":
return "image/jpeg"
case ".png":
return "image/png"
case ".gif":
return "image/gif"
case ".pdf":
return "application/pdf"
case ".doc":
return "application/msword"
case ".docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
default:
return "application/octet-stream"
}
}
9. 数据库集成
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/driver/mysql"
"time"
"log"
)
// 数据库配置
const (
DBUser = "root"
DBPassword = "password"
DBHost = "localhost"
DBPort = "3306"
DBName = "testdb"
)
// 用户模型
type User struct {
ID uint `gorm:"primarykey" json:"id"`
Username string `gorm:"size:32;uniqueIndex;not null" json:"username"`
Email string `gorm:"size:128;uniqueIndex;not null" json:"email"`
Password string `gorm:"size:128;not null" json:"-"` // 密码不返回给客户端
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// 文章模型
type Article struct {
ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"size:128;not null" json:"title"`
Content string `gorm:"type:text" json:"content"`
UserID uint `json:"user_id"`
User User `gorm:"foreignKey:UserID" json:"user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
var db *gorm.DB
func main() {
// 初始化数据库连接
initDB()
r := gin.Default()
// 用户相关路由
users := r.Group("/users")
{
users.POST("", createUser)
users.GET("", listUsers)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
// 文章相关路由
articles := r.Group("/articles")
{
articles.POST("", createArticle)
articles.GET("", listArticles)
articles.GET("/:id", getArticle)
articles.PUT("/:id", updateArticle)
articles.DELETE("/:id", deleteArticle)
}
r.Run(":8080")
}
// 初始化数据库
func initDB() {
var err error
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
DBUser, DBPassword, DBHost, DBPort, DBName)
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 自动迁移
db.AutoMigrate(&User{}, &Article{})
}
// 用户相关处理函数
func createUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 创建用户
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败"})
return
}
c.JSON(http.StatusCreated, user)
}
func listUsers(c *gin.Context) {
var users []User
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
// 分页查询
offset := (page - 1) * limit
if err := db.Offset(offset).Limit(limit).Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
return
}
c.JSON(http.StatusOK, users)
}
func getUser(c *gin.Context) {
id := c.Param("id")
var user User
if err := db.First(&user, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
return
}
c.JSON(http.StatusOK, user)
}
func updateUser(c *gin.Context) {
id := c.Param("id")
var user User
// 检查用户是否存在
if err := db.First(&user, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
return
}
// 绑定更新数据
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 更新用户
if err := db.Save(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户失败"})
return
}
c.JSON(http.StatusOK, user)
}
func deleteUser(c *gin.Context) {
id := c.Param("id")
if err := db.Delete(&User{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除用户失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "用户已删除"})
}
// 文章相关处理函数
// ... 类似的 CRUD 操作 ...
10. 认证与授权
package main
import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"time"
"errors"
"strings"
)
// JWT配置
const (
JWT_SECRET = "your-secret-key"
JWT_EXPIRE = 24 * time.Hour
)
// 用户模型
type User struct {
gorm.Model
Username string `gorm:"uniqueIndex;not null" json:"username"`
Password string `gorm:"not null" json:"-"`
Email string `gorm:"uniqueIndex;not null" json:"email"`
Role string `gorm:"default:user" json:"role"` // admin or user
}
// JWT Claims
type Claims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// 登录请求
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
// 注册请求
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=32"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
}
func main() {
r := gin.Default()
// 公开路由
public := r.Group("/api")
{
public.POST("/register", register)
public.POST("/login", login)
}
// 需要认证的路由
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
// 用户路由
authorized.GET("/profile", getProfile)
authorized.PUT("/profile", updateProfile)
authorized.PUT("/password", changePassword)
// 管理员路由
admin := authorized.Group("/admin")
admin.Use(AdminRequired())
{
admin.GET("/users", listUsers)
admin.DELETE("/users/:id", deleteUser)
}
}
r.Run(":8080")
}
// 注册处理
func register(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 检查用户名是否已存在
var existingUser User
if err := db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
c.JSON(400, gin.H{"error": "用户名已存在"})
return
}
// 加密密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(500, gin.H{"error": "密码加密失败"})
return
}
// 创建用户
user := User{
Username: req.Username,
Password: string(hashedPassword),
Email: req.Email,
Role: "user", // 默认角色
}
if err := db.Create(&user).Error; err != nil {
c.JSON(500, gin.H{"error": "用户创建失败"})
return
}
c.JSON(201, gin.H{"message": "注册成功"})
}
// 登录处理
func login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 查找用户
var user User
if err := db.Where("username = ?", req.Username).First(&user).Error; err != nil {
c.JSON(401, gin.H{"error": "用户名或密码错误"})
return
}
// 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
c.JSON(401, gin.H{"error": "用户名或密码错误"})
return
}
// 生成Token
token, err := generateToken(user)
if err != nil {
c.JSON(500, gin.H{"error": "Token生成失败"})
return
}
c.JSON(200, gin.H{
"token": token,
"user": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"role": user.Role,
},
})
}
// JWT认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取Token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(401, gin.H{"error": "未提供认证token"})
c.Abort()
return
}
// 解析Token
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
claims, err := parseToken(tokenString)
if err != nil {
c.JSON(401, gin.H{"error": "无效的token"})
c.Abort()
return
}
// 将用户信息存储到上下文
c.Set("userID", claims.UserID)
c.Set("role", claims.Role)
c.Next()
}
}
// 管理员权限中间件
func AdminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(403, gin.H{"error": "需要管理员权限"})
c.Abort()
return
}
c.Next()
}
}
// 生成Token
func generateToken(user User) (string, error) {
claims := Claims{
UserID: user.ID,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRE)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(JWT_SECRET))
}
// 解析Token
func parseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(JWT_SECRET), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// 获取个人资料
func getProfile(c *gin.Context) {
userID, _ := c.Get("userID")
var user User
if err := db.First(&user, userID).Error; err != nil {
c.JSON(404, gin.H{"error": "用户不存在"})
return
}
c.JSON(200, gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"role": user.Role,
})
}
// 更新个人资料
func updateProfile(c *gin.Context) {
userID, _ := c.Get("userID")
var user User
if err := db.First(&user, userID).Error; err != nil {
c.JSON(404, gin.H{"error": "用户不存在"})
return
}
// 只允许更新某些字段
type UpdateRequest struct {
Email string `json:"email" binding:"omitempty,email"`
}
var req UpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 更新用户信息
if req.Email != "" {
user.Email = req.Email
}
if err := db.Save(&user).Error; err != nil {
c.JSON(500, gin.H{"error": "更新失败"})
return
}
c.JSON(200, gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"role": user.Role,
})
}
// 修改密码
func changePassword(c *gin.Context) {
userID, _ := c.Get("userID")
type PasswordRequest struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
var req PasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
var user User
if err := db.First(&user, userID).Error; err != nil {
c.JSON(404, gin.H{"error": "用户不存在"})
return
}
// 验证旧密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
c.JSON(400, gin.H{"error": "旧密码错误"})
return
}
// 加密新密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
c.JSON(500, gin.H{"error": "密码加密失败"})
return
}
// 更新密码
user.Password = string(hashedPassword)
if err := db.Save(&user).Error; err != nil {
c.JSON(500, gin.H{"error": "密码更新失败"})
return
}
c.JSON(200, gin.H{"message": "密码修改成功"})
}
这个认证与授权模块包含了:
- JWT token 的生成和验证
- 用户注册和登录
- 密码加密和验证
- 角色基础的访问控制(RBAC)
- 个人资料管理
- 密码修改功能
- 中间件实现的权限控制
主要特点:
- 使用 bcrypt 进行密码加密
- 使用 JWT 进行身份验证
- 实现了基本的角色权限控制
- 提供了完整的用户管理功能
- 包含了必要的安全措施