高效稳定: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)

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)

Lua 复制代码
server:
  port: 8080

# 腾讯云OSS配置
tencent:
  cos:
    secret-id: AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******  # 替换为你的SecretId
    secret-key: Gu5t9xGARNpq86cd98joQYCN3*******    # 替换为你的SecretKey
    region: ap-beijing                              # 存储桶地域
    bucket-name: your-bucket-1250000000             # 存储桶名称
    base-url: https://your-bucket.cos.ap-beijing.myqcloud.com # 基础URL
    multipart-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: 2GB
      max-request-size: 2GB

4.配置属性类 (TencentCOSProperties.java)

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;

    // 基础访问URL
    private 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)

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客户端
     */
    @Bean
    public 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);
    }

    /**
     * 创建分片上传线程池
     */
    @Bean
    public ExecutorService cosTransferThreadPool() {
        return new ThreadPoolExecutor(
                cosProperties.getThreadPoolCoreSize(),
                cosProperties.getThreadPoolMaxSize(),
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

6.OSS 工具类 (COSUtils.java)

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. 返回文件URL
            return 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);
        
        // 生成预签名URL
        URL 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. 返回文件URL
            return 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)

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)

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.最佳实践建议

  1. 安全优化

    java 复制代码
    // 在控制器中添加文件类型校验
    @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());
    }
  2. 存储优化

    java 复制代码
    // 按日期分目录存储
    private String generateUniqueFileName(String originalFilename) {
        String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date());
        return dateDir + "/" + UUID.randomUUID() + getFileExtension(originalFilename);
    }
  3. 性能优化

    Lua 复制代码
    # 调整线程池配置(高并发场景)
    tencent:
      cos:
        thread-pool-core-size: 20
        thread-pool-max-size: 200
  4. 监控建议

    java 复制代码
    // 添加上传耗时监控
    long start = System.currentTimeMillis();
    String url = cosUtils.uploadFile(file);
    long duration = System.currentTimeMillis() - start;
    log.info("文件上传完成,耗时: {}ms", duration);