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

项目结构

为简化结构 只保留Controller、Service层
在这里插入图片描述

环境准备

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")
@NoArgsConstructor
@Data
@Slf4j
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 directory;   // 文件存储的文件夹路径

    private ChannelSftp sftp = null;

    /**
     * 连接 sftp 服务器
     * @return
     */
    public ChannelSftp getChannel(){
        Session session = null;
        Channel channel = null;
        try {
            LOG.info("-->sftp连接开始>>>>>> " + host + ":" + port + " >>>username=" + username);
            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();
            sftp = (ChannelSftp)channel;
            LOG.info("get Channel success!");
        } catch (JSchException e) {
            LOG.info("get Channel failed!", e);
        }
        return sftp;
    }

    /**
     * @param sftp  ftp连接
     * @param dir   上传的目录
     * @param file  上传的文件
     * @return
     */
    public String uploadFile(ChannelSftp sftp, String dir, InputStream file, String fileName) {
        String result = "";
        try {
            log.info("上传文件存储的路径:"+dir);
            sftp.cd(dir);
            if (file != null) {
                sftp.put(file, fileName);
                result = "上传成功!";
            } else {
                result = "文件为空!不能上传!";
            }
        } catch (Exception e) {
            LOG.info("上传失败!", e);
            result = "上传失败!";
        }finally {
            closeStream(file,null);
        }
        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 sftp
     * @param filePath    文件所在目录及文件名(文件的全路径)
     * @param response
     */
    public void download(ChannelSftp sftp, String filePath, HttpServletResponse response) {
        OutputStream out = null;
        try {
            String directory = "";
            String fileName = "";
            if(!StringUtils.isEmpty(filePath) && filePath.contains("/")){
                directory = filePath.substring(0,filePath.lastIndexOf("/"));
                fileName = filePath.substring(filePath.lastIndexOf("/")+1);
            }else {
                log.info("要下载的文件路径异常:"+filePath);
                throw new Exception();
            }
            sftp.cd(directory);
            // 将文件名为filename的文件下载到本地默认路径 /
            sftp.get(fileName,"/");
            File file = new File(fileName);
            FileInputStream 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);
            }
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            out.flush();
            out.close();
            // 删除下载到本地的临时文件
            if(!file.delete()){
                log.info("临时文件未成功删除");
            }
            inputStream.close();
            disConnect(sftp);
        } catch (Exception e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("文件下载出现异常,[{}]", e);
            }
            throw new RuntimeException("文件下载出现异常,[{}]", e);
        } finally {
            closeStream(null,out);
        }
    }

    /**
     * 多文件下载
     * @param sftp
     * @param filePathArray 文件的全路径数组(文件所在目录及文件名)
     * @param zipFileName 压缩文件名
     */
    public ResponseEntity<byte[]> download(ChannelSftp sftp, String[] filePathArray,String zipFileName){
        ZipOutputStream zipOutStream = null;
        FileInputStream zipSource = null;
        BufferedInputStream bufferStream = null;
        File zipFile = null;
        try {
            // 存放服务器上zip文件的目录
            String zipPath = "/"+zipFileName;
            // 创建本地临时zip文件
            zipFile = new File(zipPath);
            // 构造最终压缩包的输出流
            zipOutStream = new ZipOutputStream(new FileOutputStream(zipFile));
            for(String filePath:filePathArray){
                log.info("要下载的文件全路径:"+filePath);
                String directory = "";
                String fileName = "";
                if(!StringUtils.isEmpty(filePath) && filePath.contains("/")){
                    directory = filePath.substring(0,filePath.lastIndexOf("/"));
                    fileName = filePath.substring(filePath.lastIndexOf("/")+1);
                }else {
                    log.info("要下载的文件路径异常:"+filePath);
                    throw new Exception();
                }
                sftp.cd(directory);
                // 将文件名为filename的文件下载到本地默认路径 /
                sftp.get(fileName,"/");
                File file = new File(fileName);
                if(file.exists()){
                    //将需要压缩的文件格式化为输入流
                    zipSource = new FileInputStream(file);
                }
                //在压缩目录中文件的名字
                ZipEntry zipEntry = new ZipEntry(fileName);
                //定位该压缩条目位置,开始写入文件到压缩包中
                zipOutStream.putNextEntry(zipEntry);
                bufferStream = new BufferedInputStream(zipSource, 1024 * 10);
                int read = 0;
                byte[] buf = new byte[1024 * 10];
                while((read = bufferStream.read(buf, 0, 1024 * 10)) != -1)
                {
                    zipOutStream.write(buf, 0, read);
                }
                bufferStream.close();
                // 删除下载到本地的临时文件
                if(!file.delete()){
                    log.info("临时文件未成功删除");
                }
            }
            disConnect(sftp);
            zipOutStream.close();
            zipOutStream.flush();

            HttpHeaders headers = new HttpHeaders();
            // 压缩文件名转码
            zipFileName = new String(zipFileName.getBytes(),"utf-8");
            headers.setContentDispositionFormData("attachment", zipFileName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // 写回压缩文件
            return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(zipFile),headers, HttpStatus.CREATED);
        } catch (Exception e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("文件下载出现异常,[{}]", e);
            }
            throw new RuntimeException("文件下载出现异常,[{}]", e);
        } finally {
            //判断系统压缩文件是否存在,该压缩文件通过流输出给客户端后删除该压缩文件
            if(zipFile.exists()){
                zipFile.delete();
            }
        }
    }

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

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


    /**
     * 删除文件
     *
     * @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);
        }
    }

    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;
    }
}
使用PostMan测试文件上传下载

1. 文件上传
测试上传 txt 、xls 、png 三种格式文件,成功后返回文件路径
在这里插入图片描述
查看ftp服务器:
在这里插入图片描述
2. 多文件下载
(单文件下载不再测试)
此处传入的压缩包名字格式以zip结尾,代码中未控制
使用中文作为压缩包名会出现乱码情况,暂未解决
在这里插入图片描述
点击 save to a file:
在这里插入图片描述
根据需求改动存入ftp服务器的文件名,本例使用UUID

发布了4 篇原创文章 · 获赞 0 · 访问量 78

猜你喜欢

转载自blog.csdn.net/weixin_43958996/article/details/104006532
今日推荐