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

14 KiB
Raw Blame History

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

📋 概述

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

基础信息

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

🔐 认证方式

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

Authorization: Bearer {your_admin_jwt_token}

📡 API接口列表

1. 生成OSS POST签名

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

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

请求参数

{
  "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

响应示例

{
  "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

请求示例

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

响应示例

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

3. 批量删除文件

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

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

请求参数

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

响应示例

{
  "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 文件的完整路径

请求示例

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

响应示例

{
  "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

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

响应示例

{
  "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 示例

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 示例

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}/         # 自定义目录

文件命名建议

// 推荐的文件命名格式
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