# 管理端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)*
