feat: 新增文件上传的多种方式

This commit is contained in:
weizhiqiang 2024-08-18 17:28:29 +08:00
parent 8414a93be7
commit 29dd20b076
25 changed files with 1187 additions and 5 deletions

View file

@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
/**
* @ClassName: AbstractFileClient
* @Description: 文件客户端的抽象类提供模板方法减少子类的冗余代码
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 9:25
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Slf4j
public abstract class AbstractFileClient<Config extends FileClientConfig> implements FileClient {
/**
* 配置编号
*/
private final Long id;
/**
* 文件配置
*/
protected Config config;
public AbstractFileClient(Long id, Config config) {
this.id = id;
this.config = config;
}
/**
* 初始化
*/
public final void init() {
doInit();
log.debug("[init][配置({}) 初始化完成]", config);
}
/**
* 自定义初始化
*/
protected abstract void doInit();
public final void refresh(Config config) {
// 判断是否更新
if (config.equals(this.config)) {
return;
}
log.info("[refresh][配置({})发生变化,重新初始化]", config);
this.config = config;
// 初始化
this.init();
}
@Override
public Long getId() {
return id;
}
/**
* 格式化文件的 URL 访问地址
* 使用场景localftpdb通过 FileController getFile 来获取文件内容
*
* @param domain 自定义域名
* @param path 文件路径
* @return URL 访问地址
*/
protected String formatFileUrl(String domain, String path) {
return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path);
}
}

View file

@ -0,0 +1,62 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client;
import com.skyeye.framework.file.core.client.s3.FilePresignedUrlRespDTO;
/**
* @ClassName: FileClient
* @Description: 文件客户端
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 12:01
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public interface FileClient {
/**
* 获得客户端编号
*
* @return 客户端编号
*/
Long getId();
/**
* 上传文件
*
* @param content 文件流
* @param path 相对路径
* @return 完整路径 HTTP 访问地址
* @throws Exception 上传文件时抛出 Exception 异常
*/
String upload(byte[] content, String path, String type) throws Exception;
/**
* 删除文件
*
* @param path 相对路径
* @throws Exception 删除文件时抛出 Exception 异常
*/
void delete(String path) throws Exception;
/**
* 获得文件的内容
*
* @param path 相对路径
* @return 文件的内容
*/
byte[] getContent(String path) throws Exception;
/**
* 获得文件预签名地址
*
* @param path 相对路径
* @return 文件预签名地址
*/
default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
throw new UnsupportedOperationException("不支持的操作");
}
}

View file

@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client;
/**
* @ClassName: FileClientConfig
* @Description: 文件客户端的配置不同实现的客户端需要不同的配置通过子类来定义
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 9:21
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public interface FileClientConfig {
}

View file

@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client;
import com.skyeye.upload.enums.FileStorageEnum;
public interface FileClientFactory {
/**
* 获得文件客户端
*
* @param configId 配置编号
* @return 文件客户端
*/
FileClient getFileClient(Long configId);
/**
* 创建文件客户端
*
* @param configId 配置编号
* @param storage 存储器的枚举 {@link FileStorageEnum}
* @param config 文件配置
*/
<Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config);
}

View file

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import com.skyeye.upload.enums.FileStorageEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @ClassName: FileClientFactoryImpl
* @Description: 文件客户端的工厂实现类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 9:23
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Slf4j
public class FileClientFactoryImpl implements FileClientFactory {
/**
* 文件客户端 Map
* key配置编号
*/
private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>();
@Override
public FileClient getFileClient(Long configId) {
AbstractFileClient<?> client = clients.get(configId);
if (client == null) {
log.error("[getFileClient][配置编号({}) 找不到客户端]", configId);
}
return client;
}
@Override
public <Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config) {
AbstractFileClient<Config> client = (AbstractFileClient<Config>) clients.get(configId);
if (client == null) {
client = this.createFileClient(configId, storage, config);
client.init();
clients.put(client.getId(), client);
} else {
client.refresh(config);
}
}
private <Config extends FileClientConfig> AbstractFileClient<Config> createFileClient(
Long configId, Integer storage, Config config) {
FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage);
Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum));
// 创建客户端
return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config);
}
}

View file

@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.db;
import com.skyeye.framework.file.core.client.AbstractFileClient;
/**
* @ClassName: DBFileClient
* @Description: 基于 DB 存储的文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:24
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public class DBFileClient extends AbstractFileClient<DBFileClientConfig> {
// private FileContentMapper fileContentMapper;
public DBFileClient(Long id, DBFileClientConfig config) {
super(id, config);
}
@Override
protected void doInit() {
// fileContentMapper = SpringUtil.getBean(FileContentMapper.class);
}
@Override
public String upload(byte[] content, String path, String type) {
// FileContentDO contentDO = new FileContentDO().setConfigId(getId())
// .setPath(path).setContent(content);
// fileContentMapper.insert(contentDO);
// // 拼接返回路径
// return super.formatFileUrl(config.getDomain(), path);
return null;
}
@Override
public void delete(String path) {
// fileContentMapper.deleteByConfigIdAndPath(getId(), path);
}
@Override
public byte[] getContent(String path) {
// List<FileContentDO> list = fileContentMapper.selectListByConfigIdAndPath(getId(), path);
// if (CollUtil.isEmpty(list)) {
// return null;
// }
// // 排序后 id 最大的即最后上传的
// list.sort(Comparator.comparing(FileContentDO::getId));
// return CollUtil.getLast(list).getContent();
return null;
}
}

View file

@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.db;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.framework.file.core.client.FileClientConfig;
import lombok.Data;
/**
* @ClassName: DBFileClientConfig
* @Description: 基于 DB 存储的文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 10:47
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@ApiModel("基于 DB 存储的文件客户端的配置类")
public class DBFileClientConfig implements FileClientConfig {
@ApiModelProperty(value = "自定义域名", required = "required")
private String domain;
}

View file

@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.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 com.skyeye.framework.file.core.client.AbstractFileClient;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* @ClassName: FtpFileClient
* @Description: Ftp 文件客户端
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:24
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
private Ftp ftp;
public FtpFileClient(Long id, FtpFileClientConfig config) {
super(id, config);
}
@Override
protected void doInit() {
// 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
// ftp的路径是 / 结尾
if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
config.setBasePath(config.getBasePath() + StrUtil.SLASH);
}
// 初始化 Ftp 对象
this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
CharsetUtil.CHARSET_UTF_8, FtpMode.valueOf(config.getMode()));
}
@Override
public String upload(byte[] content, String path, String type) {
// 执行写入
String filePath = getFilePath(path);
String fileName = FileUtil.getName(filePath);
String dir = StrUtil.removeSuffix(filePath, fileName);
ftp.reconnectIfTimeout();
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.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();
}
private String getFilePath(String path) {
return config.getBasePath() + path;
}
}

View file

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.ftp;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.framework.file.core.client.FileClientConfig;
import lombok.Data;
/**
* @ClassName: FtpFileClientConfig
* @Description: Ftp 文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 11:10
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@ApiModel("Ftp 文件客户端的配置类")
public class FtpFileClientConfig implements FileClientConfig {
@ApiModelProperty(value = "基础路径", required = "required")
private String basePath;
@ApiModelProperty(value = "自定义域名", required = "required")
private String domain;
@ApiModelProperty(value = "主机地址", required = "required")
private String host;
@ApiModelProperty(value = "主机端口", required = "required")
private Integer port;
@ApiModelProperty(value = "用户名", required = "required")
private String username;
@ApiModelProperty(value = "密码", required = "required")
private String password;
/**
* 连接模式
* <p>
* 使用 {@link cn.hutool.extra.ftp.FtpMode} 对应的字符串
*/
@ApiModelProperty(value = "连接模式", required = "required")
private String mode;
}

View file

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.local;
import cn.hutool.core.io.FileUtil;
import com.skyeye.framework.file.core.client.AbstractFileClient;
import java.io.File;
/**
* @ClassName: LocalFileClient
* @Description: 本地文件客户端
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:25
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
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 type) {
// 执行写入
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;
}
}

View file

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.local;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.framework.file.core.client.FileClientConfig;
import lombok.Data;
/**
* @ClassName: LocalFileClientConfig
* @Description: 本地文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:22
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@ApiModel("本地文件客户端的配置类")
public class LocalFileClientConfig implements FileClientConfig {
@ApiModelProperty(value = "基础路径", required = "required")
private String basePath;
@ApiModelProperty(value = "自定义域名", required = "required")
private String domain;
}

View file

@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.s3;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName: FilePresignedUrlRespDTO
* @Description: 文件预签名地址 Response DTO
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:25
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FilePresignedUrlRespDTO {
/**
* 文件上传 URL用于上传
* <p>
* 例如说
*/
private String uploadUrl;
/**
* 文件 URL用于读取下载等
*/
private String url;
}

View file

@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.s3;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.skyeye.framework.file.core.client.AbstractFileClient;
import io.minio.*;
import io.minio.http.Method;
import java.io.ByteArrayInputStream;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: S3FileClient
* @Description: 基于 S3 协议的文件客户端实现 MinIO阿里云腾讯云七牛云华为云等云服务
* * <p>
* * S3 协议的客户端采用亚马逊提供的 software.amazon.awssdk.s3
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 12:03
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
private MinioClient client;
public S3FileClient(Long id, S3FileClientConfig config) {
super(id, config);
}
@Override
protected void doInit() {
// 补全 domain
if (StrUtil.isEmpty(config.getDomain())) {
config.setDomain(buildDomain());
}
// 初始化客户端
client = MinioClient.builder()
.endpoint(buildEndpointURL()) // Endpoint URL
.region(buildRegion()) // Region
.credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
.build();
enableVirtualStyleEndpoint();
}
/**
* 基于 endpoint 构建调用云服务的 URL 地址
*
* @return URI 地址
*/
private String buildEndpointURL() {
// 如果已经是 http 或者 https则不进行拼接.主要适配 MinIO
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
return config.getEndpoint();
}
return StrUtil.format("https://{}", config.getEndpoint());
}
/**
* 基于 bucket + endpoint 构建访问的 Domain 地址
*
* @return Domain 地址
*/
private String buildDomain() {
// 如果已经是 http 或者 https则不进行拼接.主要适配 MinIO
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
}
// 阿里云腾讯云华为云都适合七牛云比较特殊必须有自定义域名
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
}
/**
* 基于 bucket 构建 region 地区
*
* @return region 地区
*/
private String buildRegion() {
// 阿里云必须有 region否则会报错
if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_ALIYUN)) {
return StrUtil.subBefore(config.getEndpoint(), '.', false)
.replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
.replaceAll("https://", "");
}
// 腾讯云必须有 region否则会报错
if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_TENCENT)) {
return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
.replaceAll("." + S3FileClientConfig.ENDPOINT_TENCENT, ""); // 去除 Endpoint
}
return null;
}
/**
* 开启 VirtualStyle 模式
*/
private void enableVirtualStyleEndpoint() {
if (StrUtil.containsAny(config.getEndpoint(),
S3FileClientConfig.ENDPOINT_TENCENT, // 腾讯云 https://cloud.tencent.com/document/product/436/41284
S3FileClientConfig.ENDPOINT_VOLCES)) { // 火山云 https://www.volcengine.com/docs/6349/1288493
client.enableVirtualStyleEndpoint();
}
}
@Override
public String upload(byte[] content, String path, String type) throws Exception {
// 执行上传
client.putObject(PutObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.contentType(type)
.object(path) // 相对路径作为 key
.stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
.build());
// 拼接返回路径
return config.getDomain() + "/" + path;
}
@Override
public void delete(String path) throws Exception {
client.removeObject(RemoveObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.object(path) // 相对路径作为 key
.build());
}
@Override
public byte[] getContent(String path) throws Exception {
GetObjectResponse response = client.getObject(GetObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.object(path) // 相对路径作为 key
.build());
return IoUtil.readBytes(response);
}
@Override
public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(config.getBucket())
.object(path)
.expiry(10, TimeUnit.MINUTES) // 过期时间秒数取值范围1 ~ 7
.build()
);
return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
}
}

View file

@ -0,0 +1,91 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.s3;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.framework.file.core.client.FileClientConfig;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
/**
* @ClassName: S3FileClientConfig
* @Description: S3 文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 11:55
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@ApiModel("S3 文件客户端的配置类")
public class S3FileClientConfig implements FileClientConfig {
public static final String ENDPOINT_QINIU = "qiniucs.com";
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
public static final String ENDPOINT_TENCENT = "myqcloud.com";
public static final String ENDPOINT_VOLCES = "volces.com"; // 火山云字节
/**
* 节点地址
* 1. MinIOhttps://www.iocoder.cn/Spring-Boot/MinIO 例如说http://127.0.0.1:9000
* 2. 阿里云https://help.aliyun.com/document_detail/31837.html
* 3. 腾讯云https://cloud.tencent.com/document/product/436/6224
* 4. 七牛云https://developer.qiniu.com/kodo/4088/s3-access-domainname
* 5. 华为云https://developer.huaweicloud.com/endpoint?OBS
*/
@ApiModelProperty(value = "节点地址", required = "required")
private String endpoint;
/**
* 自定义域名
* 1. MinIO通过 Nginx 配置
* 2. 阿里云https://help.aliyun.com/document_detail/31836.html
* 3. 腾讯云https://cloud.tencent.com/document/product/436/11142
* 4. 七牛云https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name
* 5. 华为云https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html
*/
@ApiModelProperty(value = "自定义域名", required = "required")
private String domain;
@ApiModelProperty(value = "存储 Bucket", required = "required")
private String bucket;
/**
* 访问 Key
* 1. MinIOhttps://www.iocoder.cn/Spring-Boot/MinIO
* 2. 阿里云https://ram.console.aliyun.com/manage/ak
* 3. 腾讯云https://console.cloud.tencent.com/cam/capi
* 4. 七牛云https://portal.qiniu.com/user/key
* 5. 华为云https://support.huaweicloud.com/qs-obs/obs_qs_0005.html
*/
@ApiModelProperty(value = "访问 Key", required = "required")
private String accessKey;
@ApiModelProperty(value = "访问 Secret", required = "required")
private String accessSecret;
/**
* TODO 待补充
*
* @return
*/
@SuppressWarnings("RedundantIfStatement")
@AssertTrue(message = "domain 不能为空")
@JsonIgnore
public boolean isDomainValid() {
// 如果是七牛必须带有 domain
if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.sftp;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.ssh.Sftp;
import com.skyeye.framework.file.core.client.AbstractFileClient;
import java.io.File;
/**
* @ClassName: SftpFileClient
* @Description: Sftp 文件客户端
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:26
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
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 type) {
// 执行写入
String filePath = getFilePath(path);
File file = com.skyeye.common.util.FileUtil.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 = com.skyeye.common.util.FileUtil.createTempFile();
sftp.download(filePath, destFile);
return FileUtil.readBytes(destFile);
}
private String getFilePath(String path) {
return config.getBasePath() + path;
}
}

View file

@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.framework.file.core.client.sftp;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.framework.file.core.client.FileClientConfig;
import lombok.Data;
/**
* @ClassName: SftpFileClientConfig
* @Description: Sftp 文件客户端的配置类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 11:59
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@ApiModel("Sftp 文件客户端的配置类")
public class SftpFileClientConfig implements FileClientConfig {
@ApiModelProperty(value = "基础路径", required = "required")
private String basePath;
@ApiModelProperty(value = "自定义域名", required = "required")
private String domain;
@ApiModelProperty(value = "主机地址", required = "required")
private String host;
@ApiModelProperty(value = "主机端口", required = "required")
private Integer port;
@ApiModelProperty(value = "用户名", required = "required")
private String username;
@ApiModelProperty(value = "密码", required = "required")
private String password;
}

View file

@ -237,7 +237,7 @@ public class ServiceBeanServiceImpl extends SkyeyeBusinessServiceImpl<ServiceBea
@Override
public Map<String, ServiceBean> queryServiceClass(List<String> classNames) {
if (CollectionUtil.isEmpty(classNames)) {
return CollectionUtil.newHashMap();
return cn.hutool.core.map.MapUtil.newHashMap();
}
// 获取所属应用信息
List<Map<String, Object>> applications = applicationService.queryApplicationList();

View file

@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.upload.controller;
import com.skyeye.annotation.api.Api;
import com.skyeye.upload.service.FileConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: FileConfigController
* @Description: 文件配置控制层
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:20
* @Copyright: 2024 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@RestController
@Api(value = "文件配置", tags = "文件配置", modelName = "文件配置")
public class FileConfigController {
@Autowired
private FileConfigService fileConfigService;
}

View file

@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.upload.dao;
import com.skyeye.eve.dao.SkyeyeBaseMapper;
import com.skyeye.upload.entity.FileConfig;
/**
* @ClassName: FileConfigDao
* @Description: 文件配置数据接口层
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:17
* @Copyright: 2024 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public interface FileConfigDao extends SkyeyeBaseMapper<FileConfig> {
}

View file

@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.upload.entity;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.skyeye.annotation.api.ApiModel;
import com.skyeye.annotation.api.ApiModelProperty;
import com.skyeye.annotation.cache.RedisCacheField;
import com.skyeye.common.entity.features.BaseGeneralInfo;
import com.skyeye.framework.file.core.client.FileClientConfig;
import com.skyeye.framework.file.core.client.db.DBFileClientConfig;
import com.skyeye.framework.file.core.client.ftp.FtpFileClientConfig;
import com.skyeye.framework.file.core.client.local.LocalFileClientConfig;
import com.skyeye.framework.file.core.client.s3.S3FileClientConfig;
import com.skyeye.framework.file.core.client.sftp.SftpFileClientConfig;
import lombok.Data;
/**
* @ClassName: FileConfig
* @Description: 文件配置实体类
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 9:35
* @Copyright: 2024 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Data
@RedisCacheField(name = "skyeye:fileConfig")
@TableName(value = "skyeye_file_config", autoResultMap = true)
@ApiModel("文件配置实体类")
public class FileConfig extends BaseGeneralInfo {
@TableField(value = "is_default")
@ApiModelProperty(value = "是否默认,参考#IsDefaultEnum", required = "required,num")
private Integer isDefault;
@TableField(value = "storage")
@ApiModelProperty(value = "存储器,参考#FileStorageEnum", required = "required,num")
private Integer storage;
@TableField(value = "config", typeHandler = FileClientConfigTypeHandler.class)
@ApiModelProperty(value = "配置信息", required = "required,json")
private FileClientConfig config;
public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {
@Override
public Object parse(String json) {
FileClientConfig config = JSONUtil.toBean(json, FileClientConfig.class);
if (config != null) {
return config;
}
// 兼容老版本的包路径
String className = StrUtil.subAfter(config.getClass().getName(), ".", true);
switch (className) {
case "DBFileClientConfig":
return JSONUtil.toBean(json, DBFileClientConfig.class);
case "FtpFileClientConfig":
return JSONUtil.toBean(json, FtpFileClientConfig.class);
case "LocalFileClientConfig":
return JSONUtil.toBean(json, LocalFileClientConfig.class);
case "SftpFileClientConfig":
return JSONUtil.toBean(json, SftpFileClientConfig.class);
case "S3FileClientConfig":
return JSONUtil.toBean(json, S3FileClientConfig.class);
default:
throw new IllegalArgumentException("未知的 FileClientConfig 类型:" + json);
}
}
@Override
public String toJson(Object obj) {
return JSONUtil.toJsonStr(obj);
}
}
}

View file

@ -0,0 +1,67 @@
package com.skyeye.upload.enums;
import cn.hutool.core.util.ArrayUtil;
import com.skyeye.common.base.classenum.SkyeyeEnumClass;
import com.skyeye.framework.file.core.client.FileClient;
import com.skyeye.framework.file.core.client.FileClientConfig;
import com.skyeye.framework.file.core.client.db.DBFileClient;
import com.skyeye.framework.file.core.client.db.DBFileClientConfig;
import com.skyeye.framework.file.core.client.ftp.FtpFileClient;
import com.skyeye.framework.file.core.client.ftp.FtpFileClientConfig;
import com.skyeye.framework.file.core.client.local.LocalFileClient;
import com.skyeye.framework.file.core.client.local.LocalFileClientConfig;
import com.skyeye.framework.file.core.client.s3.S3FileClient;
import com.skyeye.framework.file.core.client.s3.S3FileClientConfig;
import com.skyeye.framework.file.core.client.sftp.SftpFileClient;
import com.skyeye.framework.file.core.client.sftp.SftpFileClientConfig;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* @ClassName: FileStorageEnum
* @Description: 文件存储器枚举
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 9:18
* @Copyright: 2021 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum FileStorageEnum implements SkyeyeEnumClass {
DB(1, "数据库存储", DBFileClientConfig.class, DBFileClient.class, true, true),
LOCAL(10, "本地存储", LocalFileClientConfig.class, LocalFileClient.class, true, false),
FTP(11, "FTP存储", FtpFileClientConfig.class, FtpFileClient.class, true, false),
SFTP(12, "SFTP存储", SftpFileClientConfig.class, SftpFileClient.class, true, false),
S3(20, "S3存储", S3FileClientConfig.class, S3FileClient.class, true, false);
/**
* 存储器
*/
private Integer key;
private String value;
/**
* 配置类
*/
private Class<? extends FileClientConfig> configClass;
/**
* 客户端类
*/
private Class<? extends FileClient> clientClass;
private Boolean show;
private Boolean isDefault;
public static FileStorageEnum getByStorage(Integer storage) {
return ArrayUtil.firstMatch(o -> o.getKey().equals(storage), values());
}
}

View file

@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.upload.service;
import com.skyeye.base.business.service.SkyeyeBusinessService;
import com.skyeye.upload.entity.FileConfig;
/**
* @ClassName: FileConfigService
* @Description: 文件配置服务接口层
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:19
* @Copyright: 2024 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
public interface FileConfigService extends SkyeyeBusinessService<FileConfig> {
}

View file

@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright 卫志强 QQ598748873@qq.com Inc. All rights reserved. 开源地址https://gitee.com/doc_wei01/skyeye
******************************************************************************/
package com.skyeye.upload.service.impl;
import com.skyeye.annotation.service.SkyeyeService;
import com.skyeye.base.business.service.impl.SkyeyeBusinessServiceImpl;
import com.skyeye.upload.dao.FileConfigDao;
import com.skyeye.upload.entity.FileConfig;
import com.skyeye.upload.service.FileConfigService;
import org.springframework.stereotype.Service;
/**
* @ClassName: FileConfigServiceImpl
* @Description: 文件配置服务层
* @author: skyeye云系列--卫志强
* @date: 2024/8/18 17:19
* @Copyright: 2024 https://gitee.com/doc_wei01/skyeye Inc. All rights reserved.
* 注意本内容仅限购买后使用.禁止私自外泄以及用于其他的商业目的
*/
@Service
@SkyeyeService(name = "文件配置", groupName = "文件配置")
public class FileConfigServiceImpl extends SkyeyeBusinessServiceImpl<FileConfigDao, FileConfig> implements FileConfigService {
}

View file

@ -5,6 +5,7 @@
package com.skyeye.tenant.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.skyeye.annotation.service.SkyeyeService;
import com.skyeye.base.business.service.impl.SkyeyeBusinessServiceImpl;
@ -128,7 +129,7 @@ public class TenantAppServiceImpl extends SkyeyeBusinessServiceImpl<TenantAppDao
public Map<String, TenantApp> queryTenantAppByAppId(String... appId) {
List<String> appIdList = Arrays.asList(appId);
if (CollectionUtil.isEmpty(appIdList)) {
return CollectionUtil.newHashMap();
return MapUtil.newHashMap();
}
QueryWrapper<TenantApp> queryWrapper = new QueryWrapper<>();
queryWrapper.in(CommonConstants.ID, appIdList);

View file

@ -5,16 +5,16 @@ spring:
application:
name: skyeye-pro-${spring.profiles.active} # 服务名
profiles:
active: public
active: dev
cloud:
nacos:
discovery:
server-addr: 172.18.92.40:9000 # 配置服务注册nacos地址
server-addr: localhost:9000 # 配置服务注册nacos地址
namespace: ${spring.profiles.active} # 配置命名空间
service: ${spring.application.name} # 配置服务名
config:
# 指定nacos server的地址
server-addr: 172.18.92.40:9000
server-addr: localhost:9000
# 配置文件后缀
file-extension: yml
# 命名空间 常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等