配置类:
java
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AwsProperties.class)
public class AwsConfig {
@Autowired
AwsProperties awsProperties;
@Bean
AmazonS3 s3Client() {
//设置连接时的参数
ClientConfiguration config = new ClientConfiguration();
//设置连接方式为HTTP,可选参数为HTTP和HTTPS
config.setProtocol(Protocol.HTTP);
//设置网络访问超时时间
config.setConnectionTimeout(5000);
config.setUseExpectContinue(true);
//获取访问凭证
AWSCredentials credentials = null;
credentials = new BasicAWSCredentials(awsProperties.getAccessKeyId(),awsProperties.getSecretAccessKey());
//设置Endpoint
AwsClientBuilder.EndpointConfiguration end_point = null;
end_point = new AwsClientBuilder.EndpointConfiguration(awsProperties.getEndpoint(), "");
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withClientConfiguration(config)
.withEndpointConfiguration(end_point)
.withPathStyleAccessEnabled(true).build();
}
}
java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "s3.storage")
public class AwsProperties {
private String accessKeyId;
private String secretAccessKey;
private String endpoint;
private String bucketName;
}
yml:
java
s3:
storage:
accessKeyId:
secretAccessKey:
endpoint: 127.0.0.1:8999
bucketName:
服务层实现:
java
import com.amazonaws.services.s3.model.PutObjectResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.Future;
public interface AwsService {
List<S3Obj> getObjListByBucketAndPath(String bucketName, String folderPath) throws Exception;
/**
* 新建文件夹
* @param bucketName
* @param folderPath
* @return
* @throws Exception
*/
PutObjectResult addFolder(String bucketName, String folderPath) throws Exception;
/**
* 删除文件夹
* (会删除 folderPath 路径下所有文件)
* @param bucketName
* @param folderPath
* @throws Exception
*/
void deleteFolder(String bucketName, String folderPath) throws Exception;
/**
* 批量上传文件
* @param files
* @param bucketName
* @param folderPath
* @throws Exception
*/
Future<String> batchUpload(MultipartFile[] files, String bucketName, String folderPath);
/**
* 下载文件或文件夹
* @param bucketName
* @param path 路径列表
* @throws Exception
*/
Future<String> download(String bucketName, String path, HttpServletResponse response);
}
java
import cn.hutool.core.io.IoUtil;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.iterable.S3Objects;
import com.amazonaws.services.s3.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Future;
import static cn.hutool.core.io.file.FileWrapper.DEFAULT_CHARSET;
import static cn.hutool.core.util.ZipUtil.getZipOutputStream;
@EnableAsync
@Slf4j
@Service
public class AwsServiceImpl implements AwsService {
@Autowired
public AwsProperties awsProperties;
@Autowired
public AmazonS3 amazonS3;
/**
* 根据桶和路径获取对象列表
* @param bucketName
* @return
*/
@Override
public List<S3Obj> getObjListByBucketAndPath(String bucketName, String folderPath) throws Exception {
if(!folderPath.endsWith("/")){
folderPath = folderPath + "/";
}
ListObjectsV2Request req = new ListObjectsV2Request().withBucketName(bucketName).withPrefix(folderPath).withDelimiter("/");
ListObjectsV2Result listObjectsV2Result = amazonS3.listObjectsV2(req);
List<S3Obj> list = new ArrayList<>();
// 遍历前缀列表
for (String commonPrefix : listObjectsV2Result.getCommonPrefixes()) {
S3Obj entity = new S3Obj();
InetAddress address = InetAddress.getLocalHost();
String hostAddress = address.getHostAddress();
entity.setIp(hostAddress);
String[] ids = commonPrefix.split("/");
String name = ids[ids.length-1];
entity.setName(name);
entity.setPath("/" + commonPrefix);
entity.setSize(null);
int separatorIndex = entity.getName().lastIndexOf(".");
if (separatorIndex < 0) {
entity.setType("folder");
}
entity.setLastModified(null);
list.add(entity);
}
// 遍历 folderPath 下所有 对象
for ( S3ObjectSummary summary : S3Objects.withPrefix(amazonS3, bucketName, folderPath) ) {
for (S3ObjectSummary summ : listObjectsV2Result.getObjectSummaries()) {
if(summ.getKey().equals(summary.getKey())){
if(!folderPath.equals(("/"+summ.getKey()))){
S3Obj entity = new S3Obj();
InetAddress address = InetAddress.getLocalHost();
String hostAddress = address.getHostAddress();
entity.setIp(hostAddress);
int index = summary.getKey().lastIndexOf("/");
if(index < 0){
entity.setName(summary.getKey());
}else{
entity.setName(summary.getKey().substring(index + 1).toLowerCase());
}
entity.setPath("/"+summary.getKey());
DecimalFormat df = new DecimalFormat("#.00");
entity.setSize(df.format((double) summ.getSize() / 1024));
int separatorIndex = entity.getName().lastIndexOf(".");
if (separatorIndex < 0) {
entity.setType("folder");
}else{
entity.setType(entity.getName().substring(separatorIndex + 1).toLowerCase());
}
Date lastModified = summ.getLastModified();
entity.setLastModified(lastModified);
list.add(entity);
}
}
}
}
if (list.size() <= 0){
return list;
}
// 使用 Comparator 进行降序排序,处理字段 lastModified 可能为空的情况
list.sort((o1, o2) -> {
if (o1.getLastModified() == null && o2.getLastModified() == null) {
return 0; // 如果两个对象都为空,则认为它们相等
} else if (o1.getLastModified() == null) {
return 1; // 如果第一个对象为空,则认为它比第二个对象大
} else if (o2.getLastModified() == null) {
return -1; // 如果第二个对象为空,则认为它比第一个对象大
} else {
return o2.getLastModified().compareTo(o1.getLastModified()); // 否则按降序比较
}
});
return list;
}
@Override
public PutObjectResult addFolder(String bucketName, String folderPath) throws Exception {
if(!folderPath.endsWith("/")){
folderPath = folderPath + "/";
}
PutObjectResult object = amazonS3.putObject(bucketName, folderPath, "");
return object;
}
@Override
public void deleteFolder(String bucketName, String path) throws Exception {
AwsFileDto file = isFile(path);
if ("file".equals(file.getObjType())){
amazonS3.deleteObject(bucketName, path);
}
else if ("folder".equals(file.getObjType())){
for (S3ObjectSummary summary : S3Objects.withPrefix(amazonS3, bucketName, path)) {
amazonS3.deleteObject(bucketName,"/" + summary.getKey());
}
}
}
@Async
@Override
public Future<String> batchUpload(MultipartFile[] files, String bucketName, String folderPath) {
try{
if(!folderPath.endsWith("/")){
folderPath = folderPath + "/";
}
for (MultipartFile file : files){
String localFileName = file.getOriginalFilename();
File uploadFile = new File(localFileName);
FileUtils.copyInputStreamToFile(file.getInputStream(), uploadFile);
String filePath = folderPath + localFileName;
amazonS3.putObject(bucketName, filePath, uploadFile);
if(uploadFile.exists()){
uploadFile.delete();
}
}
return new AsyncResult<>("上传成功");
}catch (Exception e){
log.error("batchUpload:{}", e);
return new AsyncResult<>("上传失败");
}
}
public AwsFileDto isFile(String path){
AwsFileDto dto = new AwsFileDto();
int index = path.lastIndexOf("/");
String pathName, objName, objType;
if (index != -1 && index < path.length() - 1){
pathName = path.substring(index);
objName = path.substring(index + 1);
objType = "file"; // 文件
dto.setPathName(pathName);
dto.setObjName(objName);
dto.setObjType(objType);
} else {
String subPath = path.substring(0, path.length() - 1);
pathName = subPath.substring(subPath.lastIndexOf("/"));
objName = subPath.substring(subPath.lastIndexOf("/") + 1);
objType = "folder"; // 文件夹
dto.setPathName(pathName);
dto.setObjName(objName);
dto.setObjType(objType);
}
return dto;
}
@Async
@Override
public Future<String> download(String bucketName, String path, HttpServletResponse response) {
try{
AwsFileDto file = isFile(path);
// 文件下载
if ("file".equals(file.getObjType())){
S3Object object = amazonS3.getObject(bucketName, path);
if(object != null){
//log.info("ETag: [{}]", object.getObjectMetadata().getETag());
InputStream inputStream = object.getObjectContent();
try {
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getObjName(), "UTF-8"));
OutputStream outputStream = response.getOutputStream();
// 使用 Hutool 的 IoUtil 进行流的复制
IoUtil.copy(inputStream, outputStream);
} catch (Exception e) {
log.error("下载文件异常", e);
throw new Exception("下载文件异常!");
}
}
}
// 压缩包下载 @todo
else if ("folder".equals(file.getObjType())){
}
return new AsyncResult<>("下载成功");
}catch (Exception e){
log.error("download:{}", e);
return new AsyncResult<>("下载失败");
}
}
}
java
@Data
public class S3Obj {
@ApiModelProperty(value = "文件名",position = 1 )
private String name;
@ApiModelProperty(value = "路径",position = 2 )
private String path;
@ApiModelProperty(value = "文件大小 单位kb",position = 3 )
private String size;
@ApiModelProperty(value = "文件类型 folder:文件夹,doc:文档,text:文本,jpg,png:照片等",position = 4 )
private String type;
@ApiModelProperty(value = "地址",position = 4 )
private String ip;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
@ApiModelProperty(value = "最后修改时间",position = 5 )
private Date lastModified;
}