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.最佳实践建议
-
安全优化
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()); }
-
存储优化
java// 按日期分目录存储 private String generateUniqueFileName(String originalFilename) { String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date()); return dateDir + "/" + UUID.randomUUID() + getFileExtension(originalFilename); }
-
性能优化
Lua# 调整线程池配置(高并发场景) tencent: cos: thread-pool-core-size: 20 thread-pool-max-size: 200
-
监控建议
java// 添加上传耗时监控 long start = System.currentTimeMillis(); String url = cosUtils.uploadFile(file); long duration = System.currentTimeMillis() - start; log.info("文件上传完成,耗时: {}ms", duration);