feat: 分片上传断点续传、临时目录后台配置与清扫、宇恒云账号管理

- 管理端大文件分片上传与 sessionStorage 续传;Nginx 大请求体/超时
- .chunk-uploads 定期清扫;system_config 后台配置保留时长与扫描间隔
- 宇恒云 POST /register 对接与 yuheng_cloud_register_records 留痕;yuheng_cloud:manage 权限

Made-with: Cursor
This commit is contained in:
whm
2026-04-13 14:50:27 +08:00
parent 03f5fbb41a
commit 0800982224
20 changed files with 1413 additions and 47 deletions

View File

@@ -0,0 +1,91 @@
<template>
<div class="chunk-upload-cleanup">
<el-card>
<template #header>
<span>分片上传临时目录清理</span>
</template>
<p class="hint">
大文件分片上传时未完成合并的会话保存在上传目录下的 <code>.chunk-uploads</code>超过下方保留时长的目录会被定期删除未在后台保存时可使用环境变量
<code>YH_CHUNK_UPLOAD_MAX_AGE_HOURS</code><code>YH_CHUNK_UPLOAD_SWEEP_MINUTES</code>保存后台配置后优先生效
</p>
<el-form v-if="canEdit" :model="form" label-width="140px" style="max-width: 520px">
<el-form-item label="保留时长(小时)">
<el-input-number v-model="form.max_age_hours" :min="6" :max="336" :step="6" controls-position="right" />
<span class="form-tip">6336默认 72超过此时长未合并的会话视为非活动</span>
</el-form-item>
<el-form-item label="扫描间隔(分钟)">
<el-input-number v-model="form.sweep_minutes" :min="5" :max="1440" :step="5" controls-position="right" />
<span class="form-tip">51440默认 60服务端按此频率检查是否需清扫</span>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleSave">保存</el-button>
</el-form-item>
</el-form>
<el-alert v-else type="warning" title="无权限" description="需要「站点管理」权限。" :closable="false" />
</el-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { getChunkUploadCleanup, updateChunkUploadCleanup } from '../../api/admin'
import { useAuthStore } from '../../stores/auth'
const authStore = useAuthStore()
const loading = ref(false)
const form = reactive({
max_age_hours: 72,
sweep_minutes: 60
})
const canEdit = computed(() => authStore.hasPermission('site:manage'))
const fetchConfig = async () => {
if (!canEdit.value) return
try {
const res = await getChunkUploadCleanup()
form.max_age_hours = Number(res.max_age_hours) || 72
form.sweep_minutes = Number(res.sweep_minutes) || 60
} catch (e) {
ElMessage.error(e.message || '获取配置失败')
}
}
const handleSave = async () => {
loading.value = true
try {
await updateChunkUploadCleanup({
max_age_hours: form.max_age_hours,
sweep_minutes: form.sweep_minutes
})
ElMessage.success('保存成功,新参数在下次清扫周期生效')
} catch (e) {
ElMessage.error(e.response?.data?.error || e.message || '保存失败')
} finally {
loading.value = false
}
}
onMounted(fetchConfig)
</script>
<style scoped>
.chunk-upload-cleanup {
padding: 0;
}
.hint {
margin: 0 0 20px;
font-size: 13px;
color: #606266;
line-height: 1.65;
max-width: 720px;
}
.form-tip {
display: block;
margin-top: 6px;
font-size: 12px;
color: #909399;
line-height: 1.4;
}
</style>