18 KiB
18 KiB
管理端OSS上传使用示例
🚀 快速开始
1. 获取管理员Token
// 管理员登录获取Token
const login = async (username, password) => {
const response = await fetch('/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await response.json();
return result.data.token; // 保存这个token
};
2. 基础文件上传
// 简单的文件上传函数
async function uploadFile(file, directory = 'uploads') {
const token = localStorage.getItem('adminToken');
try {
// 1. 获取上传签名
const signResponse = await fetch('/admin/oss/post-signature', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
fileName: file.name,
directory: directory
})
});
const { data: signature } = await signResponse.json();
// 2. 构建上传表单 - 使用正确的字段名
const formData = new FormData();
const fileKey = `${signature.dir}${Date.now()}_${file.name}`;
formData.append('key', fileKey);
formData.append('policy', signature.policy);
formData.append('x-oss-credential', signature.x_oss_credential);
formData.append('x-oss-date', signature.x_oss_date);
formData.append('x-oss-signature-version', signature.x_oss_signature_version);
formData.append('x-oss-signature', signature.x_oss_signature);
formData.append('success_action_status', '200');
formData.append('file', file);
// 3. 上传到OSS
const uploadResponse = await fetch(signature.host, {
method: 'POST',
body: formData
});
if (uploadResponse.ok) {
const fileUrl = `${signature.host}/${fileKey}`;
console.log('上传成功:', fileUrl);
return { success: true, url: fileUrl, key: fileKey };
}
throw new Error('上传失败');
} catch (error) {
console.error('上传出错:', error);
return { success: false, error: error.message };
}
}
// 使用示例
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
const result = await uploadFile(file, 'banners');
if (result.success) {
alert('上传成功: ' + result.url);
}
}
});
📋 常用场景示例
Banner图片上传
<!-- HTML -->
<div class="banner-upload">
<input type="file" id="bannerFile" accept="image/*">
<button onclick="uploadBanner()">上传Banner</button>
<div id="uploadProgress" style="display:none;">上传中...</div>
</div>
// JavaScript
async function uploadBanner() {
const fileInput = document.getElementById('bannerFile');
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
// 显示进度
document.getElementById('uploadProgress').style.display = 'block';
try {
const result = await uploadFile(file, 'banners');
if (result.success) {
alert('Banner上传成功!\n文件地址: ' + result.url);
// 这里可以保存到数据库
saveBannerToDatabase(result.url, result.key);
}
} finally {
document.getElementById('uploadProgress').style.display = 'none';
}
}
// 保存Banner到数据库的示例
async function saveBannerToDatabase(imageUrl, objectKey) {
const token = localStorage.getItem('adminToken');
await fetch('/admin/banners/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
image: imageUrl,
title: '新Banner',
description: '通过OSS上传的Banner图片',
linkType: 'internal',
link: '/',
sortOrder: 1,
isEnabled: true
})
});
}
批量文件管理
// 批量删除文件
async function deleteMultipleFiles(fileKeys) {
const token = localStorage.getItem('adminToken');
const response = await fetch('/admin/oss/batch-delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(fileKeys)
});
const result = await response.json();
console.log('删除结果:', result.data);
alert(`删除完成: 成功${result.data.successCount}个,失败${result.data.failedCount}个`);
}
// 使用示例
const filesToDelete = [
'user_img/banners/old_banner1.jpg',
'user_img/banners/old_banner2.jpg'
];
deleteMultipleFiles(filesToDelete);
Vue.js 组件示例
<template>
<div class="admin-upload">
<el-upload
ref="upload"
:before-upload="beforeUpload"
:http-request="customUpload"
:show-file-list="false"
accept="image/*,video/*,.pdf,.doc,.docx"
>
<el-button type="primary" :loading="uploading">
{{ uploading ? '上传中...' : '选择文件' }}
</el-button>
</el-upload>
<div v-if="uploadedFiles.length > 0" class="file-list">
<h4>已上传文件:</h4>
<div v-for="file in uploadedFiles" :key="file.key" class="file-item">
<span>{{ file.name }}</span>
<a :href="file.url" target="_blank">查看</a>
<el-button type="danger" size="small" @click="deleteFile(file)">删除</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AdminOssUpload',
data() {
return {
uploading: false,
uploadedFiles: [],
directory: 'uploads' // 可以根据需要修改
};
},
methods: {
beforeUpload(file) {
// 文件类型检查
const allowedTypes = [
'image/jpeg', 'image/png', 'image/gif',
'video/mp4', 'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (!allowedTypes.includes(file.type)) {
this.$message.error('不支持的文件类型');
return false;
}
// 文件大小检查 (500MB)
if (file.size > 500 * 1024 * 1024) {
this.$message.error('文件大小不能超过500MB');
return false;
}
return true;
},
async customUpload(options) {
this.uploading = true;
try {
const file = options.file;
const result = await this.uploadToOss(file);
if (result.success) {
this.uploadedFiles.push({
name: file.name,
url: result.url,
key: result.key
});
this.$message.success('上传成功');
} else {
this.$message.error('上传失败: ' + result.error);
}
} catch (error) {
this.$message.error('上传出错: ' + error.message);
} finally {
this.uploading = false;
}
},
async uploadToOss(file) {
const token = this.$store.getters.adminToken;
// 获取签名
const signResponse = await this.$http.post('/admin/oss/post-signature', {
fileName: file.name,
directory: this.directory,
maxSizeMB: Math.ceil(file.size / (1024 * 1024))
}, {
headers: { Authorization: `Bearer ${token}` }
});
const signature = signResponse.data.data;
// 上传到OSS
const formData = new FormData();
const fileKey = `${signature.dir}${Date.now()}_${file.name}`;
formData.append('key', fileKey);
formData.append('policy', signature.policy);
// 注意:不需要这个字段,OSS POST V4使用x-oss-credential
formData.append('x-oss-signature-version', signature.x_oss_signature_version);
formData.append('x-oss-credential', signature.x_oss_credential);
formData.append('x-oss-date', signature.x_oss_date);
formData.append('x-oss-signature', signature.x_oss_signature);
formData.append('success_action_status', '200');
formData.append('file', file);
const uploadResponse = await fetch(signature.host, {
method: 'POST',
body: formData
});
if (uploadResponse.ok) {
return {
success: true,
url: `${signature.host}/${fileKey}`,
key: fileKey
};
}
throw new Error('OSS上传失败');
},
async deleteFile(file) {
try {
const token = this.$store.getters.adminToken;
await this.$http.delete(`/admin/oss/file?objectKey=${encodeURIComponent(file.key)}`, {
headers: { Authorization: `Bearer ${token}` }
});
// 从列表中移除
const index = this.uploadedFiles.findIndex(f => f.key === file.key);
if (index > -1) {
this.uploadedFiles.splice(index, 1);
}
this.$message.success('删除成功');
} catch (error) {
this.$message.error('删除失败');
}
}
}
};
</script>
🛠️ 工具函数
// OSS上传工具类
class AdminOssManager {
constructor(baseURL, getToken) {
this.baseURL = baseURL;
this.getToken = getToken; // 获取token的函数
}
// 上传文件
async upload(file, directory = 'uploads', options = {}) {
const {
maxSizeMB = Math.ceil(file.size / (1024 * 1024)),
onProgress = () => {},
onSuccess = () => {},
onError = () => {}
} = options;
try {
onProgress(0);
// 获取签名
const signature = await this.getSignature(file.name, directory, maxSizeMB);
onProgress(20);
// 上传文件
const result = await this.uploadToOss(file, signature, (progress) => {
onProgress(20 + progress * 0.8); // 20-100
});
onSuccess(result);
return result;
} catch (error) {
onError(error);
throw error;
}
}
// 获取上传签名
async getSignature(fileName, directory, maxSizeMB) {
const response = await fetch(`${this.baseURL}/admin/oss/post-signature`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getToken()}`
},
body: JSON.stringify({
fileName,
directory,
maxSizeMB
})
});
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message);
}
return result.data;
}
// 上传到OSS
async uploadToOss(file, signature, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
const fileKey = `${signature.dir}${this.generateFileName(file.name)}`;
// 构建表单数据
formData.append('key', fileKey);
formData.append('policy', signature.policy);
// 注意:不需要这个字段,OSS POST V4使用x-oss-credential
formData.append('x-oss-signature-version', signature.x_oss_signature_version);
formData.append('x-oss-credential', signature.x_oss_credential);
formData.append('x-oss-date', signature.x_oss_date);
formData.append('x-oss-signature', signature.x_oss_signature);
formData.append('success_action_status', '200');
formData.append('file', file);
// 监听进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const progress = (e.loaded / e.total) * 100;
onProgress(progress);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve({
success: true,
url: `${signature.host}/${fileKey}`,
key: fileKey
});
} else {
reject(new Error(`Upload failed: ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('Upload failed'));
});
xhr.open('POST', signature.host);
xhr.send(formData);
});
}
// 删除文件
async delete(objectKey) {
const response = await fetch(`${this.baseURL}/admin/oss/file?objectKey=${encodeURIComponent(objectKey)}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${this.getToken()}`
}
});
const result = await response.json();
return result.code === 200;
}
// 批量删除
async batchDelete(objectKeys) {
const response = await fetch(`${this.baseURL}/admin/oss/batch-delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getToken()}`
},
body: JSON.stringify(objectKeys)
});
const result = await response.json();
return result.data;
}
// 生成唯一文件名
generateFileName(originalName) {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2);
const ext = originalName.substring(originalName.lastIndexOf('.'));
return `${timestamp}_${random}${ext}`;
}
}
// 使用示例
const ossManager = new AdminOssManager(
'https://your-api.com',
() => localStorage.getItem('adminToken')
);
// 上传文件
const uploadFile = async (file) => {
try {
const result = await ossManager.upload(file, 'banners', {
onProgress: (progress) => console.log(`上传进度: ${progress}%`),
onSuccess: (result) => console.log('上传成功:', result.url),
onError: (error) => console.error('上传失败:', error)
});
return result;
} catch (error) {
console.error('上传出错:', error);
}
};
📱 移动端适配
// 移动端文件选择和上传
class MobileOssUpload {
constructor(ossManager) {
this.ossManager = ossManager;
}
// 选择并上传图片(支持相机和相册)
async selectAndUploadImage(directory = 'images') {
return new Promise((resolve, reject) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.capture = 'environment'; // 优先使用摄像头
input.onchange = async (e) => {
const file = e.target.files[0];
if (file) {
try {
// 压缩图片(可选)
const compressedFile = await this.compressImage(file);
const result = await this.ossManager.upload(compressedFile, directory);
resolve(result);
} catch (error) {
reject(error);
}
}
};
input.click();
});
}
// 图片压缩
async compressImage(file, quality = 0.8, maxWidth = 1920) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
// 计算压缩后的尺寸
let { width, height } = img;
if (width > maxWidth) {
height = (height * maxWidth) / width;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
// 绘制压缩后的图片
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
const compressedFile = new File([blob], file.name, {
type: file.type,
lastModified: Date.now()
});
resolve(compressedFile);
}, file.type, quality);
};
img.src = URL.createObjectURL(file);
});
}
}
// 使用示例
const mobileUpload = new MobileOssUpload(ossManager);
// 移动端上传按钮
document.getElementById('mobileUploadBtn').addEventListener('click', async () => {
try {
const result = await mobileUpload.selectAndUploadImage('mobile');
alert('上传成功: ' + result.url);
} catch (error) {
alert('上传失败: ' + error.message);
}
});
🔧 调试工具
// 调试和测试工具
const OssDebugger = {
// 测试上传配置
async testConfig() {
try {
const response = await fetch('/admin/oss/upload-config', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
}
});
const result = await response.json();
console.log('上传配置:', result.data);
return result.data;
} catch (error) {
console.error('获取配置失败:', error);
}
},
// 测试文件上传
async testUpload() {
// 创建一个测试文件
const testContent = 'This is a test file for OSS upload';
const blob = new Blob([testContent], { type: 'text/plain' });
const testFile = new File([blob], 'test.txt', { type: 'text/plain' });
try {
const result = await uploadFile(testFile, 'test');
console.log('测试上传结果:', result);
// 测试删除
if (result.success) {
await this.testDelete(result.key);
}
} catch (error) {
console.error('测试上传失败:', error);
}
},
// 测试文件删除
async testDelete(objectKey) {
try {
const token = localStorage.getItem('adminToken');
const response = await fetch(`/admin/oss/file?objectKey=${encodeURIComponent(objectKey)}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
const result = await response.json();
console.log('测试删除结果:', result);
} catch (error) {
console.error('测试删除失败:', error);
}
}
};
// 在控制台运行测试
// OssDebugger.testConfig();
// OssDebugger.testUpload();
📝 注意事项
- Token管理: 确保Token有效且不过期
- 文件命名: 建议使用时间戳+随机数避免重名
- 错误处理: 做好网络异常和服务器错误的处理
- 进度显示: 大文件上传时显示进度提升用户体验
- 移动端优化: 考虑移动设备的网络和性能限制
更多详细信息请参考: 管理端OSS上传API文档