SpringBoot 3.x集成阿里云OSS:文件上传 断点续传 权限控制
SpringBoot 3.x集成阿里云OSS:文件上传/断点续传/权限控制
- Spring Boot 3.x 集成阿里云 OSS 终极指南
- 一、环境准备与依赖配置
- 1. 添加阿里云 OSS SDK 依赖
- 2. 配置 OSS 连接参数
- 二、基础文件上传服务
- 1. OSS 客户端配置
- 2. 文件上传服务
- 三、断点续传高级实现
- 1. 断点续传服务
- 2. 断点续传恢复机制
- 四、精细化权限控制
- 1. STS 临时凭证服务
- 2. 前端直传签名服务
- 五、SDK 坑位指南与最佳实践
- 1. 常见问题解决方案
- 2. 性能优化配置
- 3. 安全最佳实践
- 六、完整文件管理控制器
- 七、部署与监控
- 1. 健康检查端点
- 2. Prometheus 监控指标
- 八、总结与最佳实践
- 1. 架构选择建议
- 2. 安全防护措施
- 3. 性能优化方案
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-idaccess-key-secret: your-access-key-secretbucket-name: your-bucket-namests:role-arn: acs:ram::1234567890123456:role/oss-sts-role # RAM角色ARNpolicy: | # 权限策略{"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;@Beanpublic 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@AllArgsConstructorpublic 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@AllArgsConstructorpublic 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@AllArgsConstructorpublic 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); // 连接超时30sconfig.setSocketTimeout(120 * 1000); // 读写超时120s// 开启HTTP重试config.setMaxErrorRetry(3); // 开启HTTPSconfig.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级文件的上传需求,同时满足企业级安全合规要求。