CI 测试 / go vet + go test (push) Successful in 49s
- 全项目版本号统一:v3.x 迭代号(26.9/26.9/26.9/26.9 及裸 v2/v3)→ 26.9, 覆盖 Go 注释 / 文档 / openapi.yaml / README×4 / 前端源码(80+ 处) - v31_test.go 更名 custom_code_test.go;TestV2AccessorDefaults → TestKVAccessorDefaults - docs/api/00-overview.md 更新日志合并为单条 26.9 条目(修复错位拼接) - .goreleaser.yaml 头部注释与实际一致(Pro 2.18.1 / GITEA_TOKEN / semver tag 要求) - CI:release-image.yml → ci.yml,仅保留 vet+test 门禁; 镜像发布移交 GoReleaser Pro(原 build-push 的 tag 校验与 26.9 版本方案冲突,历史 9 次失败) - 前端重建:server/web/dist 与 web-embed 同步(docs 文案嵌入更新)
104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
package middleware
|
||
|
||
// 26.9:上传/下载带宽限速(字节/秒,0=不限速)。
|
||
//
|
||
// 设计要点:
|
||
// - 令牌桶(token bucket):每 Read 计算自上次起累计可消费字节,
|
||
// 未达上限则阻塞至令牌补足;保证长期平均速率,瞬时按桶容量突发。
|
||
// - 桶容量 = rate(1 秒突发量):简单且足够友好;后续若要更细粒度可参数化。
|
||
// - 0 速率直接透传,不引入任何开销(性能基线保持)。
|
||
// - 对 gin 中间件"上传限速":包裹 c.Request.Body,使 multipart 解析/单分片读取
|
||
// 都被天然节流;下载侧由 api 层在 storage.Open 之后包裹 ReadCloser。
|
||
|
||
import (
|
||
"io"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"fileshare/internal/response"
|
||
)
|
||
|
||
// rateLimitedReader 按 bytes/sec 限速的 io.Reader(基于时间窗的精确调度)。
|
||
// 实现:累计已读字节,按 rate 推算"应到达时间";超前则 sleep 补齐。
|
||
// - rate <= 0 时退化为直通;
|
||
// - 长期速率严格 ≤ rate,瞬时按调用方 Read 块大小自然突发;
|
||
// - 并发安全(每次请求持独立实例即可,但保留锁便于复用)。
|
||
type rateLimitedReader struct {
|
||
r io.Reader
|
||
rate int // 字节/秒
|
||
start time.Time // 起始时间(首 Read 时记)
|
||
bytes int64 // 累计已读字节
|
||
mu sync.Mutex
|
||
}
|
||
|
||
// NewRateLimitedReader 构造限速 reader;rate<=0 返回原始 reader。
|
||
func NewRateLimitedReader(r io.Reader, rate int) io.Reader {
|
||
if r == nil || rate <= 0 {
|
||
return r
|
||
}
|
||
return &rateLimitedReader{r: r, rate: rate}
|
||
}
|
||
|
||
// Read 限速:底层 Read 后若已超时应到达时间,sleep 补齐;错误/EOF 透传。
|
||
func (rl *rateLimitedReader) Read(p []byte) (int, error) {
|
||
if rl.rate <= 0 {
|
||
return rl.r.Read(p)
|
||
}
|
||
n, err := rl.r.Read(p)
|
||
if n <= 0 {
|
||
return n, err
|
||
}
|
||
rl.mu.Lock()
|
||
if rl.start.IsZero() {
|
||
rl.start = time.Now()
|
||
}
|
||
rl.bytes += int64(n)
|
||
// 已读 n 字节的"应到达时间" = start + bytes/rate
|
||
expected := rl.start.Add(time.Duration(float64(time.Second) * float64(rl.bytes) / float64(rl.rate)))
|
||
now := time.Now()
|
||
rl.mu.Unlock()
|
||
if now.Before(expected) {
|
||
time.Sleep(expected.Sub(now))
|
||
}
|
||
return n, err
|
||
}
|
||
|
||
// UploadBandwidthMiddleware 限速上传请求体(包裹 c.Request.Body)。
|
||
// rateProvider 每次请求动态读取最新配置(管理端改 KV 后立即生效)。
|
||
func UploadBandwidthMiddleware(rateProvider func() int) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
rate := 0
|
||
if rateProvider != nil {
|
||
rate = rateProvider()
|
||
}
|
||
if rate > 0 && c.Request != nil && c.Request.Body != nil {
|
||
c.Request.Body = WrapReadCloser(c.Request.Body, rate)
|
||
}
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// WrapReadCloser 包裹任意 io.ReadCloser 为限速版本(io.Copy 等下游使用)。
|
||
func WrapReadCloser(rc io.ReadCloser, rate int) io.ReadCloser {
|
||
if rc == nil || rate <= 0 {
|
||
return rc
|
||
}
|
||
return &rateLimitedReadCloser{rc: rc, rl: &rateLimitedReader{r: rc, rate: rate}}
|
||
}
|
||
|
||
type rateLimitedReadCloser struct {
|
||
rc io.ReadCloser
|
||
rl *rateLimitedReader
|
||
}
|
||
|
||
func (r *rateLimitedReadCloser) Read(p []byte) (int, error) { return r.rl.Read(p) }
|
||
func (r *rateLimitedReadCloser) Close() error { return r.rc.Close() }
|
||
|
||
// DownloadRateReporter 限速命中提示:超长下载时可选上报。
|
||
// 当前仅占位——真正接入可在 audit 中按"下载耗时/字节比"标记慢速,但用户暂不需要。
|
||
var _ = response.Fail // 保留 response 包以备后续扩展
|
||
var _ = http.StatusOK
|