springboot配置多种存储方式

2024-08-24 15:37 孙水迪 316

一、目标

实现阿里云oss,ftp和本地文件存储的功能,通过配置方式选择启用不同的功能

二、接口

在接口里添加上传,删除,下载以及远程访问方法

public interface FileClient {
    /**
     * 上传文件
     *
     * @param inputStream 文件流
     * @param path        相对路径
     * @return 完整路径,即 HTTP 访问地址
     */
    String upload(InputStream inputStream, String path);

    /**
     * 删除文件
     *
     * @param path 相对路径
     */
    void delete(String path);

    /**
     * 获得文件的内容
     *
     * @param path 相对路径
     * @return 文件的内容
     */
    byte[] getContent(String path);

    /**
     * 获得文件的签名地址
     *
     * @param path 相对路径
     * @return 文件的内容
     */
    String getSignedUrl(String path);

    ClinetTypeEnum getClientType();

}
public enum ClinetTypeEnum {

    //ftp
    FTP("ftp"),
    //oss
    OSS("oss"),
    //local
    LOCAL("local");

    private final String type;

    ClinetTypeEnum(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}
public interface FileClientConfig {

}

三、ftp实现

在file.ftp.enable的情况下生成FtpFileConfig,在生成FtpFileConfig的情况下生成FtpFileClient

通过配置是否启用ftp,可选择性的生成FtpFileClient

ftp库由hutool依赖提供

getSignedUrl方法可通过实际情况自定义

@Component
@ConditionalOnBean(value = FtpFileConfig.class)
@Log4j2
public class FtpFileClient implements FileClient {

    private final FtpFileConfig ftpFileConfig;
    private final Ftp ftp;

    @Autowired
    public FtpFileClient(FtpFileConfig ftpFileConfig) {
        this.ftpFileConfig = ftpFileConfig;
        ftp = new Ftp(ftpFileConfig.getIp(), ftpFileConfig.getPort(), ftpFileConfig.getUsername(), ftpFileConfig.getPassword());
        ftp.setMode(FtpMode.Passive);
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        ftp.reconnectIfTimeout();
        boolean success = ftp.upload(dir, fileName, inputStream);
        if (!success) {
            throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
        }
        return null;
    }

    @Override
    public void delete(String path) {
        String filePath = getFilePath(path);
        ftp.reconnectIfTimeout();
        ftp.delFile(filePath);
    }

    @Override
    public byte[] getContent(String path) {
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ftp.reconnectIfTimeout();
        ftp.download(dir, fileName, out);
        return out.toByteArray();
    }

    @Override
    public String getSignedUrl(String path) {
        return null;
    }

    private String getFilePath(String path) {
        return ftpFileConfig.getBasePath() + path;
    }

    @Override
    public ClinetTypeEnum getClientType() {
        return ClinetTypeEnum.FTP;
    }
}
@Data
@Component
@ConditionalOnProperty(value = "file.ftp.enable", havingValue = "true")
public class FtpFileConfig implements FileClientConfig {
    @Value("{{file.ftp.ip}")
    private String ip;
    @Value("{{file.ftp.port}")
    private int port;
    @Value("{{file.ftp.username}")
    private String username;
    @Value("{{file.ftp.password}")
    private String password;
    @Value("{{file.ftp.basePath}")
    private String basePath;
}

四、阿里云oss实现

在file.oss.enable的情况下生成OssFileConfig,在生成FtpFileConfig的情况下生成OssFileClient

通过配置是否启用oss,可选择性的生成OssFileClient

oss库由阿里云sdk提供

@Component
@ConditionalOnBean(value = OssFileConfig.class)
@Log4j2
@Data
public class OssFileClient implements FileClient {


    private final OssFileConfig ossFileConfig;

    private final OSSClient inClient;

    private final OSSClient outClient;

    @Autowired
    public OssFileClient(OssFileConfig ossFileConfig) {
        this.ossFileConfig = ossFileConfig;
        this.inClient = new OSSClient(ossFileConfig.getInEndpoint(), ossFileConfig.getAccessKeyId(), ossFileConfig.getAccessKeySecret());
        this.outClient = new OSSClient(ossFileConfig.getEndpoint(), ossFileConfig.getAccessKeyId(), ossFileConfig.getAccessKeySecret());
    }


    @Override
    public String upload(InputStream inputStream, String path) {
        OSSClient client = getInClient();
        String bucket = ossFileConfig.getBucket();
        client.putObject(bucket, path, inputStream);
        //使用内网地址上传,返回地址改为外网地址
        String url = ossFileConfig.getHttpBase() + path;
        log.info("[{}] OSS上传成功,返回签名URL: {}", Thread.currentThread().getName(), url);
        return url;
    }

    @Override
    public void delete(String path) {
        OSSClient client = getInClient();
        client.deleteObject(ossFileConfig.getBucket(), path);
    }

    @Override
    public byte[] getContent(String path) {
        OSSClient ossClient = getInClient();
        OSSObject ossObject = ossClient.getObject(ossFileConfig.getBucket(), path);
        return IoUtil.readBytes(ossObject.getObjectContent());
    }

    @Override
    public String getSignedUrl(String path) {
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000L);
        String bucket = ossFileConfig.getBucket();
        if (path.startsWith(ossFileConfig.getHttpBase())) {
            path = path.replace(ossFileConfig.getHttpBase(), "");
        }
        URL url = getOutClient().generatePresignedUrl(bucket, path, expiration);
        return url.toString();
    }

    @Override
    public ClinetTypeEnum getClientType() {
        return ClinetTypeEnum.OSS;
    }
}
@Data
@Component
@ConditionalOnProperty(value = "file.oss.enable", havingValue = "true")
public class OssFileConfig implements FileClientConfig {

    @Value("{{file.oss.bucket}")
    private String bucket;

    @Value("{{file.oss.accessKeyId}")
    private String accessKeyId;

    @Value("{{file.oss.accessKeySecret}")
    private String accessKeySecret;

    @Value("{{file.oss.endpoint}")
    private String endpoint;

    @Value("{{file.oss.httpBase}")
    private String httpBase;

    @Value("{{file.oss.inEndpoint}")
    private String inEndpoint;

    @Value("{{file.oss.inHttpBase}")
    private String inHttpBase;

}

五、本地存储实现

在file.local.enable的情况下生成LocalFileConfig,在生成LocalFileConfig的情况下生成LocalFileClient

通过配置是否启用local,可选择性的生成LocalFileClient

文件将会保存在服务器本地

getSignedUrl方法可通过实际情况自定义,如果不需要签名,也可以直接使用nginx代理地址

@Component
@ConditionalOnBean(value = LocalFileConfig.class)
@Log4j2
public class LocalFileClient implements FileClient {

    private final LocalFileConfig localFileConfig;

    @Autowired
    public LocalFileClient(LocalFileConfig localFileConfig) {
        this.localFileConfig = localFileConfig;
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        String filePath = getFilePath(path);
        FileUtil.writeFromStream(inputStream, filePath);
        return null;
    }

    @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);
    }

    @Override
    public String getSignedUrl(String path) {
        return null;
    }

    private String getFilePath(String path) {
        return localFileConfig.getBasePath() + path;
    }

    @Override
    public ClinetTypeEnum getClientType() {
        return ClinetTypeEnum.LOCAL;
    }
}
@Data
@Component
@ConditionalOnProperty(value = "file.local.enable", havingValue = "true")
public class LocalFileConfig implements FileClientConfig {
    @Value("{{file.local.basePath}")
    private String basePath;
}

六、使用

在配置文件里配置不同存储方式的配置和是否启用


file:
  local:
    enable: true
    basePath: /temp/
  oss:
    enable: true
    bucket: 1
    accessKeyId: 1
    accessKeySecret: 1
    endpoint: http://oss-cn-hangzhou.aliyuncs.com
    httpBase: http://1.oss-cn-hangzhou.aliyuncs.com/
    inEndpoint: http://oss-cn-hangzhou-internal.aliyuncs.com
    inHttpBase: http://1.oss-cn-hangzhou-internal.aliyuncs.com/
  ftp:
    enable: true
    ip: 192.168.1.1
    port: 7763
    username: 1
    password: 2
    basePath: /file
   

在代码里注入FileClient后即可调用不同的功能

@Autowired
private List<FileClient> fileClients;