前言

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;
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐