[Claude Workbench] Initial commit - preserving existing code
This commit is contained in:
658
docs/admin-oss-upload-examples.md
Normal file
658
docs/admin-oss-upload-examples.md
Normal file
@@ -0,0 +1,658 @@
|
||||
# 管理端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)*
|
||||
Reference in New Issue
Block a user