SpringBoot 3.x集成阿里云OSS:文件上传/断点续传/权限控制
Spring Boot 3.x 集成阿里云 OSS 终极指南
一、环境准备与依赖配置
1. 添加阿里云 OSS SDK 依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
2. 配置 OSS 连接参数
# application.yml
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
sts:
role-arn: acs:ram::1234567890123456:role/oss-sts-role # RAM角色ARN
policy: | # 权限策略
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:GetObject",
"oss:PutObject"
],
"Resource": [
"acs:oss:*:*:your-bucket-name/*"
]
}
]
}
二、基础文件上传服务
1. OSS 客户端配置
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
2. 文件上传服务
@Service
@Slf4j
public class OssService {
private final OSS ossClient;
private final String bucketName;
public OssService(OSS ossClient,
@Value("${aliyun.oss.bucket-name}") String bucketName) {
this.ossClient = ossClient;
this.bucketName = bucketName;
}
/**
* 简单文件上传
* @param file 上传文件
* @param objectKey 对象键(OSS路径)
* @return 文件URL
*/
public String uploadFile(MultipartFile file, String objectKey) throws IOException {
try (InputStream inputStream = file.getInputStream()) {
ossClient.putObject(bucketName, objectKey, inputStream);
return generateUrl(objectKey);
} catch (Exception e) {
log.error("文件上传失败: {}", objectKey, e);
throw new OssException("文件上传失败");
}
}
/**
* 生成文件URL(带签名)
*/
private String generateUrl(String objectKey) {
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000); // 1小时有效
return ossClient.generatePresignedUrl(bucketName, objectKey, expiration).toString();
}
}
三、断点续传高级实现
1. 断点续传服务
@Service
public class ResumableUploadService {
private final OSS ossClient;
private final String bucketName;
public ResumableUploadService(OSS ossClient,
@Value("${aliyun.oss.bucket-name}") String bucketName) {
this.ossClient = ossClient;
this.bucketName = bucketName;
}
/**
* 断点续传上传
* @param file 上传文件
* @param objectKey 对象键
* @return 上传结果
*/
public UploadResult resumableUpload(MultipartFile file, String objectKey) {
try {
// 创建上传请求
UploadFileRequest request = new UploadFileRequest(
bucketName,
objectKey,
file.getInputStream(),
file.getSize()
);
// 配置上传参数
request.setPartSize(5 * 1024 * 1024); // 5MB分片
request.setTaskNum(5); // 并发线程数
request.setEnableCheckpoint(true); // 开启断点记录
// 设置断点文件存储位置
String checkpointDir = System.getProperty("java.io.tmpdir") + "/oss-checkpoints";
request.setCheckpointFile(checkpointDir + "/" + objectKey + ".ucp");
// 执行上传
UploadFileResult result = ossClient.uploadFile(request);
return new UploadResult(
generateUrl(objectKey),
result.getMultipartUploadResult().getETag(),
result.getMultipartUploadResult().getLocation()
);
} catch (Throwable e) {
throw new OssException("断点续传失败", e);
}
}
@Data
@AllArgsConstructor
public static class UploadResult {
private String fileUrl;
private String eTag;
private String location;
}
}
2. 断点续传恢复机制
public void resumeUpload(String objectKey) {
String checkpointFile = getCheckpointFilePath(objectKey);
if (new File(checkpointFile).exists()) {
UploadFileRequest request = new UploadFileRequest(bucketName, objectKey);
request.setCheckpointFile(checkpointFile);
try {
ossClient.uploadFile(request);
} catch (Throwable e) {
throw new OssException("续传失败", e);
}
} else {
throw new OssException("未找到断点记录");
}
}
四、精细化权限控制
1. STS 临时凭证服务
@Service
public class StsService {
@Value("${aliyun.oss.sts.role-arn}")
private String roleArn;
@Value("${aliyun.oss.sts.policy}")
private String policy;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
/**
* 获取STS临时凭证
* @param sessionName 会话名称
* @param durationSeconds 有效期(秒)
* @return STS凭证
*/
public StsToken getStsToken(String sessionName, long durationSeconds) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
IAcsClient client = new DefaultAcsClient(profile);
AssumeRoleRequest request = new AssumeRoleRequest();
request.setRoleArn(roleArn);
request.setRoleSessionName(sessionName);
request.setDurationSeconds(durationSeconds);
request.setPolicy(policy);
try {
AssumeRoleResponse response = client.getAcsResponse(request);
AssumeRoleResponse.Credentials credentials = response.getCredentials();
return new StsToken(
credentials.getAccessKeyId(),
credentials.getAccessKeySecret(),
credentials.getSecurityToken(),
credentials.getExpiration()
);
} catch (ClientException e) {
throw new StsException("STS获取失败", e);
}
}
@Data
@AllArgsConstructor
public static class StsToken {
private String accessKeyId;
private String accessKeySecret;
private String securityToken;
private String expiration;
}
}
2. 前端直传签名服务
@Service
public class OssSignatureService {
private final OSS ossClient;
private final String bucketName;
public OssSignatureService(OSS ossClient,
@Value("${aliyun.oss.bucket-name}") String bucketName) {
this.ossClient = ossClient;
this.bucketName = bucketName;
}
/**
* 生成前端直传签名
* @param objectKey 对象键
* @param expireSeconds 过期时间(秒)
* @return 签名信息
*/
public SignatureInfo generateSignature(String objectKey, long expireSeconds) {
Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000);
// 创建策略
PolicyConditions policy = new PolicyConditions();
policy.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 104857600); // 100MB限制
policy.addConditionItem(PolicyConditions.COND_KEY, objectKey);
// 生成签名
String postPolicy = ossClient.generatePostPolicy(expiration, policy);
String encodedPolicy = BinaryUtil.toBase64String(postPolicy.getBytes());
String signature = ossClient.calculatePostSignature(postPolicy);
return new SignatureInfo(
ossClient.getEndpoint().toString(),
bucketName,
objectKey,
encodedPolicy,
signature,
expiration
);
}
@Data
@AllArgsConstructor
public static class SignatureInfo {
private String endpoint;
private String bucket;
private String key;
private String policy;
private String signature;
private Date expiration;
}
}
五、SDK 坑位指南与最佳实践
1. 常见问题解决方案
问题类型 | 现象 | 解决方案 |
---|---|---|
连接超时 | 上传大文件时超时 | 增加超时时间: ossClient.setTimeout(300000) |
内存溢出 | 大文件上传时OOM | 使用文件流代替内存流: request.setUploadFile(filePath) |
分片失败 | 分片上传卡死 | 设置合理的分片大小: request.setPartSize(5 * 1024 * 1024) |
签名失效 | 前端直传签名过期 | 签名有效期至少600秒,建议1200秒 |
权限不足 | STS操作失败 | 检查RAM角色权限策略 |
2. 性能优化配置
@Bean
public OSS ossClient(OssProperties properties) {
ClientBuilderConfiguration config = new ClientBuilderConfiguration();
// 连接池配置
config.setMaxConnections(200); // 最大连接数
config.setConnectionTimeout(30 * 1000); // 连接超时30s
config.setSocketTimeout(120 * 1000); // 读写超时120s
// 开启HTTP重试
config.setMaxErrorRetry(3);
// 开启HTTPS
config.setProtocol(Protocol.HTTPS);
return new OSSClientBuilder()
.build(properties.getEndpoint(),
properties.getAccessKeyId(),
properties.getAccessKeySecret(),
config);
}
3. 安全最佳实践
/**
* 安全文件上传验证
*/
public void validateFileUpload(MultipartFile file, String objectKey) {
// 1. 文件类型验证
String contentType = file.getContentType();
if (!Arrays.asList("image/jpeg", "image/png").contains(contentType)) {
throw new OssException("不支持的文件类型");
}
// 2. 文件大小验证
if (file.getSize() > 10 * 1024 * 1024) { // 10MB限制
throw new OssException("文件大小超过限制");
}
// 3. 文件名安全过滤
if (objectKey.contains("..") || objectKey.contains("/")) {
throw new OssException("非法文件名");
}
// 4. 病毒扫描(集成第三方服务)
if (!virusScanService.scan(file)) {
throw new OssException("文件安全检测未通过");
}
}
六、完整文件管理控制器
@RestController
@RequestMapping("/oss")
@RequiredArgsConstructor
public class OssController {
private final OssService ossService;
private final ResumableUploadService resumableService;
private final StsService stsService;
private final OssSignatureService signatureService;
/**
* 普通文件上传
*/
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam("path") String path) throws IOException {
String objectKey = "uploads/" + path + "/" + file.getOriginalFilename();
String url = ossService.uploadFile(file, objectKey);
return ResponseEntity.ok(url);
}
/**
* 断点续传接口
*/
@PostMapping("/resumable-upload")
public ResponseEntity<ResumableUploadService.UploadResult> resumableUpload(
@RequestParam("file") MultipartFile file,
@RequestParam("path") String path) {
String objectKey = "uploads/" + path + "/" + file.getOriginalFilename();
return ResponseEntity.ok(resumableService.resumableUpload(file, objectKey));
}
/**
* 获取STS临时凭证
*/
@GetMapping("/sts-token")
public ResponseEntity<StsService.StsToken> getStsToken() {
String sessionName = "user-" + SecurityUtils.getCurrentUserId();
return ResponseEntity.ok(stsService.getStsToken(sessionName, 3600));
}
/**
* 生成前端直传签名
*/
@GetMapping("/signature")
public ResponseEntity<OssSignatureService.SignatureInfo> getSignature(
@RequestParam String fileName) {
String objectKey = "uploads/user/" + SecurityUtils.getCurrentUserId() + "/" + fileName;
return ResponseEntity.ok(signatureService.generateSignature(objectKey, 1200));
}
}
七、部署与监控
1. 健康检查端点
@RestController
@RequestMapping("/actuator")
public class OssHealthController {
private final OSS ossClient;
private final String bucketName;
@GetMapping("/oss-health")
public ResponseEntity<String> checkOssHealth() {
try {
boolean exists = ossClient.doesBucketExist(bucketName);
return exists ?
ResponseEntity.ok("OSS connection is healthy") :
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("Bucket not found");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("OSS connection failed: " + e.getMessage());
}
}
}
2. Prometheus 监控指标
@Bean
public MeterRegistryCustomizer<MeterRegistry> ossMetrics(OSS ossClient) {
return registry -> {
Gauge.builder("oss.connection.count", ossClient,
client -> client.getClientConfiguration().getMaxConnections())
.description("OSS connection pool size")
.register(registry);
Counter.builder("oss.upload.count")
.description("Total OSS upload operations")
.register(registry);
};
}
八、总结与最佳实践
1. 架构选择建议
- 小文件上传:直接使用简单上传接口
- 大文件上传:使用断点续传(>10MB)
- 前端直传:使用STS临时凭证或签名直传
- 敏感文件:服务端中转上传+病毒扫描
2. 安全防护措施
- 权限最小化:STS策略只授予必要权限
- 文件类型过滤:限制可上传文件类型
- 病毒扫描:集成ClamAV等扫描引擎
- 访问日志:开启OSS访问日志审计
- WAF防护:配置Web应用防火墙规则
3. 性能优化方案
通过本方案,可实现安全高效的OSS文件管理,支持从KB到TB级文件的上传需求,同时满足企业级安全合规要求。