阿里云oss使用
阿里云Oss
一、阿里云 OSS 是什么?
阿里云对象存储服务(Object Storage Service,简称 OSS) 是阿里云提供的海量、安全、低成本、高可靠的云存储服务。
核心概念:
- 存储空间(Bucket):存储对象的容器,每个对象都必须包含在某个存储空间中
- 对象(Object):OSS 存储的基本单元,可以是任意类型的文件
- 地域(Region):OSS 数据中心的物理位置
- 访问域名(Endpoint):OSS 对外服务的访问地址
- AccessKey:访问 OSS 的密钥,包括 AccessKeyId 和 AccessKeySecret
OSS 的主要优势:
- 高可靠性:数据持久性不低于 99.9999999999%
- 高可用性:服务可用性不低于 99.995%
- 安全性强:支持多种加密和权限控制方式
- 成本低廉:按实际使用量付费,无最低消费
- 易于扩展:存储容量无限扩展
二、准备工作
1. 开通 OSS 服务
- 登录阿里云控制台
- 搜索并开通 OSS 服务
- 创建 Bucket(存储空间)
2. 获取 AccessKey
- 进入阿里云控制台 → 访问控制 RAM
- 创建用户并授予 OSS 相关权限
- 获取 AccessKeyId 和 AccessKeySecret
三、Spring Boot 集成 OSS
1. 添加依赖
Maven:
xml
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
或者使用阿里云官方 Starter(推荐):
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2. 配置文件(application.yml)
yaml
# 阿里云 OSS 配置
alibaba:
cloud:
access-key: your-access-key-id # 替换为你的 AccessKeyId
secret-key: your-access-key-secret # 替换为你的 AccessKeySecret
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com # 替换为你的 Bucket 所在区域的 Endpoint
bucket: your-bucket-name # 替换为你的 Bucket 名称
cname: false # 是否使用自定义域名
path-style-access: false # 是否使用路径风格访问
# 自定义配置(可选)
oss:
max-size: 10485760 # 文件大小限制:10MB
allowed-extensions: .jpg,.png,.gif,.pdf,.doc,.docx # 允许的文件扩展名
callback-url: http://your-domain.com/callback # 回调地址(如果需要)
dir-prefix: images/ # 文件存储目录前缀
四、配置类详解
1. 基础配置类
java
package com.example.config;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "alibaba.cloud.oss")
public class AliyunOSSConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
/**
* 创建 OSS 客户端
*/
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKey, secretKey);
}
}
2. 完整配置类(推荐)
java
package com.example.config;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.comm.Protocol;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Data
@Configuration
@ConfigurationProperties(prefix = "oss")
public class OSSConfig {
// OSS 基本配置
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
// 业务配置
private Long maxSize; // 文件大小限制
private String allowedExtensions; // 允许的文件类型
private String callbackUrl; // 回调地址
private String dirPrefix; // 目录前缀
// 线程池配置(用于异步上传)
private int corePoolSize = 5;
private int maxPoolSize = 20;
private int queueCapacity = 100;
/**
* 创建 OSS 客户端
*/
@Bean
public OSS ossClient() {
// 创建客户端配置
com.aliyun.oss.ClientBuilderConfiguration config =
new com.aliyun.oss.ClientBuilderConfiguration();
// 设置协议(HTTP 或 HTTPS)
config.setProtocol(Protocol.HTTPS);
// 设置连接超时时间(默认50秒)
config.setConnectionTimeout(50000);
// 设置Socket超时时间(默认50秒)
config.setSocketTimeout(50000);
// 设置最大连接数(默认1024)
config.setMaxConnections(1024);
return new OSSClientBuilder().build(endpoint, accessKey, secretKey, config);
}
/**
* 创建文件上传线程池
*/
@Bean("ossThreadPool")
public ThreadPoolExecutor ossThreadPool() {
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程处理
);
}
/**
* 获取允许的文件扩展名数组
*/
public String[] getAllowedExtensionArray() {
if (allowedExtensions == null || allowedExtensions.trim().isEmpty()) {
return new String[0];
}
return allowedExtensions.split(",");
}
}
五、服务类实现
1. OSS 服务接口
java
package com.example.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
public interface OSSService {
/**
* 上传文件
*/
String uploadFile(MultipartFile file);
/**
* 上传文件到指定目录
*/
String uploadFile(MultipartFile file, String directory);
/**
* 上传文件(自定义文件名)
*/
String uploadFile(InputStream inputStream, String fileName);
/**
* 批量上传文件
*/
List<String> uploadFiles(List<MultipartFile> files);
/**
* 下载文件
*/
InputStream downloadFile(String fileName);
/**
* 删除文件
*/
boolean deleteFile(String fileName);
/**
* 批量删除文件
*/
boolean deleteFiles(List<String> fileNames);
/**
* 检查文件是否存在
*/
boolean doesFileExist(String fileName);
/**
* 获取文件访问URL
*/
String getFileUrl(String fileName);
/**
* 获取文件访问URL(带过期时间)
*/
String getFileUrl(String fileName, long expiration);
}
2. OSS 服务实现类
java
package com.example.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.example.config.OSSConfig;
import com.example.service.OSSService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
@Slf4j
@Service
public class OSSServiceImpl implements OSSService {
@Autowired
private OSS ossClient;
@Autowired
private OSSConfig ossConfig;
@Autowired
private ThreadPoolExecutor ossThreadPool;
/**
* 上传文件
*/
@Override
public String uploadFile(MultipartFile file) {
return uploadFile(file, ossConfig.getDirPrefix());
}
/**
* 上传文件到指定目录
*/
@Override
public String uploadFile(MultipartFile file, String directory) {
// 参数校验
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("文件不能为空");
}
// 文件大小校验
if (file.getSize() > ossConfig.getMaxSize()) {
throw new IllegalArgumentException("文件大小不能超过 " + ossConfig.getMaxSize() / 1024 / 1024 + "MB");
}
// 文件类型校验
String originalFilename = file.getOriginalFilename();
String fileExtension = getFileExtension(originalFilename);
if (!isAllowedExtension(fileExtension)) {
throw new IllegalArgumentException("不支持的文件类型: " + fileExtension);
}
try {
// 生成唯一文件名
String fileName = generateFileName(directory, fileExtension);
// 创建上传请求
PutObjectRequest putObjectRequest = new PutObjectRequest(
ossConfig.getBucket(),
fileName,
file.getInputStream()
);
// 设置对象元数据
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(getContentType(fileExtension));
putObjectRequest.setMetadata(metadata);
// 上传文件
ossClient.putObject(putObjectRequest);
log.info("文件上传成功: {}", fileName);
return fileName;
} catch (IOException e) {
log.error("文件上传失败: {}", e.getMessage(), e);
throw new RuntimeException("文件上传失败", e);
}
}
/**
* 异步上传文件
*/
@Async
public CompletableFuture<String> uploadFileAsync(MultipartFile file) {
return CompletableFuture.supplyAsync(() -> uploadFile(file), ossThreadPool);
}
/**
* 上传文件(自定义文件名)
*/
@Override
public String uploadFile(InputStream inputStream, String fileName) {
try {
PutObjectRequest putObjectRequest = new PutObjectRequest(
ossConfig.getBucket(),
fileName,
inputStream
);
ossClient.putObject(putObjectRequest);
log.info("文件上传成功: {}", fileName);
return fileName;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
log.error("关闭流失败: {}", e.getMessage());
}
}
}
/**
* 批量上传文件
*/
@Override
public List<String> uploadFiles(List<MultipartFile> files) {
return files.stream()
.map(this::uploadFile)
.collect(Collectors.toList());
}
/**
* 下载文件
*/
@Override
public InputStream downloadFile(String fileName) {
if (!doesFileExist(fileName)) {
throw new IllegalArgumentException("文件不存在: " + fileName);
}
OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), fileName);
return ossObject.getObjectContent();
}
/**
* 删除文件
*/
@Override
public boolean deleteFile(String fileName) {
try {
ossClient.deleteObject(ossConfig.getBucket(), fileName);
log.info("文件删除成功: {}", fileName);
return true;
} catch (Exception e) {
log.error("文件删除失败: {}", fileName, e);
return false;
}
}
/**
* 批量删除文件
*/
@Override
public boolean deleteFiles(List<String> fileNames) {
try {
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(ossConfig.getBucket())
.withKeys(fileNames);
ossClient.deleteObjects(deleteObjectsRequest);
log.info("批量删除文件成功: {}", fileNames);
return true;
} catch (Exception e) {
log.error("批量删除文件失败: {}", fileNames, e);
return false;
}
}
/**
* 检查文件是否存在
*/
@Override
public boolean doesFileExist(String fileName) {
try {
return ossClient.doesObjectExist(ossConfig.getBucket(), fileName);
} catch (Exception e) {
log.error("检查文件是否存在失败: {}", fileName, e);
return false;
}
}
/**
* 获取文件访问URL
*/
@Override
public String getFileUrl(String fileName) {
// 设置URL过期时间为1小时
return getFileUrl(fileName, 3600);
}
/**
* 获取文件访问URL(带过期时间)
*/
@Override
public String getFileUrl(String fileName, long expiration) {
try {
Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
URL url = ossClient.generatePresignedUrl(ossConfig.getBucket(), fileName, expirationDate);
return url.toString();
} catch (Exception e) {
log.error("生成文件URL失败: {}", fileName, e);
return null;
}
}
// ========== 私有方法 ==========
/**
* 生成唯一文件名
*/
private String generateFileName(String directory, String extension) {
String uuid = UUID.randomUUID().toString().replace("-", "");
String timestamp = String.valueOf(System.currentTimeMillis());
if (StringUtils.isNotBlank(directory)) {
if (!directory.endsWith("/")) {
directory += "/";
}
return directory + timestamp + "_" + uuid + extension;
}
return timestamp + "_" + uuid + extension;
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
if (StringUtils.isBlank(filename)) {
return "";
}
int lastDotIndex = filename.lastIndexOf(".");
return lastDotIndex >= 0 ? filename.substring(lastDotIndex).toLowerCase() : "";
}
/**
* 检查文件扩展名是否允许
*/
private boolean isAllowedExtension(String extension) {
if (StringUtils.isBlank(extension)) {
return false;
}
String[] allowedExtensions = ossConfig.getAllowedExtensionArray();
if (allowedExtensions.length == 0) {
return true; // 如果没有配置限制,则允许所有类型
}
return Arrays.asList(allowedExtensions).contains(extension.toLowerCase());
}
/**
* 获取Content-Type
*/
private String getContentType(String fileExtension) {
switch (fileExtension.toLowerCase()) {
case ".jpg":
case ".jpeg":
return "image/jpeg";
case ".png":
return "image/png";
case ".gif":
return "image/gif";
case ".pdf":
return "application/pdf";
case ".doc":
return "application/msword";
case ".docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
default:
return "application/octet-stream";
}
}
}
六、控制器类
java
package com.example.controller;
import com.example.service.OSSService;
import com.example.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/oss")
public class OSSController {
@Autowired
private OSSService ossService;
/**
* 单文件上传
*/
@PostMapping("/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam(value = "directory", required = false) String directory) {
try {
String fileName;
if (directory != null) {
fileName = ossService.uploadFile(file, directory);
} else {
fileName = ossService.uploadFile(file);
}
String fileUrl = ossService.getFileUrl(fileName);
return Result.success("文件上传成功", fileUrl);
} catch (Exception e) {
log.error("文件上传失败: {}", e.getMessage());
return Result.error(e.getMessage());
}
}
/**
* 多文件上传
*/
@PostMapping("/upload/batch")
public Result<List<String>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
try {
List<String> fileNames = ossService.uploadFiles(files);
List<String> fileUrls = fileNames.stream()
.map(ossService::getFileUrl)
.collect(java.util.stream.Collectors.toList());
return Result.success("文件上传成功", fileUrls);
} catch (Exception e) {
log.error("批量文件上传失败: {}", e.getMessage());
return Result.error(e.getMessage());
}
}
/**
* 删除文件
*/
@DeleteMapping("/delete")
public Result<Boolean> deleteFile(@RequestParam("fileName") String fileName) {
try {
boolean result = ossService.deleteFile(fileName);
return result ?
Result.success("文件删除成功", true) :
Result.error("文件删除失败");
} catch (Exception e) {
log.error("文件删除失败: {}", e.getMessage());
return Result.error(e.getMessage());
}
}
/**
* 获取文件URL
*/
@GetMapping("/url")
public Result<String> getFileUrl(@RequestParam("fileName") String fileName) {
try {
String fileUrl = ossService.getFileUrl(fileName);
return Result.success("获取成功", fileUrl);
} catch (Exception e) {
log.error("获取文件URL失败: {}", e.getMessage());
return Result.error(e.getMessage());
}
}
}
七、工具类和异常处理
1. 统一返回结果
java
package com.example.utils;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
private int code;
private String message;
private T data;
private long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}
2. 全局异常处理
java
package com.example.handler;
import com.example.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 文件大小超限异常
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.warn("文件大小超过限制: {}", e.getMessage());
return Result.error(413, "文件大小超过限制");
}
/**
* 非法参数异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public Result<?> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数错误: {}", e.getMessage());
return Result.error(400, e.getMessage());
}
/**
* 其他异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
八、测试和使用
1. 前端调用示例
html
<!-- 单文件上传 -->
<form id="uploadForm">
<input type="file" name="file" />
<input type="text" name="directory" placeholder="存储目录(可选)" />
<button type="submit">上传</button>
</form>
<script>
// 单文件上传
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch('/api/oss/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('上传结果:', result);
});
// 多文件上传
async function uploadMultipleFiles(files) {
const formData = new FormData();
files.forEach(file => {
formData.append('files', file);
});
const response = await fetch('/api/oss/upload/batch', {
method: 'POST',
body: formData
});
return await response.json();
}
</script>
2. 单元测试
java
package com.example.service;
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.nio.charset.StandardCharsets;
@SpringBootTest
class OSSServiceImplTest {
@Autowired
private OSSService ossService;
@Test
void testUploadFile() {
// 创建模拟文件
MultipartFile file = new MockMultipartFile(
"test.txt",
"test.txt",
"text/plain",
"Hello OSS".getBytes(StandardCharsets.UTF_8)
);
try {
String fileName = ossService.uploadFile(file, "test/");
System.out.println("上传成功,文件名: " + fileName);
// 获取文件URL
String fileUrl = ossService.getFileUrl(fileName);
System.out.println("文件URL: " + fileUrl);
// 删除测试文件
ossService.deleteFile(fileName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
九、注意事项
- 安全性:AccessKey 要妥善保管,不要硬编码在代码中
- 权限控制:使用 RAM 子账号并授予最小必要权限
- 网络超时:根据文件大小合理设置超时时间
- 异常处理:做好网络异常、权限异常等处理
- 资源释放:及时关闭 InputStream 等资源
- 费用控制:监控存储量和访问量,避免意外费用
总结
通过以上配置和代码,你可以在 Spring Boot 项目中完整地集成阿里云 OSS 服务,实现文件的上传、下载、删除等功能。这种设计具有良好的扩展性和可维护性,适合在生产环境中使用。
关键点:
- 使用配置类统一管理 OSS 参数
- 服务类封装所有 OSS 操作
- 控制器提供 RESTful API
- 完善的异常处理和日志记录
- 支持异步上传提高性能

浙公网安备 33010602011771号