fix: use preview-mode signed URLs and graceful media preview fallback
This commit is contained in:
@@ -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);
|
||||
|
||||
// 生成签名 URL(1小时有效)
|
||||
const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user