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:
Dev Team
2026-01-20 22:23:37 +08:00
parent 53ca5e56e8
commit 78b64b50ab
6 changed files with 154 additions and 264 deletions

View File

@@ -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) {