前言
对象存储(OSS)是云存储服务的核心组件,广泛用于文件存储、图片视频托管等场景。本文将介绍 Spring Boot 集成阿里云 OSS 的完整方案。
快速开始
1. 添加依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.1</version>
</dependency>
2. 基础配置
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: ${OSS_ACCESS_KEY_ID}
access-key-secret: ${OSS_ACCESS_KEY_SECRET}
bucket-name: demo-bucket
bucket-domain: https://demo-bucket.oss-cn-hangzhou.aliyuncs.com
3. 配置类
@Configuration
@ConfigurationProperties(prefix = "aliyun.oss")
@Data
public class OssConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String bucketDomain;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(
endpoint,
accessKeyId,
accessKeySecret
);
}
}
4. 文件上传服务
@Service
@RequiredArgsConstructor
public class OssService {
private final OSS ossClient;
private final OssConfig ossConfig;
/**
* 上传文件
*/
public String uploadFile(MultipartFile file) {
try {
String objectName = generateObjectName(file.getOriginalFilename());
ossClient.putObject(
ossConfig.getBucketName(),
objectName,
file.getInputStream()
);
String fileUrl = getFileUrl(objectName);
log.info("文件上传成功:{}", fileUrl);
return fileUrl;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败", e);
}
}
/**
* 上传文件到指定目录
*/
public String uploadFileToFolder(MultipartFile file, String folder) {
try {
String fileName = file.getOriginalFilename();
String objectName = folder + "/" + generateObjectName(fileName);
ossClient.putObject(
ossConfig.getBucketName(),
objectName,
file.getInputStream()
);
return getFileUrl(objectName);
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败", e);
}
}
/**
* 上传 Base64 图片
*/
public String uploadBase64Image(String base64, String extension) {
try {
byte[] bytes = Base64.getDecoder().decode(
base64.replace("data:image/" + extension + ";base64,", "")
);
String objectName = "images/" + UUID.randomUUID() + "." + extension;
ossClient.putObject(
ossConfig.getBucketName(),
objectName,
new ByteArrayInputStream(bytes)
);
return getFileUrl(objectName);
} catch (Exception e) {
log.error("图片上传失败", e);
throw new RuntimeException("图片上传失败", e);
}
}
/**
* 生成对象名称
*/
private String generateObjectName(String originalFilename) {
String extension = getFileExtension(originalFilename);
return UUID.randomUUID().toString() + extension;
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
if (filename == null || !filename.contains(".")) {
return "";
}
return filename.substring(filename.lastIndexOf("."));
}
/**
* 获取文件 URL
*/
private String getFileUrl(String objectName) {
return ossConfig.getBucketDomain() + "/" + objectName;
}
}
文件操作
1. 文件下载
@Service
@RequiredArgsConstructor
public class OssService {
/**
* 下载文件
*/
public byte[] downloadFile(String objectName) {
try {
OSSObject ossObject = ossClient.getObject(
ossConfig.getBucketName(),
objectName
);
return ossObject.getObjectContent().readAllBytes();
} catch (IOException e) {
log.error("文件下载失败", e);
throw new RuntimeException("文件下载失败", e);
}
}
/**
* 下载文件到本地
*/
public void downloadFileToLocal(String objectName, String localPath) {
try {
ossClient.getObject(
ossConfig.getBucketName(),
objectName,
new File(localPath)
);
log.info("文件下载到本地:{}", localPath);
} catch (OSSException e) {
log.error("文件下载失败", e);
throw new RuntimeException("文件下载失败", e);
}
}
/**
* 获取文件下载 URL(带签名)
*/
public String getDownloadUrl(String objectName, long expiration) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
URL url = ossClient.generatePresignedUrl(
ossConfig.getBucketName(),
objectName,
expirationDate
);
return url.toString();
}
}
2. 文件删除
@Service
@RequiredArgsConstructor
public class OssService {
/**
* 删除单个文件
*/
public void deleteFile(String objectName) {
ossClient.deleteObject(
ossConfig.getBucketName(),
objectName
);
log.info("文件删除成功:{}", objectName);
}
/**
* 批量删除文件
*/
public void deleteFiles(List<String> objectNames) {
DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(
ossConfig.getBucketName()
);
deleteRequest.setKeys(objectNames);
DeleteObjectsResult result = ossClient.deleteObjects(deleteRequest);
log.info("批量删除成功,删除数量:{}", result.getDeletedObjects().size());
}
/**
* 删除整个目录
*/
public void deleteFolder(String prefix) {
ObjectListing objectListing = ossClient.listObjects(
ossConfig.getBucketName(),
prefix
);
List<String> keys = objectListing.getObjectSummaries().stream()
.map(OSSObjectSummary::getKey)
.collect(Collectors.toList());
if (!keys.isEmpty()) {
deleteFiles(keys);
}
}
}
3. 文件列表
@Service
@RequiredArgsConstructor
public class OssService {
/**
* 列出文件
*/
public List<OSSObjectSummary> listFiles(String prefix, int maxKeys) {
ObjectListing objectListing = ossClient.listObjects(
ossConfig.getBucketName(),
prefix
);
return objectListing.getObjectSummaries();
}
/**
* 分页列出文件
*/
public PageResult<FileDTO> listFilesPage(String prefix, int page, int size) {
ObjectListing objectListing = ossClient.listObjects(
ossConfig.getBucketName(),
prefix
);
List<FileDTO> files = objectListing.getObjectSummaries().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return PageResult.of(files, files.size(), page, size);
}
private FileDTO convertToDTO(OSSObjectSummary summary) {
FileDTO dto = new FileDTO();
dto.setName(summary.getKey());
dto.setSize(summary.getSize());
dto.setLastModified(summary.getLastModified());
dto.setUrl(getFileUrl(summary.getKey()));
return dto;
}
}
图片处理
1. 图片缩放
@Service
@RequiredArgsConstructor
public class ImageService {
private final OssService ossService;
private final OssConfig ossConfig;
/**
* 图片缩放 URL
*/
public String resizeImage(String objectName, int width, int height) {
String imageUrl = ossService.getFileUrl(objectName);
// OSS 图片处理参数
return imageUrl + "?x-oss-process=image/resize,w_" + width + ",h_" + height;
}
/**
* 图片裁剪
*/
public String cropImage(String objectName, int x, int y, int width, int height) {
String imageUrl = ossService.getFileUrl(objectName);
return imageUrl + "?x-oss-process=image/crop,w_" + width + ",h_" + height + ",x_" + x + ",y_" + y;
}
/**
* 图片旋转
*/
public String rotateImage(String objectName, int degree) {
String imageUrl = ossService.getFileUrl(objectName);
return imageUrl + "?x-oss-process=image/rotate," + degree;
}
/**
* 图片水印
*/
public String addWatermark(String objectName, String text) {
String imageUrl = ossService.getFileUrl(objectName);
String watermarkText = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
return imageUrl + "?x-oss-process=image/watermark,text_" + watermarkText;
}
/**
* 图片格式转换
*/
public String convertFormat(String objectName, String format) {
String imageUrl = ossService.getFileUrl(objectName);
return imageUrl + "?x-oss-process=image/format," + format;
}
/**
* 组合处理
*/
public String processImage(String objectName, ImageProcessParams params) {
String imageUrl = ossService.getFileUrl(objectName);
List<String> operations = new ArrayList<>();
if (params.getWidth() != null) {
operations.add("resize,w_" + params.getWidth());
}
if (params.getQuality() != null) {
operations.add("quality,q_" + params.getQuality());
}
if (params.getFormat() != null) {
operations.add("format," + params.getFormat());
}
if (operations.isEmpty()) {
return imageUrl;
}
return imageUrl + "?x-oss-process=" + String.join("/", operations);
}
}
2. 图片上传处理
@Service
@RequiredArgsConstructor
public class ImageUploadService {
private final OssService ossService;
/**
* 上传图片并生成缩略图
*/
public ImageUploadResult uploadWithThumbnail(MultipartFile file) {
// 上传原图
String originalUrl = ossService.uploadFileToFolder(file, "images/original");
// 生成缩略图
String thumbnailUrl = generateThumbnail(originalUrl);
ImageUploadResult result = new ImageUploadResult();
result.setOriginalUrl(originalUrl);
result.setThumbnailUrl(thumbnailUrl);
return result;
}
/**
* 生成缩略图
*/
private String generateThumbnail(String originalUrl) {
// 提取 objectName
String objectName = extractObjectName(originalUrl);
// 生成缩略图 URL
return originalUrl + "?x-oss-process=image/resize,w_200";
}
private String extractObjectName(String url) {
return url.replace(ossConfig.getBucketDomain() + "/", "");
}
}
最佳实践
1. 文件类型校验
@Service
public class FileUploadService {
private static final Set<String> ALLOWED_IMAGE_TYPES = Set.of(
"image/jpeg", "image/png", "image/gif", "image/webp"
);
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
public String uploadFile(MultipartFile file) {
// 校验文件大小
if (file.getSize() > MAX_FILE_SIZE) {
throw new BusinessException("文件大小不能超过 10MB");
}
// 校验文件类型
String contentType = file.getContentType();
if (!ALLOWED_IMAGE_TYPES.contains(contentType)) {
throw new BusinessException("只支持 JPG、PNG、GIF、WebP 格式");
}
return ossService.uploadFile(file);
}
}
2. 上传进度
@Service
public class ProgressUploadService {
public String uploadWithProgress(MultipartFile file, String uploadId) {
PutObjectRequest request = new PutObjectRequest(
ossConfig.getBucketName(),
generateObjectName(file.getOriginalFilename()),
file.getInputStream()
);
// 设置进度监听
request.setProgressListener(new ProgressListener() {
@Override
public void progressChanged(ProgressEvent progressEvent) {
long uploaded = progressEvent.getBytes();
long total = file.getSize();
int percent = (int) (uploaded * 100 / total);
// 更新 Redis 进度
redisTemplate.opsForValue().set(
"upload:progress:" + uploadId,
String.valueOf(percent)
);
}
});
ossClient.putObject(request);
return getFileUrl(request.getKey());
}
}
3. CDN 加速
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
bucket-name: demo-bucket
# CDN 域名
cdn-domain: https://cdn.example.com
@Service
public class CdnService {
public String getCdnUrl(String objectName) {
return ossConfig.getCdnDomain() + "/" + objectName;
}
}
4. 签名上传
@Service
public class SignatureUploadService {
/**
* 生成上传签名
*/
public Map<String, String> generateUploadSignature(
String fileName,
long expiration
) {
// 设置过期时间
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
// 创建 Policy
PolicyConditions policyConditions = new PolicyConditions();
policyConditions.addConditionItem(
PolicyConditions.COND_CONTENT_LENGTH_RANGE,
0,
104857600 // 100MB
);
String postPolicy = ossClient.generatePostPolicy(expirationDate, policyConditions);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> signature = new HashMap<>();
signature.put("accessKeyId", ossConfig.getAccessKeyId());
signature.put("policy", postPolicy);
signature.put("signature", postSignature);
signature.put("host", ossConfig.getBucketDomain());
signature.put("expire", String.valueOf(expirationDate.getTime()));
return signature;
}
}
5. 文件元数据
@Service
public class MetadataService {
public void uploadWithMetadata(MultipartFile file, Map<String, String> metadata) {
ObjectMetadata objectMetadata = new ObjectMetadata();
// 设置内容类型
objectMetadata.setContentType(file.getContentType());
// 设置自定义元数据
metadata.forEach(objectMetadata::addUserMetadata);
PutObjectRequest request = new PutObjectRequest(
ossConfig.getBucketName(),
generateObjectName(file.getOriginalFilename()),
file.getInputStream(),
objectMetadata
);
ossClient.putObject(request);
}
}
总结
OSS 集成要点:
- ✅ 基础使用 - 上传、下载、删除
- ✅ 图片处理 - 缩放、裁剪、水印
- ✅ 文件管理 - 列表、分页、元数据
- ✅ 最佳实践 - 类型校验、进度、CDN
- ✅ 签名上传 - 客户端直传
OSS 是云存储的核心组件。