fix: use preview-mode signed URLs and graceful media preview fallback
This commit is contained in:
@@ -686,6 +686,30 @@ function getBusyDownloadMessage() {
|
|||||||
return '当前网络繁忙,请稍后再试';
|
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) {
|
function parseDateTimeValue(value) {
|
||||||
if (!value || typeof value !== 'string') {
|
if (!value || typeof value !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@@ -4534,6 +4558,8 @@ app.post('/api/files/upload-complete', authMiddleware, async (req, res) => {
|
|||||||
// 生成 OSS 下载签名 URL(用户直连 OSS 下载,不经过后端)
|
// 生成 OSS 下载签名 URL(用户直连 OSS 下载,不经过后端)
|
||||||
app.get('/api/files/download-url', authMiddleware, async (req, res) => {
|
app.get('/api/files/download-url', authMiddleware, async (req, res) => {
|
||||||
const filePath = req.query.path;
|
const filePath = req.query.path;
|
||||||
|
const mode = String(req.query.mode || 'download').toLowerCase();
|
||||||
|
const isPreviewMode = mode === 'preview';
|
||||||
|
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -4610,12 +4636,22 @@ app.get('/api/files/download-url', authMiddleware, async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 GetObject 命令
|
// 创建 GetObject 命令(预览模式使用 inline,下载模式使用 attachment)
|
||||||
const command = new GetObjectCommand({
|
const fileName = normalizedPath.split('/').pop() || 'download.bin';
|
||||||
|
const commandInput = {
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: objectKey,
|
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小时有效)
|
// 生成签名 URL(1小时有效)
|
||||||
const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });
|
const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });
|
||||||
|
|||||||
@@ -3818,7 +3818,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-viewer-body">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3838,7 +3838,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-viewer-body">
|
<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>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
@@ -3863,7 +3863,7 @@
|
|||||||
<div class="audio-player-icon">
|
<div class="audio-player-icon">
|
||||||
<i class="fas fa-music"></i>
|
<i class="fas fa-music"></i>
|
||||||
</div>
|
</div>
|
||||||
<audio controls :src="currentMediaUrl" class="media-viewer-audio">
|
<audio controls :src="currentMediaUrl" class="media-viewer-audio" @error="handleMediaPreviewError('audio')">
|
||||||
您的浏览器不支持音频播放
|
您的浏览器不支持音频播放
|
||||||
</audio>
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1937,7 +1937,10 @@ handleDragLeave(e) {
|
|||||||
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
|
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
|
||||||
params: { path: filePath }
|
params: {
|
||||||
|
path: filePath,
|
||||||
|
mode: 'preview'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
return data.downloadUrl;
|
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) {
|
async openVideoPlayer(file) {
|
||||||
const url = await this.getMediaUrl(file);
|
const url = await this.getMediaUrl(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user