Michael Yang
2025-04-17 ecf64a17b646270f29fbb9cb6b5f7210262df1f3
refactor: optimize file storage and impls
3个文件已修改
3 文件已重命名
1个文件已添加
371 ■■■■ 已修改文件
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/StorageConfig.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/impl/S3FileStorageServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/impl/XFileStorageServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/AccessPolicyType.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/PolicyType.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/S3Client.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/S3StorageConfig.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/StorageConfig.java
@@ -8,92 +8,8 @@
@ConfigurationProperties(prefix = "aiflowy.storage")
public class StorageConfig {
    //支持 local、minio...
    //支持 local、s3、xfile...
    private String type;
    /**
     * 域名
     */
    private String endpoint;
    /**
     * 自定义域名
     */
    private String domain;
    /**
     * 前缀
     */
    private String prefix;
    /**
     * ACCESS_KEY
     */
    private String accessKey;
    /**
     * SECRET_KEY
     */
    private String secretKey;
    /**
     * 存储空间名
     */
    private String bucketName;
    /**
     * 存储区域
     */
    private String region;
    /**
     * 是否https(1=是)
     */
    private int isHttps = 1;
    /**
     * 桶权限类型(0private 1public 2custom)
     */
    private int accessPolicy;
    public String getDomain() {
        return domain;
    }
    public void setDomain(String domain) {
        this.domain = domain;
    }
    public String getPrefix() {
        return prefix;
    }
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
    public String getRegion() {
        return region;
    }
    public void setRegion(String region) {
        this.region = region;
    }
    public int getIsHttps() {
        return isHttps;
    }
    public void setIsHttps(int isHttps) {
        this.isHttps = isHttps;
    }
    public int getAccessPolicy() {
        return accessPolicy;
    }
    public void setAccessPolicy(int accessPolicy) {
        this.accessPolicy = accessPolicy;
    }
    public String getType() {
        return type;
@@ -103,39 +19,7 @@
        this.type = type;
    }
    public String getEndpoint() {
        return endpoint;
    }
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }
    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;
    }
    public static StorageConfig getInstance(){
    public static StorageConfig getInstance() {
        return SpringContextUtil.getBean(StorageConfig.class);
    }
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/impl/S3FileStorageServiceImpl.java
@@ -2,24 +2,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import tech.aiflowy.common.filestorage.FileStorageService;
import tech.aiflowy.common.filestorage.OssClient;
import tech.aiflowy.common.filestorage.StorageConfig;
import tech.aiflowy.common.util.DateUtil;
import tech.aiflowy.common.filestorage.s3.S3Client;
import tech.aiflowy.common.filestorage.s3.S3StorageConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Date;
import java.util.UUID;
@Component("s3")
@@ -27,13 +20,13 @@
    private static final Logger LOG = LoggerFactory.getLogger(S3FileStorageServiceImpl.class);
    private OssClient client;
    private S3Client client;
    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        StorageConfig instance = StorageConfig.getInstance();
        if (!"local".equals(instance.getType())){
            client = new OssClient(instance);
        if ("s3".equals(instance.getType())) {
            client = new S3Client();
        }
    }
@@ -41,8 +34,7 @@
    @Override
    public String save(MultipartFile file) {
        try {
            String path = client.upload(file);
            return path;
            return client.upload(file);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/impl/XFileStorageServiceImpl.java
@@ -12,11 +12,12 @@
import org.springframework.web.multipart.MultipartFile;
import tech.aiflowy.common.filestorage.FileStorageService;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@Component("xFileStorageServiceImpl")
@Component("xfile")
@ConditionalOnBean(org.dromara.x.file.storage.core.FileStorageService.class)
public class XFileStorageServiceImpl implements FileStorageService {
@@ -26,7 +27,7 @@
    @Value("${aiflowy.storage.x-file-storage.platform}")
    private String platform;
    @Autowired
    @Resource
    private org.dromara.x.file.storage.core.FileStorageService xFileStorageService;
    @EventListener(ApplicationReadyEvent.class)
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/AccessPolicyType.java
File was renamed from aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/AccessPolicyType.java
@@ -1,4 +1,4 @@
package tech.aiflowy.common.filestorage;
package tech.aiflowy.common.filestorage.s3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/PolicyType.java
File was renamed from aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/PolicyType.java
@@ -1,4 +1,4 @@
package tech.aiflowy.common.filestorage;
package tech.aiflowy.common.filestorage.s3;
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/S3Client.java
File was renamed from aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/OssClient.java
@@ -1,4 +1,4 @@
package tech.aiflowy.common.filestorage;
package tech.aiflowy.common.filestorage.s3;
import cn.hutool.core.date.DateField;
@@ -7,12 +7,10 @@
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
@@ -25,6 +23,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import tech.aiflowy.common.filestorage.StorageConfig;
import java.io.*;
import java.net.URL;
@@ -33,22 +32,20 @@
/**
 * S3 存储协议 所有兼容S3协议的云厂商均支持 阿里云 腾讯云 七牛云 minio
 *
 *
 */
public class OssClient {
    private static final Logger log = LoggerFactory.getLogger(OssClient.class);
public class S3Client {
    private static final Logger log = LoggerFactory.getLogger(S3Client.class);
    /**
     * https 状态
     */
    private final String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
    private final String[] CLOUD_SERVICE = new String[]{"aliyun", "qcloud", "qiniu", "obs"};
    private static final int IS_HTTPS = 1;
    private final StorageConfig properties;
    private final S3StorageConfig properties;
    private final AmazonS3 client;
    public OssClient(StorageConfig ossProperties) {
        this.properties = ossProperties;
    public S3Client() {
        this.properties = S3StorageConfig.getInstance();
        try {
            AwsClientBuilder.EndpointConfiguration endpointConfig =
                    new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
@@ -79,6 +76,7 @@
        return !StrUtil.containsAny(endpoint,
                CLOUD_SERVICE);
    }
    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
@@ -98,6 +96,7 @@
    public void upload(byte[] data, String objectPath, String contentType) {
        upload(new ByteArrayInputStream(data), objectPath, contentType);
    }
    public void upload(InputStream inputStream, String objectPath, String contentType) {
        if (!(inputStream instanceof ByteArrayInputStream)) {
            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
@@ -152,11 +151,11 @@
        }
        return hexString.toString(); // 返回字符串类型的哈希值
    }
    /**
     * 根据OSS对象存储路径, 从OSS存储删除文件
     *
     * @param objectPath
     *            文件访问路径
     *
     * @param objectPath 文件访问路径
     */
    public void delete(String objectPath) {
        try {
@@ -169,9 +168,8 @@
    /**
     * 根据OSS对象存储路径, 获取文件元数据
     *
     * @param objectPath
     *            文件访问路径(对应OSS存储数据的key)
     *
     * @param objectPath 文件访问路径(对应OSS存储数据的key)
     */
    public ObjectMetadata getObjectMetadata(String objectPath) {
        return client.getObjectMetadata(properties.getBucketName(), objectPath);
@@ -179,9 +177,8 @@
    /**
     * 根据OSS对象存储路径, 获取文件内容
     *
     * @param objectPath
     *            文件访问路径
     *
     * @param objectPath 文件访问路径
     * @return 文件内容
     */
    public InputStream getObjectContent(String objectPath) {
@@ -190,13 +187,10 @@
    /**
     * 根据OSS对象存储路径, 获取文件内容(指定文件字节范围)
     *
     * @param objectPath
     *            文件访问路径
     * @param start
     *            起始字节
     * @param end
     *            结束字节
     *
     * @param objectPath 文件访问路径
     * @param start      起始字节
     * @param end        结束字节
     * @return 文件内容(指定文件字节范围)
     */
    public InputStream getObjectContent(String objectPath, Long start, Long end) {
@@ -219,7 +213,7 @@
    /**
     * 获取OSS基础路径
     *
     *
     * @return 基础路径
     */
    public String getBasePath() {
@@ -235,7 +229,7 @@
    /**
     * 根据访问url, 获取OSS对象存储路径
     *
     *
     * @return OSS对象存储路径
     */
    public String getObjectPath(String url) {
@@ -247,9 +241,8 @@
    /**
     * 根据OSS对象存储路径, 获取访问url
     *
     * @param objectPath
     *            文件访问路径
     *
     * @param objectPath 文件访问路径
     * @return 访问url
     */
    public String getUrl(String objectPath) {
@@ -269,11 +262,9 @@
    /**
     * 根据前缀和后缀, 生成OSS对象存储路径
     *
     * @param prefix
     *            前缀
     * @param suffix
     *            后缀
     *
     * @param prefix 前缀
     * @param suffix 后缀
     * @return OSS对象存储路径
     */
    public String getObjectPath(String prefix, String suffix) {
@@ -290,15 +281,13 @@
    /**
     * 获取私有URL链接
     *
     * @param objectKey
     *            对象KEY
     * @param second
     *            授权时间
     * @param objectKey 对象KEY
     * @param second    授权时间
     */
    public String getPrivateUrl(String objectKey, Integer second) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
                new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                        .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }
@@ -334,7 +323,7 @@
        builder.append("\"\n},\n");
        if (policyType == PolicyType.READ) {
            builder.append(
                "{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
                    "{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n},\n");
        }
@@ -342,11 +331,11 @@
        switch (policyType) {
            case WRITE:
                builder.append(
                    "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                        "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                break;
            case READ_WRITE:
                builder.append(
                    "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                        "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                break;
            default:
                builder.append("\"s3:GetObject\",\n");
@@ -360,8 +349,7 @@
    /**
     * @param path
     *            相对路径
     * @param path 相对路径
     * @return 文件内容
     * @throws Exception
     * @description 获取文件内容
@@ -383,13 +371,12 @@
    /**
     * 获取私有URL链接
     *
     * @param objectKey
     *            对象KEY
     * @param objectKey 对象KEY
     */
    public String getPreSignedAccessUrl(String objectKey, DateField dateField, Integer time) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                .withExpiration(DateUtil.offset(DateUtil.date(), dateField, time));
                new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                        .withExpiration(DateUtil.offset(DateUtil.date(), dateField, time));
        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }
aiflowy-commons/aiflowy-common-file-storage/src/main/java/tech/aiflowy/common/filestorage/s3/S3StorageConfig.java
New file
@@ -0,0 +1,131 @@
package tech.aiflowy.common.filestorage.s3;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import tech.aiflowy.common.util.SpringContextUtil;
@Configuration
@ConfigurationProperties(prefix = "aiflowy.storage.s3")
public class S3StorageConfig {
    /**
     * 域名
     */
    private String endpoint;
    /**
     * 自定义域名
     */
    private String domain;
    /**
     * 前缀
     */
    private String prefix;
    /**
     * ACCESS_KEY
     */
    private String accessKey;
    /**
     * SECRET_KEY
     */
    private String secretKey;
    /**
     * 存储空间名
     */
    private String bucketName;
    /**
     * 存储区域
     */
    private String region;
    /**
     * 是否https(1=是)
     */
    private int isHttps = 1;
    /**
     * 桶权限类型(0private 1public 2custom)
     */
    private int accessPolicy;
    public String getDomain() {
        return domain;
    }
    public void setDomain(String domain) {
        this.domain = domain;
    }
    public String getPrefix() {
        return prefix;
    }
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
    public String getRegion() {
        return region;
    }
    public void setRegion(String region) {
        this.region = region;
    }
    public int getIsHttps() {
        return isHttps;
    }
    public void setIsHttps(int isHttps) {
        this.isHttps = isHttps;
    }
    public int getAccessPolicy() {
        return accessPolicy;
    }
    public void setAccessPolicy(int accessPolicy) {
        this.accessPolicy = accessPolicy;
    }
    public String getEndpoint() {
        return endpoint;
    }
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }
    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;
    }
    public static S3StorageConfig getInstance(){
        return SpringContextUtil.getBean(S3StorageConfig.class);
    }
}