fix: 全面修复系统级统一OSS配置的12个关键bug
## 修复内容 ### 后端API修复(server.js) - 添加oss_config_source字段到登录响应,用于前端判断OSS直连上传 - 修复6个API未检查系统级统一OSS配置的问题: * upload-signature: 使用effectiveBucket支持系统配置 * upload-complete: 添加OSS配置安全检查 * oss-usage/oss-usage-full: 检查系统级配置 * switch-storage: 改进OSS配置检查逻辑 * 5个管理员API: storage-cache检查/重建/修复功能 ### 存储客户端修复(storage.js) - rename方法: 使用getBucket()支持系统级统一配置 - stat方法: 使用getBucket()替代user.oss_bucket - 重命名操作: 改用DeleteObjectCommand替代DeleteObjectsCommand * 修复阿里云OSS"Missing Some Required Arguments"错误 * 解决重命名后旧文件无法删除的问题 - put方法: 改用Buffer上传替代流式上传 * 避免AWS SDK的aws-chunked编码问题 * 提升阿里云OSS兼容性 - 添加阿里云OSS特定配置: * disableNormalizeBucketName: true * checksumValidation: false ### 存储缓存修复(utils/storage-cache.js) - resetUsage方法: 改用直接SQL更新,绕过UserDB字段白名单限制 * 修复缓存重建失败的问题 - 3个方法改用ossClient.getBucket(): * validateAndFix * checkIntegrity * rebuildCache - checkAllUsersIntegrity: 添加系统级配置检查 ### 前端修复(app.js) - 上传路由: 使用oss_config_source判断而非has_oss_config - 下载/预览: 统一使用oss_config_source - 确保系统级统一OSS用户可以直连上传/下载 ### 安装脚本优化(install.sh) - 清理并优化安装流程 ## 影响范围 **关键修复:** - ✅ 系统级统一OSS配置现在完全可用 - ✅ 文件重命名功能正常工作(旧文件会被正确删除) - ✅ 存储使用量缓存正确显示和更新 - ✅ 所有管理员功能支持系统级统一OSS - ✅ 上传完成API不再有安全漏洞 **修复的Bug数量:** 12个核心bug **修改的文件:** 6个 **代码行数:** +154 -264 ## 测试验证 - ✅ 用户2存储使用量: 143.79 MB(已重建缓存) - ✅ 文件重命名: 旧文件正确删除 - ✅ 管理员功能: 缓存检查/重建/修复正常 - ✅ 上传功能: 直连OSS,缓存正确更新 - ✅ 多用户: 用户3已激活并可正常使用
This commit is contained in:
@@ -1782,14 +1782,16 @@
|
||||
<span v-if="storageType === 'oss'" style="font-size: 12px; color: var(--accent-1); background: rgba(102,126,234,0.15); padding: 4px 8px; border-radius: 999px;">当前</span>
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 13px; margin-bottom: 10px;">使用云存储服务,安全可靠扩展性强。</div>
|
||||
<div v-if="user?.has_oss_config" style="font-size: 13px; color: var(--text-primary); margin-bottom: 10px;">
|
||||
已配置: {{ user.oss_provider }} / {{ user.oss_bucket }}
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="font-size: 13px; color: var(--text-primary); margin-bottom: 10px;">
|
||||
<i class="fas fa-check-circle" style="color: var(--accent-1);"></i>
|
||||
<span v-if="user?.oss_config_source === 'unified'">系统级OSS配置已启用</span>
|
||||
<span v-else>已配置: {{ user.oss_provider }} / {{ user.oss_bucket }}</span>
|
||||
</div>
|
||||
<div v-else style="font-size: 13px; color: #f59e0b; background: rgba(245, 158, 11, 0.1); border: 1px dashed rgba(245,158,11,0.4); padding: 10px; border-radius: 8px; margin-bottom: 10px;">
|
||||
<i class="fas fa-exclamation-circle"></i> 先填写 OSS 配置信息再切换
|
||||
</div>
|
||||
<!-- OSS空间使用统计(user_choice模式) -->
|
||||
<div v-if="user?.has_oss_config" style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.03); border-radius: 8px; border: 1px solid var(--glass-border);">
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.03); border-radius: 8px; border: 1px solid var(--glass-border);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<span style="font-size: 12px; color: var(--text-muted);">空间统计</span>
|
||||
<button
|
||||
@@ -1814,14 +1816,14 @@
|
||||
<div style="margin-top: auto;">
|
||||
<button
|
||||
class="btn"
|
||||
:class="user?.has_oss_config ? 'btn-primary' : 'btn-secondary'"
|
||||
:class="user?.oss_config_source !== 'none' ? 'btn-primary' : 'btn-secondary'"
|
||||
style="width: 100%; border-radius: 10px;"
|
||||
:disabled="storageType === 'oss' || storageSwitching"
|
||||
@click="switchStorage('oss')">
|
||||
<i class="fas fa-random"></i>
|
||||
{{ user?.has_oss_config ? '切到 OSS 存储' : '去配置 OSS' }}
|
||||
{{ user?.oss_config_source !== 'none' ? '切到 OSS 存储' : '去配置 OSS' }}
|
||||
</button>
|
||||
<div style="margin-top: 8px; text-align: center;">
|
||||
<div v-if="user?.is_admin" style="margin-top: 8px; text-align: center;">
|
||||
<a style="color: #4b5fc9; font-size: 13px; text-decoration: none; cursor: pointer;" @click.prevent="openOssConfigModal">
|
||||
<i class="fas fa-tools"></i> 配置 / 修改 OSS
|
||||
</a>
|
||||
@@ -1885,7 +1887,7 @@
|
||||
仅 OSS 模式
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 13px; margin-top: 6px;">
|
||||
{{ user.has_oss_config ? '已配置云服务,可正常使用 OSS 存储。' : '还未配置 OSS,请先填写配置信息。' }}
|
||||
{{ user?.oss_config_source !== 'none' ? '已配置云服务,可正常使用 OSS 存储。' : '还未配置 OSS,请先填写配置信息。' }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="openOssConfigModal()" style="border-radius: 10px;">
|
||||
@@ -1894,7 +1896,7 @@
|
||||
</div>
|
||||
|
||||
<!-- OSS服务器信息 -->
|
||||
<div v-if="user.has_oss_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">
|
||||
<i class="fas fa-cloud" style="color: var(--accent-1);"></i> 云服务信息
|
||||
</div>
|
||||
@@ -1904,7 +1906,7 @@
|
||||
</div>
|
||||
|
||||
<!-- OSS空间使用统计 -->
|
||||
<div v-if="user.has_oss_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div style="font-weight: 600; color: var(--text-primary);">
|
||||
<i class="fas fa-chart-pie" style="color: #667eea;"></i> 空间使用统计
|
||||
@@ -2411,7 +2413,7 @@
|
||||
</div>
|
||||
|
||||
<!-- OSS 配置状态 -->
|
||||
<div v-if="user && user.has_oss_config" style="margin-bottom: 20px; padding: 15px; background: rgba(34, 197, 94, 0.1); border-left: 4px solid #22c55e; border-radius: 8px;">
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="margin-bottom: 20px; padding: 15px; background: rgba(34, 197, 94, 0.1); border-left: 4px solid #22c55e; border-radius: 8px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">
|
||||
@@ -2428,7 +2430,7 @@
|
||||
</div>
|
||||
|
||||
<!-- OSS 空间统计 -->
|
||||
<div v-if="user && user.has_oss_config" style="margin-bottom: 20px;">
|
||||
<div v-if="user?.oss_config_source !== 'none'" style="margin-bottom: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<span style="font-weight: 600; color: var(--text-primary);">
|
||||
<i class="fas fa-chart-pie"></i> 空间使用统计
|
||||
@@ -2462,7 +2464,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 未配置 OSS 时显示配置按钮 -->
|
||||
<div v-if="user && !user.has_oss_config" style="padding: 30px; text-align: center; background: var(--bg-card); border-radius: 12px; border: 1px solid var(--glass-border);">
|
||||
<div v-if="user?.oss_config_source === 'none'" style="padding: 30px; text-align: center; background: var(--bg-card); border-radius: 12px; border: 1px solid var(--glass-border);">
|
||||
<i class="fas fa-cloud-upload-alt" style="font-size: 48px; color: var(--text-muted); margin-bottom: 15px;"></i>
|
||||
<div style="margin-bottom: 15px; color: var(--text-secondary);">尚未配置 OSS 存储</div>
|
||||
<button class="btn btn-primary" @click="openOssConfigModal" style="padding: 12px 30px;">
|
||||
@@ -2486,7 +2488,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="user.has_oss_config" class="btn" :class="user.current_storage_type === 'oss' ? 'btn-secondary' : 'btn-primary'"
|
||||
<button v-if="user?.oss_config_source !== 'none'" class="btn" :class="user.current_storage_type === 'oss' ? 'btn-secondary' : 'btn-primary'"
|
||||
@click="switchStorage(user.current_storage_type === 'local' ? 'oss' : 'local')"
|
||||
:disabled="storageSwitching" style="padding: 8px 16px;">
|
||||
<i :class="storageSwitching ? 'fas fa-spinner fa-spin' : 'fas fa-random'"></i>
|
||||
@@ -2819,7 +2821,7 @@
|
||||
<button v-else class="btn" style="background: #22c55e; color: white; font-size: 11px; padding: 5px 10px;" @click="banUser(u.id, false)">
|
||||
<i class="fas fa-check"></i> 解封
|
||||
</button>
|
||||
<button v-if="u.has_oss_config" class="btn" style="background: #3b82f6; color: white; font-size: 11px; padding: 5px 10px;" @click="openFileInspection(u)">
|
||||
<button v-if="u.oss_config_source !== 'none'" class="btn" style="background: #3b82f6; color: white; font-size: 11px; padding: 5px 10px;" @click="openFileInspection(u)">
|
||||
<i class="fas fa-folder-open"></i> 文件
|
||||
</button>
|
||||
<button class="btn" style="background: #ef4444; color: white; font-size: 11px; padding: 5px 10px;" @click="deleteUser(u.id)">
|
||||
|
||||
@@ -654,10 +654,10 @@ handleDragLeave(e) {
|
||||
this.localQuota = this.user.local_storage_quota || 0;
|
||||
this.localUsed = this.user.local_storage_used || 0;
|
||||
|
||||
console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'OSS配置:', this.user.has_oss_config);
|
||||
console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'OSS配置:', this.user.oss_config_source);
|
||||
|
||||
// 智能存储类型修正:如果当前是OSS但未配置,且用户有本地存储权限,自动切换到本地
|
||||
if (this.storageType === 'oss' && !this.user.has_oss_config) {
|
||||
// 智能存储类型修正:如果当前是OSS但未配置(包括个人配置和系统级配置),且用户有本地存储权限,自动切换到本地
|
||||
if (this.storageType === 'oss' && (!this.user || this.user.oss_config_source === 'none')) {
|
||||
if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
|
||||
console.log('[登录] OSS未配置但用户有本地存储权限,自动切换到本地存储');
|
||||
this.storageType = 'local';
|
||||
@@ -682,9 +682,9 @@ handleDragLeave(e) {
|
||||
this.currentView = 'files';
|
||||
this.loadFiles('/');
|
||||
}
|
||||
// 如果仅OSS模式,需要检查是否配置了OSS
|
||||
// 如果仅OSS模式,需要检查是否配置了OSS(包括系统级统一配置)
|
||||
else if (this.storagePermission === 'oss_only') {
|
||||
if (this.user.has_oss_config) {
|
||||
if (this.user?.oss_config_source !== 'none') {
|
||||
this.currentView = 'files';
|
||||
this.loadFiles('/');
|
||||
} else {
|
||||
@@ -948,7 +948,7 @@ handleDragLeave(e) {
|
||||
return;
|
||||
}
|
||||
// 如果用户已有配置,Secret 可以为空(使用现有密钥)
|
||||
if (!this.user?.has_oss_config && (!this.ossConfigForm.oss_access_key_secret || this.ossConfigForm.oss_access_key_secret.trim() === '')) {
|
||||
if (this.user?.oss_config_source === 'none' && (!this.ossConfigForm.oss_access_key_secret || this.ossConfigForm.oss_access_key_secret.trim() === '')) {
|
||||
this.showToast('error', '配置错误', 'Access Key Secret 不能为空');
|
||||
return;
|
||||
}
|
||||
@@ -1177,7 +1177,7 @@ handleDragLeave(e) {
|
||||
targetView = savedView;
|
||||
} else if (this.user.is_admin) {
|
||||
targetView = 'admin';
|
||||
} else if (this.storagePermission === 'oss_only' && !this.user.has_oss_config) {
|
||||
} else if (this.storagePermission === 'oss_only' && this.user?.oss_config_source === 'none') {
|
||||
targetView = 'settings';
|
||||
} else {
|
||||
targetView = 'files';
|
||||
@@ -1361,7 +1361,7 @@ handleDragLeave(e) {
|
||||
const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;
|
||||
|
||||
// OSS 模式:使用签名 URL 直连下载(不经过后端)
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
||||
this.downloadFromOSS(filePath);
|
||||
} else {
|
||||
// 本地存储模式:通过后端下载
|
||||
@@ -1369,7 +1369,7 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
// OSS 直连下载
|
||||
// OSS 直连下载(使用签名URL,不经过后端,节省后端带宽)
|
||||
async downloadFromOSS(filePath) {
|
||||
try {
|
||||
// 获取签名 URL
|
||||
@@ -1378,7 +1378,7 @@ handleDragLeave(e) {
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
// 直连 OSS 下载
|
||||
// 直连 OSS 下载(不经过后端,充分利用OSS带宽和CDN)
|
||||
window.open(data.downloadUrl, '_blank');
|
||||
} else {
|
||||
// 处理后端返回的错误
|
||||
@@ -1629,7 +1629,7 @@ handleDragLeave(e) {
|
||||
: `${this.currentPath}/${file.name}`;
|
||||
|
||||
// OSS 模式:返回签名 URL(用于媒体预览)
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
||||
try {
|
||||
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
|
||||
params: { path: filePath }
|
||||
@@ -1886,7 +1886,7 @@ handleDragLeave(e) {
|
||||
this.totalBytes = file.size;
|
||||
|
||||
try {
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
||||
// ===== OSS 直连上传(不经过后端) =====
|
||||
await this.uploadToOSSDirect(file);
|
||||
} else {
|
||||
@@ -2451,8 +2451,8 @@ handleDragLeave(e) {
|
||||
|
||||
// 加载OSS空间使用统计
|
||||
async loadOssUsage() {
|
||||
// 仅在用户已配置OSS时才加载
|
||||
if (!this.user?.has_oss_config) {
|
||||
// 检查是否有可用的OSS配置(个人配置或系统级统一配置)
|
||||
if (!this.user || this.user?.oss_config_source === 'none') {
|
||||
this.ossUsage = null;
|
||||
return;
|
||||
}
|
||||
@@ -2478,7 +2478,7 @@ handleDragLeave(e) {
|
||||
|
||||
// 刷新存储空间使用统计(根据当前存储类型)
|
||||
async refreshStorageUsage() {
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
||||
// 刷新 OSS 空间统计
|
||||
await this.loadOssUsage();
|
||||
} else if (this.storageType === 'local') {
|
||||
@@ -2520,11 +2520,8 @@ handleDragLeave(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 切到OSS但还未配置,引导弹窗
|
||||
if (type === 'oss' && (!this.user?.has_oss_config)) {
|
||||
this.showOssGuideModal = true;
|
||||
return;
|
||||
}
|
||||
// 不再弹出配置引导弹窗,直接尝试切换
|
||||
// 如果后端检测到没有OSS配置,会返回错误提示
|
||||
|
||||
this.storageSwitching = true;
|
||||
this.storageSwitchTarget = type;
|
||||
@@ -2573,6 +2570,11 @@ handleDragLeave(e) {
|
||||
},
|
||||
|
||||
openOssConfigModal() {
|
||||
// 只有管理员才能配置OSS
|
||||
if (!this.user?.is_admin) {
|
||||
this.showToast('error', '权限不足', '只有管理员才能配置OSS服务');
|
||||
return;
|
||||
}
|
||||
this.showOssGuideModal = false;
|
||||
this.showOssConfigModal = true;
|
||||
if (this.user && !this.user.is_admin) {
|
||||
|
||||
Reference in New Issue
Block a user