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:
@@ -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)',
|
||||
|
||||
@@ -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: '天',
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user