package com.zhentao.osspicture; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.PutObjectRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.UUID; @Component @Slf4j public class OssUtil { // 从配置文件中读取OSS相关信息 private static final String endpoint = "https://oss-cn-beijing.aliyuncs.com"; private static final String accessKeyId = "LTAI5tH9VHPZwGJu4UX3hrL5"; private static final String accessKeySecret = "mbsutFJYLkzosvvKNr0DD28XSg4mqA"; private static final String bucketName = "fjj1"; private static final boolean useCDN = true; private static final String cdnDomain = "https://cdn.yourdomain.com"; // MIME类型到扩展名的映射 private static final Map MIME_TO_EXTENSION = new HashMap<>(); static { // 图片类型 MIME_TO_EXTENSION.put("image/jpeg", "jpg"); MIME_TO_EXTENSION.put("image/png", "png"); MIME_TO_EXTENSION.put("image/gif", "gif"); MIME_TO_EXTENSION.put("image/bmp", "bmp"); MIME_TO_EXTENSION.put("image/webp", "webp"); MIME_TO_EXTENSION.put("image/svg+xml", "svg"); // 视频类型 MIME_TO_EXTENSION.put("video/mp4", "mp4"); MIME_TO_EXTENSION.put("video/quicktime", "mov"); MIME_TO_EXTENSION.put("video/x-msvideo", "avi"); MIME_TO_EXTENSION.put("video/x-matroska", "mkv"); MIME_TO_EXTENSION.put("video/x-flv", "flv"); MIME_TO_EXTENSION.put("video/webm", "webm"); // 音频类型 MIME_TO_EXTENSION.put("audio/mpeg", "mp3"); MIME_TO_EXTENSION.put("audio/wav", "wav"); MIME_TO_EXTENSION.put("audio/ogg", "ogg"); MIME_TO_EXTENSION.put("audio/flac", "flac"); MIME_TO_EXTENSION.put("audio/aac", "aac"); // 文档类型 MIME_TO_EXTENSION.put("application/pdf", "pdf"); MIME_TO_EXTENSION.put("application/msword", "doc"); MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); MIME_TO_EXTENSION.put("application/vnd.ms-excel", "xls"); MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"); MIME_TO_EXTENSION.put("application/vnd.ms-powerpoint", "ppt"); MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"); MIME_TO_EXTENSION.put("text/plain", "txt"); } public String uploadFile(MultipartFile file) throws IOException { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("上传文件不能为空"); } OSS ossClient = null; try { ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); String originalFilename = file.getOriginalFilename(); String contentType = file.getContentType(); long fileSize = file.getSize(); // 确定文件类型和扩展名 FileTypeInfo fileTypeInfo = determineFileType(originalFilename, contentType); String extension = fileTypeInfo.getExtension(); String fileCategory = fileTypeInfo.getCategory(); // 生成唯一的文件名 String fileName = generateUniqueFileName(fileCategory, extension); // 创建文件元数据 ObjectMetadata metadata = createMetadata(originalFilename, contentType, fileSize, fileCategory); // 上传文件 try (InputStream inputStream = file.getInputStream()) { PutObjectRequest putObjectRequest = new PutObjectRequest( bucketName, fileName, inputStream, metadata ); ossClient.putObject(putObjectRequest); } // 生成文件访问URL String fileUrl = generateFileUrl(fileName); log.info("文件上传成功: {} ({}), 大小: {} KB, URL: {}", originalFilename, fileCategory, fileSize / 1024, fileUrl); return fileUrl; } catch (Exception e) { log.error("文件上传失败: {}", e.getMessage(), e); throw new IOException("文件上传失败: " + e.getMessage(), e); } finally { if (ossClient != null) { ossClient.shutdown(); } } } private String generateUniqueFileName(String category, String extension) { return "uploads/" + category + "/" + UUID.randomUUID() + "." + extension; } private ObjectMetadata createMetadata(String filename, String contentType, long fileSize, String category) { ObjectMetadata metadata = new ObjectMetadata(); // 设置基础元数据 metadata.setContentType(contentType != null ? contentType : "application/octet-stream"); metadata.setContentLength(fileSize); // 设置下载时的文件名 if (filename != null) { metadata.setContentDisposition("attachment; filename=\"" + filename + "\""); } // 添加自定义元数据 metadata.addUserMetadata("Original-Filename", filename != null ? filename : ""); metadata.addUserMetadata("File-Category", category); return metadata; } private FileTypeInfo determineFileType(String filename, String contentType) { String extension = "bin"; String category = "other"; // 1. 尝试从文件名获取扩展名 if (filename != null && filename.contains(".")) { String fileExt = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); if (isValidExtension(fileExt)) { extension = fileExt; category = determineCategory(extension); } } // 2. 如果扩展名无效,尝试从内容类型获取 if ("bin".equals(extension) && contentType != null) { // 使用映射表获取标准扩展名 String mappedExt = MIME_TO_EXTENSION.get(contentType.toLowerCase()); if (mappedExt != null) { extension = mappedExt; category = determineCategory(extension); } } // 3. 最终确定文件分类 if ("other".equals(category)) { category = determineCategory(extension); } return new FileTypeInfo(extension, category); } private boolean isValidExtension(String ext) { // 检查扩展名是否有效(不含特殊字符) return ext.matches("[a-z0-9]{1,10}"); } private String determineCategory(String extension) { if (extension.matches("png|jpe?g|gif|bmp|webp|svg|heic|heif")) { return "images"; } else if (extension.matches("mp4|mov|avi|mkv|flv|webm|m4v|wmv|3gp")) { return "videos"; } else if (extension.matches("mp3|wav|ogg|flac|aac|m4a|wma|amr")) { return "audios"; } else if (extension.matches("pdf|docx?|xlsx?|pptx?|txt|rtf|csv|pages|numbers|key")) { return "documents"; } else if (extension.matches("zip|rar|7z|tar|gz")) { return "archives"; } return "other"; } private String generateFileUrl(String fileName) { // 使用CDN加速域名(如果配置了),否则使用标准OSS域名 if (useCDN && cdnDomain != null && !cdnDomain.isEmpty()) { return cdnDomain + "/" + fileName; } // 构建标准OSS域名 String domain = endpoint.startsWith("http") ? endpoint : "https://" + endpoint; return domain.replaceFirst("://", "://" + bucketName + ".") + "/" + fileName; } // 文件类型信息辅助类 private static class FileTypeInfo { private final String extension; private final String category; public FileTypeInfo(String extension, String category) { this.extension = extension; this.category = category; } public String getExtension() { return extension; } public String getCategory() { return category; } } }