Files
SKYMirror 84df9996cb
CI 测试 / go vet + go test (push) Successful in 49s
26.9:版本号统一 + CI 精简 + 前端产物重建
- 全项目版本号统一: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 文案嵌入更新)
2026-09-08 03:16:51 +08:00

104 lines
3.5 KiB
Go
Raw Permalink 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.
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 构造限速 readerrate<=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