mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 20:28:44 +08:00 
			
		
		
		
	完善 yudao-spring-boot-starter-file 组件,支持 S3 对接云存储、local、ftp、sftp 等协议
This commit is contained in:
		@@ -1,5 +1,6 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.FileClient;
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@@ -55,4 +56,16 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 格式化文件的 URL 访问地址
 | 
			
		||||
     * 使用场景:local、ftp、db,通过 FileController 的 getFile 来获取文件内容
 | 
			
		||||
     *
 | 
			
		||||
     * @param domain 自定义域名
 | 
			
		||||
     * @param path 文件路径
 | 
			
		||||
     * @return URL 访问地址
 | 
			
		||||
     */
 | 
			
		||||
    protected String formatFileUrl(String domain, String path) {
 | 
			
		||||
        return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.ftp;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.io.FileUtil;
 | 
			
		||||
import cn.hutool.core.util.CharsetUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.hutool.extra.ftp.Ftp;
 | 
			
		||||
import cn.hutool.extra.ftp.FtpException;
 | 
			
		||||
import cn.hutool.extra.ftp.FtpMode;
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ftp 文件客户端
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
 | 
			
		||||
 | 
			
		||||
    private Ftp ftp;
 | 
			
		||||
 | 
			
		||||
    public FtpFileClient(Long id, FtpFileClientConfig config) {
 | 
			
		||||
        super(id, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doInit() {
 | 
			
		||||
        // 补全风格。例如说 Linux 是 /,Windows 是 \
 | 
			
		||||
        if (!config.getBasePath().endsWith(File.separator)) {
 | 
			
		||||
            config.setBasePath(config.getBasePath() + File.separator);
 | 
			
		||||
        }
 | 
			
		||||
        // 初始化 Ftp 对象
 | 
			
		||||
        this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
 | 
			
		||||
                CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String upload(byte[] content, String path) {
 | 
			
		||||
        // 执行写入
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        String fileName = FileUtil.getName(filePath);
 | 
			
		||||
        String dir = StrUtil.removeSuffix(filePath, fileName);
 | 
			
		||||
        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
 | 
			
		||||
        if (!success) {
 | 
			
		||||
            throw new FtpException(StrUtil.format("上海文件到目标目录 ({}) 失败", filePath));
 | 
			
		||||
        }
 | 
			
		||||
        // 拼接返回路径
 | 
			
		||||
        return super.formatFileUrl(config.getDomain(), path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        ftp.delFile(filePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getContent(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        String fileName = FileUtil.getName(filePath);
 | 
			
		||||
        String dir = StrUtil.removeSuffix(path, fileName);
 | 
			
		||||
        ByteArrayOutputStream out = new ByteArrayOutputStream();
 | 
			
		||||
        ftp.download(dir, fileName, out);
 | 
			
		||||
        return out.toByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getFilePath(String path) {
 | 
			
		||||
        return config.getBasePath() + path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.ftp;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.hibernate.validator.constraints.URL;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotEmpty;
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ftp 文件客户端的配置类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class FtpFileClientConfig implements FileClientConfig {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基础路径
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "基础路径不能为空")
 | 
			
		||||
    private String basePath;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义域名
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "domain 不能为空")
 | 
			
		||||
    @URL(message = "domain 必须是 URL 格式")
 | 
			
		||||
    private String domain;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 主机地址
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "host 不能为空")
 | 
			
		||||
    private String host;
 | 
			
		||||
    /**
 | 
			
		||||
     * 主机端口
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "port 不能为空")
 | 
			
		||||
    private Integer port;
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "用户名不能为空")
 | 
			
		||||
    private String username;
 | 
			
		||||
    /**
 | 
			
		||||
     * 密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "密码不能为空")
 | 
			
		||||
    private String password;
 | 
			
		||||
    /**
 | 
			
		||||
     * 连接模式
 | 
			
		||||
     *
 | 
			
		||||
     * 使用 {@link  cn.hutool.extra.ftp.FtpMode} 对应的字符串
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "连接模式不能为空")
 | 
			
		||||
    private String mode;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.local;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.io.FileUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 本地文件客户端
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {
 | 
			
		||||
 | 
			
		||||
    public LocalFileClient(Long id, LocalFileClientConfig config) {
 | 
			
		||||
        super(id, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doInit() {
 | 
			
		||||
        // 补全风格。例如说 Linux 是 /,Windows 是 \
 | 
			
		||||
        if (!config.getBasePath().endsWith(File.separator)) {
 | 
			
		||||
            config.setBasePath(config.getBasePath() + File.separator);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String upload(byte[] content, String path) {
 | 
			
		||||
        // 执行写入
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        FileUtil.writeBytes(content, filePath);
 | 
			
		||||
        // 拼接返回路径
 | 
			
		||||
        return super.formatFileUrl(config.getDomain(), path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        FileUtil.del(filePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getContent(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        return FileUtil.readBytes(filePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getFilePath(String path) {
 | 
			
		||||
        return config.getBasePath() + path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.local;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.hibernate.validator.constraints.URL;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotEmpty;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 本地文件客户端的配置类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class LocalFileClientConfig implements FileClientConfig {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基础路径
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "基础路径不能为空")
 | 
			
		||||
    private String basePath;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义域名
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "domain 不能为空")
 | 
			
		||||
    @URL(message = "domain 必须是 URL 格式")
 | 
			
		||||
    private String domain;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,8 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 | 
			
		||||
import software.amazon.awssdk.core.sync.RequestBody;
 | 
			
		||||
import software.amazon.awssdk.regions.Region;
 | 
			
		||||
import software.amazon.awssdk.services.s3.S3Client;
 | 
			
		||||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
 | 
			
		||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
 | 
			
		||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
 | 
			
		||||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
@@ -14,7 +16,7 @@ import java.net.URI;
 | 
			
		||||
import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
 | 
			
		||||
 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
 | 
			
		||||
 *
 | 
			
		||||
 * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
 | 
			
		||||
 *
 | 
			
		||||
@@ -84,12 +86,18 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete(String path) {
 | 
			
		||||
 | 
			
		||||
        DeleteObjectRequest.Builder request = DeleteObjectRequest.builder()
 | 
			
		||||
                .bucket(config.getBucket()) // bucket 必须传递
 | 
			
		||||
                .key(path); // 相对路径作为 key
 | 
			
		||||
        client.deleteObject(request.build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getContent(String path) {
 | 
			
		||||
        return new byte[0];
 | 
			
		||||
        GetObjectRequest.Builder request = GetObjectRequest.builder()
 | 
			
		||||
                .bucket(config.getBucket()) // bucket 必须传递
 | 
			
		||||
                .key(path); // 相对路径作为 key
 | 
			
		||||
        return client.getObjectAsBytes(request.build()).asByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.sftp;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.io.FileUtil;
 | 
			
		||||
import cn.hutool.extra.ssh.Sftp;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sftp 文件客户端
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> {
 | 
			
		||||
 | 
			
		||||
    private Sftp sftp;
 | 
			
		||||
 | 
			
		||||
    public SftpFileClient(Long id, SftpFileClientConfig config) {
 | 
			
		||||
        super(id, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doInit() {
 | 
			
		||||
        // 补全风格。例如说 Linux 是 /,Windows 是 \
 | 
			
		||||
        if (!config.getBasePath().endsWith(File.separator)) {
 | 
			
		||||
            config.setBasePath(config.getBasePath() + File.separator);
 | 
			
		||||
        }
 | 
			
		||||
        // 初始化 Ftp 对象
 | 
			
		||||
        this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String upload(byte[] content, String path) {
 | 
			
		||||
        // 执行写入
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        File file = FileUtils.createTempFile(content);
 | 
			
		||||
        sftp.upload(filePath, file);
 | 
			
		||||
        // 拼接返回路径
 | 
			
		||||
        return super.formatFileUrl(config.getDomain(), path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        sftp.delFile(filePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getContent(String path) {
 | 
			
		||||
        String filePath = getFilePath(path);
 | 
			
		||||
        File destFile = FileUtils.createTempFile();
 | 
			
		||||
        sftp.download(filePath, destFile);
 | 
			
		||||
        return FileUtil.readBytes(destFile);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getFilePath(String path) {
 | 
			
		||||
        return config.getBasePath() + path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.file.core.client.impl.sftp;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.hibernate.validator.constraints.URL;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotEmpty;
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sftp 文件客户端的配置类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SftpFileClientConfig implements FileClientConfig {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基础路径
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "基础路径不能为空")
 | 
			
		||||
    private String basePath;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义域名
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "domain 不能为空")
 | 
			
		||||
    @URL(message = "domain 必须是 URL 格式")
 | 
			
		||||
    private String domain;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 主机地址
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "host 不能为空")
 | 
			
		||||
    private String host;
 | 
			
		||||
    /**
 | 
			
		||||
     * 主机端口
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "port 不能为空")
 | 
			
		||||
    private Integer port;
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "用户名不能为空")
 | 
			
		||||
    private String username;
 | 
			
		||||
    /**
 | 
			
		||||
     * 密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotEmpty(message = "密码不能为空")
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user