本文目录导读:

我来为您提供一个完整的Spring Boot整合阿里云OSS的案例。
添加依赖
<!-- pom.xml -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置文件
application.yml
server:
port: 8080
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
# 阿里云OSS配置
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
bucket-name: your-bucket-name
url-prefix: https://your-bucket-name.oss-cn-hangzhou.aliyuncs.com/
# 可选配置
max-file-size: 10485760 # 10MB
application-dev.yml(可选)
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: your-dev-access-key
access-key-secret: your-dev-access-secret
bucket-name: dev-bucket
url-prefix: https://dev-bucket.oss-cn-hangzhou.aliyuncs.com/
OSS配置类
package com.example.oss.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 = "aliyun.oss")
public class OssConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String urlPrefix;
private Long maxFileSize;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
OSS工具类
package com.example.oss.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.example.oss.config.OssConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Component
@RequiredArgsConstructor
public class OssUtil {
private final OSS ossClient;
private final OssConfig ossConfig;
/**
* 上传文件到OSS
*/
public String upload(MultipartFile file) {
// 校验文件大小
if (file.getSize() > ossConfig.getMaxFileSize()) {
throw new RuntimeException("文件大小超出限制");
}
// 生成文件路径
String originalFilename = file.getOriginalFilename();
String filePath = generateFilePath(originalFilename);
try {
// 创建上传请求
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());
// 上传文件
ossClient.putObject(ossConfig.getBucketName(), filePath, file.getInputStream(), metadata);
// 返回文件访问URL
return ossConfig.getUrlPrefix() + filePath;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败: " + e.getMessage());
}
}
/**
* 上传文件到指定目录
*/
public String upload(MultipartFile file, String directory) {
String originalFilename = file.getOriginalFilename();
String filePath = buildFilePath(directory, originalFilename);
try {
ossClient.putObject(ossConfig.getBucketName(), filePath, file.getInputStream());
return ossConfig.getUrlPrefix() + filePath;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败: " + e.getMessage());
}
}
/**
* 字节数组上传
*/
public String upload(byte[] data, String fileName) {
String filePath = generateFilePath(fileName);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data)) {
ossClient.putObject(ossConfig.getBucketName(), filePath, inputStream);
return ossConfig.getUrlPrefix() + filePath;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败: " + e.getMessage());
}
}
/**
* 下载文件到本地
*/
public void download(String filePath, String localFilePath) {
try {
ossClient.getObject(
new GetObjectRequest(ossConfig.getBucketName(), filePath),
new File(localFilePath)
);
} catch (Exception e) {
log.error("文件下载失败", e);
throw new RuntimeException("文件下载失败: " + e.getMessage());
}
}
/**
* 下载文件为字节数组
*/
public byte[] download(String filePath) {
try {
OSSObject ossObject = ossClient.getObject(ossConfig.getBucketName(), filePath);
try (InputStream inputStream = ossObject.getObjectContent();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
return outputStream.toByteArray();
}
} catch (IOException e) {
log.error("文件下载失败", e);
throw new RuntimeException("文件下载失败: " + e.getMessage());
}
}
/**
* 删除文件
*/
public void delete(String filePath) {
try {
// 从URL中提取文件路径
String objectPath = filePath;
if (filePath.startsWith(ossConfig.getUrlPrefix())) {
objectPath = filePath.substring(ossConfig.getUrlPrefix().length());
}
ossClient.deleteObject(ossConfig.getBucketName(), objectPath);
log.info("文件删除成功: {}", objectPath);
} catch (Exception e) {
log.error("文件删除失败", e);
throw new RuntimeException("文件删除失败: " + e.getMessage());
}
}
/**
* 批量删除文件
*/
public void deleteBatch(String... filePaths) {
try {
for (String filePath : filePaths) {
delete(filePath);
}
} catch (Exception e) {
log.error("批量删除文件失败", e);
throw new RuntimeException("批量删除文件失败: " + e.getMessage());
}
}
/**
* 生成签名URL(临时访问权限)
*/
public String generatePresignedUrl(String filePath, long expiresInSeconds) {
Date expiration = new Date(System.currentTimeMillis() + expiresInSeconds * 1000);
URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), filePath, expiration);
return url.toString();
}
/**
* 判断文件是否存在
*/
public boolean doesObjectExist(String filePath) {
return ossClient.doesObjectExist(ossConfig.getBucketName(), filePath);
}
/**
* 获取文件元数据
*/
public ObjectMetadata getObjectMetadata(String filePath) {
return ossClient.getObjectMetadata(ossConfig.getBucketName(), filePath);
}
/**
* 生成文件路径
*/
private String generateFilePath(String originalFilename) {
// 获取文件扩展名
String fileExtension = "";
if (StringUtils.hasText(originalFilename) && originalFilename.contains(".")) {
fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
// 生成新的文件名
String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExtension;
// 按日期分目录存储:yyyy/MM/dd/UUID.ext
String datePath = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new Date());
return datePath + "/" + newFileName;
}
/**
* 构建文件路径
*/
private String buildFilePath(String directory, String filename) {
// 清理目录路径
directory = directory.replaceAll("^/+|/+$", "");
String datePath = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new Date());
// 生成新的文件名
String fileExtension = "";
if (StringUtils.hasText(filename) && filename.contains(".")) {
fileExtension = filename.substring(filename.lastIndexOf("."));
}
String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExtension;
return directory + "/" + datePath + "/" + newFileName;
}
}
Service层
package com.example.oss.service;
import com.example.oss.util.OssUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class FileService {
private final OssUtil ossUtil;
/**
* 上传文件
*/
public String uploadFile(MultipartFile file) {
validateFile(file);
return ossUtil.upload(file);
}
/**
* 上传到指定目录
*/
public String uploadFileToDirectory(MultipartFile file, String directory) {
validateFile(file);
return ossUtil.upload(file, directory);
}
/**
* 批量上传
*/
public List<String> uploadFiles(List<MultipartFile> files) {
List<String> urls = new ArrayList<>();
for (MultipartFile file : files) {
urls.add(uploadFile(file));
}
return urls;
}
/**
* 删除文件
*/
public void deleteFile(String fileUrl) {
ossUtil.delete(fileUrl);
}
/**
* 下载文件
*/
public byte[] downloadFile(String filePath) {
return ossUtil.download(filePath);
}
/**
* 校验文件
*/
private void validateFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("文件不能为空");
}
// 校验文件大小
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.getSize() > maxSize) {
throw new RuntimeException("文件大小不能超过10MB");
}
// 校验文件类型
String contentType = file.getContentType();
if (!isAllowedFileType(contentType)) {
throw new RuntimeException("不支持的文件类型");
}
}
/**
* 判断文件类型是否支持
*/
private boolean isAllowedFileType(String contentType) {
// 允许的文件类型
String[] allowedTypes = {
"image/jpeg", // .jpg
"image/png", // .png
"image/gif", // .gif
"application/pdf", // .pdf
"application/msword", // .doc
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
"application/vnd.ms-excel", // .xls
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
"text/plain", // .txt
"video/mp4", // .mp4
"audio/mpeg" // .mp3
};
for (String type : allowedTypes) {
if (type.equals(contentType)) {
return true;
}
}
return false;
}
}
Controller层
package com.example.oss.controller;
import com.example.oss.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/file")
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
/**
* 上传文件
*/
@PostMapping("/upload")
public ResponseEntity<Map<String, String>> upload(@RequestParam("file") MultipartFile file) {
String url = fileService.uploadFile(file);
return ResponseEntity.ok(Map.of("url", url));
}
/**
* 上传到指定目录
*/
@PostMapping("/upload/{directory}")
public ResponseEntity<Map<String, String>> uploadToDirectory(
@RequestParam("file") MultipartFile file,
@PathVariable("directory") String directory) {
String url = fileService.uploadFileToDirectory(file, directory);
return ResponseEntity.ok(Map.of("url", url));
}
/**
* 批量上传
*/
@PostMapping("/upload/batch")
public ResponseEntity<Map<String, Object>> uploadBatch(
@RequestParam("files") List<MultipartFile> files) {
List<String> urls = fileService.uploadFiles(files);
return ResponseEntity.ok(Map.of("urls", urls));
}
/**
* 删除文件
*/
@DeleteMapping("/delete")
public ResponseEntity<Void> delete(@RequestParam("fileUrl") String fileUrl) {
fileService.deleteFile(fileUrl);
return ResponseEntity.noContent().build();
}
/**
* 下载文件
*/
@GetMapping("/download")
public ResponseEntity<byte[]> download(@RequestParam("filePath") String filePath) {
byte[] data = fileService.downloadFile(filePath);
String filename = filePath.substring(filePath.lastIndexOf("/") + 1);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"")
.body(data);
}
/**
* 获取文件访问URL
*/
@GetMapping("/url")
public ResponseEntity<Map<String, String>> getFileUrl(
@RequestParam("filePath") String filePath) {
String url = fileService.getFileUrl(filePath);
return ResponseEntity.ok(Map.of("url", url));
}
}
全局异常处理
package com.example.oss.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, String>> handleRuntimeException(RuntimeException e) {
log.error("运行时异常", e);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(Map.of("message", e.getMessage()));
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<Map<String, String>> handleMaxUploadSizeExceededException(
MaxUploadSizeExceededException e) {
log.error("文件大小超出限制", e);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(Map.of("message", "文件大小超出限制"));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleException(Exception e) {
log.error("系统异常", e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message", "系统异常,请稍后重试"));
}
}
补充Service方法
// 在FileService中添加这些方法
public String getFileUrl(String filePath) {
return ossUtil.getFileUrl(filePath);
}
在OssUtil中添加方法
/**
* 获取文件公开访问URL
*/
public String getFileUrl(String filePath) {
// 如果文件路径已经是完整URL
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
return filePath;
}
// 去除路径开头的斜杠
filePath = filePath.replaceAll("^/+", "");
return ossConfig.getUrlPrefix() + filePath;
}
/**
* 生成临时签名URL
*/
public String generatePresignedUrl(String filePath, long expiresInMinutes) {
Date expiration = new Date(System.currentTimeMillis() + expiresInMinutes * 60 * 1000);
URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), filePath, expiration);
return url.toString();
}
使用示例
// 测试上传
@RestController
@RequiredArgsConstructor
public class TestController {
private final FileService fileService;
@PostMapping("/test/upload")
public String testUpload(@RequestParam("file") MultipartFile file) {
return fileService.uploadFile(file);
}
@DeleteMapping("/test/delete")
public String testDelete(@RequestParam("fileUrl") String fileUrl) {
fileService.deleteFile(fileUrl);
return "删除成功";
}
}
前端调用示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">OSS文件上传</title>
</head>
<body>
<form id="uploadForm">
<input type="file" name="file" id="file">
<button type="button" onclick="upload()">上传</button>
</form>
<div id="result" style="margin-top: 20px;"></div>
<script>
function upload() {
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
const formData = new FormData();
formData.append('file', file);
fetch('/api/file/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
document.getElementById('result').innerHTML =
'上传成功:<a href="' + data.url + '" target="_blank">' + data.url + '</a>';
})
.catch(error => {
console.error('上传失败:', error);
alert('上传失败');
});
}
</script>
</body>
</html>
注意事项
-
Security凭证:不要在生产环境代码中直接写明AccessKey,建议使用环境变量或配置中心
-
文件校验:必须校验文件类型和大小,防止恶意上传
-
错误处理:完善的异常处理机制,包括网络异常、OSS服务异常等
-
日志记录:记录关键操作日志,便于问题排查
-
性能优化:对于大文件,可以考虑使用分片上传
-
权限控制:根据业务需求配置Bucket的访问权限(私有或公共读)
-
成本控制:设置生命周期规则,定期清理无用文件
-
安全配置:启用HTTPS,对敏感数据加密存储