Files
1818web-hoduan/docs/admin-oss-upload-examples.md
2025-11-14 17:41:15 +08:00

659 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 管理端OSS上传使用示例
## 🚀 快速开始
### 1. 获取管理员Token
```javascript
// 管理员登录获取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. 基础文件上传
```javascript
// 简单的文件上传函数
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
<!-- 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
// 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
})
});
}
```
### 批量文件管理
```javascript
// 批量删除文件
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 组件示例
```vue
<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>
```
## 🛠️ 工具函数
```javascript
// 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);
}
};
```
## 📱 移动端适配
```javascript
// 移动端文件选择和上传
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);
}
});
```
## 🔧 调试工具
```javascript
// 调试和测试工具
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();
```
---
## 📝 注意事项
1. **Token管理**: 确保Token有效且不过期
2. **文件命名**: 建议使用时间戳+随机数避免重名
3. **错误处理**: 做好网络异常和服务器错误的处理
4. **进度显示**: 大文件上传时显示进度提升用户体验
5. **移动端优化**: 考虑移动设备的网络和性能限制
---
*更多详细信息请参考: [管理端OSS上传API文档](./admin-oss-upload-api.md)*