高效稳定:Spring Boot集成腾讯云OSS实现大文件分片上传与全路径获取
1.项目结构说明
src/main/java ├── com.example.oss │ ├── config │ │ ├── TencentCOSConfig.java # OSS配置类 │ │ └── GlobalExceptionHandler.java # 全局异常处理 │ ├── controller │ │ └── FileController.java # 文件上传控制器 │ ├── properties │ │ └── TencentCOSProperties.java # 配置属性类 │ ├── service │ │ └── COSUtils.java # OSS工具类 │ └── OssApplication.java # 启动类 resources └── application.yml # 配置文件
2.添加 Maven 依赖 (pom.xml
)
<dependencies><!-- 腾讯云 OSS SDK --><dependency><groupId>com.qcloud</groupId><artifactId>cos_api</artifactId><version>5.6.154</version></dependency><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Configuration Processor --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- Lombok 简化代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>
3.配置文件 (application.yml
)
server:port: 8080# 腾讯云OSS配置
tencent:cos:secret-id: AKIDz8krbsJ5yKBZQpn74WFkmLPx3******* # 替换为你的SecretIdsecret-key: Gu5t9xGARNpq86cd98joQYCN3******* # 替换为你的SecretKeyregion: ap-beijing # 存储桶地域bucket-name: your-bucket-1250000000 # 存储桶名称base-url: https://your-bucket.cos.ap-beijing.myqcloud.com # 基础URLmultipart-upload-threshold: 5242880 # 5MB分片阈值minimum-upload-part-size: 1048576 # 1MB最小分片thread-pool-core-size: 10 # 核心线程数thread-pool-max-size: 50 # 最大线程数# 文件上传大小限制
spring:servlet:multipart:max-file-size: 2GBmax-request-size: 2GB
4.配置属性类 (TencentCOSProperties.java
)
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 腾讯云OSS配置属性* 前缀: tencent.cos*/
@Data
@Component
@ConfigurationProperties(prefix = "tencent.cos")
public class TencentCOSProperties {// 访问密钥ID (从腾讯云控制台获取)private String secretId;// 访问密钥Key (从腾讯云控制台获取)private String secretKey;// 存储桶地域 (如: ap-beijing)private String region;// 存储桶名称private String bucketName;// 基础访问URLprivate String baseUrl;// 分片上传阈值 (默认5MB)private long multipartUploadThreshold = 5 * 1024 * 1024;// 最小分片大小 (默认1MB)private long minimumUploadPartSize = 1024 * 1024;// 线程池核心线程数private int threadPoolCoreSize = 10;// 线程池最大线程数private int threadPoolMaxSize = 50;
}
5.OSS 配置类 (TencentCOSConfig.java
)
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.region.Region;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** 腾讯云OSS配置类*/
@Configuration
@RequiredArgsConstructor
public class TencentCOSConfig {private final TencentCOSProperties cosProperties;/*** 创建COS客户端*/@Beanpublic COSClient cosClient() {// 1. 初始化身份认证COSCredentials cred = new BasicCOSCredentials(cosProperties.getSecretId(), cosProperties.getSecretKey());// 2. 设置客户端配置ClientConfig clientConfig = new ClientConfig(new Region(cosProperties.getRegion()));clientConfig.setConnectionTimeout(30_000); // 连接超时30秒clientConfig.setSocketTimeout(30_000); // 读写超时30秒clientConfig.setMaxConnectionsCount(1024); // 最大连接数// 3. 创建并返回COS客户端return new COSClient(cred, clientConfig);}/*** 创建分片上传线程池*/@Beanpublic ExecutorService cosTransferThreadPool() {return new ThreadPoolExecutor(cosProperties.getThreadPoolCoreSize(),cosProperties.getThreadPoolMaxSize(),60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());}
}
6.OSS 工具类 (COSUtils.java
)
import com.qcloud.cos.COSClient;
import com.qcloud.cos.model.*;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.UUID;/*** 腾讯云OSS操作工具类*/
@Slf4j
@Component
@RequiredArgsConstructor
public class COSUtils {private final COSClient cosClient;private final TencentCOSProperties cosProperties;private final ExecutorService cosTransferThreadPool;/*** 普通文件上传* @param file 上传的文件* @return 文件访问URL*/public String uploadFile(MultipartFile file) {try {// 1. 生成唯一文件名并创建文件元数据String fileName = generateUniqueFileName(file.getOriginalFilename());ObjectMetadata metadata = createObjectMetadata(file);// 2. 创建上传请求PutObjectRequest request = new PutObjectRequest(cosProperties.getBucketName(),fileName,file.getInputStream(),metadata);// 3. 执行上传cosClient.putObject(request);// 4. 返回文件URLreturn getFullFileUrl(fileName);} catch (IOException e) {log.error("文件上传失败", e);throw new RuntimeException("文件上传失败: " + e.getMessage());}}/*** 自动分片上传(支持大文件)* @param file 上传的文件* @return 文件访问URL*/public String multipartUpload(MultipartFile file) {try {// 1. 创建临时文件Path tempPath = Files.createTempFile("cos-", getFileExtension(file));file.transferTo(tempPath);// 2. 生成唯一文件名String fileName = generateUniqueFileName(file.getOriginalFilename());// 3. 执行分片上传String fileUrl = doMultipartUpload(tempPath.toFile(), fileName);// 4. 删除临时文件Files.deleteIfExists(tempPath);return fileUrl;} catch (Exception e) {log.error("分片上传失败", e);throw new RuntimeException("分片上传失败: " + e.getMessage());}}/*** 获取文件完整URL* @param fileName OSS存储的文件名* @return 完整访问URL*/public String getFullFileUrl(String fileName) {// 处理特殊字符String encodedFileName = fileName.replace(" ", "%20");return cosProperties.getBaseUrl() + "/" + encodedFileName;}/*** 生成带签名的临时URL(默认1小时有效)* @param fileName OSS存储的文件名* @return 带签名的URL*/public String generatePresignedUrl(String fileName) {// 设置过期时间(1小时后)Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);// 生成预签名URLURL url = cosClient.generatePresignedUrl(cosProperties.getBucketName(),fileName,expiration);return url.toString();}// ============== 私有方法 ==============/*** 执行分片上传*/private String doMultipartUpload(File file, String fileName) {// 1. 创建分片上传管理器TransferManager transferManager = createTransferManager();try {// 2. 创建上传请求PutObjectRequest request = new PutObjectRequest(cosProperties.getBucketName(),fileName,file);// 3. 执行上传(自动分片)Upload upload = transferManager.upload(request);UploadResult result = upload.waitForUploadResult();// 4. 返回文件URLreturn getFullFileUrl(fileName);} catch (Exception e) {throw new RuntimeException("分片上传失败", e);} finally {// 5. 关闭分片上传管理器transferManager.shutdownNow();}}/*** 创建分片上传管理器*/private TransferManager createTransferManager() {TransferManager transferManager = new TransferManager(cosClient, cosTransferThreadPool);// 配置分片参数TransferManagerConfiguration config = new TransferManagerConfiguration();config.setMultipartUploadThreshold(cosProperties.getMultipartUploadThreshold());config.setMinimumUploadPartSize(cosProperties.getMinimumUploadPartSize());transferManager.setConfiguration(config);return transferManager;}/*** 创建文件元数据*/private ObjectMetadata createObjectMetadata(MultipartFile file) throws IOException {ObjectMetadata metadata = new ObjectMetadata();metadata.setContentLength(file.getSize());metadata.setContentType(file.getContentType());// 设置缓存控制(1年缓存)metadata.setHeader("Cache-Control", "max-age=31536000");return metadata;}/*** 生成唯一文件名*/private String generateUniqueFileName(String originalFilename) {// 获取文件扩展名String ext = getFileExtension(originalFilename);// 使用UUID + 时间戳生成唯一文件名return "files/" + UUID.randomUUID() + "-" + System.currentTimeMillis() + ext;}/*** 获取文件扩展名*/private String getFileExtension(String filename) {if (filename == null || !filename.contains(".")) {return "";}return filename.substring(filename.lastIndexOf("."));}
}
7.文件控制器 (FileController.java
)
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;/*** 文件上传控制器*/
@RestController
@RequestMapping("/oss")
@RequiredArgsConstructor
public class FileController {private final COSUtils cosUtils;/*** 普通文件上传* @param file 上传的文件* @return 上传结果*/@PostMapping("/upload")public ApiResult uploadFile(@RequestParam("file") MultipartFile file) {try {String fileUrl = cosUtils.uploadFile(file);return ApiResult.success("文件上传成功", fileUrl);} catch (Exception e) {return ApiResult.fail("文件上传失败: " + e.getMessage());}}/*** 大文件分片上传* @param file 上传的文件* @return 上传结果*/@PostMapping("/multipart-upload")public ApiResult multipartUpload(@RequestParam("file") MultipartFile file) {try {String fileUrl = cosUtils.multipartUpload(file);return ApiResult.success("分片上传成功", fileUrl);} catch (Exception e) {return ApiResult.fail("分片上传失败: " + e.getMessage());}}/*** 获取文件URL* @param fileName OSS存储的文件名* @return 文件URL*/@GetMapping("/url")public ApiResult getFileUrl(@RequestParam String fileName) {try {String fileUrl = cosUtils.getFullFileUrl(fileName);return ApiResult.success(fileUrl);} catch (Exception e) {return ApiResult.fail("获取URL失败: " + e.getMessage());}}/*** 获取带签名的临时URL* @param fileName OSS存储的文件名* @return 临时URL*/@GetMapping("/presigned-url")public ApiResult getPresignedUrl(@RequestParam String fileName) {try {String presignedUrl = cosUtils.generatePresignedUrl(fileName);return ApiResult.success(presignedUrl);} catch (Exception e) {return ApiResult.fail("生成临时URL失败: " + e.getMessage());}}
}/*** 统一API响应结构*/
record ApiResult<T>(int code, String message, T data) {public static ApiResult<Object> success(Object data) {return new ApiResult<>(200, "success", data);}public static ApiResult<Object> success(String message, Object data) {return new ApiResult<>(200, message, data);}public static ApiResult<Object> fail(String message) {return new ApiResult<>(500, message, null);}
}
8.全局异常处理 (GlobalExceptionHandler.java
)
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;/*** 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 文件大小超限异常处理*/@ExceptionHandler(MaxUploadSizeExceededException.class)public ApiResult handleSizeExceeded() {return ApiResult.fail("文件大小超过限制");}/*** 通用异常处理*/@ExceptionHandler(Exception.class)public ApiResult handleException(Exception e) {return ApiResult.fail("服务异常: " + e.getMessage());}
}
9.最佳实践建议
安全优化
// 在控制器中添加文件类型校验 @PostMapping("/upload") public ApiResult uploadFile(@RequestParam("file") MultipipartFile file) {if (!isValidFileType(file)) {return ApiResult.fail("不支持的文件类型");}// ... }private boolean isValidFileType(MultipartFile file) {String[] allowedTypes = {"image/jpeg", "application/pdf"};return Arrays.asList(allowedTypes).contains(file.getContentType()); }
存储优化
// 按日期分目录存储 private String generateUniqueFileName(String originalFilename) {String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date());return dateDir + "/" + UUID.randomUUID() + getFileExtension(originalFilename); }
性能优化
# 调整线程池配置(高并发场景) tencent:cos:thread-pool-core-size: 20thread-pool-max-size: 200
监控建议
// 添加上传耗时监控 long start = System.currentTimeMillis(); String url = cosUtils.uploadFile(file); long duration = System.currentTimeMillis() - start; log.info("文件上传完成,耗时: {}ms", duration);