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:
2026-09-06 10:20:04 +08:00
parent 113c514531
commit 00a8c16ab7
74 changed files with 9405 additions and 8 deletions
+3
View File
@@ -6,6 +6,9 @@ v2(任务 t3):液态玻璃主题(亮/暗/跟随系统)、中英双语
背景图 / 页脚自定义 / 系统通知(读 `GET /api/v1/config` 下发的 v2 字段)、上传页策略动态渲染、
管理设置页全量策略表单(PATCH `/admin/config/update`)。
v3.2 起在管理设置「上传频率限制」卡底部新增**上下行带宽限速**字段(MB/s,0=不限速,
提交后换算为字节/秒写入 `upload_rate`/`download_rate` KV)。
## 页面
| 路由 | 说明 |
+4
View File
@@ -360,6 +360,10 @@ export default {
uploadCountHint: 'Min 1, max {max}',
uploadMinute: 'Window length (minutes) · uploadMinute',
uploadMinuteHint: 'Min 1, max {max}',
uploadRate: 'Upload bandwidth (optional)',
uploadRateHint: '0 = unlimited; MB/s; range 0~1024. Live-effective (admin reads latest KV each request)',
downloadRate: 'Download bandwidth (optional)',
downloadRateHint: '0 = unlimited; MB/s; range 0~1024. S3 presigned direct upload cannot be throttled server-side',
// —— v3: friendly units + storage engine ——
unitHour: 'Hour(s)',
unitDay: 'Day(s)',
+4
View File
@@ -354,6 +354,10 @@ export default {
uploadCountHint: '最小 1,最大 {max}',
uploadMinute: '频率窗口(分钟)uploadMinute',
uploadMinuteHint: '最小 1,最大 {max}',
uploadRate: '上行带宽(可选)',
uploadRateHint: '0 = 不限速;单位 MB/s;范围 0~1024。修改后立即生效(管理端读最新 KV)',
downloadRate: '下行带宽(可选)',
downloadRateHint: '0 = 不限速;单位 MB/s;范围 0~1024。S3 预签名直传(客户端→S3)无法限速',
// —— v3:单位友好化 + 存储引擎 ——
unitHour: '小时',
unitDay: '天',
+47
View File
@@ -42,6 +42,7 @@ const FOOTER_BEIAN_MAX = 128
const BACKGROUND_URL_MAX = 2048
const UPLOAD_COUNT_MAX = 10000
const UPLOAD_MINUTE_MAX = 1440
const RATE_MAX_MB = 1024 // 1 GiB/s 上限(与后端 KVSchema 1<<30 字节对齐)
const form = reactive({
site_name: '',
@@ -61,6 +62,9 @@ const form = reactive({
allowed_file_types: '',
uploadCount: 0,
uploadMinute: 0,
// v3.2:上下行带宽(前端友好单位 = MB/s;提交时换算为字节/秒)
uploadRate: 0,
downloadRate: 0,
})
// —— v3:友好单位状态(canonical 单位仍为秒/字节,换算在前端)——
@@ -162,6 +166,9 @@ async function load(): Promise<void> {
form.allowed_file_types = types.join(',')
form.uploadCount = toNum(pick<unknown>(cfg, ['uploadCount', 'upload_count']), 0)
form.uploadMinute = toNum(pick<unknown>(cfg, ['uploadMinute', 'upload_minute']), 0)
// v3.2:带宽(KV 字节/秒 → UI MB/s,0 保留为不限)
form.uploadRate = Math.round(toNum(pick<unknown>(cfg, ['upload_rate', 'uploadRate']), 0) / MB_BYTES)
form.downloadRate = Math.round(toNum(pick<unknown>(cfg, ['download_rate', 'downloadRate']), 0) / MB_BYTES)
// v3:当前引擎(get 的 _engine_hint.storage_backend 或公开 config 的 storage_engine
const hint = pick<Record<string, unknown>>(cfg, ['_engine_hint'])
const backend = hint ? String(pick<string>(hint, ['storage_backend']) ?? '') : ''
@@ -229,6 +236,9 @@ async function save(): Promise<void> {
// 上传频率限制(键名对齐参考既有 camelCase 键)
uploadCount: clamp(form.uploadCount || 0, 1, UPLOAD_COUNT_MAX),
uploadMinute: clamp(form.uploadMinute || 0, 1, UPLOAD_MINUTE_MAX),
// v3.2:带宽(MB/s → 字节/秒;负数与 NaN 归 0)
upload_rate: clamp(Number(form.uploadRate) || 0, 0, RATE_MAX_MB) * MB_BYTES,
download_rate: clamp(Number(form.downloadRate) || 0, 0, RATE_MAX_MB) * MB_BYTES,
})
toast.success(t('admin.settings.saved'))
await config.load() // 立即刷新导航 Logo / favicon / 站点名称 / 背景 / 页脚 / 通知
@@ -256,6 +266,8 @@ function restoreDefaults(): void {
form.allowed_file_types = '*'
form.uploadCount = 10
form.uploadMinute = 1
form.uploadRate = 0
form.downloadRate = 0
// v3:单位状态复位
saveTimeValue.value = 0
saveTimeUnit.value = 'day'
@@ -736,6 +748,41 @@ onMounted(load)
<p class="hint">{{ t('admin.settings.uploadMinuteHint', { max: UPLOAD_MINUTE_MAX }) }}</p>
</div>
</div>
<!-- v3.2上下行带宽与上传次数限制并列 -->
<div class="field-row" style="margin-top: 14px">
<div class="field">
<label for="set-upload-rate">{{ t('admin.settings.uploadRate') }}</label>
<div class="unit-row">
<input
id="set-upload-rate"
v-model.number="form.uploadRate"
class="input"
type="number"
:min="0"
:max="RATE_MAX_MB"
step="1"
/>
<span class="unit-suffix">MB/s</span>
</div>
<p class="hint">{{ t('admin.settings.uploadRateHint') }}</p>
</div>
<div class="field">
<label for="set-download-rate">{{ t('admin.settings.downloadRate') }}</label>
<div class="unit-row">
<input
id="set-download-rate"
v-model.number="form.downloadRate"
class="input"
type="number"
:min="0"
:max="RATE_MAX_MB"
step="1"
/>
<span class="unit-suffix">MB/s</span>
</div>
<p class="hint">{{ t('admin.settings.downloadRateHint') }}</p>
</div>
</div>
</section>
<div class="save-row">