P0(高优先级): - P0-1: 确认数据库复合索引已存在(GORM tag),composite_index_test 验证通过 - P0-2: 连接池调优 MaxIdleConns 5→10, ConnMaxLifetime 30min→5min - P0-3: Redis 智能探测(ProbeRedis),无 Redis 自动降级到纯内存模式 P1(中优先级): - P1-1: GZIP 压缩中间件(compress/gzip 标准库,零新依赖) - P1-2: 权限缓存 TTL 30min→5min - P1-3: Argon2id 启动自适应校准(CalibrateArgon2id) 历史优化(含本次提交): - L1Cache O(n)→O(1) LRU 重构 - Auth 中间件 DB 查询合并 + 5s L1 缓存 - Logger 异步化(4096 缓冲通道) 验证: go build/vet/test 41/41 PASS, govulncheck 无漏洞
164 lines
3.6 KiB
Go
164 lines
3.6 KiB
Go
package middleware
|
||
|
||
import (
|
||
"compress/gzip"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
"sync"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// gzipMinLength 小于此字节数的响应不压缩(避免小响应压缩反而增大体积)
|
||
const gzipMinLength = 1024
|
||
|
||
// gzipPool 复用 gzip.Writer,减少 GC 压力
|
||
var gzipPool = sync.Pool{
|
||
New: func() interface{} {
|
||
w, _ := gzip.NewWriterLevel(io.Discard, gzip.BestSpeed)
|
||
return w
|
||
},
|
||
}
|
||
|
||
// gzipResponseWriter 包装 gin.ResponseWriter,按需启用 gzip 压缩。
|
||
// 所有写入先缓冲;第一次超过阈值时决定是否压缩。
|
||
type gzipResponseWriter struct {
|
||
gin.ResponseWriter
|
||
gz *gzip.Writer
|
||
buf []byte
|
||
threshold int
|
||
decided bool // 已决定是否压缩
|
||
}
|
||
|
||
func (g *gzipResponseWriter) Write(data []byte) (int, error) {
|
||
if g.decided {
|
||
if g.gz != nil {
|
||
return g.gz.Write(data)
|
||
}
|
||
return g.ResponseWriter.Write(data)
|
||
}
|
||
|
||
// 积累数据
|
||
g.buf = append(g.buf, data...)
|
||
if len(g.buf) >= g.threshold {
|
||
return len(data), g.decide()
|
||
}
|
||
return len(data), nil
|
||
}
|
||
|
||
func (g *gzipResponseWriter) WriteString(s string) (int, error) {
|
||
return g.Write([]byte(s))
|
||
}
|
||
|
||
// decide 根据已缓冲内容和 Content-Type 决定是否压缩,并写出缓冲数据
|
||
func (g *gzipResponseWriter) decide() error {
|
||
g.decided = true
|
||
|
||
ct := g.ResponseWriter.Header().Get("Content-Type")
|
||
if g.gz != nil && shouldCompress(ct) {
|
||
// 启用 gzip
|
||
g.ResponseWriter.Header().Set("Content-Encoding", "gzip")
|
||
g.ResponseWriter.Header().Set("Vary", "Accept-Encoding")
|
||
g.ResponseWriter.Header().Del("Content-Length")
|
||
g.gz.Reset(g.ResponseWriter)
|
||
if len(g.buf) > 0 {
|
||
_, err := g.gz.Write(g.buf)
|
||
g.buf = nil
|
||
return err
|
||
}
|
||
} else {
|
||
// 不压缩:回收 gzip.Writer
|
||
if g.gz != nil {
|
||
gzipPool.Put(g.gz)
|
||
g.gz = nil
|
||
}
|
||
if len(g.buf) > 0 {
|
||
_, err := g.ResponseWriter.Write(g.buf)
|
||
g.buf = nil
|
||
return err
|
||
}
|
||
}
|
||
g.buf = nil
|
||
return nil
|
||
}
|
||
|
||
// finalize 在请求处理完毕后刷出剩余缓冲数据并关闭 gzip.Writer
|
||
func (g *gzipResponseWriter) finalize() {
|
||
if !g.decided {
|
||
// 响应体小于阈值,直接透传(不压缩)
|
||
g.decided = true
|
||
if g.gz != nil {
|
||
gzipPool.Put(g.gz)
|
||
g.gz = nil
|
||
}
|
||
if len(g.buf) > 0 {
|
||
_, _ = g.ResponseWriter.Write(g.buf)
|
||
g.buf = nil
|
||
}
|
||
return
|
||
}
|
||
|
||
if g.gz != nil {
|
||
_ = g.gz.Flush()
|
||
_ = g.gz.Close()
|
||
gzipPool.Put(g.gz)
|
||
g.gz = nil
|
||
}
|
||
}
|
||
|
||
// shouldCompress 根据 Content-Type 判断是否值得压缩(二进制流不压缩)
|
||
func shouldCompress(contentType string) bool {
|
||
ct := strings.ToLower(strings.SplitN(contentType, ";", 2)[0])
|
||
switch ct {
|
||
case "application/json",
|
||
"application/javascript",
|
||
"text/html",
|
||
"text/plain",
|
||
"text/css",
|
||
"text/xml",
|
||
"application/xml",
|
||
"application/x-www-form-urlencoded":
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// GzipMiddleware 对 JSON/文本类响应启用 GZIP 压缩。
|
||
//
|
||
// 仅在满足以下条件时压缩:
|
||
// - 客户端发送了 Accept-Encoding: gzip
|
||
// - 响应 Content-Type 为 JSON/文本类
|
||
// - 响应体超过 gzipMinLength(默认 1 KiB)
|
||
//
|
||
// 其余情况透传,不影响性能。
|
||
func GzipMiddleware() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
// 客户端不接受 gzip 则跳过
|
||
if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
|
||
c.Next()
|
||
return
|
||
}
|
||
|
||
gz := gzipPool.Get().(*gzip.Writer)
|
||
|
||
grw := &gzipResponseWriter{
|
||
ResponseWriter: c.Writer,
|
||
gz: gz,
|
||
threshold: gzipMinLength,
|
||
}
|
||
|
||
c.Writer = grw
|
||
|
||
defer func() {
|
||
grw.finalize()
|
||
c.Writer = grw.ResponseWriter
|
||
}()
|
||
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// Ensure gzipResponseWriter implements http.Hijacker forwarding (needed by some WebSocket libs)
|
||
var _ http.ResponseWriter = (*gzipResponseWriter)(nil)
|