fix: use preview-mode signed URLs and graceful media preview fallback

This commit is contained in:
2026-02-17 19:36:49 +08:00
parent 2b700978ad
commit f0e7381c1d
3 changed files with 58 additions and 8 deletions

View File

@@ -686,6 +686,30 @@ function getBusyDownloadMessage() {
return '当前网络繁忙,请稍后再试';
}
function getPreviewContentTypeByPath(filePath = '') {
const ext = path.extname(String(filePath || '')).toLowerCase();
const map = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.bmp': 'image/bmp',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.mp4': 'video/mp4',
'.mov': 'video/quicktime',
'.webm': 'video/webm',
'.mkv': 'video/x-matroska',
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav',
'.flac': 'audio/flac',
'.ogg': 'audio/ogg',
'.m4a': 'audio/mp4',
'.aac': 'audio/aac'
};
return map[ext] || '';
}
function parseDateTimeValue(value) {
if (!value || typeof value !== 'string') {
return null;
@@ -4534,6 +4558,8 @@ app.post('/api/files/upload-complete', authMiddleware, async (req, res) => {
// 生成 OSS 下载签名 URL用户直连 OSS 下载,不经过后端)
app.get('/api/files/download-url', authMiddleware, async (req, res) => {
const filePath = req.query.path;
const mode = String(req.query.mode || 'download').toLowerCase();
const isPreviewMode = mode === 'preview';
if (!filePath) {
return res.status(400).json({
@@ -4610,12 +4636,22 @@ app.get('/api/files/download-url', authMiddleware, async (req, res) => {
}
}
// 创建 GetObject 命令
const command = new GetObjectCommand({
// 创建 GetObject 命令(预览模式使用 inline下载模式使用 attachment
const fileName = normalizedPath.split('/').pop() || 'download.bin';
const commandInput = {
Bucket: bucket,
Key: objectKey,
ResponseContentDisposition: `attachment; filename="${encodeURIComponent(normalizedPath.split('/').pop())}"`
});
ResponseContentDisposition: isPreviewMode
? `inline; filename*=UTF-8''${encodeURIComponent(fileName)}`
: `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`
};
if (isPreviewMode) {
const previewContentType = getPreviewContentTypeByPath(fileName);
if (previewContentType) {
commandInput.ResponseContentType = previewContentType;
}
}
const command = new GetObjectCommand(commandInput);
// 生成签名 URL1小时有效
const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });

View File

@@ -3818,7 +3818,7 @@
</div>
</div>
<div class="media-viewer-body">
<img :src="currentMediaUrl" :alt="currentMediaName" class="media-viewer-image">
<img :src="currentMediaUrl" :alt="currentMediaName" class="media-viewer-image" @error="handleMediaPreviewError('image')">
</div>
</div>
</div>
@@ -3838,7 +3838,7 @@
</div>
</div>
<div class="media-viewer-body">
<video controls :src="currentMediaUrl" class="media-viewer-video">
<video controls :src="currentMediaUrl" class="media-viewer-video" @error="handleMediaPreviewError('video')">
您的浏览器不支持视频播放
</video>
</div>
@@ -3863,7 +3863,7 @@
<div class="audio-player-icon">
<i class="fas fa-music"></i>
</div>
<audio controls :src="currentMediaUrl" class="media-viewer-audio">
<audio controls :src="currentMediaUrl" class="media-viewer-audio" @error="handleMediaPreviewError('audio')">
您的浏览器不支持音频播放
</audio>
</div>

View File

@@ -1937,7 +1937,10 @@ handleDragLeave(e) {
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 }
params: {
path: filePath,
mode: 'preview'
}
});
if (data.success) {
return data.downloadUrl;
@@ -1995,6 +1998,17 @@ handleDragLeave(e) {
}
},
handleMediaPreviewError(type = 'file') {
const typeTextMap = {
image: '图片',
video: '视频',
audio: '音频'
};
const typeText = typeTextMap[type] || '文件';
this.showToast('error', '预览失败', `${typeText}预览失败,请尝试下载后查看`);
this.closeMediaViewer();
},
// 打开视频播放器
async openVideoPlayer(file) {
const url = await this.getMediaUrl(file);