Skip to content
清晨的一缕阳光
返回

Spring Boot 对象存储 OSS 集成

前言

对象存储(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 集成要点:

OSS 是云存储的核心组件。


分享这篇文章到:

上一篇文章
OAuth2 认证基础
下一篇文章
Java 字节码基础