阿里云 OSS
阿里云对象存储 OSS(Object Storage Service)适合公有云场景,提供 CDN 加速、免运维、按量计费。
依赖
implementation 'com.aliyun.oss:aliyun-sdk-oss:3.17.4'<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>配置
app:
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
bucket: your-bucket-name
url-prefix: https://your-bucket.oss-cn-hangzhou.aliyuncs.com密钥建议通过环境变量注入,不要硬编码。参见 配置管理。
配置类
@Configuration
@ConfigurationProperties(prefix = "app.oss")
@Data
public class OssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucket;
private String urlPrefix;
}
@Configuration
@RequiredArgsConstructor
public class OssConfig {
private final OssProperties props;
@Bean
public OSS ossClient() {
return new OSSClientBuilder()
.build(props.getEndpoint(),
props.getAccessKeyId(),
props.getAccessKeySecret());
}
}基础操作
@Service
@RequiredArgsConstructor
public class OssStorageService implements FileStorageService {
private final OSS ossClient;
private final OssProperties props;
@Override
public String store(MultipartFile file) throws IOException {
String objectName = buildObjectName(file.getOriginalFilename());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
ossClient.putObject(
props.getBucket(), objectName,
file.getInputStream(), metadata
);
return props.getUrlPrefix() + "/" + objectName;
}
public void delete(String objectName) {
ossClient.deleteObject(props.getBucket(), objectName);
}
public boolean exists(String objectName) {
return ossClient.doesObjectExist(props.getBucket(), objectName);
}
public byte[] download(String objectName) throws IOException {
OSSObject obj = ossClient.getObject(props.getBucket(), objectName);
try (InputStream is = obj.getObjectContent()) {
return is.readAllBytes();
}
}
private String buildObjectName(String originalFilename) {
String ext = "";
if (originalFilename != null && originalFilename.contains(".")) {
ext = originalFilename.substring(originalFilename.lastIndexOf('.'));
}
String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
return date + "/" + UUID.randomUUID() + ext;
}
}预签名 URL(临时访问)
私有 Bucket 下生成有时效的访问链接,无需暴露密钥:
// 下载用预签名 URL
public String getPresignedUrl(String objectName, int expiryMinutes) {
Date expiration = new Date(System.currentTimeMillis()
+ (long) expiryMinutes * 60 * 1000);
URL url = ossClient.generatePresignedUrl(
props.getBucket(), objectName, expiration);
return url.toString();
}
// 上传用预签名 URL(前端直传)
public String getUploadPresignedUrl(String objectName, int expiryMinutes) {
Date expiration = new Date(System.currentTimeMillis()
+ (long) expiryMinutes * 60 * 1000);
GeneratePresignedUrlRequest request =
new GeneratePresignedUrlRequest(props.getBucket(), objectName, HttpMethod.PUT);
request.setExpiration(expiration);
request.setContentType("application/octet-stream");
return ossClient.generatePresignedUrl(request).toString();
}前端直传(服务端签名)
绕过服务端中转,前端直接 POST 到 OSS,降低服务器带宽压力。
服务端生成签名
@GetMapping("/oss/policy")
public Map<String, String> getUploadPolicy(@RequestParam String filename) {
long expireSeconds = 300L;
String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/";
PolicyConditions conditions = new PolicyConditions();
conditions.addConditionItem(
PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 100 * 1024 * 1024L);
conditions.addConditionItem(
MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000);
String policy = ossClient.generatePostPolicy(expiration, conditions);
String encodedPolicy = Base64.getEncoder()
.encodeToString(policy.getBytes(StandardCharsets.UTF_8));
String signature = ossClient.calculatePostSignature(policy);
String ext = filename.substring(filename.lastIndexOf('.'));
String objectName = dir + UUID.randomUUID() + ext;
String host = "https://" + props.getBucket() + "."
+ props.getEndpoint().replace("https://", "");
return Map.of(
"host", host,
"key", objectName,
"policy", encodedPolicy,
"OSSAccessKeyId", props.getAccessKeyId(),
"signature", signature,
"expire", String.valueOf(expiration.getTime() / 1000)
);
}前端直传
async function uploadToOss(file) {
const { data } = await axios.get('/oss/policy', {
params: { filename: file.name }
})
const form = new FormData()
form.append('key', data.key)
form.append('policy', data.policy)
form.append('OSSAccessKeyId', data.OSSAccessKeyId)
form.append('signature', data.signature)
form.append('file', file)
await axios.post(data.host, form)
return data.host + '/' + data.key
}图片处理(OSS 图片服务)
在 URL 后追加参数实时处理图片,无需额外服务:
# 缩放到宽 300px(高等比)
https://bucket.oss-cn-hangzhou.aliyuncs.com/img.jpg?x-oss-process=image/resize,w_300
# 裁剪 200×200 居中
?x-oss-process=image/crop,w_200,h_200,g_center
# 转换格式为 WebP
?x-oss-process=image/format,webp
# 组合:缩放 + WebP + 质量 80
?x-oss-process=image/resize,w_800/format,webp/quality,q_80
Bucket 权限说明
| 权限 | 说明 | 适用场景 |
|---|---|---|
| 私有 | 读写均需鉴权 | 用户隐私文件 |
| 公共读 | 任何人可读,写需鉴权 | 静态资源、图片 |
| 公共读写 | 任何人可读写 | 不推荐生产使用 |
跨域配置(前端直传必须)
OSS 控制台 → Bucket → 跨域设置 → 新增规则:
来源: https://your-domain.com
允许 Methods: GET, POST, PUT
允许 Headers: *
暴露 Headers: ETag, x-oss-request-id