前言
SFTP是基于默认的22端口,是SSH内含的协议,只要启动了sshd就可以使用。相比于高效率的FTP协议,SFTP的优点是更安全的通信。
一、centos7搭建SFTP
1、创建sftp组,查看组信息
groupadd sftp
cat /etc/group
2、创建一个sftp用户【szysftp】并加入到创建的sftp组中,同时修改【szysftp】用户的密码
useradd -g sftp -s /bin/false szysftp
passwd szysftp
3、新建目录,指定为【szysftp】用户的主目录
mkdir -p /sftp/szysftp
usermod -d /sftp/szysftp szysftp
4、编辑配置文件/etc/ssh/sshd_config
将如下这行注释
# Subsystem sftp /usr/libexec/openssh/sftp-server
然后在文件末尾添加如下几行,并保存
Subsystem sftp internal-sftp
Match Group sftp
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
5、设置Chroot目录权限
chown root:sftp /sftp/szysftp #文件夹所有者必须为root,用户组可以不是root
chmod 755 /sftp/szysftp #权限不能超过755,否则会导致登录报错,可以是755
存在多级目录设权只设置了第一层目录问题,如果出现这个问题,可以一级一级目录设置权限。
6、新建一个目录供sftp用户【szysftp】上传文件,这个目录所有者为【szysftp】所有组为sftp,所有者有写入权限所有组无写入权限
mkdir /sftp/szysftp/upload
chown szysftp:sftp /sftp/szysftp/upload
chmod 755 /sftp/szysftp/upload
新建的目录用作java连接时的目录,即:
sftp.cd(path)//写入目录
7、关闭selinux并重启sshd服务,然后测试
setenforce 0
service sshd restart
8、sftp常用命令
cat /etc/passwd 查看所有用户
userdel ftpuser 删除用户ftpuser
cd 路径 进入某路径
lcd 路径` 更改本地目录到某路径”
chgrp group a.txt 将文件a.txt的组更改为group
chmod 777 a.txt 将文件a.txt的权限更改为777
chown owner a.txt 将文件a.txt的属主更改为owner
exit 退出 sftp
quit 退出 sftp
get 远程路径 下载文件
ln existingpath linkpath 符号链接远程文件
ls [参数] [路径] 显示远程目录列表
lls [参数] [路径] 显示本地目录列表
mkdir 路径 创建远程目录
lmkdir 路径 创建本地目录
mv oldpath newpath 移动远程文件
put 本地路径 上传文件
pwd 显示远程工作目录
lpwd 打印本地工作目录
rmdir 路径 删除远程目录
lrmdir 路径 移除删除目录
rm 路径 删除远程文件
lrm 路径 删除本地文件
二、连接sftp会话
sftp> lcd E:\sql
sftp>
sftp> cd /home/sql
sftp>
sftp> put dsm_mysql.sql
Uploading dsm_mysql.sql to /home/sql/dsm_mysql.sql
100% 331KB 331KB/s 00:00:00
E:\sql\dsm_mysql.sql: 339166 bytes transferred in 0 seconds (331 KB/s)
sftp>
三、Java实现文件上传下载
在配置文件【application.properties】里配置sftp连接参数
sftp.ip=192.168.***
sftp.user=***
sftp.password=***
sftp.port=22
sftp.img.ip=192.168.***
sftp.img.user=***
sftp.img.password=***
sftp.img.port=22
package com.bw.note.util;
import com.bw.common.redis.StringRedisService;
import com.bw.note.config.interceptor.LoginInterceptor;
import com.bw.note.entity.po.DsmFilelist;
import com.bw.note.mapper.DictMapper;
import com.google.common.base.Strings;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.apache.commons.lang.StringUtils;
import org.apache.tika.Tika;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.mime.MimeType;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* <文件上传>
* <功能详细描述>
*
* @author ly
* @version [版本号, 2021/06/07 09:51]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
@Component
public class FileUploadUtil {
private static final TikaConfig TIKA_CONFIG = TikaConfig.getDefaultConfig();
private static final MimeTypes MIME_TYPES = TIKA_CONFIG.getMimeRepository();
private static final Tika TIKA = new Tika();
//指定的服务器地址
private static String ip;
//用户名
private static String user;
//密码
private static String password;
//服务器端口
private static int port;
//指定的服务器地址
private static String imgIp;
//用户名
private static String imgUser;
//密码
private static String imgPassword;
//服务器端口 默认
private static int imgPort;
@Value("${sftp.ip}")
public void setIp(String ip) {
FileUploadUtil.ip = ip;
}
@Value("${sftp.user}")
public void setUser(String user) {
FileUploadUtil.user = user;
}
@Value("${sftp.password}")
public void setPassword(String password) {
FileUploadUtil.password = password;
}
@Value("${sftp.port}")
public void setPort(int port) {
FileUploadUtil.port = port;
}
@Value("${sftp.img.ip}")
public void setImgIp(String imgIp) {
FileUploadUtil.imgIp = imgIp;
}
@Value("${sftp.img.user}")
public void setImgUser(String imgUser) {
FileUploadUtil.imgUser = imgUser;
}
@Value("${sftp.img.password}")
public void setImgPassword(String imgPassword) {
FileUploadUtil.imgPassword = imgPassword;
}
@Value("${sftp.img.port}")
public void setImgPort(int imgPort) {
FileUploadUtil.imgPort = imgPort;
}
/**
* ly
* <上传文件至远程服务器>
* @param bytes 文件字节流
* @param fileName 文件名
* @return void
* @see [类、类#方法、类#成员]
*/
public static DsmFilelist sftpUploadFile(byte[] bytes, String fileName,String path, String token) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
fileName = sdf.format(new Date()) + "_" + fileName;
Session session;
Channel channel = null;
JSch jsch = new JSch();
if (port <= 0) {
//连接服务器
session = jsch.getSession(user, ip);
} else {
//采用指定的端口连接服务器
session = jsch.getSession(user, ip, port);
}
//如果服务器连接不上,则抛出异常
if (session == null) {
throw new Exception("session is null");
}
//设置登陆主机的密码
session.setPassword(password);//设置密码
//设置第一次登陆的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");
//设置登陆超时时间
session.connect(30000);
OutputStream outstream = null;
try {
//创建sftp通信通道
channel = (Channel) session.openChannel("sftp");
channel.connect(1000);
ChannelSftp sftp = (ChannelSftp) channel;
//进入服务器指定的文件夹
sftp.cd(path);
outstream = sftp.put(fileName);
outstream.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关流操作
if (outstream != null) {
outstream.flush();
outstream.close();
}
if (session != null) {
session.disconnect();
}
if (channel != null) {
channel.disconnect();
}
}
DsmFilelist filelist = new DsmFilelist();
filelist.setFileName(fileName);
filelist.setFilePath(path+File.separator+fileName);
String userId = LoginInterceptor.loginUtilByUserId(token);
filelist.setCreateBy(userId);
return filelist;
}
static void upload(){
}
/**
* ly
* <上传图片至远程服务器>
* @param bytes 文件字节流
* @param fileName 文件名
* @return void
* @see [类、类#方法、类#成员]
*/
public static String sftpUploadImg(byte[] bytes, String fileName,String path) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
fileName = sdf.format(new Date()) + "_" + fileName;
Session session;
Channel channel = null;
JSch jsch = new JSch();
if (imgPort <= 0) {
//连接服务器
session = jsch.getSession(imgUser, imgIp);
} else {
//采用指定的端口连接服务器
session = jsch.getSession(imgUser, imgIp, imgPort);
}
//如果服务器连接不上,则抛出异常
if (session == null) {
throw new Exception("session is null");
}
//设置登陆主机的密码
session.setPassword(imgPassword);//设置密码
//设置第一次登陆的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");
//设置登陆超时时间
session.connect(30000);
OutputStream outstream = null;
try {
//创建sftp通信通道
channel = (Channel) session.openChannel("sftp");
channel.connect(1000);
ChannelSftp sftp = (ChannelSftp) channel;
//进入服务器指定的文件夹
sftp.cd(path);
outstream = sftp.put(fileName);
outstream.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关流操作
if (outstream != null) {
outstream.flush();
outstream.close();
}
if (session != null) {
session.disconnect();
}
if (channel != null) {
channel.disconnect();
}
}
return path+File.separator+fileName;
}
/**
* ly
* <下载远程服务器文件>
* @param fileName 文件名称
* @return byte[]
* @see [类、类#方法、类#成员]
*/
public static byte[] sftpDownloadFile(String fileName,String path) throws Exception {
Session session;
Channel channel = null;
JSch jsch = new JSch();
if (port <= 0) {
session = jsch.getSession(user, ip);
} else {
//采用指定的端口连接服务器
session = jsch.getSession(user, ip, port);
}
//如果服务器连接不上,则抛出异常
if (session == null) {
throw new Exception("session is null");
}
//设置登陆主机的密码
session.setPassword(password);//设置密码
//设置第一次登陆的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");
//设置登陆超时时间
session.connect(30000);
InputStream inputStream = null;
byte[] bytes = null;
try {
//创建sftp通信通道
channel = (Channel) session.openChannel("sftp");
channel.connect(1000);
ChannelSftp sftp = (ChannelSftp) channel;
//进入服务器指定的文件夹
sftp.cd(path);
//列出服务器指定的文件列表
// Vector v = sftp.ls("*");
// for(int i=0;i<v.size();i++){
// System.out.println(v.get(i));
// }
inputStream = sftp.get(fileName);
bytes = readInputStream(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关流操作
if (session != null) {
session.disconnect();
}
if (channel != null) {
channel.disconnect();
}
}
return bytes;
}
//输入流转二进制
public static byte[] readInputStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
return bos.toByteArray();
}
public static DsmFilelist uploadFile(MultipartFile file, String savePath, String token) {
detectFileMimeType(file);
DsmFilelist filelist = new DsmFilelist();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = sdf.format(new Date()) + "_" + file.getOriginalFilename();
//文件路径
File storeDirectory = new File(savePath);
//没有则创建
if (!storeDirectory.exists()) {
storeDirectory.mkdirs();
}
String path = savePath + File.separator + fileName;
File file1 = new File(path);
try {
file.transferTo(file1);
} catch (IOException e) {
e.printStackTrace();
}
filelist.setFileName(file.getOriginalFilename());
filelist.setFilePath(file1.getPath());
String userId = LoginInterceptor.loginUtilByUserId(token);
filelist.setCreateBy(userId);
return filelist;
}
public static void detectFileMimeType(MultipartFile file){
// 通过文件名称截取的后缀名称
String postfix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
String externFileName = null;
try {
externFileName = getFileExtension(detectFileMimeType(file.getInputStream(), file.getOriginalFilename()), file.getOriginalFilename());
if (StringUtils.isEmpty(externFileName)) {
throw new RuntimeException("文件格式错误");
}
//是否是允许的类型
String simpleActualFileType = externFileName.replace(".", "").trim().toLowerCase();
//判断文件实际类型和扩展名是否一致
if (!simpleActualFileType.equals(postfix.replace(".", "").trim().toLowerCase())) {
throw new RuntimeException("文件实际类型与上传类型不一致");
}
} catch (IOException e) {
e.printStackTrace();
} catch (MimeTypeException e) {
e.printStackTrace();
}
if (StringUtils.isNotBlank(externFileName) && StringUtils.isNotBlank(postfix)) {
if (!(externFileName.toUpperCase().contains("BMP")
|| externFileName.toUpperCase().contains("JPG")
|| externFileName.toUpperCase().contains("JPEG")
|| externFileName.toUpperCase().contains("PNG")
|| externFileName.toUpperCase().contains("DOCX")
|| externFileName.toUpperCase().contains("PDF")
|| externFileName.toUpperCase().contains("XLS")
|| externFileName.toUpperCase().contains("XLSX")
|| externFileName.toUpperCase().contains("GP")
|| externFileName.toUpperCase().contains("DOC")
&& externFileName.toUpperCase().contains(postfix.toUpperCase()))){
throw new RuntimeException("请上传!图片【PNG,JPEG,JPG】;文件【DOCX,PDF,XLS,XLSX,BMP,GP,DOC】类型文件");
}
}
}
/**
* lzm
* <一句话功能简述>
* <功能详细描述>
*
* @param len 文件长度
* @param size 限制大小
* @param unit 限制单位(B,K,M,G)
* @return boolean
* @see [类、类#方法、类#成员]
*/
public static boolean checkFileSize(Long len, int size, String unit) {
double fileSize = 0;
if ("B".equals(unit.toUpperCase())) {
fileSize = (double) len;
} else if ("K".equals(unit.toUpperCase())) {
fileSize = (double) len / 1024;
} else if ("M".equals(unit.toUpperCase())) {
fileSize = (double) len / 1048576;
} else if ("G".equals(unit.toUpperCase())) {
fileSize = (double) len / 1073741824;
}
if (fileSize > size) {
return false;
}
return true;
}
/**
* <判断文件是否是图片>
*
* @author ly
* @version [版本号, 2019/10/22 15:42]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public static boolean checkImg(InputStream inputStream) {
try {
Image image = ImageIO.read(inputStream);
return image != null;
} catch (IOException ex) {
return false;
}
}
/**
* ly
* <一句话功能简述>
* <判断图片长宽>
*
* @param file 图片
* @param width 图片宽度
* @param height 图片高度
* @return boolean
* @see [类、类#方法、类#成员]
*/
public static boolean checkImgSize(MultipartFile file, Integer width, Integer height, String savePath) throws IOException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = sdf.format(new Date()) + "_" + file.getOriginalFilename();
//文件路径
File storeDirectory = new File(savePath);
//没有则创建
if (!storeDirectory.exists()) {
storeDirectory.mkdirs();
}
String path = savePath + File.separator + fileName;
File file1 = new File(path);
Boolean flag = false;
FileInputStream inputStream = null;
try (InputStream stream = file.getInputStream();
FileOutputStream fs = new FileOutputStream(path)){
/*InputStream stream = file.getInputStream();
FileOutputStream fs = new FileOutputStream(path);*/
byte[] buffer = new byte[1024 * 1024];
int bytesum = 0;
int byteread = 0;
while ((byteread = stream.read(buffer)) != -1) {
bytesum += byteread;
fs.write(buffer, 0, byteread);
fs.flush();
}
fs.close();
stream.close();
inputStream = new FileInputStream(file1);
BufferedImage sourceImg = ImageIO.read(inputStream);
if (width.equals(sourceImg.getWidth()) && height.equals(sourceImg.getHeight())) {
return true;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
inputStream.close();
file1.delete();
}
return false;
}
public static void setInputStream(InputStream inStream, HttpServletResponse response) {
// 循环取出流中的数据
byte[] b = new byte[Constant.GLOBAL_ONE_HUNDRED];
int len;
try {
while ((len = inStream.read(b)) > Constant.GLOBAL_INT_ZERO)
response.getOutputStream().write(b, Constant.GLOBAL_INT_ZERO, len);
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* detect file mime type using apache tika.
*
* @param inputStream file input stream
* @param originalFilename original file name
* @return file mime type
* @throws IOException if read file failed
* @throws MimeTypeException if detect failed
*/
public static MimeType detectFileMimeType(InputStream inputStream, String originalFilename)
throws IOException, MimeTypeException {
String contentType = TIKA.detect(inputStream, originalFilename);
if (Strings.isNullOrEmpty(contentType)) {
return null;
}
return createMimeType(contentType);
}
public static MimeType createMimeType(String contentType) throws MimeTypeException {
return MIME_TYPES.forName(contentType);
}
/**
* 获取文件的扩展名. 首先根据content-type获取对应的扩展名, 如果无法获取, 获取文件名的后缀
*
* @param contentType file content type
* @param filename file name
* @return file extension, if can not detect, return empty string
*/
public static String getFileExtension(MimeType contentType, String filename) {
String extension = "";
if (contentType != null) {
extension = contentType.getExtension();
}
if (Strings.isNullOrEmpty(extension) && !Strings.isNullOrEmpty(filename)) {
extension = getFileExtension(filename);
}
return extension;
}
/**
* 获取文件扩展名.
*
* @param filename file name
* @return file extension or empty string
*/
public static String getFileExtension(String filename) {
int dotIndex = filename.lastIndexOf('.');
if (dotIndex != -1) {
return filename.substring(dotIndex);
}
return "";
}
/**
* 获取文件名. 如果包含路径, 去除路径; 如果包含扩展名,去掉扩展名.
*
* @param filePath file path
* @return file name
*/
public static String getFilenameWithoutExtension(String filePath) {
String filename = Paths.get(filePath).toFile().getName();
int dotIndex = filename.lastIndexOf('.');
if (dotIndex != -1) {
return filename.substring(0, dotIndex);
}
return filename;
}
}