该工具类可用于linux单机环境上传文件,根据maven信息和文件魔术自动分类文件
用到的第三方依赖:
<properties>
<jsch.version>0.1.54</jsch.version>
<apache-tika-version>1.25</apache-tika-version>
<maven-resources-plugin-version>2.6</maven-resources-plugin-version>
</properties>
<dependencies>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>${jsch.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers</artifactId>
<version>${apache-tika-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin-version}</version>
<configuration>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</build>
sftp配置类:
/**
* @ClassName SftpServerConfig
* @Author: yurj
* @Mail:1638234804@qq.com
* @Date: Create in 17:10 2020/12/27
* @version: 1.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "sftp")
public class SftpServerConfig {
/**
* IP
*/
private String ipAddress;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String passWord;
/**
* 主机
*/
private String hosts;
/**
* 根目录
*/
private String rootDirectory;
/**
* 默认端口
*/
private String defaultPort;
}
sftp对应yml配置文件:
sftp:
ip-address: cs.slantcc.com
hosts: http://resource.slantcc.com
user-name: root
pass-word: 123456
root-directory: /home/upload/
default-port: 19
maven配置类:
/**
* @ClassName MavenInfoConfig
* @Author: yurj
* @Mail:1638234804@qq.com
* @Date: Create in 10:41 2021/2/24
* @version: 1.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "project.build")
public class MavenInfoConfig {
/**
* 自定义id
*/
private String artifactId;
/**
* 父级自定义id
*/
private String parentArtifactId;
/**
* 组id
*/
private String groupId;
}
maven配置类对应yml:
project.build.artifactId=@project.artifactId@
project.build.parentArtifactId=@project.parent.artifactId@
project.build.groupId=@project.groupId@
工具类:
/**
* sftp上传 工具
*
* @ClassName SftpUtil
* @Author: yurj
* @Mail:1638234804@qq.com
* @Date: Create in 17:09 2020/12/27
* @version: 1.0
*/
@Slf4j
@Component
public class SftpUtil {
/**
* sftp服务器配置
*/
private static SftpServerConfig sftpServerConfig;
/**
* maven配置
*/
private static MavenInfoConfig mavenInfoConfig;
/**
* boolean字符
*/
private final String CHARSET_TRUE = "true";
private final String CHARSET_FALSE = "false";
/**
* 输出关键字
*/
private final String OUTPUT_KEYWORD = "echo";
/**
* 斜线
*/
private final String SLASH = "/";
/**
* JSCH session
*/
private static Session session;
/**
* 私有的构造方法
*/
private SftpUtil() {
}
/**
* 私有的工具对象
*/
private static SftpUtil sftpUtil;
/**
* 获取当前工具实例
*
* @param
* @return com.hclc.util.sftp.SftpUtil
* @author yurj
* @version 1.0
* @date 2020/12/27 17:41
*/
public static synchronized SftpUtil builder() {
if (Objects.isNull(sftpUtil)) {
sftpUtil = new SftpUtil();
}
if (Objects.isNull(session) || !session.isConnected()) {
sftpUtil.sftpUploadUtilLogin();
AssertUtil.isTrue(session.isConnected(), "连接超时,请重试");
}
return sftpUtil;
}
/**
* 远程登陆
*
* @param
* @return void
* @author yurj
* @version 1.0
* @date 2020/12/28 10:11
*/
private void sftpUploadUtilLogin() {
// 创建jSch对象
JSch jSch = new JSch();
// 获取到jSch的session, 根据用户名、主机ip、端口号获取一个Session对象
try {
session = jSch.getSession(sftpServerConfig.getUserName(), sftpServerConfig.getIpAddress(), Integer.valueOf(sftpServerConfig.getDefaultPort()));
// 设置密码
session.setPassword(sftpServerConfig.getPassWord());
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
// 为Session对象设置properties
session.setConfig(config);
// 设置超时
session.setTimeout(30000);
// 通过Session建立连接
session.connect();
} catch (JSchException e) {
log.error("远程主机登录失败,IP = {},userName = {}", sftpServerConfig.getIpAddress(), sftpServerConfig.getUserName(), e);
}
}
/**
* 关闭连接
*
* @param
* @return void
* @author yurj
* @version 1.0
* @date 2020/12/28 10:11
*/
private void closeSession() {
// 调用session的关闭连接的方法
if (Objects.nonNull(session)) {
// 如果session不为空,调用session的关闭连接的方法
session.disconnect();
}
}
/**
* 将输入流转换为string
*
* @param in
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2020/12/28 10:12
*/
public static String processInputStreamData(InputStream in) throws IOException {
String strData = new BufferedReader(new InputStreamReader(in))
.lines()
.parallel()
.collect(Collectors.joining(System.lineSeparator()));
in.close();
return strData;
}
/**
* 将连续输入流转换为String
*
* @param in
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2020/12/30 14:51
*/
public static String processInputStreamData(ChannelShell channelShell, OutputStream out, InputStream in) throws IOException {
/**
* shell管道本身就是交互模式的。要想停止,有两种方式:
* 一、人为的发送一个exit命令,告诉程序本次交互结束
* 二、使用字节流中的available方法,来获取数据的总大小,然后循环去读。
* 为了避免阻塞
*/
StringBuilder result = new StringBuilder();
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) {
break;
}
String s = new String(tmp, 0, i);
if (s.indexOf("--More--") >= 0) {
out.write((" ").getBytes());
out.flush();
}
result.append(s);
}
if (channelShell.isClosed()) {
break;
}
try {
Thread.sleep(500);
} catch (Exception e) {
log.error("流解析异常", e);
}
}
out.close();
in.close();
channelShell.disconnect();
return result.toString();
}
/**
* 执行单条linux命令并返回结果
*
* @param
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2020/12/30 11:59
*/
public String executeCommandByExec(String command) throws JSchException, IOException {
// 单条命令(也可执行符合指令)
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
// 执行结果
InputStream execInputStream = channelExec.getInputStream();
channelExec.setCommand(command);
channelExec.setErrStream(System.err);
channelExec.connect();
String data = processInputStreamData(execInputStream);
channelExec.disconnect();
return data;
}
/**
* 执行多条linux命令并返回结果
*
* @param commandList
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2020/12/30 14:47
*/
public String executeCommandByShell(List<String> commandList) throws IOException, JSchException {
ChannelShell channelShell = (ChannelShell) session.openChannel("shell");
// 执行结果
InputStream inputStream = channelShell.getInputStream();
channelShell.setPty(true);
channelShell.connect();
// 待输出命令
OutputStream outputStream = channelShell.getOutputStream();
// 使用PrintWriter 就是为了使用println 这个方法
// 好处就是不需要每次手动给字符加\n
PrintWriter printWriter = new PrintWriter(outputStream);
commandList.stream().forEach(s -> {
printWriter.println(s);
});
printWriter.println("exit");//为了结束本次交互
printWriter.flush();//把缓冲区的数据强行输出
String result = processInputStreamData(channelShell, outputStream, inputStream);
return result;
}
/**
* 上传文件
*
* @param originalFilename
* @param file
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2021/1/6 16:20
*/
public String uploadFile(String originalFilename, MultipartFile file) {
String artifactId = mavenInfoConfig.getParentArtifactId();
// 拼接目录
String directory = sftpServerConfig.getRootDirectory() + artifactId;
return uploadFile(originalFilename, file, directory);
}
/**
* 上传文件
*
* @param originalFilename
* @param file
* @param directory
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2020/12/28 10:12
*/
public String uploadFile(String originalFilename, MultipartFile file, String directory) {
String url = StringUtils.EMPTY;
try {
// 打开channelSftp
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
// 远程连接
channelSftp.connect(100000);
// 判断文件夹是否存在
if (!objectExists(directory, ObjectTypeEnum.OBJECT_DIRECTORY)) {
// 只能创建一层目录,多层会异常
//channelSftp.mkdir(directory);
// 初始化上传文件夹
initDirectory(directory);
}
// 获取文件归属文件夹
String mimeTypeDirectory = getMimeTypeByStream(file.getInputStream());
directory = directory + SLASH + mimeTypeDirectory;
// 进入该目录
channelSftp.cd(directory);
// 输出资源到根目录
channelSftp.put(file.getInputStream(), originalFilename, ChannelSftp.OVERWRITE);
// 关闭连接
closeSession();
// 切断远程连接
channelSftp.exit();
url = sftpServerConfig.getHosts() + directory + SLASH + originalFilename;
log.debug(originalFilename + " 文件上传成功.....");
} catch (IOException e) {
log.error("上传文件读取流异常,message = {}", e.getMessage(), e);
} catch (JSchException e) {
log.error("JSch异常,message = {}", e.getMessage(), e);
} catch (SftpException e) {
log.error("Sftp异常,message = {}", e.getMessage(), e);
}
return url;
}
/**
* 初始化上传文件夹
*
* @param directory
* @return void
* @author yurj
* @version 1.0
* @date 2021/1/9 14:31
*/
private void initDirectory(String directory) {
String mimeDirectory = MimeTypeEnum.mimeTypeEnums.stream().map(s -> StringUtils.lowerCase(s.getNameValue())).collect(Collectors.joining(","));
// 命令行
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.append("mkdir -p");
commandBuilder.append(StringUtils.SPACE);
commandBuilder.append(directory);
commandBuilder.append(SLASH);
commandBuilder.append("{");
commandBuilder.append(mimeDirectory);
commandBuilder.append("}");
try {
String result = executeCommandByExec(commandBuilder.toString());
log.debug("初始化文件夹,result ={}", result);
} catch (Exception e) {
log.error("初始化文件夹远程执行linux单命令出错", e);
}
}
/**
* 获取当前流文件所属文件夹
*
* @param in
* @return java.lang.String
* @author yurj
* @version 1.0
* @date 2021/1/9 14:56
*/
private String getMimeTypeByStream(InputStream in) throws IOException {
// 检测文件mimeType
Tika tika = new Tika();
String mimeType = tika.detect(in);
// 文件所属文件夹类型
String currentFileType = MimeTypeEnum.mimeTypeEnums.stream()
.map(s -> StringUtils.lowerCase(s.getNameValue()))
.filter(s -> mimeType.startsWith(s))
.findFirst()
.orElse(StringUtils.lowerCase(MimeTypeEnum.APPLICATION.getNameValue()));
return currentFileType;
}
/**
* 文件下载
*
* @param src linux服务器文件地址
* @param dst 本地存放地址
* @return void
* @author yurj
* @version 1.0
* @date 2020/12/28 10:13
*/
public void fileDownload(String src, String dst) {
try {
// src 是linux服务器文件地址,dst 本地存放地址
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
// 远程连接
channelSftp.connect();
// 下载文件,多个重载方法
channelSftp.get(src, dst);
// 切断远程连接,quit()等同于exit(),都是调用disconnect()
channelSftp.quit();
// 关闭连接
closeSession();
log.debug("src = {} ,下载文件成功.....", src);
} catch (Exception e) {
log.error("远程连接失败......", e);
}
}
/**
* 删除文件
*
* @param directoryFile 要删除文件所在目录
* @return void
* @author yurj
* @version 1.0
* @date 2020/12/28 10:14
*/
public void deleteFile(String directoryFile) {
try {
// 打开openChannel的sftp
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
// 远程连接
channelSftp.connect();
// 删除文件
channelSftp.rm(directoryFile);
// 切断远程连接
channelSftp.exit();
} catch (Exception e) {
log.error("删除文件失败", e);
}
}
/**
* 判定当前目录下指定类型对象是否存在
*
* @param objectName
* @param type
* @return boolean
* @author yurj
* @version 1.0
* @date 2020/12/30 16:54
*
* <p>
* linux语法示例
* [ -f hello.txt ] && echo yes || echo no
*/
public boolean objectExists(String objectName, ObjectTypeEnum type) {
// 根据类型拼接命令
String command = "[ " + type.getCommand() + " " + objectName + " ] && " + OUTPUT_KEYWORD + " " + CHARSET_TRUE + " || " + OUTPUT_KEYWORD + " " + CHARSET_FALSE;
try {
String result = executeCommandByExec(command);
return Objects.equals(CHARSET_TRUE, result);
} catch (Exception e) {
log.error("判定对象远程执行linux单命令出错", e);
}
return false;
}
/**
* 对象类型枚举
* <p>
* command扩展:
* -e 判断对象是否存在
* -d 判断对象是否存在,并且为目录
* -f 判断对象是否存在,并且为常规文件
* -L 判断对象是否存在,并且为符号链接
* -h 判断对象是否存在,并且为软链接
* -s 判断对象是否存在,并且长度不为0
* -r 判断对象是否存在,并且可读
* -w 判断对象是否存在,并且可写
* -x 判断对象是否存在,并且可执行
* -O 判断对象是否存在,并且属于当前用户
* -G 判断对象是否存在,并且属于当前用户组
* -nt 判断file1是否比file2新 [ "/data/file1" -nt "/data/file2" ]
* -ot 判断file1是否比file2旧 [ "/data/file1" -ot "/data/file2" ]
*/
@AllArgsConstructor
public enum ObjectTypeEnum {
OBJECT_FILE(1, "文件", "-f"),
OBJECT_DIRECTORY(2, "目录", "-d");
private final int code;
private final String desc;
private final String command;
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
public String getCommand() {
return command;
}
public static ObjectTypeEnum find(int code) {
for (ObjectTypeEnum messageType : ObjectTypeEnum.values()) {
if (messageType.getCode() == code) {
return messageType;
}
}
return null;
}
}
/**
* 文件扩展类型枚举
*/
@AllArgsConstructor
public enum MimeTypeEnum {
IMAGE(1, "图片"),
AUDIO(2, "音频"),
VIDEO(3, "视频"),
TEXT(4, "文本"),
APPLICATION(5, "应用文件");
private final int code;
private final String desc;
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 文件扩展类型set
*/
static EnumSet<MimeTypeEnum> mimeTypeEnums = EnumSet.allOf(MimeTypeEnum.class);
public static MimeTypeEnum find(int code) {
for (MimeTypeEnum messageType : MimeTypeEnum.values()) {
if (messageType.getCode() == code) {
return messageType;
}
}
return null;
}
public String getNameValue() {
return String.valueOf(this);
}
}
/**
* 初始化sftp配置
*
* @param sftpServerConfig
* @return void
* @author yurj
* @version 1.0
* @date 2021/1/6 16:57
*/
@Autowired
public void setSftpServerConfig(SftpServerConfig sftpServerConfig) {
SftpUtil.sftpServerConfig = sftpServerConfig;
}
/**
* 初始化maven配置
*
* @param mavenInfoConfig
* @return void
* @author yurj
* @version 1.0
* @date 2021/2/24 10:45
*/
@Autowired
public void setMinioServerConfig(MavenInfoConfig mavenInfoConfig) {
SftpUtil.mavenInfoConfig = mavenInfoConfig;
}
}