静态文件 #

一、静态文件概述 #

1.1 什么是静态文件 #

静态文件是不需要服务器处理的文件,直接返回给客户端:

text
静态文件类型:
├── CSS样式 (.css)
├── JavaScript脚本 (.js)
├── 图片 (.png, .jpg, .gif, .svg)
├── 字体 (.woff, .ttf)
├── 媒体文件 (.mp4, .mp3)
└── 其他 (.ico, .pdf)

1.2 静态文件方法 #

方法 说明
Static 映射静态文件目录
StaticFS 使用http.FileSystem
StaticFile 映射单个文件

二、基本静态文件服务 #

2.1 Static方法 #

go
func main() {
    r := gin.Default()
    
    // 映射静态文件目录
    // 访问 /assets/* 对应 ./assets/* 文件
    r.Static("/assets", "./assets")
    
    r.Run()
}

目录结构:

text
project/
├── assets/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── app.js
│   └── images/
│       └── logo.png
└── main.go

访问示例:

text
/assets/css/style.css    → ./assets/css/style.css
/assets/js/app.js        → ./assets/js/app.js
/assets/images/logo.png  → ./assets/images/logo.png

2.2 StaticFile方法 #

go
func main() {
    r := gin.Default()
    
    // 映射单个文件
    r.StaticFile("/favicon.ico", "./favicon.ico")
    r.StaticFile("/robots.txt", "./robots.txt")
    
    r.Run()
}

2.3 StaticFS方法 #

go
func main() {
    r := gin.Default()
    
    // 使用http.FileSystem
    r.StaticFS("/static", http.Dir("./static"))
    
    r.Run()
}

三、多目录配置 #

3.1 多个静态目录 #

go
func main() {
    r := gin.Default()
    
    // CSS目录
    r.Static("/css", "./public/css")
    
    // JS目录
    r.Static("/js", "./public/js")
    
    // 图片目录
    r.Static("/images", "./public/images")
    
    // 上传文件目录
    r.Static("/uploads", "./uploads")
    
    r.Run()
}

3.2 嵌套目录 #

go
func main() {
    r := gin.Default()
    
    // 一个目录包含所有静态资源
    r.Static("/static", "./static")
    
    r.Run()
}

目录结构:

text
static/
├── css/
│   ├── main.css
│   └── admin/
│       └── dashboard.css
├── js/
│   ├── app.js
│   └── admin/
│       └── dashboard.js
└── images/
    └── logo.png

访问示例:

text
/static/css/main.css
/static/css/admin/dashboard.css
/static/js/app.js
/static/images/logo.png

四、在模板中使用静态文件 #

4.1 引用CSS #

html
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <h1>Hello, Gin!</h1>
</body>
</html>

4.2 引用JavaScript #

html
<!DOCTYPE html>
<html>
<body>
    <script src="/static/js/app.js"></script>
</body>
</html>

4.3 引用图片 #

html
<img src="/static/images/logo.png" alt="Logo">

4.4 动态路径 #

go
func main() {
    r := gin.Default()
    
    r.SetFuncMap(template.FuncMap{
        "asset": func(path string) string {
            return "/static/" + path
        },
    })
    
    r.LoadHTMLGlob("templates/*")
    r.Static("/static", "./static")
    
    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{})
    })
    
    r.Run()
}

模板中使用:

html
<link rel="stylesheet" href="{{ asset "css/style.css" }}">
<script src="{{ asset "js/app.js" }}"></script>
<img src="{{ asset "images/logo.png" }}">

五、文件缓存 #

5.1 设置缓存头 #

go
func main() {
    r := gin.Default()
    
    // 静态文件缓存中间件
    r.Use(func(c *gin.Context) {
        if strings.HasPrefix(c.Request.URL.Path, "/static/") {
            c.Header("Cache-Control", "public, max-age=31536000")
        }
        c.Next()
    })
    
    r.Static("/static", "./static")
    
    r.Run()
}

5.2 不同文件类型缓存 #

go
func StaticCacheMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        path := c.Request.URL.Path
        
        // 根据文件类型设置缓存
        switch {
        case strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".js"):
            c.Header("Cache-Control", "public, max-age=31536000")
        case strings.HasSuffix(path, ".png") || strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".gif"):
            c.Header("Cache-Control", "public, max-age=31536000")
        case strings.HasSuffix(path, ".html"):
            c.Header("Cache-Control", "no-cache")
        }
        
        c.Next()
    }
}

func main() {
    r := gin.Default()
    r.Use(StaticCacheMiddleware())
    r.Static("/static", "./static")
    r.Run()
}

5.3 ETag支持 #

go
func ETagMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if c.Writer.Status() == 200 {
            // 生成ETag
            body := c.Writer.(*gin.ResponseWriter)
            if body != nil {
                hash := md5.Sum(body.Bytes())
                etag := hex.EncodeToString(hash[:])
                
                c.Header("ETag", etag)
                
                // 检查客户端ETag
                if c.GetHeader("If-None-Match") == etag {
                    c.Status(304)
                }
            }
        }
    }
}

六、文件压缩 #

6.1 Gzip压缩 #

bash
go get github.com/gin-contrib/gzip
go
import "github.com/gin-contrib/gzip"

func main() {
    r := gin.Default()
    
    // 启用Gzip压缩
    r.Use(gzip.Gzip(gzip.DefaultCompression))
    
    r.Static("/static", "./static")
    
    r.Run()
}

6.2 排除某些文件 #

go
func main() {
    r := gin.Default()
    
    r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{
        ".png", ".jpg", ".gif", ".mp4", ".mp3",
    })))
    
    r.Static("/static", "./static")
    
    r.Run()
}

七、安全配置 #

7.1 禁止目录浏览 #

go
func main() {
    r := gin.Default()
    
    // 自定义静态文件处理
    r.NoRoute(func(c *gin.Context) {
        path := c.Request.URL.Path
        
        // 防止目录遍历
        if strings.Contains(path, "..") {
            c.String(400, "Bad Request")
            return
        }
        
        // 防止访问隐藏文件
        if strings.Contains(path, "/.") {
            c.String(404, "Not Found")
            return
        }
        
        c.String(404, "Not Found")
    })
    
    r.Static("/static", "./static")
    
    r.Run()
}

7.2 文件类型限制 #

go
func main() {
    r := gin.Default()
    
    // 只允许特定文件类型
    allowedExts := map[string]bool{
        ".css":  true,
        ".js":   true,
        ".png":  true,
        ".jpg":  true,
        ".gif":  true,
        ".svg":  true,
        ".woff": true,
        ".ttf":  true,
    }
    
    r.Use(func(c *gin.Context) {
        if strings.HasPrefix(c.Request.URL.Path, "/static/") {
            ext := strings.ToLower(filepath.Ext(c.Request.URL.Path))
            if !allowedExts[ext] {
                c.String(403, "Forbidden")
                c.Abort()
                return
            }
        }
        c.Next()
    })
    
    r.Static("/static", "./static")
    
    r.Run()
}

八、CDN集成 #

8.1 CDN配置 #

go
type CDNConfig struct {
    Enabled bool
    BaseURL string
}

func main() {
    r := gin.Default()
    
    cdnConfig := CDNConfig{
        Enabled: true,
        BaseURL: "https://cdn.example.com",
    }
    
    r.SetFuncMap(template.FuncMap{
        "static": func(path string) string {
            if cdnConfig.Enabled {
                return cdnConfig.BaseURL + "/static/" + path
            }
            return "/static/" + path
        },
    })
    
    r.LoadHTMLGlob("templates/*")
    r.Static("/static", "./static")
    
    r.Run()
}

8.2 模板中使用CDN #

html
<link rel="stylesheet" href="{{ static "css/style.css" }}">
<script src="{{ static "js/app.js" }}"></script>
<img src="{{ static "images/logo.png" }}">

8.3 版本控制 #

go
func main() {
    r := gin.Default()
    
    version := "1.0.0"
    
    r.SetFuncMap(template.FuncMap{
        "static": func(path string) string {
            return fmt.Sprintf("/static/%s?v=%s", path, version)
        },
    })
    
    r.LoadHTMLGlob("templates/*")
    r.Static("/static", "./static")
    
    r.Run()
}

模板中使用:

html
<link rel="stylesheet" href="{{ static "css/style.css" }}">
<!-- 输出: /static/css/style.css?v=1.0.0 -->

九、完整示例 #

9.1 项目结构 #

text
project/
├── main.go
├── static/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── app.js
│   └── images/
│       └── logo.png
└── templates/
    └── index.html

9.2 完整代码 #

go
package main

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

func main() {
    r := gin.Default()
    
    // Gzip压缩
    r.Use(gzip.Gzip(gzip.DefaultCompression))
    
    // 静态文件缓存
    r.Use(StaticCacheMiddleware())
    
    // 模板函数
    r.SetFuncMap(template.FuncMap{
        "static": func(path string) string {
            return "/static/" + path
        },
    })
    
    // 加载模板
    r.LoadHTMLGlob("templates/*")
    
    // 静态文件
    r.Static("/static", "./static")
    r.StaticFile("/favicon.ico", "./favicon.ico")
    
    // 路由
    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{
            "title": "首页",
        })
    })
    
    r.Run(":8080")
}

func StaticCacheMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        path := c.Request.URL.Path
        
        if strings.HasPrefix(path, "/static/") {
            ext := strings.ToLower(filepath.Ext(path))
            
            switch ext {
            case ".css", ".js", ".png", ".jpg", ".gif", ".svg", ".woff", ".ttf":
                c.Header("Cache-Control", "public, max-age=31536000")
            case ".html":
                c.Header("Cache-Control", "no-cache")
            }
        }
        
        c.Next()
    }
}

9.3 模板文件 #

templates/index.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>{{ .title }}</title>
    <link rel="stylesheet" href="{{ static "css/style.css" }}">
</head>
<body>
    <img src="{{ static "images/logo.png" }}" alt="Logo">
    <h1>Hello, Gin!</h1>
    
    <script src="{{ static "js/app.js" }}"></script>
</body>
</html>

十、总结 #

10.1 核心要点 #

要点 说明
Static 映射静态文件目录
StaticFile 映射单个文件
StaticFS 使用http.FileSystem
缓存 设置Cache-Control
压缩 使用Gzip

10.2 最佳实践 #

实践 说明
目录组织 按类型分目录
缓存策略 静态资源长期缓存
版本控制 使用查询参数
CDN加速 使用CDN分发

10.3 下一步 #

现在你已经掌握了静态文件服务,接下来让我们学习 数据库概述,了解Gin与数据库的集成!

最后更新:2026-03-28