【SpringBoot 】[OSS]----Spring Boot 3 整合 OSS文件服务 实战与单元测试详细教程,附代码
·
文章目录
一、OSS配置类及相关代码
1. OSS配置属性类(OSSProperties.java)
package com.zgb.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* OSS配置属性类,用于绑定nacos或yml中的OSS配置
* 自动映射配置文件中前缀为"zgb.oss"的配置项
*/
@Data
@Component
@ConfigurationProperties(prefix = "zgb.oss")
public class OSSProperties {
/**
* OSS服务端点(Endpoint),不同地域不同,例如:oss-cn-beijing.aliyuncs.com
*/
private String endpoint;
/**
* 阿里云访问密钥ID(AccessKey ID)
*/
private String accessKeyId;
/**
* 阿里云访问密钥Secret(AccessKey Secret)
*/
private String accessKeySecret;
/**
* OSS存储空间名称(Bucket Name)
*/
private String bucketName;
/**
* 文件访问基础URL(可选,若配置则用于拼接文件访问路径)
* 例如:https://bucketName.endpoint/
*/
private String baseUrl;
/**
* 默认文件存储目录(可选,如未指定则存储在根目录)
*/
private String defaultDir;
}
2. OSS配置类(OSSConfig.java)
package com.zgb.config;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* OSS客户端配置类,用于创建阿里云OSS客户端实例
*/
@Configuration
@RequiredArgsConstructor
public class OSSConfig {
private final OSSProperties ossProperties;
/**
* 创建OSS客户端实例(单例)
*
* @return OSS客户端对象,用于操作阿里云OSS服务
*/
@Bean
public OSS ossClient() {
return new OSSClientBuilder()
.build(
ossProperties.getEndpoint(),
ossProperties.getAccessKeyId(),
ossProperties.getAccessKeySecret()
);
}
}
3. OSS工具类(OSSUtil.java)
package com.zgb.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.zgb.config.OSSProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* OSS工具类,封装阿里云OSS的常用操作(上传、下载、删除、查询等)
*/
@Component
@RequiredArgsConstructor
public class OSSUtil {
private final OSS ossClient;
private final OSSProperties ossProperties;
/**
* 上传文件到OSS
*
* @param file 待上传的文件(MultipartFile类型)
* @param customDir 自定义存储目录(可选,若为null则使用默认目录)
* @return 上传成功后的文件访问URL
* @throws IOException 当文件输入流获取失败时抛出
*/
public String uploadFile(MultipartFile file, String customDir) throws IOException {
// 1. 处理存储目录:优先使用自定义目录,否则使用默认目录
String dir = customDir != null ? customDir : ossProperties.getDefaultDir();
// 2. 生成文件名:UUID + 原文件名(避免重名)
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + "_" + originalFilename;
// 3. 拼接完整存储路径(目录 + 日期目录 + 文件名,按日期分类存储)
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String objectName = dir + "/" + dateDir + "/" + fileName;
// 4. 上传文件
try (InputStream inputStream = file.getInputStream()) {
PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), objectName, inputStream);
// 设置文件ContentType(自动识别文件类型)
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
request.setMetadata(metadata);
ossClient.putObject(request);
}
// 5. 拼接访问URL并返回
return buildFileUrl(objectName);
}
/**
* 下载OSS文件(获取文件输入流)
*
* @param objectName OSS中的文件完整路径(如:dir/20240929/file.jpg)
* @return 文件输入流,需调用方自行关闭
*/
public InputStream downloadFile(String objectName) {
GetObjectRequest request = new GetObjectRequest(ossProperties.getBucketName(), objectName);
OSSObject ossObject = ossClient.getObject(request);
return ossObject.getObjectContent();
}
/**
* 删除OSS中的文件
*
* @param objectName OSS中的文件完整路径(如:dir/20240929/file.jpg)
* @return 删除成功返回true,否则返回false
*/
public boolean deleteFile(String objectName) {
if (ossClient.doesObjectExist(ossProperties.getBucketName(), objectName)) {
ossClient.deleteObject(ossProperties.getBucketName(), objectName);
return true;
}
return false;
}
/**
* 批量删除OSS中的文件
*
* @param objectNames 文件路径列表(如:["dir/file1.jpg", "dir/file2.jpg"])
* @return 成功删除的文件数量
*/
public int batchDeleteFiles(List<String> objectNames) {
if (objectNames == null || objectNames.isEmpty()) {
return 0;
}
DeleteObjectsRequest request = new DeleteObjectsRequest(ossProperties.getBucketName())
.withKeys(objectNames);
DeleteObjectsResult result = ossClient.deleteObjects(request);
return result.getDeletedObjects().size();
}
/**
* 查询指定目录下的文件列表
*
* @param prefix 目录前缀(如:"dir/20240929/",查询该目录下的所有文件)
* @return 文件路径列表(OSS中的完整路径)
*/
public List<String> listFiles(String prefix) {
List<String> fileList = new ArrayList<>();
// 设置查询参数
ListObjectsRequest request = new ListObjectsRequest(ossProperties.getBucketName())
.withPrefix(prefix)
.withMaxKeys(1000); // 最多返回1000条
ObjectListing listing;
do {
listing = ossClient.listObjects(request);
// 遍历文件并添加到列表
for (OSSObjectSummary summary : listing.getObjectSummaries()) {
fileList.add(summary.getKey());
}
// 分页查询(如果有更多结果)
request.setMarker(listing.getNextMarker());
} while (listing.isTruncated());
return fileList;
}
/**
* 构建文件访问URL
*
* @param objectName OSS中的文件完整路径
* @return 文件可访问的URL
*/
private String buildFileUrl(String objectName) {
// 若配置了baseUrl,则直接拼接;否则使用默认格式:https://bucket.endpoint/objectName
if (ossProperties.getBaseUrl() != null && !ossProperties.getBaseUrl().isEmpty()) {
return ossProperties.getBaseUrl() + objectName;
} else {
return "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/" + objectName;
}
}
}
二、配置文件(application.yml)
# OSS配置
zgb:
oss:
# OSS服务端点(根据地域选择,参考阿里云文档)
endpoint: oss-cn-beijing.aliyuncs.com
# 阿里云AccessKey ID(需替换为实际值)
accessKeyId: your-access-key-id
# 阿里云AccessKey Secret(需替换为实际值)
accessKeySecret: your-access-key-secret
# 存储空间名称(需替换为实际Bucket名称)
bucketName: your-bucket-name
# 文件访问基础URL(可选,如:https://your-bucket-name.oss-cn-beijing.aliyuncs.com/)
baseUrl:
# 默认文件存储目录(如:upload/files/,结尾建议加"/")
defaultDir: upload/files
三、单元测试(OSSUtilTest.java)
package com.zgb.util;
import com.aliyun.oss.OSS;
import com.zgb.config.OSSProperties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* OSS工具类单元测试
*/
@SpringBootTest
public class OSSUtilTest {
@Autowired
private OSSUtil ossUtil;
@Autowired
private OSS ossClient;
@Autowired
private OSSProperties ossProperties;
// 测试用文件路径
private String testObjectName;
/**
* 测试前准备:创建一个测试文件
*/
@BeforeEach
public void setUp() throws IOException {
// 创建测试文件
MultipartFile testFile = new MockMultipartFile(
"test.txt",
"test.txt",
"text/plain",
"Hello OSS!".getBytes()
);
// 上传文件并记录路径(从URL中提取objectName)
String fileUrl = ossUtil.uploadFile(testFile, "test");
testObjectName = extractObjectName(fileUrl);
}
/**
* 测试后清理:删除测试文件
*/
@AfterEach
public void tearDown() {
if (testObjectName != null) {
ossClient.deleteObject(ossProperties.getBucketName(), testObjectName);
}
}
/**
* 测试文件上传
*/
@Test
public void testUploadFile() throws IOException {
// 1. 创建测试文件
MultipartFile file = new MockMultipartFile(
"upload-test.txt",
"upload-test.txt",
"text/plain",
"Upload test content".getBytes()
);
// 2. 执行上传
String fileUrl = ossUtil.uploadFile(file, "test/upload");
assertNotNull(fileUrl, "文件上传失败,URL为空");
// 3. 验证文件是否存在
String objectName = extractObjectName(fileUrl);
assertTrue(ossClient.doesObjectExist(ossProperties.getBucketName(), objectName), "上传的文件不存在");
// 4. 清理测试文件
ossClient.deleteObject(ossProperties.getBucketName(), objectName);
}
/**
* 测试文件下载
*/
@Test
public void testDownloadFile() throws IOException {
// 执行下载
try (InputStream is = ossUtil.downloadFile(testObjectName)) {
assertNotNull(is, "文件下载失败,输入流为空");
// 验证文件内容
byte[] content = is.readAllBytes();
assertEquals("Hello OSS!", new String(content), "文件内容不一致");
}
}
/**
* 测试文件删除
*/
@Test
public void testDeleteFile() {
// 执行删除
boolean result = ossUtil.deleteFile(testObjectName);
assertTrue(result, "文件删除失败");
// 验证文件已删除
assertFalse(ossClient.doesObjectExist(ossProperties.getBucketName(), testObjectName), "文件未被删除");
// 重置testObjectName(避免tearDown重复删除)
testObjectName = null;
}
/**
* 测试批量删除文件
*/
@Test
public void testBatchDeleteFiles() throws IOException {
// 1. 准备多个测试文件
MultipartFile file1 = new MockMultipartFile("file1.txt", "file1.txt", "text/plain", "1".getBytes());
MultipartFile file2 = new MockMultipartFile("file2.txt", "file2.txt", "text/plain", "2".getBytes());
String url1 = ossUtil.uploadFile(file1, "test/batch");
String url2 = ossUtil.uploadFile(file2, "test/batch");
List<String> objectNames = List.of(extractObjectName(url1), extractObjectName(url2));
// 2. 执行批量删除
int deletedCount = ossUtil.batchDeleteFiles(objectNames);
assertEquals(2, deletedCount, "批量删除数量不正确");
// 3. 验证文件已删除
for (String name : objectNames) {
assertFalse(ossClient.doesObjectExist(ossProperties.getBucketName(), name), "文件未被删除:" + name);
}
}
/**
* 测试文件列表查询
*/
@Test
public void testListFiles() {
// 执行查询(查询test目录下的文件)
List<String> fileList = ossUtil.listFiles("test/");
assertFalse(fileList.isEmpty(), "查询不到文件列表");
assertTrue(fileList.contains(testObjectName), "文件列表中不包含测试文件");
}
/**
* 从文件URL中提取OSS的objectName
*/
private String extractObjectName(String fileUrl) {
String base = "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/";
if (fileUrl.startsWith(base)) {
return fileUrl.substring(base.length());
}
if (ossProperties.getBaseUrl() != null && fileUrl.startsWith(ossProperties.getBaseUrl())) {
return fileUrl.substring(ossProperties.getBaseUrl().length());
}
throw new IllegalArgumentException("无法从URL中提取objectName:" + fileUrl);
}
}
说明
- 依赖说明:需在
pom.xml中添加阿里云OSS SDK依赖:<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.16.1</version> </dependency> - 安全性:AccessKey建议通过Nacos配置中心管理,避免硬编码在代码中。
- 异常处理:实际生产环境中需根据业务需求完善异常处理(如网络异常、权限不足等)。
- 扩展性:可根据需要添加更多功能(如文件分片上传、签名URL生成、文件元信息获取等)。
更多推荐

所有评论(0)