# 管理端OSS文件上传接口文档

## 📋 概述

管理端OSS文件上传接口提供了完整的文件管理功能，包括文件上传签名生成、文件删除、批量删除和文件信息查询。**管理端和用户端的文件存储在同一目录下**（`user_img/`），便于统一管理。

### 基础信息
- **基础路径**: `/admin/oss`
- **权限要求**: 需要管理员或工作人员JWT Token
- **文件存储**: 与用户端共享同一目录 (`user_img/`)
- **最大文件**: 500MB
- **有效期**: 2小时

---

## 🔐 认证方式

所有管理端接口都需要在请求头中携带JWT Token：

```http
Authorization: Bearer {your_admin_jwt_token}
```

---

## 📡 API接口列表

### 1. 生成OSS POST签名

**接口地址**: `POST /admin/oss/post-signature`

**功能描述**: 生成管理端文件上传的OSS POST签名，支持多种文件格式和大文件上传。

#### 请求参数

```json
{
  "fileName": "banner.jpg",
  "directory": "banners",
  "description": "Banner图片",
  "fileCategory": "image",
  "maxSizeMB": 50
}
```

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| fileName | string | ✅ | 文件名，包含扩展名 |
| directory | string | ❌ | 子目录名称（不包含user_img前缀） |
| description | string | ❌ | 文件描述 |
| fileCategory | string | ❌ | 文件分类：image/document/compressed/video/audio/other |
| maxSizeMB | integer | ❌ | 最大文件大小(MB)，默认50MB，最大500MB |

#### 响应示例

```json
{
  "code": 200,
  "message": "管理端POST签名生成成功",
  "data": {
    "version": "OSS4-HMAC-SHA256",
    "policy": "eyJleHBpcmF0aW9uIjoiMjAyNC0xMi0yNVQxNDowMDowMC4wMDBaIi...",
    "x_oss_credential": "LTAI5t7Cn8mLa9K8NQy7S9Vj/20241225/cn-hangzhou/oss/aliyun_v4_request",
    "x_oss_date": "20241225T120000Z",
    "signature": "a1b2c3d4e5f6789...",
    "security_token": "",
    "dir": "user_img/banners/",
    "host": "https://oss-1818ai-user-img.oss-cn-hangzhou.aliyuncs.com",
    "accessKeyId": "LTAI5t7Cn8mLa9K8NQy7S9Vj",
    "adminId": "123",
    "fileName": "banner.jpg",
    "fileType": "image",
    "maxFileSize": 52428800,
    "maxFileSizeMB": 50,
    "supportedFormats": [
      "图片: jpg, jpeg, png, gif, bmp, webp, svg, ico, tiff",
      "文档: pdf, txt, md, json, xml, csv, doc, docx, xls, xlsx, ppt, pptx",
      "压缩包: zip, rar, 7z, tar, gz, bz2, xz",
      "音频: mp3, wav, flac, aac, ogg, wma",
      "视频: mp4, avi, mov, wmv, flv, mkv, webm",
      "其他: html, css, js, sql, log"
    ],
    "uploadTips": "支持常见图片格式，建议使用JPG/PNG格式以获得更好的兼容性。"
  }
}
```

---

### 2. 删除文件

**接口地址**: `DELETE /admin/oss/file`

**功能描述**: 删除指定的OSS文件。

#### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| objectKey | string | ✅ | 文件的完整路径，如：user_img/banners/banner.jpg |

#### 请求示例

```http
DELETE /admin/oss/file?objectKey=user_img/banners/banner.jpg
Authorization: Bearer {admin_jwt_token}
```

#### 响应示例

```json
{
  "code": 200,
  "message": "操作成功",
  "data": "文件删除成功"
}
```

---

### 3. 批量删除文件

**接口地址**: `POST /admin/oss/batch-delete`

**功能描述**: 批量删除多个OSS文件。

#### 请求参数

```json
[
  "user_img/banners/banner1.jpg",
  "user_img/banners/banner2.jpg",
  "user_img/documents/file.pdf"
]
```

#### 响应示例

```json
{
  "code": 200,
  "message": "批量删除操作完成",
  "data": {
    "success": [
      "user_img/banners/banner1.jpg",
      "user_img/banners/banner2.jpg"
    ],
    "failed": [
      "user_img/documents/file.pdf"
    ],
    "total": 3,
    "successCount": 2,
    "failedCount": 1
  }
}
```

---

### 4. 获取文件信息

**接口地址**: `GET /admin/oss/file-info`

**功能描述**: 获取OSS文件的详细信息。

#### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| objectKey | string | ✅ | 文件的完整路径 |

#### 请求示例

```http
GET /admin/oss/file-info?objectKey=user_img/banners/banner.jpg
Authorization: Bearer {admin_jwt_token}
```

#### 响应示例

```json
{
  "code": 200,
  "message": "获取文件信息成功",
  "data": {
    "objectKey": "user_img/banners/banner.jpg",
    "size": 1024000,
    "lastModified": "2024-12-25T12:00:00.000Z",
    "contentType": "image/jpeg"
  }
}
```

---

### 5. 获取上传配置

**接口地址**: `GET /admin/oss/upload-config`

**功能描述**: 获取管理端文件上传的配置信息。

#### 响应示例

```json
{
  "code": 200,
  "message": "获取上传配置成功",
  "data": {
    "maxFileSize": 524288000,
    "maxFileSizeMB": 500,
    "supportedFormats": [
      "图片: jpg, jpeg, png, gif, bmp, webp, svg, ico, tiff",
      "文档: pdf, txt, md, json, xml, csv, doc, docx, xls, xlsx, ppt, pptx",
      "压缩包: zip, rar, 7z, tar, gz, bz2, xz",
      "音频: mp3, wav, flac, aac, ogg, wma",
      "视频: mp4, avi, mov, wmv, flv, mkv, webm",
      "其他: html, css, js, sql, log"
    ],
    "uploadDirectories": [
      "banners",
      "images", 
      "documents",
      "videos",
      "audios",
      "uploads"
    ],
    "tips": "管理端支持多种文件格式，最大支持500MB文件上传。文件将与用户端文件存储在同一目录下，建议根据用途选择合适的子目录。"
  }
}
```

---

## 💻 前端使用示例

### JavaScript/Vue.js 示例

```javascript
class AdminOssUploader {
  constructor(baseURL, token) {
    this.baseURL = baseURL;
    this.token = token;
  }

  // 获取上传签名
  async getUploadSignature(fileName, directory = 'uploads', maxSizeMB = 50) {
    const response = await fetch(`${this.baseURL}/admin/oss/post-signature`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`
      },
      body: JSON.stringify({
        fileName,
        directory,
        fileCategory: this.getFileCategory(fileName),
        maxSizeMB
      })
    });
    
    const result = await response.json();
    if (result.code === 200) {
      return result.data;
    }
    throw new Error(result.message);
  }

  // 上传文件到OSS
  async uploadFile(file, directory = 'uploads') {
    try {
      // 1. 获取签名
      const signature = await this.getUploadSignature(file.name, directory);
      
      // 2. 构建FormData
      const formData = new FormData();
      formData.append('key', `${signature.dir}${this.generateFileName(file.name)}`);
      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 uploadedUrl = `${signature.host}/${formData.get('key')}`;
        return {
          success: true,
          url: uploadedUrl,
          key: formData.get('key')
        };
      }
      throw new Error('Upload failed');
    } catch (error) {
      console.error('Upload error:', error);
      return { success: false, error: error.message };
    }
  }

  // 删除文件
  async deleteFile(objectKey) {
    const response = await fetch(`${this.baseURL}/admin/oss/file?objectKey=${encodeURIComponent(objectKey)}`, {
      method: 'DELETE',
      headers: {
        'Authorization': `Bearer ${this.token}`
      }
    });
    
    const result = await response.json();
    return result.code === 200;
  }

  // 批量删除文件
  async batchDeleteFiles(objectKeys) {
    const response = await fetch(`${this.baseURL}/admin/oss/batch-delete`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`
      },
      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}`;
  }

  // 获取文件分类
  getFileCategory(fileName) {
    const ext = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
    
    if (['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'].includes(ext)) {
      return 'image';
    } else if (['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv'].includes(ext)) {
      return 'video';
    } else if (['.mp3', '.wav', '.flac', '.aac', '.ogg'].includes(ext)) {
      return 'audio';
    } else if (['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'].includes(ext)) {
      return 'document';
    } else if (['.zip', '.rar', '.7z', '.tar', '.gz'].includes(ext)) {
      return 'compressed';
    }
    return 'other';
  }
}

// 使用示例
const uploader = new AdminOssUploader('https://your-api.com', 'your-admin-token');

// 上传Banner图片
document.getElementById('bannerInput').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (file) {
    const result = await uploader.uploadFile(file, 'banners');
    if (result.success) {
      console.log('上传成功:', result.url);
    } else {
      console.error('上传失败:', result.error);
    }
  }
});
```

### React Hook 示例

```jsx
import { useState, useCallback } from 'react';

const useAdminOssUpload = (token) => {
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);

  const uploadFile = useCallback(async (file, directory = 'uploads') => {
    setUploading(true);
    setProgress(0);

    try {
      // 获取签名
      const response = await fetch('/admin/oss/post-signature', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({
          fileName: file.name,
          directory,
          maxSizeMB: Math.ceil(file.size / (1024 * 1024))
        })
      });

      const { data: signature } = await response.json();
      
      // 上传到OSS
      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);

      const uploadResponse = await fetch(signature.host, {
        method: 'POST',
        body: formData
      });

      if (uploadResponse.ok) {
        setProgress(100);
        return {
          success: true,
          url: `${signature.host}/${fileKey}`,
          key: fileKey
        };
      }
      throw new Error('Upload failed');

    } catch (error) {
      return { success: false, error: error.message };
    } finally {
      setUploading(false);
    }
  }, [token]);

  return { uploadFile, uploading, progress };
};

// 使用示例
const AdminFileUpload = () => {
  const token = localStorage.getItem('adminToken');
  const { uploadFile, uploading } = useAdminOssUpload(token);

  const handleUpload = async (e) => {
    const file = e.target.files[0];
    if (file) {
      const result = await uploadFile(file, 'banners');
      if (result.success) {
        alert('上传成功: ' + result.url);
      } else {
        alert('上传失败: ' + result.error);
      }
    }
  };

  return (
    <div>
      <input type="file" onChange={handleUpload} disabled={uploading} />
      {uploading && <p>上传中...</p>}
    </div>
  );
};
```

---

## 📁 目录结构说明

### 存储路径规则

- **基础目录**: `user_img/` (与用户端共享)
- **完整路径**: `user_img/{directory}/{filename}`

### 推荐目录结构

```
user_img/
├── banners/          # Banner图片
├── images/           # 通用图片
├── documents/        # 文档文件
├── videos/           # 视频文件
├── audios/           # 音频文件
├── uploads/          # 默认上传目录
└── {custom}/         # 自定义目录
```

### 文件命名建议

```javascript
// 推荐的文件命名格式
const generateFileName = (originalName) => {
  const timestamp = Date.now();
  const random = Math.random().toString(36).substring(2);
  const ext = originalName.substring(originalName.lastIndexOf('.'));
  return `${timestamp}_${random}${ext}`;
};
```

---

## ⚠️ 注意事项

### 文件大小限制
- **用户端**: 最大10MB
- **管理端**: 最大500MB (可通过maxSizeMB参数调整)

### 文件格式支持
- **图片**: jpg, jpeg, png, gif, bmp, webp, svg, ico, tiff
- **文档**: pdf, txt, md, json, xml, csv, doc, docx, xls, xlsx, ppt, pptx
- **压缩包**: zip, rar, 7z, tar, gz, bz2, xz
- **音频**: mp3, wav, flac, aac, ogg, wma
- **视频**: mp4, avi, mov, wmv, flv, mkv, webm
- **其他**: html, css, js, sql, log

### 安全性
- 所有管理端接口都需要JWT认证
- 文件类型严格验证
- 文件大小限制保护
- 操作日志完整记录

### 错误码
- **200**: 操作成功
- **400**: 请求参数错误
- **401**: 未授权访问
- **403**: 权限不足
- **404**: 文件不存在
- **500**: 服务器内部错误

---

## 🔄 与用户端的差异

| 特性 | 用户端 | 管理端 |
|------|--------|--------|
| **权限** | 无需认证 | 需要管理员Token |
| **文件大小** | 10MB | 500MB |
| **文件格式** | 基础格式 | 全格式支持 |
| **目录** | user_img/ | user_img/ (相同) |
| **有效期** | 1小时 | 2小时 |
| **管理功能** | 仅上传 | 完整CRUD |

---

## 📞 技术支持

如遇到问题，请检查：
1. JWT Token是否有效
2. 文件格式是否支持
3. 文件大小是否超限
4. 网络连接是否正常
5. OSS配置是否正确

---

*最后更新时间: 2024-12-25*
