2026-01-12 14:47:28 +08:00
|
|
|
|
package com.njcn.ali.oss.util;
|
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.io.FileUtil;
|
|
|
|
|
|
import cn.hutool.core.util.ObjUtil;
|
|
|
|
|
|
import com.aliyun.oss.OSS;
|
|
|
|
|
|
import com.aliyun.oss.common.comm.ResponseMessage;
|
|
|
|
|
|
import com.aliyun.oss.model.*;
|
|
|
|
|
|
import com.njcn.ali.oss.config.AliYunOssConfig;
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
|
import java.io.*;
|
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 阿里云OSS核心工具类
|
|
|
|
|
|
* 封装常用操作:上传、下载、删除、查询、生成临时访问URL等
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author web2023
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Component
|
|
|
|
|
|
public class AliYunOssUtils {
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(AliYunOssUtils.class);
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
private OSS ossClient;
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
private AliYunOssConfig ossConfig;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件(字节数组)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName 文件在OSS中的路径/名称
|
|
|
|
|
|
* @param content 文件字节数组
|
|
|
|
|
|
* @return 上传结果ETag
|
|
|
|
|
|
*/
|
|
|
|
|
|
public String uploadFile(String objectName, byte[] content) {
|
|
|
|
|
|
return uploadFile(objectName, new ByteArrayInputStream(content));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件(InputStream)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName 文件在OSS中的路径/名称
|
|
|
|
|
|
* @param inputStream 文件输入流
|
|
|
|
|
|
* @return 上传结果ETag
|
|
|
|
|
|
*/
|
|
|
|
|
|
public String uploadFile(String objectName, InputStream inputStream) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
PutObjectResult result = ossClient.putObject(
|
|
|
|
|
|
ossConfig.getBucket(),
|
|
|
|
|
|
objectName,
|
|
|
|
|
|
inputStream
|
|
|
|
|
|
);
|
|
|
|
|
|
return result.getETag();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}上传失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件上传失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件(MultipartFile)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName 文件在OSS中的路径/名称
|
|
|
|
|
|
* @param multipartFile 文件输入流
|
|
|
|
|
|
* @return 上传结果ETag
|
|
|
|
|
|
*/
|
|
|
|
|
|
public String uploadFile(String objectName, MultipartFile multipartFile) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
PutObjectResult result = ossClient.putObject(
|
|
|
|
|
|
ossConfig.getBucket(),
|
|
|
|
|
|
objectName,
|
|
|
|
|
|
multipartFile.getInputStream()
|
|
|
|
|
|
);
|
|
|
|
|
|
return result.getETag();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}上传失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件上传失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传本地文件
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName 文件在OSS中的路径/名称
|
|
|
|
|
|
* @param localFilePath 本地文件路径
|
|
|
|
|
|
* @return 上传结果ETag
|
|
|
|
|
|
*/
|
|
|
|
|
|
public String uploadLocalFile(String objectName, String localFilePath) {
|
|
|
|
|
|
File file = new File(localFilePath);
|
|
|
|
|
|
if (!file.exists()) {
|
|
|
|
|
|
throw new RuntimeException("本地文件不存在:" + localFilePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
PutObjectResult result = ossClient.putObject(
|
|
|
|
|
|
ossConfig.getBucket(),
|
|
|
|
|
|
objectName,
|
|
|
|
|
|
file
|
|
|
|
|
|
);
|
|
|
|
|
|
return result.getETag();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("本地文件{}上传失败", localFilePath, e);
|
|
|
|
|
|
throw new RuntimeException("本地文件上传失败:" + localFilePath, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文件到本地
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
* @param localFilePath 本地保存路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
public void downloadFile(String objectName, String localFilePath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
ossClient.getObject(
|
|
|
|
|
|
new GetObjectRequest(ossConfig.getBucket(), objectName),
|
|
|
|
|
|
new File(localFilePath)
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}下载失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件下载失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文件为字节流
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
* @return 文件字节流
|
|
|
|
|
|
*/
|
|
|
|
|
|
public InputStream downloadStream(String objectName) {
|
2026-01-13 16:01:11 +08:00
|
|
|
|
try {
|
|
|
|
|
|
OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), objectName);
|
|
|
|
|
|
InputStream inputStream = ossObject.getObjectContent();
|
2026-01-12 14:47:28 +08:00
|
|
|
|
return inputStream;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}下载为字节数组失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件下载为字节数组失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文件为字节数组
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
* @return 文件字节数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
public byte[] downloadFileToBytes(String objectName) {
|
2026-01-13 16:01:11 +08:00
|
|
|
|
try {
|
|
|
|
|
|
OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), objectName);
|
|
|
|
|
|
InputStream inputStream = ossObject.getObjectContent();
|
|
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
2026-01-12 14:47:28 +08:00
|
|
|
|
byte[] buffer = new byte[1024];
|
|
|
|
|
|
int len;
|
|
|
|
|
|
while ((len = inputStream.read(buffer)) != -1) {
|
|
|
|
|
|
outputStream.write(buffer, 0, len);
|
|
|
|
|
|
}
|
|
|
|
|
|
return outputStream.toByteArray();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}下载为字节数组失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件下载为字节数组失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文件为文件
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param folderPath OSS中的文件名称/路径
|
|
|
|
|
|
* @return 文件
|
|
|
|
|
|
*/
|
|
|
|
|
|
public File getLastFile(String folderPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 列出文件夹下所有文件(分页处理,避免文件过多)
|
|
|
|
|
|
ObjectListing objectListing = ossClient.listObjects(ossConfig.getBucket(), folderPath);
|
|
|
|
|
|
List<OSSObjectSummary> objectSummaries = objectListing.getObjectSummaries();
|
|
|
|
|
|
|
|
|
|
|
|
// 处理空文件夹
|
|
|
|
|
|
if (objectSummaries.isEmpty()) {
|
|
|
|
|
|
logger.warn("OSS文件夹 {} 下无文件", folderPath);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按最后修改时间降序排序,取第一个(最新)
|
|
|
|
|
|
String latestFileName = objectSummaries.stream()
|
|
|
|
|
|
.filter(summary -> !summary.getKey().equals(folderPath))
|
|
|
|
|
|
.max(Comparator.comparing(OSSObjectSummary::getLastModified))
|
|
|
|
|
|
.map(OSSObjectSummary::getKey)
|
|
|
|
|
|
.orElse(null);
|
|
|
|
|
|
if (ObjUtil.isNull(latestFileName)) {
|
|
|
|
|
|
throw new RuntimeException("OSS文件夹 " + folderPath + " 下无可用文件");
|
|
|
|
|
|
}
|
|
|
|
|
|
File file = File.createTempFile(latestFileName.split(".xlsx")[0], ".xlsx");
|
|
|
|
|
|
InputStream inputStream = ossClient.getObject(ossConfig.getBucket(), latestFileName).getObjectContent();
|
|
|
|
|
|
FileUtil.writeFromStream(inputStream, file);
|
|
|
|
|
|
inputStream.close();
|
|
|
|
|
|
return file;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("查找OSS文件夹 {} 最新文件失败", folderPath, e);
|
|
|
|
|
|
throw new RuntimeException("查找最新文件失败:" + folderPath, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除单个文件
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
public void deleteFile(String objectName) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
VoidResult voidResult = ossClient.deleteObject(ossConfig.getBucket(), objectName);
|
|
|
|
|
|
ResponseMessage response = voidResult.getResponse();
|
|
|
|
|
|
System.out.println(response);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("文件{}删除失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("文件删除失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量删除文件
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectNames 文件名称列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
public void deleteFiles(List<String> objectNames) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 静默删除,不返回删除结果
|
|
|
|
|
|
DeleteObjectsRequest request = new DeleteObjectsRequest(ossConfig.getBucket())
|
|
|
|
|
|
.withKeys(objectNames);
|
|
|
|
|
|
ossClient.deleteObjects(request);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("批量删除文件失败", e);
|
|
|
|
|
|
throw new RuntimeException("批量删除文件失败", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断文件是否存在
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
* @return true-存在,false-不存在
|
|
|
|
|
|
*/
|
|
|
|
|
|
public boolean isFileExist(String objectName) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
boolean exists = ossClient.doesObjectExist(ossConfig.getBucket(), objectName);
|
|
|
|
|
|
return exists;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("判断文件{}是否存在失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("判断文件是否存在失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成文件临时访问URL
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param objectName OSS中的文件名称/路径
|
|
|
|
|
|
* @param expireSeconds 过期时间(秒)
|
|
|
|
|
|
* @return 临时访问URL
|
|
|
|
|
|
*/
|
|
|
|
|
|
public String generatePresignedUrl(String objectName, int expireSeconds) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000L);
|
|
|
|
|
|
URL url = ossClient.generatePresignedUrl(
|
|
|
|
|
|
ossConfig.getBucket(),
|
|
|
|
|
|
objectName,
|
|
|
|
|
|
expiration
|
|
|
|
|
|
);
|
|
|
|
|
|
String urlStr = url.toString();
|
|
|
|
|
|
logger.info("文件{}生成临时访问URL成功,过期时间:{}秒", objectName, expireSeconds);
|
|
|
|
|
|
return urlStr;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("生成文件{}临时访问URL失败", objectName, e);
|
|
|
|
|
|
throw new RuntimeException("生成临时访问URL失败:" + objectName, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭OSS客户端
|
|
|
|
|
|
*/
|
|
|
|
|
|
public void close() {
|
|
|
|
|
|
if (ossClient != null) {
|
|
|
|
|
|
ossClient.shutdown();
|
|
|
|
|
|
logger.info("OSS客户端已关闭");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|