天天看点

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

项目结构

为简化结构 只保留Controller、Service层

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

环境准备

SpringBoot相关依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> 
    </parent>

    <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <profileActive>dev</profileActive>
                <serverPort>2203</serverPort>
            </properties>
            <activation>
                <!-- 默认启用的是dev环境配置-->
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <profileActive>test</profileActive>
                <serverPort>2203</serverPort>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <profileActive>prod</profileActive>
                <serverPort>2203</serverPort>
            </properties>
            <!-- <activation>
                 <activeByDefault>true</activeByDefault>
             </activation> -->
        </profile>
    </profiles>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <hibernate.version>5.2.3.Final</hibernate.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.49</version>
        </dependency>

        <!--使用 @ConfigurationProperties注解时添加此依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>

        <!--会使用到FileUtils把文件转成二进制流-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>
           

启动类

@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        /**
         * 不加上下面这个if判断的话,在IDE中运行没有问题;但是如果打成jar包,通过bat文件启动,会报一个null异常
         */
        if (AuthConfigFactory.getFactory() == null) {
            AuthConfigFactory.setFactory(new AuthConfigFactoryImpl());
        }
        SpringApplication.run(MySpringBootApplication.class, args);
    }
}
           

控制层Controller

@RestController
public class MyController {

    @Autowired
    FileService fileService;

    /**
     * 多文件的上传
     * @param files 上传的文件集合
     * @return
     */
    @PostMapping(value = "/importFile")
    public String importFile(@RequestParam("files") MultipartFile[] files) {
        return fileService.importFile(files);
    }

    /**
     * 单个文件的下载
     * @param filePath  要下载的文件全路径(目录 + 文件名)
     * @param response  
     * @return
     */
    @PostMapping(value = "/downloadFile")
    public String downloadFile(@RequestParam String filePath,HttpServletResponse response) {
        return fileService.downloadFile(filePath,response);
    }

    /**
     * 多个文件打包zip下载
     * @param filePathArray 以逗号分割的多文件的全路径集合
     * @param zipFileName   下载的压缩包名(以.zip格式结尾)
     * @return
     */
    @PostMapping(value = "/downloadMultiFile")
    public ResponseEntity downloadMultiFile(@RequestParam String filePathArray, String zipFileName) {
        return fileService.downloadMultiFile(filePathArray.split(","),zipFileName);
    }
}
           

业务层Service

@Service
@Slf4j
public class FileService {
    @Autowired
    FtpUtil ftpUtil;

    public String importFile(MultipartFile[] files){
        try {
            // 文件名集合
            StringBuilder fileNames = new StringBuilder();
            // 获取ftp服务器连接
            ChannelSftp channel = ftpUtil.getChannel();
            if(files.length > 0){
                for(MultipartFile file:files){
                    if(file.getSize() > 0){
                        // 拼接UUID文件名    获取文件原本的文件名:file.getOriginalFilename()
                        String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
                        String fileName = UUID.randomUUID().toString().replace("-","") + fileType;
                        ftpUtil.uploadFile(channel,ftpUtil.getDirectory(),file.getInputStream(),fileName);
                        log.info("已上传文件:"+ fileName);
                        fileNames.append(ftpUtil.getDirectory()).append("/").append(fileName).append(",");
                    }
                }
            }
            ftpUtil.disConnect(channel);
            fileNames.deleteCharAt(fileNames.length()-1);
            // 省略存储文件路径到数据库的步骤
            return "已上传完成的文件集合:"+fileNames;
        } catch (IOException e) {
            e.printStackTrace();
            return "上传失败";
        }
    }

    public String downloadFile(String filePath , HttpServletResponse response) {
        try {
            if(StringUtils.isEmpty(filePath)){
                log.info("下载的文件路径为空:"+filePath);
                throw new Exception();
            }
            ChannelSftp channel = ftpUtil.getChannel();
            ftpUtil.download(channel,filePath,response);
            return "下载成功!";
        }catch (Exception e) {
            e.printStackTrace();
            return "下载失败!";
        }
    }

    public ResponseEntity downloadMultiFile(String[] filePathArray, String zipFileName) {
        try{
            if(filePathArray == null || StringUtils.isEmpty(zipFileName)){
                log.info("下载的文件路径或文件名异常:"+ Arrays.toString(filePathArray));
                throw new Exception();
            }
            ChannelSftp channel = ftpUtil.getChannel();
            return ftpUtil.download(channel,filePathArray,zipFileName);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity("文件下载异常", HttpStatus.valueOf("500"));
        }
    }
}
           

配置文件

默认使用dev环境

application.properties:

spring.profiles.active=@profileActive@
           

application-dev.properties:

ftp.server.host = 192.168.0.54
ftp.server.port = 22
ftp.server.username = ***
ftp.server.password = ***
# 存储上传文件的文件夹路径
ftp.server.directory = /home/template

           

上传下载工具类

__ Springboot1.5以上版本,在使用 @ConfigurationProperties注解的时候会提示:“Spring Boot Configuration Annotation Processor not found in classpath”,解决方案是在POM文件中增加spring-boot-configuration-processor依赖__

@Component
@ConfigurationProperties(prefix = "ftp.server")
@Data
public class FtpUtil {
    private static final Logger LOG = LoggerFactory.getLogger(FtpUtil.class);

    private String host;
    private Integer port;
    private String username;
    private String password;
    private String filePath;

    public FtpUtil(){

    }

    public Channel getChannel(Session session) {
        Channel channel = null;
        try {
            channel = session.openChannel("sftp");
            channel.connect();
            LOG.info("get Channel success!");
        } catch (JSchException e) {
            LOG.info("get Channel fail!", e);
        }
        return channel;
    }

    public Channel getChannel(){
        Session session = null;
        Channel channel = null;
        try {
            JSch jsch = new JSch();
            jsch.getSession(username, host, port);
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(sshConfig);
            session.connect();
            LOG.info("Session connected!");
            channel = session.openChannel("sftp");
            channel.connect();
            LOG.info("get Channel success!");
        } catch (JSchException e) {
            LOG.info("get Channel failed!", e);
        }
        return channel;
    }

    public Session getSession(String host, int port, String username,
                              final String password) {
        Session session = null;
        try {
            JSch jsch = new JSch();
            jsch.getSession(username, host, port);
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(sshConfig);
            session.connect();
            LOG.info("Session connected!");
        } catch (JSchException e) {
            LOG.info("get Channel failed!", e);
        }
        return session;
    }

    /**
     * 创建文件夹
     *
     * @param sftp
     * @param dir
     *            文件夹名称
     */
    public void mkdir(ChannelSftp sftp, String dir) {
        try {
            sftp.mkdir(dir);
            System.out.println("创建文件夹成功!");
        } catch (SftpException e) {
            System.out.println("创建文件夹失败!");
            LOG.error("error!" ,e);
        }
    }

    /**
     * @param sftp
     * @param dir
     *            上传目录
     * @param file
     *            上传文件
     * @return
     */
    public String uploadFile(ChannelSftp sftp, String dir, InputStream file, String fileName) {
        String result = "";
        try {
            sftp.cd(dir);
            if (file != null) {
                sftp.put(file, fileName);
                result = "上传成功!";
            } else {
                result = "文件为空!不能上传!";
            }
        } catch (Exception e) {
            LOG.info("上传失败!", e);
            result = "上传失败!";
        }
        return result;
    }

    /**
     * 下载文件
     *
     * @param directory
     *            下载目录
     * @param downloadFile
     *            下载的文件
     * @param saveFile
     *            存在本地的路径
     * @param sftp
     */
    public String download(String directory, String downloadFile,
                           String saveFile, ChannelSftp sftp) {
        String result = "";
        try {
            sftp.cd(directory);
            sftp.get(downloadFile, saveFile);
            result = "下载成功!";
        } catch (Exception e) {
            result = "下载失败!";
            LOG.info("下载失败!", e);
            ;
        }
        return result;
    }

    /**
     *  下载文件
     * @param filePath
     * @param response
     * @param sftp
     */
    public void download(ChannelSftp sftp, String filePath, HttpServletResponse response) {
        String directory = "";
        String fileName = "";
        if(filePath.contains("/")){
            directory = filePath.substring(0,filePath.lastIndexOf("/"));
            fileName = filePath.substring(filePath.lastIndexOf("/")+1);
        }
        String savePath = File.separator;
//        String savePath =this.getClass().getResource("/").getPath();
        OutputStream out = null;
        FileInputStream inputStream = null;
        try {
            sftp.cd(directory);

            File file = new File(savePath+fileName);
            sftp.get(fileName, new FileOutputStream(file)); // 之前错误操作:先缓存到本地,再获取流返回给浏览器,最后删除临时文件

            LOG.warn("已进入ftp服务器文件夹:"+directory);
//            sftp.get(fileName,savePath);
            LOG.warn("下载生成的临时文件:"+fileName);
//            File file = new File(fileName);
            inputStream = new FileInputStream(file);
            out = response.getOutputStream();
//            int b = 0;
//            byte[] buffer = new byte[512];
//            while (b != -1){
//                b = inputStream.read(buffer);
//                out.write(buffer,0,b);
//            }
            byte[] b = new byte[1024];
            int len = -1;
            while ((len = inputStream.read(b)) != -1){
                out.write(b, 0, len);
            }
            inputStream.close();

            String fileType = fileName.substring(fileName.lastIndexOf(".")+1);
            if("xls".equals(fileType)||"xlsx".equals(fileType)){

            }
            response.setCharacterEncoding("UTF-8");
            response.setHeader("content-type", "application/octet-stream");
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            out.flush();
            out.close();
            file.delete();
            disConnect(sftp);
        } catch (Exception e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("文件下载出现异常,[{}]", e);
            }
            throw new RuntimeException("文件下载出现异常,[{}]", e);
        } finally {
            closeStream(inputStream,out);
        }
    }

    /**
     * 断掉连接
     */
    public void disConnect(ChannelSftp sftp) {
        try {
            sftp.disconnect();
            sftp.getSession().disconnect();
        } catch (Exception e) {
            LOG.error("error!" ,e);
        }
    }
    /**
     * 关闭流
     * @param outputStream
     */
    private void closeStream(InputStream inputStream,OutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                LOG.error("error!" ,e);
            }
        }
        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {
                LOG.error("error!" ,e);
            }
        }
    }




    /**
     * 删除文件
     *
     * @param directory
     * @param deleteFile
     * @param sftp
     */
    public String delete(String directory, String deleteFile, ChannelSftp sftp) {
        String result = "";
        try {
            sftp.cd(directory);
            sftp.rm(deleteFile);
            result = "删除成功!";
        } catch (Exception e) {
            result = "删除失败!";
            LOG.info("删除失败!", e);
        }
        return result;
    }

    private void closeChannel(Channel channel) {
        if (channel != null) {
            if (channel.isConnected()) {
                channel.disconnect();
            }
        }
    }

    private void closeSession(Session session) {
        if (session != null) {
            if (session.isConnected()) {
                session.disconnect();
            }
        }
    }

    public void closeAll(ChannelSftp sftp, Channel channel, Session session) {
        try {
            closeChannel(sftp);
            closeChannel(channel);
            closeSession(session);
        } catch (Exception e) {
            LOG.info("closeAll", e);
        }
    }
}
           
使用PostMan测试文件上传下载

1. 文件上传

测试上传 txt 、xls 、png 三种格式文件,成功后返回文件路径

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

查看ftp服务器:

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

2. 多文件下载

(单文件下载不再测试)

此处传入的压缩包名字格式以zip结尾,代码中未控制

使用中文作为压缩包名会出现乱码情况,暂未解决

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

点击 save to a file:

SpringBoot使用jcraft连接FTP服务器完成多文件的上传下载

根据需求改动存入ftp服务器的文件名,本例使用UUID