一、目标
实现阿里云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;