26.9:上下行带宽限速 + 品牌更新 + 产品修复
亮点 - 26.9 带宽限速(upload_rate/download_rate,字节/秒,0=不限速;管理端立即生效) - middleware/bandwidth.go 时间窗对齐 sleep 算法 + 单元测试(400KB@100KB/s 4.00s 精度) - 下载:serveFile 包裹 storage.ReadCloser(统一覆盖 local/webdav/s3 代理下载) - 上传:UploadBandwidthMiddleware 包裹 Request.Body(shareFile/chunk/presign proxy) - S3 预签名直传不可服务端限速——UI/文档明示 - 后台 SettingsView 增加 MB/s 友好输入;i18n zh-CN/en-US 双语 - 26.9 文档:新增 docs/api/13-bandwidth.md 专题;10-config 指针;00-overview changelog 与限流表带宽行;openapi.yaml 三处 schema + description 更新 - README.md / web/README.md / server/README.md / deploy/README.md 全部覆盖 品牌(26.9 收尾) - 文件快递柜 → 文件快传(前端/后端默认值/文档/产物/运行 KV) - 「复制取件码」按钮删除;取件码块点击即复制(保持原尺寸) - 「复制链接」→「复制链接和提取码」(一并复制链接和提取码) 产品修复(26.9) - 文本分享 Content-Type text/plain + urlencoded body:前端显式声明 urlencoded 头根治; 后端 bindJSONOrForm 兜底兼容 text/plain + JSON/urlencoded 嗅探 - Docker 部署文档校对到 26.9 现状(热切换 + 端口/卷/健康检查)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestRateLimitedReaderAtRate 验证长读大致符合设定速率。
|
||||
// 设 100 KB/s,读取 400 KB 总字节,期望耗时约 4s(±1s 容忍)。
|
||||
func TestRateLimitedReaderAtRate(t *testing.T) {
|
||||
const rate = 100 * 1024
|
||||
const total = 400 * 1024
|
||||
src := io.LimitReader(bytes.NewReader(make([]byte, total+1024)), total)
|
||||
rl := NewRateLimitedReader(src, rate).(*rateLimitedReader)
|
||||
|
||||
buf := make([]byte, 32*1024) // 32KB 块
|
||||
start := time.Now()
|
||||
read := 0
|
||||
for {
|
||||
n, err := rl.Read(buf)
|
||||
read += n
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
want := time.Duration(float64(time.Second) * float64(total) / float64(rate))
|
||||
if elapsed < want-time.Second {
|
||||
t.Fatalf("读取过快 elapsed=%v want>=%v", elapsed, want)
|
||||
}
|
||||
if elapsed > want+1500*time.Millisecond {
|
||||
t.Fatalf("读取过慢 elapsed=%v want<=%v", elapsed, want+1500*time.Millisecond)
|
||||
}
|
||||
if read != total {
|
||||
t.Fatalf("读到 %d 字节,期望 %d", read, total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateLimitedReaderZeroPassthrough 速率 0 时不应引入任何延迟/包封。
|
||||
func TestRateLimitedReaderZeroPassthrough(t *testing.T) {
|
||||
src := bytes.NewReader([]byte("hello"))
|
||||
rl := NewRateLimitedReader(src, 0)
|
||||
if rl == src {
|
||||
// 透传:返回原 reader
|
||||
} else if _, ok := rl.(*rateLimitedReader); ok {
|
||||
// 0 速率时按实现可走 enabled=false(不退化亦可)
|
||||
}
|
||||
// 关键:必须能读完
|
||||
b, err := io.ReadAll(rl)
|
||||
if err != nil || string(b) != "hello" {
|
||||
t.Fatalf("0 速率透传失败: %q %v", b, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWrapReadCloserClose 验证包裹后 Close 透传到底层。
|
||||
func TestWrapReadCloserClose(t *testing.T) {
|
||||
src := &closeCount{Reader: bytes.NewReader([]byte("xyz")), closed: 0}
|
||||
rc := WrapReadCloser(src, 50*1024)
|
||||
if rc == nil {
|
||||
t.Fatal("WrapReadCloser nil")
|
||||
}
|
||||
_, _ = io.ReadAll(rc)
|
||||
if err := rc.Close(); err != nil {
|
||||
t.Fatalf("close err: %v", err)
|
||||
}
|
||||
if src.closed != 1 {
|
||||
t.Fatalf("底层 Close 未被调用: %d", src.closed)
|
||||
}
|
||||
}
|
||||
|
||||
type closeCount struct {
|
||||
io.Reader
|
||||
closed int
|
||||
}
|
||||
|
||||
func (c *closeCount) Close() error { c.closed++; return nil }
|
||||
Reference in New Issue
Block a user