一、简介
我在之前的文章(Java实现文件上传和下载)里讲过非FTP文件的上传和下载,也讲过Java实现FTP文件上传和下载。本文主要讲通过FTP下载文件到浏览器,本文测试过程中Spring Boot 版本为2.6.0,commons-net 版本为3.8.0,JDK环境为 1.8。
二、maven依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alian</groupId>
<artifactId>ftp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ftp</name>
<description>java实现FTP下载文件到浏览器</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
三、配置类
AppProperties.java
package com.alian.ftp.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "ftp")
public class AppProperties {
/**
* ftp主机地址
*/
private String hostName;
/**
* ftp端口
*/
private int port;
/**
* ftp用户名
*/
private String userName;
/**
* ftp密码
*/
private String password;
}
相关的属性配置如下:
application.yml
server:
port: 8081
servlet:
context-path: /ftp
ftp:
host-name: 192.168.0.151
port: 21
user-name: alian
password: alian012
四、工具类
ApacheFtpUtil.java
package com.alian.ftp.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Slf4j
public class ApacheFtpUtil {
private FTPClient ftpClient;
/**
* 实例化
*
* @param hostName FTP服务器地址
* @param port FTP服务器端口
* @param userName FTP登录账户
* @param password FTP登录密码
* @throws IOException
*/
public ApacheFtpUtil(String hostName, int port, String userName, String password) throws IOException {
ftpClient = new FTPClient();
//设置传输命令的超时
ftpClient.setDefaultTimeout(20000);//毫秒
//设置两个服务连接超时时间
ftpClient.setConnectTimeout(10000);//毫秒
//被动模式下设置数据传输的超时时间
ftpClient.setDataTimeout(15000);//毫秒
//连接FTP
ftpClient.connect(hostName, port);
//更加账户密码登录服务
ftpClient.login(userName, password);
//被动模式
ftpClient.enterLocalPassiveMode();
}
public Pair<Boolean, String> downloadFile(String ftpFilePath, String fileName, HttpServletResponse response) {
InputStream input = null;
OutputStream out = null;
try {
response.reset();
//此处你就根据你要下载的类型去设置即可,比如我下载.avi格式的文件,可以设置response.setContentType("video/avi");
response.setContentType("application/download");
//解决中文不能生成文件(包含空格)
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName,"UTF-8").replaceAll("\\+","%20")+"\"");
//传输模式
ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
// 设置以二进制流的方式传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//进入目录
ftpClient.changeWorkingDirectory(ftpFilePath);
FTPFile[] files = ftpClient.listFiles();
if (files.length < 1) {
return Pair.of(false, "目录为空");
}
boolean fileExist = false;
boolean downloadFlag = false;
for (FTPFile ftpFile : files) {
String ftpFileName=new String(ftpFile.getName().getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
if (fileName.equals(ftpFileName)) {
fileExist = true;
input = ftpClient.retrieveFileStream(new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
out = response.getOutputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = input.read(bytes)) != -1) {
out.write(bytes, 0, len);
}
out.flush();
downloadFlag = true;
break;
}
}
if (!fileExist) {
return Pair.of(false, "FTP服务器上文件不存在");
}
return Pair.of(downloadFlag, downloadFlag ? "下载成功" : "下载失败");
} catch (IOException e) {
e.printStackTrace();
return Pair.of(false, "下载文件异常");
} finally {
try {
if (out != null) {
out.close();
}
if (input != null) {
input.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void close() {
try {
if (ftpClient != null && ftpClient.isConnected()) {
ftpClient.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
大致的流程:
- 设置ContentType和Header
- 设置二进制流的传输模式
- 进入到要下载的文件的目录,获取到文件列表进行遍历
- 如果文件存在,则通过流的方式进行文件的读写
- 输出流刷到页面(依赖Header中的设置)
4.1、服务器文件名中文处理
关于中文文件名编码说明:
//设置编码(支持UTF-8字符集的操作系统)
ftpClient.setAutodetectUTF8(true);
有些小伙伴可能会去设置上面这个代码,这个就是在支持UTF-8字符集的操作系统里会自动处理中文,比如这个apache文件服务器上文件一般默认是ISO-8859-1编码,使用它就会自动变成UTF-8。但是服务器可能不支持UTF-8字符集,ftpClient.setAutodetectUTF8(true)不会生效,又会有问题,所有建议不加,那就使用下面的代码进行编码转换了。
String ftpFileName=new String(ftpFile.getName().getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
4.2、下载文件名中文处理
假设下载的文件的文件名有中文时,我们就需要进行URLEncoder,下面这个就是对中文的处理,同时可以重命名文件。
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName,"UTF-8"));
假设下载的文件的文件名有空格,可能会变成加号:+,我们可以通过替换函数进行处理:replaceAll(“\+”,“%20”),其他特殊字符看情况处理了
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName,"UTF-8").replaceAll("\\+","%20")+"\"");
五、接口验证
FtpController.java
package com.alian.ftp.controller;
import com.alian.ftp.config.AppProperties;
import com.alian.ftp.utils.ApacheFtpClient;
import com.alian.ftp.utils.ApacheFtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
@RequestMapping(value = "/file")
public class FtpController {
@Autowired
private AppProperties appProperties;
@RequestMapping("download")
public String downloadFtpFile(HttpServletRequest request, HttpServletResponse response) {
ApacheFtpUtil ftpUtil = null;
try {
//http://localhost:8081/ftp/file/download
String ftpFilePath="/icss/a2ce09b3536e57de";
String fileName="20220803下载的视频.avi";
log.info("文件路径:{}",ftpFilePath);
log.info("文件名称:{}",fileName);
ftpUtil = new ApacheFtpUtil(appProperties.getHostName(), appProperties.getPort(), appProperties.getUserName(), appProperties.getPassword());
Pair<Boolean, String> pair = ftpUtil.downloadFile(ftpFilePath, fileName, response);
return pair.getRight();
} catch (Exception e) {
log.error("下载异常",e);
return "下载文件异常";
} finally {
if (ftpUtil != null) {
ftpUtil.close();
}
}
}
}
请求地址:http://localhost:8081/ftp/file/download