Merge branch '20260204'
# Conflicts: # .idea/compiler.xml # .idea/vcs.xml # lidee-admin/src/main/resources/application-local.yaml # lidee-admin/target/classes/application-local.yaml # lidee-admin/target/lidee-admin.jar # lidee-admin/target/lidee-admin.jar.original # lidee-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-core/src/main/java/com/lideeyunji/core/framework/entity/ReportFieldEntity.java # lidee-core/src/main/java/com/lideeyunji/core/framework/service/impl/ReportServiceImpl.java # lidee-core/target/classes/com/lideeyunji/core/framework/entity/ReportFieldEntity.class # lidee-core/target/classes/com/lideeyunji/core/framework/service/impl/ReportServiceImpl.class # lidee-core/target/lidee-core-2.2.4.jar # lidee-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-module/lidee-module-api/target/lidee-module-api-2.2.4.jar # lidee-module/lidee-module-api/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-module/lidee-module-biz/target/lidee-module-biz-2.2.4.jar # lidee-module/lidee-module-biz/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-service/lidee-service-infra-api/target/lidee-service-infra-api-2.2.4.jar # lidee-service/lidee-service-infra-biz/target/lidee-service-infra-biz-2.2.4.jar # lidee-service/lidee-service-system-api/target/lidee-service-system-api-2.2.4.jar # lidee-service/lidee-service-system-biz/target/lidee-service-system-biz-2.2.4.jar # lidee-tool/tool-common/target/tool-common-2.2.4.jar # lidee-tool/tool-spring-boot-starter-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-ai/target/tool-spring-boot-starter-ai-2.2.4.jar # lidee-tool/tool-spring-boot-starter-captcha/target/tool-spring-boot-starter-captcha-2.2.4.jar # lidee-tool/tool-spring-boot-starter-dict/target/tool-spring-boot-starter-dict-2.2.4.jar # lidee-tool/tool-spring-boot-starter-excel/target/tool-spring-boot-starter-excel-2.2.4.jar # lidee-tool/tool-spring-boot-starter-exception/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-exception/target/tool-spring-boot-starter-exception-2.2.4.jar # lidee-tool/tool-spring-boot-starter-file/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-file/target/tool-spring-boot-starter-file-2.2.4.jar # lidee-tool/tool-spring-boot-starter-flowable/target/tool-spring-boot-starter-flowable-2.2.4.jar # lidee-tool/tool-spring-boot-starter-ip/target/tool-spring-boot-starter-ip-2.2.4.jar # lidee-tool/tool-spring-boot-starter-job/target/tool-spring-boot-starter-job-2.2.4.jar # lidee-tool/tool-spring-boot-starter-monitor/target/tool-spring-boot-starter-monitor-2.2.4.jar # lidee-tool/tool-spring-boot-starter-mybatis/target/tool-spring-boot-starter-mybatis-2.2.4.jar # lidee-tool/tool-spring-boot-starter-operatelog/target/tool-spring-boot-starter-operatelog-2.2.4.jar # lidee-tool/tool-spring-boot-starter-permission/target/tool-spring-boot-starter-permission-2.2.4.jar # lidee-tool/tool-spring-boot-starter-protection/target/tool-spring-boot-starter-protection-2.2.4.jar # lidee-tool/tool-spring-boot-starter-redis/target/tool-spring-boot-starter-redis-2.2.4.jar # lidee-tool/tool-spring-boot-starter-security/target/tool-spring-boot-starter-security-2.2.4.jar # lidee-tool/tool-spring-boot-starter-sql/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-sql/target/tool-spring-boot-starter-sql-2.2.4.jar # lidee-tool/tool-spring-boot-starter-tenant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-tenant/target/tool-spring-boot-starter-tenant-2.2.4.jar # lidee-tool/tool-spring-boot-starter-test/target/tool-spring-boot-starter-test-2.2.4.jar # lidee-tool/tool-spring-boot-starter-web/target/tool-spring-boot-starter-web-2.2.4.jar # lidee-tool/tool-spring-boot-starter-websocket/target/tool-spring-boot-starter-websocket-2.2.4.jar # lidee-tool/tool-spring-boot-starter-yunji/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst # lidee-tool/tool-spring-boot-starter-yunji/target/tool-spring-boot-starter-yunji-2.2.4.jar # logs/lideeyunji-error.log # logs/lideeyunji-info.log
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>lidee-tool</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</parent>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>tool-spring-boot-starter-file</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>文件客户端,支持多种存储器
|
||||
1. file:本地磁盘
|
||||
2. ftp:FTP 服务器
|
||||
2. sftp:SFTP 服务器
|
||||
4. db:数据库
|
||||
5. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>tool-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>tool-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>lidee-tool</artifactId>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<version>${lidee.version}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>tool-spring-boot-starter-file</artifactId>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>文件客户端,支持多种存储器
|
||||
1. file:本地磁盘
|
||||
2. ftp:FTP 服务器
|
||||
2. sftp:SFTP 服务器
|
||||
4. db:数据库
|
||||
5. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等
|
||||
</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>tool-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>com.lideeyunji</groupId>
|
||||
<artifactId>tool-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.config;
|
||||
|
||||
import com.lideeyunji.tool.framework.file.core.client.FileClientFactory;
|
||||
import com.lideeyunji.tool.framework.file.core.client.FileClientFactoryImpl;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* 文件配置类
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class FileAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FileClientFactory fileClientFactory() {
|
||||
return new FileClientFactoryImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 文件客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@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 访问地址
|
||||
* 使用场景:local、ftp、db,通过 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client;
|
||||
|
||||
import com.lideeyunji.tool.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
|
||||
/**
|
||||
* 文件客户端
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
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("不支持的操作");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
/**
|
||||
* 文件客户端的配置
|
||||
* 不同实现的客户端,需要不同的配置,通过子类来定义
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
// @JsonTypeInfo 注解的作用,Jackson 多态
|
||||
// 1. 序列化到时数据库时,增加 @class 属性。
|
||||
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
|
||||
public interface FileClientConfig {
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client;
|
||||
|
||||
import com.lideeyunji.tool.framework.file.core.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);
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.lideeyunji.tool.framework.file.core.enums.FileStorageEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 文件客户端的工厂实现类
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@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
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.db;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.lideeyunji.tool.framework.file.core.client.AbstractFileClient;
|
||||
|
||||
/**
|
||||
* 基于 DB 存储的文件客户端的配置类
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
public class DBFileClient extends AbstractFileClient<DBFileClientConfig> {
|
||||
|
||||
private DBFileContentFrameworkDAO dao;
|
||||
|
||||
public DBFileClient(Long id, DBFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
getDao().insert(getId(), path, content);
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl(config.getDomain(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
getDao().delete(getId(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
return getDao().selectContent(getId(), path);
|
||||
}
|
||||
|
||||
private DBFileContentFrameworkDAO getDao() {
|
||||
// 延迟获取,因为 SpringUtil 初始化太慢
|
||||
if (dao == null) {
|
||||
dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class);
|
||||
}
|
||||
return dao;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.db;
|
||||
|
||||
import com.lideeyunji.tool.framework.file.core.client.FileClientConfig;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* 基于 DB 存储的文件客户端的配置类
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@Data
|
||||
public class DBFileClientConfig implements FileClientConfig {
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
@NotEmpty(message = "domain 不能为空")
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.db;
|
||||
|
||||
/**
|
||||
* 文件内容 Framework DAO 接口
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
public interface DBFileContentFrameworkDAO {
|
||||
|
||||
/**
|
||||
* 插入文件内容
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @param path 路径
|
||||
* @param content 内容
|
||||
*/
|
||||
void insert(Long configId, String path, byte[] content);
|
||||
|
||||
/**
|
||||
* 删除文件内容
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @param path 路径
|
||||
*/
|
||||
void delete(Long configId, String path);
|
||||
|
||||
/**
|
||||
* 获得文件内容
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @param path 路径
|
||||
* @return 内容
|
||||
*/
|
||||
byte[] selectContent(Long configId, String path);
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.lideeyunji.tool.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.lideeyunji.tool.framework.file.core.client.AbstractFileClient;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* Ftp 文件客户端
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
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, null, null, 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.ftp;
|
||||
|
||||
import com.lideeyunji.tool.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;
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.local;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.lideeyunji.tool.framework.file.core.client.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 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.local;
|
||||
|
||||
import com.lideeyunji.tool.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;
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.s3;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 文件预签名地址 Response DTO
|
||||
*
|
||||
* @author owen
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FilePresignedUrlRespDTO {
|
||||
|
||||
/**
|
||||
* 文件上传 URL(用于上传)
|
||||
*
|
||||
* 例如说:
|
||||
*/
|
||||
private String uploadUrl;
|
||||
|
||||
/**
|
||||
* 文件 URL(用于读取、下载等)
|
||||
*/
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.s3;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.lideeyunji.tool.framework.file.core.client.AbstractFileClient;
|
||||
import io.minio.*;
|
||||
import io.minio.http.Method;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.lideeyunji.tool.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
|
||||
import static com.lideeyunji.tool.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
|
||||
|
||||
/**
|
||||
* 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||
* <p>
|
||||
* S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 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(ENDPOINT_ALIYUN)) {
|
||||
return StrUtil.subBefore(config.getEndpoint(), '.', false)
|
||||
.replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
|
||||
.replaceAll("https://", "");
|
||||
}
|
||||
// 腾讯云必须有 region,否则会报错
|
||||
if (config.getEndpoint().contains(ENDPOINT_TENCENT)) {
|
||||
return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
|
||||
.replaceAll("." + ENDPOINT_TENCENT, ""); // 去除 Endpoint
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.sftp;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.extra.ssh.Sftp;
|
||||
import com.lideeyunji.tool.framework.common.util.io.FileUtils;
|
||||
import com.lideeyunji.tool.framework.file.core.client.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 type) {
|
||||
// 执行写入
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.client.sftp;
|
||||
|
||||
import com.lideeyunji.tool.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;
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.enums;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.lideeyunji.tool.framework.file.core.client.FileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.FileClientConfig;
|
||||
import com.lideeyunji.tool.framework.file.core.client.db.DBFileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.lideeyunji.tool.framework.file.core.client.ftp.FtpFileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
import com.lideeyunji.tool.framework.file.core.client.local.LocalFileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.local.LocalFileClientConfig;
|
||||
import com.lideeyunji.tool.framework.file.core.client.s3.S3FileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import com.lideeyunji.tool.framework.file.core.client.sftp.SftpFileClient;
|
||||
import com.lideeyunji.tool.framework.file.core.client.sftp.SftpFileClientConfig;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文件存储器枚举
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum FileStorageEnum {
|
||||
|
||||
DB(1, DBFileClientConfig.class, DBFileClient.class),
|
||||
|
||||
LOCAL(10, LocalFileClientConfig.class, LocalFileClient.class),
|
||||
FTP(11, FtpFileClientConfig.class, FtpFileClient.class),
|
||||
SFTP(12, SftpFileClientConfig.class, SftpFileClient.class),
|
||||
|
||||
S3(20, S3FileClientConfig.class, S3FileClient.class),
|
||||
;
|
||||
|
||||
/**
|
||||
* 存储器
|
||||
*/
|
||||
private final Integer storage;
|
||||
|
||||
/**
|
||||
* 配置类
|
||||
*/
|
||||
private final Class<? extends FileClientConfig> configClass;
|
||||
/**
|
||||
* 客户端类
|
||||
*/
|
||||
private final Class<? extends FileClient> clientClass;
|
||||
|
||||
public static FileStorageEnum getByStorage(Integer storage) {
|
||||
return ArrayUtil.firstMatch(o -> o.getStorage().equals(storage), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.lideeyunji.tool.framework.file.core.utils;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.tika.Tika;
|
||||
|
||||
/**
|
||||
* 文件类型 Utils
|
||||
*
|
||||
* @author 金灯剑客
|
||||
*/
|
||||
public class FileTypeUtils {
|
||||
|
||||
private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
|
||||
|
||||
/**
|
||||
* 获得文件的 mineType,对于doc,jar等文件会有误差
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String getMineType(byte[] data) {
|
||||
return TIKA.get().detect(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确
|
||||
*
|
||||
* @param name 文件名
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
public static String getMineType(String name) {
|
||||
return TIKA.get().detect(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在拥有文件和数据的情况下,最好使用此方法,最为准确
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @param name 文件名
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
public static String getMineType(byte[] data, String name) {
|
||||
return TIKA.get().detect(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
com.lideeyunji.tool.framework.file.config.FileAutoConfiguration
|
||||
Reference in New Issue
Block a user