Signed-off-by: chy <chy@163.com>

This commit is contained in:
chy
2026-02-02 23:31:39 +08:00
commit f6d2459f1f
1499 changed files with 289491 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
package top.lidee.taie.oss.config;
import top.lidee.taie.oss.ossbuilder.FileClientFactory;
import top.lidee.taie.oss.ossbuilder.LideeOSSTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* springboot自动装配类
*
* @author: Devli
* @since 2022/3/22 14:16
*/
@Configuration
@EnableConfigurationProperties(OSSProperties.class)
@ConditionalOnProperty(name= "spring.lidee.subscribes.oss.enabled", havingValue="true")
public class AutoConfiguration {
private static Logger logger = LoggerFactory.getLogger(AutoConfiguration.class);
@Autowired
private OSSProperties ossProperties;
public AutoConfiguration() {
logger.info("spring lidee subscribes oss is actived");
}
@Bean
@ConditionalOnMissingBean
public LideeOSSTemplate lideeOSSTemplate() throws Exception{
FileClientFactory fileClientFactory = new FileClientFactory(ossProperties);
return fileClientFactory.getObject();
}
}

View File

@@ -0,0 +1,49 @@
package top.lidee.taie.oss.config;
import java.io.Serializable;
/**
* AmazonS3配置项
*
* @author: Devli
* @since 2022/3/22 14:16
*/
public class OSSAmazonS3Properties implements Serializable {
private String url;
private String accessKey;
private String secretKey;
private String bucketName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}

View File

@@ -0,0 +1,57 @@
package top.lidee.taie.oss.config;
import java.io.Serializable;
/**
* 云储存 公共配置项
* @author WongBin
* @date 2023/8/24
*/
public class OSSBaseProperties implements Serializable {
private String type;
private String url;
private String accessKey;
private String secretKey;
private String bucketName;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}

View File

@@ -0,0 +1,59 @@
package top.lidee.taie.oss.config;
import java.io.Serializable;
/**
* minio配置项
*
* @author: Devli
* @since 2022/3/22 14:16
*/
public class OSSMinioProperties implements Serializable {
private String url;
private int port=9000;
private String accessKey;
private String secretKey;
private String bucketName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}

View File

@@ -0,0 +1,16 @@
package top.lidee.taie.oss.config;
import java.io.Serializable;
public class OSSNFSProperties implements Serializable {
private String path = "/app/disk/upload";
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}

View File

@@ -0,0 +1,91 @@
package top.lidee.taie.oss.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* oss组件配置项
*
* @author: Devli
* @since 2022/3/22 14:16
*/
@ConfigurationProperties(prefix = "spring.lidee.subscribes.oss")
public class OSSProperties implements Serializable {
/** 默认激活oss组件 */
private boolean enabled = true;
/** 允许上传的文件后缀,为空不限制,例如:.png|.jpg|.gif|.icon|.pdf|.xlsx|.xls|.csv|.mp4|.avi */
private String fileTypeWhiteList="";
/** 防盗链允许访问的域名为空不限制例如sili.anji-plus.com,www.anji-plus.com */
private String refererWhiteList="";
/** minio组件配置项 */
private OSSMinioProperties minio;
/** AmazonS3组件配置项 */
private OSSAmazonS3Properties amazonS3;
/** 默认使用服务器本地文件夹多节点时配合nfs */
private OSSNFSProperties nfs;
private OSSBaseProperties huaweiObs;
public OSSBaseProperties getHuaweiObs() {
return huaweiObs;
}
public void setHuaweiObs(OSSBaseProperties huaweiObs) {
this.huaweiObs = huaweiObs;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getFileTypeWhiteList() {
return fileTypeWhiteList;
}
public void setFileTypeWhiteList(String fileTypeWhiteList) {
this.fileTypeWhiteList = fileTypeWhiteList;
}
public String getRefererWhiteList() {
return refererWhiteList;
}
public void setRefererWhiteList(String refererWhiteList) {
this.refererWhiteList = refererWhiteList;
}
public OSSMinioProperties getMinio() {
return minio;
}
public void setMinio(OSSMinioProperties minio) {
this.minio = minio;
}
public OSSAmazonS3Properties getAmazonS3() {
return amazonS3;
}
public void setAmazonS3(OSSAmazonS3Properties amazonS3) {
this.amazonS3 = amazonS3;
}
public OSSNFSProperties getNfs() {
return nfs;
}
public void setNfs(OSSNFSProperties nfs) {
this.nfs = nfs;
}
}

View File

@@ -0,0 +1,19 @@
package top.lidee.taie.oss.exceptions;
/**
* OSS异常
*/
public class LideeOSSException extends RuntimeException{
public LideeOSSException(String message){
super(message);
}
public LideeOSSException(Throwable throwable) {
super(throwable);
}
public LideeOSSException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@@ -0,0 +1,18 @@
package top.lidee.taie.oss.exceptions;
public class LideeOSSExceptionBuilder {
public LideeOSSExceptionBuilder() {
}
public static LideeOSSException build(String code) {
return new LideeOSSException(code);
}
public static LideeOSSException build(Throwable throwable) {
return new LideeOSSException(throwable);
}
public static LideeOSSException build(String code, Throwable throwable) {
return new LideeOSSException(code, throwable);
}
}

View File

@@ -0,0 +1,16 @@
package top.lidee.taie.oss.exceptions;
public class LideeOSSTypeLimitedException extends LideeOSSException {
public LideeOSSTypeLimitedException(String message) {
super(message);
}
public LideeOSSTypeLimitedException(Throwable throwable) {
super(throwable);
}
public LideeOSSTypeLimitedException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@@ -0,0 +1,19 @@
package top.lidee.taie.oss.exceptions;
public class LideeOSSTypeLimitedExceptionBuilder {
public LideeOSSTypeLimitedExceptionBuilder() {
}
public static LideeOSSTypeLimitedException build(String code) {
return new LideeOSSTypeLimitedException(code);
}
public static LideeOSSTypeLimitedException build(Throwable throwable) {
return new LideeOSSTypeLimitedException(throwable);
}
public static LideeOSSTypeLimitedException build(String code, Throwable throwable) {
return new LideeOSSTypeLimitedException(code, throwable);
}
}

View File

@@ -0,0 +1,78 @@
package top.lidee.taie.oss.ossbuilder;
import java.util.Objects;
import top.lidee.taie.oss.config.OSSAmazonS3Properties;
import top.lidee.taie.oss.config.OSSMinioProperties;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.ossbuilder.builders.AmazonS3Client;
import top.lidee.taie.oss.ossbuilder.builders.HuaweiOBSClient;
import top.lidee.taie.oss.ossbuilder.builders.NFSClient;
import top.lidee.taie.oss.ossbuilder.builders.MinioClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.StringUtils;
/**
* 文件存储客户端工厂
*
* @Author: Devli
* @since 2022/3/22 14:16
*/
public class FileClientFactory implements FactoryBean<LideeOSSTemplate> {
private static Logger logger = LoggerFactory.getLogger(FileClientFactory.class);
private LideeOSSTemplate lideeOSSTemplate;
private OSSProperties ossProperties;
public FileClientFactory(OSSProperties ossProperties){
this.ossProperties = ossProperties;
this.initFileClient();
}
@Override
public LideeOSSTemplate getObject() throws Exception {
return lideeOSSTemplate;
}
@Override
public Class<?> getObjectType() {
return LideeOSSTemplate.class;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
/** 初始化文件存储,顺序如下:
* 1.优先判断minio相关配置是否设置如果有使用minio
* 2.优先判断minio相关配置是否设置如果有使用minio
* 3.默认,以上没有启用时,使用本地存储代替
*/
protected void initFileClient(){
// 如果minio配置项存在时使用minio文件存储
OSSMinioProperties minio = this.ossProperties.getMinio();
if(minio != null && !StringUtils.isEmpty(minio.getUrl())){
this.lideeOSSTemplate = new MinioClient(this.ossProperties);
return;
}
// 如果AmazonS3配置项存在时使用AmazonS3服务器
OSSAmazonS3Properties amazonS3 = this.ossProperties.getAmazonS3();
if(amazonS3 != null && !StringUtils.isEmpty(amazonS3.getUrl())){
this.lideeOSSTemplate = new AmazonS3Client(this.ossProperties);
return;
}
if(Objects.nonNull(ossProperties.getHuaweiObs()) && !StringUtils.isEmpty(
ossProperties.getHuaweiObs().getUrl())){
this.lideeOSSTemplate = new HuaweiOBSClient(ossProperties);
return;
}
// 如果minio和AmazonS3配置项不存在时使用本地存储
this.lideeOSSTemplate = new NFSClient(this.ossProperties);
}
}

View File

@@ -0,0 +1,146 @@
package top.lidee.taie.oss.ossbuilder;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import top.lidee.taie.oss.exceptions.LideeOSSTypeLimitedExceptionBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.stream.Collectors;
/**
* OSS标准调用接口
* @Author: Devli
* @since 2022/3/22 14:16
*/
public interface LideeOSSTemplate {
final static Logger logger = LoggerFactory.getLogger(LideeOSSTemplate.class);
default void init(OSSProperties config){
}
default void close(){
}
/**文件后缀白名单
* @return
*/
String getFileTypeWhiteList();
/**
* 将字符串切割成list支持|或者,分割线
* @param listStr
* @return
*/
default List<String> splitToList(String listStr){
List<String> list = new ArrayList<>();
if(StringUtils.isEmpty(listStr)){
return list;
}
if(listStr.contains("|")){
list = Arrays.asList(listStr.split("\\|"));
}
if(listStr.contains(",")){
list = Arrays.asList(listStr.split(","));
}
List<String> upperList = new ArrayList<>();
upperList.addAll(list.stream().map(String::toUpperCase).collect(Collectors.toList()));
return upperList;
}
/**
* 获取上传文件的文件后缀
* @param file
* @return .png
*/
default String getSuffixName(MultipartFile file){
String originalFilename = file.getOriginalFilename();
if(StringUtils.isEmpty(originalFilename) || !originalFilename.contains(".")){
throw new LideeOSSException("original file name or type is empty");
}
//文件后缀名
String suffixName = originalFilename.substring(originalFilename.lastIndexOf("."));
return suffixName;
}
/**
* 判断文件的后缀名是否在白名单中存在返回true
* @param file
* @return
*/
default boolean isAllowedFileSuffixName(MultipartFile file){
String sufixwhiteListStr = getFileTypeWhiteList();
if(sufixwhiteListStr == null){
return true;
}
sufixwhiteListStr = sufixwhiteListStr.trim();
if(StringUtils.isEmpty(sufixwhiteListStr)){
return true;
}
String suffixName = getSuffixName(file).toUpperCase();
// 文件后缀白名单校验(不区分大小写)
List<String> sufixwhiteList = splitToList(sufixwhiteListStr);
return sufixwhiteList.contains(suffixName);
}
/**
* 判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
* @param file
*/
default void checkFileSuffixName(MultipartFile file){
// 是否是允许的文件后缀
boolean allowedFileSuffix = isAllowedFileSuffixName(file);
if(allowedFileSuffix == false){
logger.warn("file {} type is not in allow list {}", file.getOriginalFilename(), getFileTypeWhiteList());
throw LideeOSSTypeLimitedExceptionBuilder.build("file type is not in allow list");
}
}
/**
* 输入参数为前端文件上传对象MultipartFile
* 返回的是新文件名,下载删除需要
* @param file
* @return 文件名1c6bf10daa0e43ac8da32c96fc30dae7.png
*/
default String uploadFileByInputStream(MultipartFile file) throws LideeOSSException {
checkFileSuffixName(file);//判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
String suffixName = getSuffixName(file);
String fileId = UUID.randomUUID().toString();
fileId = fileId.replaceAll("-","");
String fileObjectName = fileId + suffixName;
return uploadFileByInputStream(file, fileObjectName);
}
/**
* 输入参数为前端文件上传对象MultipartFile
* 返回的是objectName 作为下载文件的依据,客服端需要存储
*
* @param file
* @param fileObjectName 上传原始文件名 OrderExcel20220322.xls
* @return 上传原始文件名 OrderExcel20220322.xls
*/
String uploadFileByInputStream(MultipartFile file, String fileObjectName) throws LideeOSSException;
/**
* 根据fileObjectName下载文件流
*
* @param fileObjectName 402b6193-e70e-40a9-bf5b-73a78ea1e8ab.png
* @return
*/
byte[] downloadFile(String fileObjectName) throws LideeOSSException;
/**
* 根据fileObjectName删除
*
* @param fileObjectName 402b6193-e70e-40a9-bf5b-73a78ea1e8ab.png
* @return
*/
void deleteFile(String fileObjectName);
void deleteFiles(List<String> fileObjectNames);
}

View File

@@ -0,0 +1,167 @@
package top.lidee.taie.oss.ossbuilder.builders;
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 com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import top.lidee.taie.oss.exceptions.LideeOSSExceptionBuilder;
import top.lidee.taie.oss.ossbuilder.LideeOSSTemplate;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件存储 使用AmazonS3服务器
*
* 接口文档 https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-guide/examples-s3.html
* 代码参考 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/java/example_code/s3/src/main/java/aws/example/s3
*
* @Author: Devli
* @since 2022/3/22 14:16
*/
public class AmazonS3Client implements LideeOSSTemplate {
private static Logger logger = LoggerFactory.getLogger(AmazonS3Client.class);
private AmazonS3 amazonS3;
// 存储桶名称
private String bucketName;
// 允许的文件后缀 白名单
private String fileTypeWhiteList;
@Override
public String getFileTypeWhiteList() {
return fileTypeWhiteList;
}
@Override
public void close() {
if(amazonS3!=null){
amazonS3.shutdown();
logger.info("shutdown-oss-client");
}
}
public AmazonS3Client(OSSProperties ossProperties){
this.fileTypeWhiteList = ossProperties.getFileTypeWhiteList();
String url = ossProperties.getAmazonS3().getUrl();
String accessKey = ossProperties.getAmazonS3().getAccessKey();
String secretKey = ossProperties.getAmazonS3().getSecretKey();
this.bucketName = ossProperties.getAmazonS3().getBucketName();
try{
AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
ClientConfiguration clientConfig = new ClientConfiguration();
clientConfig.setProtocol(Protocol.HTTP);
clientConfig.setSignerOverride("S3SignerType");
AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withClientConfiguration(clientConfig)
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(url, "cn-north-1"));
this.amazonS3 = builder.build();
// 如存储桶不存在,创建之。
boolean found = this.amazonS3.doesBucketExistV2(this.bucketName);
if (!found) {
this.amazonS3.createBucket(this.bucketName);
}
logger.info("初始化文件存储激活AmazonS3存储桶:{}", this.bucketName);
}catch (Exception e){
logger.error("初始化文件存储激活AmazonS3存储桶:{}失败:{}" ,this.bucketName, e);
}
}
@Override
public String uploadFileByInputStream(MultipartFile file, String fileObjectName) throws LideeOSSException {
//判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
checkFileSuffixName(file);
InputStream fileInputStream = null;
try {
fileInputStream = file.getInputStream();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("plain/text");
metadata.addUserMetadata("title", "someTitle");
this.amazonS3.putObject(this.bucketName, fileObjectName, fileInputStream, metadata);
} catch (Exception e) {
logger.error("save file to AmazonS3 store error:", e);
throw LideeOSSExceptionBuilder.build("save file to AmazonS3 store error", e);
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (Exception e) {
logger.error("close InputStream error:", e);
}
}
return fileObjectName;
}
@Override
public byte[] downloadFile(String fileObjectName) throws LideeOSSException {
byte[] fileBytes = null;
InputStream inputStream = null;
try {
S3Object object = this.amazonS3.getObject(new GetObjectRequest(this.bucketName, fileObjectName));
inputStream = object.getObjectContent();
if (inputStream == null) {
logger.error("file {} not exist in AmazonS3 store ", fileObjectName);
throw LideeOSSExceptionBuilder.build("file not exist in AmazonS3 store, objectName="+ fileObjectName);
}
fileBytes = IOUtils.toByteArray(inputStream);
} catch (Exception e) {
logger.error("read file from minio store error:", e);
throw LideeOSSExceptionBuilder.build("read file from AmazonS3 store error, objectName="+ fileObjectName);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
}
}
}
return fileBytes;
}
@Override
public void deleteFile(String fileObjectName) {
try{
this.amazonS3.deleteObject(this.bucketName, fileObjectName);
}catch (Exception e){
logger.warn("delete file in AmazonS3 store fail, bucket={}, file={}", this.bucketName, fileObjectName);
}
}
@Override
public void deleteFiles(List<String> fileObjectNames) {
try{
if(CollectionUtils.isEmpty(fileObjectNames)){
return;
}
List<DeleteObjectsRequest.KeyVersion> keys = fileObjectNames.stream().map(fileObject -> new DeleteObjectsRequest.KeyVersion(fileObject)).collect(Collectors.toList());
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(this.bucketName).withKeys(keys);
this.amazonS3.deleteObjects(deleteObjectsRequest);
}catch (Exception e){
logger.warn("delete file in AmazonS3 store fail, bucket={}, file={}", this.bucketName, fileObjectNames.toString());
}
}
}

View File

@@ -0,0 +1,181 @@
package top.lidee.taie.oss.ossbuilder.builders;
import org.apache.commons.io.IOUtils;
import java.io.InputStream;
import java.util.List;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import top.lidee.taie.oss.exceptions.LideeOSSExceptionBuilder;
import top.lidee.taie.oss.ossbuilder.LideeOSSTemplate;
import com.obs.services.ObsClient;
import com.obs.services.model.ObsObject;
/**
* https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0603.html
*
* @author WongBin
* @date 2023/8/24
*/
public class HuaweiOBSClient implements LideeOSSTemplate {
// 存储桶名称
private String bucketName;
// 允许的文件后缀 白名单
private String fileTypeWhiteList;
private ObsClient ossClient;
public HuaweiOBSClient(OSSProperties properties) {
this.init(properties);
}
@Override
public void init(OSSProperties config) {
// Endpoint以北京四为例其他地区请按实际情况填写。
/*String endPoint = "https://obs.cn-north-4.myhuaweicloud.com";
String ak = "*** Provide your Access Key ***";
String sk = "*** Provide your Secret Key ***";*/
// 创建ObsClient实例
ossClient = new ObsClient(config.getHuaweiObs().getAccessKey(),
config.getHuaweiObs().getSecretKey(), config.getHuaweiObs().getUrl());
this.bucketName = config.getHuaweiObs().getBucketName();
this.fileTypeWhiteList = config.getFileTypeWhiteList();
// localfile2 为待上传的本地文件路径,需要指定到具体的文件名
/*PutObjectRequest request = new PutObjectRequest();
request.setBucketName("bucketname");
request.setObjectKey("objectkey2");
request.setFile(new File("localfile2"));
obsClient.putObject(request);*/
}
@Override
public void close() {
if(ossClient!=null){
try {
ossClient.close();
}catch (Exception ex){
logger.error("close-client-err:{}",ex);
}
logger.info("shutdown-oss-client");
}
}
@Override
public String getFileTypeWhiteList() {
return fileTypeWhiteList;
}
/**
* 文件上传输入参数为InputStream
*
* @param file
* @return
*/
@Override
public String uploadFileByInputStream(MultipartFile file, String fileObjectName)
throws LideeOSSException {
//判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
checkFileSuffixName(file);
InputStream fileInputStream = null;
try {
ossClient.putObject(this.bucketName, fileObjectName, file.getInputStream());
} catch (Exception e) {
logger.error("save file error:", e);
throw LideeOSSExceptionBuilder.build("save file error", e);
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (Exception e) {
logger.error("close InputStream error:", e);
}
}
return fileObjectName;
}
/**
* 根据fileUUid下载文件
*
* @param fileObjectName
* @return
*/
@Override
public byte[] downloadFile(String fileObjectName) throws LideeOSSException {
byte[] fileBytes = null;
InputStream inputStream = null;
try {
ObsObject obsObject = ossClient.getObject(this.bucketName, fileObjectName);
if (obsObject == null) {
logger.error("file {} not exist in minio store ", fileObjectName);
throw LideeOSSExceptionBuilder.build("file not exist,objectName="+ fileObjectName);
}
inputStream = obsObject.getObjectContent();
fileBytes = IOUtils.toByteArray(inputStream);
} catch (Exception e) {
logger.error("read file error:", e);
throw LideeOSSExceptionBuilder.build("read file error, objectName="+ fileObjectName);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
}
}
}
return fileBytes;
}
@Override
public void deleteFile(String fileObjectName) {
try{
ossClient.deleteObject(this.bucketName, fileObjectName);
}catch (Exception e){
logger.warn("delete file fail, bucket={}, file={}", this.bucketName, fileObjectName);
}
}
// https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0804.html
@Override
public void deleteFiles(List<String> fileObjectNames) {
if(CollectionUtils.isEmpty(fileObjectNames)){
return;
}
fileObjectNames.stream().forEach(this::deleteFile);
/*try{
//ossClient.deleteObjects(this.bucketName, fileObjectNames);
ListVersionsRequest request = new ListVersionsRequest(bucketName);
// 每次批量删除100个对象
request.setMaxKeys(100);
//request.set
ListVersionsResult result;
do {
result = ossClient.listVersions(request);
DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucketName);
deleteRequest.setQuiet(true); // 注意此demo默认是详细模式如果要使用简单模式请添加本行代码
for(VersionOrDeleteMarker v : result.getVersions()) {
deleteRequest.addKeyAndVersion(v.getKey(), v.getVersionId());
}
DeleteObjectsResult deleteResult = ossClient.deleteObjects(deleteRequest);
// 获取删除成功的对象
logger.info("deleted-obj:{}",deleteResult.getDeletedObjectResults());
// 获取删除失败的对象
logger.info("delete-err-obj:{}",deleteResult.getErrorResults());
request.setKeyMarker(result.getNextKeyMarker());
// 如果没有开启多版本对象就不需要设置VersionIdMarker
request.setVersionIdMarker(result.getNextVersionIdMarker());
}while(result.isTruncated());
}catch (Exception e){
logger.warn("delete file fail, bucket={}, file={}", this.bucketName, fileObjectNames.toString());
}*/
}
}

View File

@@ -0,0 +1,150 @@
package top.lidee.taie.oss.ossbuilder.builders;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import top.lidee.taie.oss.exceptions.LideeOSSExceptionBuilder;
import top.lidee.taie.oss.ossbuilder.LideeOSSTemplate;
import io.minio.PutObjectOptions;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
/**
* 文件存储 使用minio服务器
*
* @Author: Devli
* @since 2022/3/22 14:16
*/
public class MinioClient implements LideeOSSTemplate {
private static Logger logger = LoggerFactory.getLogger(MinioClient.class);
private io.minio.MinioClient minioClient;
// 存储桶名称
private String bucketName;
// 允许的文件后缀 白名单
private String fileTypeWhiteList;
@Override
public String getFileTypeWhiteList() {
return fileTypeWhiteList;
}
@Override
public void close() {
if(minioClient!=null){
logger.info("shutdown-oss-client");
}
}
public MinioClient(OSSProperties ossProperties){
this.fileTypeWhiteList = ossProperties.getFileTypeWhiteList();
String url = ossProperties.getMinio().getUrl();
int port = ossProperties.getMinio().getPort();
String accessKey = ossProperties.getMinio().getAccessKey();
String secretKey = ossProperties.getMinio().getSecretKey();
this.bucketName = ossProperties.getMinio().getBucketName();
try{
this.minioClient = new io.minio.MinioClient(url, port, accessKey, secretKey);
// 如存储桶不存在,创建之。
boolean found = minioClient.bucketExists(this.bucketName);
if (!found) {
minioClient.makeBucket(this.bucketName);
}
logger.info("初始化文件存储激活Minio分布式存储桶:{}", this.bucketName);
}catch (Exception e){
logger.error("初始化文件存储激活Minio存储桶:{}失败:{}" ,this.bucketName, e);
}
}
/**
* 文件上传输入参数为InputStream
*
* @param file
* @return
*/
@Override
public String uploadFileByInputStream(MultipartFile file, String fileObjectName) throws LideeOSSException {
//判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
checkFileSuffixName(file);
InputStream fileInputStream = null;
try {
fileInputStream = file.getInputStream();
PutObjectOptions options = new PutObjectOptions(fileInputStream.available(), -1);
options.setContentType("application/octet-stream");
minioClient.putObject(this.bucketName, fileObjectName, fileInputStream, options);
} catch (Exception e) {
logger.error("save file to minio store error:", e);
throw LideeOSSExceptionBuilder.build("save file to minio store error", e);
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (Exception e) {
logger.error("close InputStream error:", e);
}
}
return fileObjectName;
}
/**
* 根据fileUUid下载文件
*
* @param fileObjectName
* @return
*/
@Override
public byte[] downloadFile(String fileObjectName) throws LideeOSSException {
byte[] fileBytes = null;
InputStream inputStream = null;
try {
inputStream = minioClient.getObject(this.bucketName, fileObjectName);
if (inputStream == null) {
logger.error("file {} not exist in minio store ", fileObjectName);
throw LideeOSSExceptionBuilder.build("file not exist in minio store, objectName="+ fileObjectName);
}
fileBytes = IOUtils.toByteArray(inputStream);
} catch (Exception e) {
logger.error("read file from minio store error:", e);
throw LideeOSSExceptionBuilder.build("read file from minio store error, objectName="+ fileObjectName);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
}
}
}
return fileBytes;
}
@Override
public void deleteFile(String fileObjectName) {
try{
minioClient.removeObject(this.bucketName, fileObjectName);
}catch (Exception e){
logger.warn("delete file in minio store fail, bucket={}, file={}", this.bucketName, fileObjectName);
}
}
@Override
public void deleteFiles(List<String> fileObjectNames) {
try{
if(CollectionUtils.isEmpty(fileObjectNames)){
return;
}
minioClient.removeObjects(this.bucketName, fileObjectNames);
}catch (Exception e){
logger.warn("delete file in minio store fail, bucket={}, file={}", this.bucketName, fileObjectNames.toString());
}
}
}

View File

@@ -0,0 +1,111 @@
package top.lidee.taie.oss.ossbuilder.builders;
import top.lidee.taie.oss.config.OSSProperties;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import top.lidee.taie.oss.exceptions.LideeOSSExceptionBuilder;
import top.lidee.taie.oss.ossbuilder.LideeOSSTemplate;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 文件存储 使用服务器本地文件夹
* @Author: Devli
* @since 2022/3/22 14:16
*/
public class NFSClient implements LideeOSSTemplate {
private static Logger logger = LoggerFactory.getLogger(NFSClient.class);
private String nfsLocalStore = "/app/disk/upload";
// 允许的文件后缀 白名单
private String fileTypeWhiteList;
@Override
public String getFileTypeWhiteList() {
return fileTypeWhiteList;
}
public NFSClient(OSSProperties ossProperties){
this.fileTypeWhiteList = ossProperties.getFileTypeWhiteList();
if (ossProperties.getNfs() != null && !StringUtils.isEmpty(ossProperties.getNfs().getPath())) {
this.nfsLocalStore = ossProperties.getNfs().getPath();
}
if (!StringUtils.endsWithIgnoreCase(this.nfsLocalStore, java.io.File.separator)) {
this.nfsLocalStore = this.nfsLocalStore + java.io.File.separator;
}
java.io.File localDir = new java.io.File(this.nfsLocalStore);
if (!localDir.exists()) {
localDir.mkdirs();
}
logger.info("初始化文件存储,激活服务器本地文件存储,路径{}", this.nfsLocalStore);
}
@Override
public String uploadFileByInputStream(MultipartFile file, String fileObjectName) throws LideeOSSException {
//判断文件后缀名是否在白名单中,如果不在报异常,中止文件保存
checkFileSuffixName(file);
java.io.File objectFile = null;
try {
// 本地文件保存路径
String filePath = nfsLocalStore + fileObjectName;
objectFile = new java.io.File(filePath);
file.transferTo(objectFile);
} catch (Exception e) {
logger.error("save file to local store error:", e);
throw LideeOSSExceptionBuilder.build("save file to local store error", e);
} finally {
objectFile = null;
}
return fileObjectName;
}
@Override
public byte[] downloadFile(String fileObjectName) throws LideeOSSException {
byte[] fileBytes = null;
java.io.File objectFile = null;
try {
// 本地文件保存路径
String filePath = nfsLocalStore + fileObjectName;
objectFile = new java.io.File(filePath);
fileBytes = FileUtils.readFileToByteArray(objectFile);
} catch (Exception e) {
logger.error("read file from local store error:", e);
throw LideeOSSExceptionBuilder.build("read file from local store error, objectName="+ fileObjectName);
} finally {
objectFile = null;
}
return fileBytes;
}
@Override
public void deleteFile(String fileObjectName) {
try{
// 本地文件保存路径
String filePath = nfsLocalStore + fileObjectName;
java.io.File file = new java.io.File(filePath);
if (file.exists()) {
file.delete();
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void deleteFiles(List<String> fileObjectNames) {
if(CollectionUtils.isEmpty(fileObjectNames)){
return;
}
fileObjectNames.stream().forEach(fileObjectName -> {
this.deleteFile(fileObjectName);
});
}
}

View File

@@ -0,0 +1,55 @@
package top.lidee.taie.oss.utils;
import top.lidee.taie.oss.exceptions.LideeOSSException;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import java.net.URLEncoder;
public class ResponseUtil {
/**
* 根据文件后缀来判断,是显示图片\视频\音频,还是下载文件
* @param fileObjectName 文件原始名称,如:订单导入.xls banner.png
* @param fileBytes 文件字节流
* @param isIEBrowser 是否是IE浏览器
* @return
*/
public static ResponseEntity<byte[]> writeBody(String fileObjectName, byte[] fileBytes, boolean isIEBrowser){
try{
if(StringUtils.isEmpty(fileObjectName) || !fileObjectName.contains(".")){
throw new LideeOSSException("original file name or type is empty");
}
// 文件后缀名
String fileSuffixName = fileObjectName.substring(fileObjectName.lastIndexOf("."));
// 初始化响应体
ResponseEntity.BodyBuilder builder = ResponseEntity.ok();
builder.contentLength(fileBytes.length);
// 判断文件是图片视频还是文件
String pattern1 = "(.png|.jpg|.jpeg|.bmp|.gif|.icon)";
String pattern2 = "(.flv|.swf|.mkv|.avi|.rm|.rmvb|.mpeg|.mpg|.ogg|.ogv|.mov|.wmv|.mp4|.webm|.wav|.mid|.mp3|.aac)";
if (StringPatternUtil.StringMatchIgnoreCase(fileSuffixName, pattern1)) {
builder.cacheControl(CacheControl.noCache()).contentType(MediaType.IMAGE_PNG);
} else if (StringPatternUtil.StringMatchIgnoreCase(fileSuffixName, pattern2)) {
builder.header("Content-Type", "video/mp4; charset=UTF-8");
} else {
//application/octet-stream 二进制数据流(最常见的文件下载)
builder.contentType(MediaType.APPLICATION_OCTET_STREAM);
fileObjectName = URLEncoder.encode(fileObjectName, "UTF-8");
if (isIEBrowser) {
builder.header("Content-Disposition", "attachment; filename=" + fileObjectName);
} else {
builder.header("Content-Disposition", "attacher; filename*=UTF-8''" + fileObjectName);
}
}
return builder.body(fileBytes);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}

View File

@@ -0,0 +1,95 @@
package top.lidee.taie.oss.utils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
public class StringPatternUtil {
private static final String STRING_STYLE_RED = "<span class='infoDanger'>%s</span>";
private static final String UNDERLINE = "_";
public StringPatternUtil() {
}
public static boolean StringMatch(String sourceStr, String pattern) {
boolean result = false;
try {
if (StringUtils.isEmpty(sourceStr) || StringUtils.isEmpty(pattern)) {
return result;
}
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(sourceStr);
if (m.find()) {
result = true;
}
} catch (Exception var5) {
result = false;
}
return result;
}
public static boolean StringMatchIgnoreCase(String sourceStr, String pattern) {
boolean result = false;
try {
if (StringUtils.isEmpty(sourceStr) || StringUtils.isEmpty(pattern)) {
return result;
}
sourceStr = sourceStr.toLowerCase();
pattern = pattern.toLowerCase();
result = StringMatch(sourceStr, pattern);
} catch (Exception var4) {
result = false;
}
return result;
}
public static String StringFind(String sourceStr, String pattern) {
String result = "";
try {
if (StringUtils.isEmpty(sourceStr) || StringUtils.isEmpty(pattern)) {
return result;
}
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(sourceStr);
if (m.find()) {
result = m.group(0);
}
} catch (Exception var5) {
result = "";
}
return result;
}
public static String replace(String sourceStr, String pattern, String replaceStr) {
String result = "";
try {
if (StringUtils.isEmpty(sourceStr) || StringUtils.isEmpty(pattern)) {
return result;
}
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(sourceStr);
result = m.replaceAll(replaceStr);
} catch (Exception var6) {
result = "";
}
return result;
}
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=top.lidee.taie.oss.config.AutoConfiguration