一、CentOS7下安装ApacheFtpServer
1.1、前期准备
1.1.1、Linux服务器项目(apache-ftpserver-1.1.1.tar)
1.1.2、jdk1.8(jdk-8u151-linux-x64.tar.gz)
1.2、环境配置
1.2.1、安装jdk配置java环境变量
参考:Linux:CentOS 7 安装JDK8_秦毅翔的专栏-CSDN博客_linux yum下载jdk
1.2.2、配置Apache-ftp-server
1、将apache-ftpserver-1.1.1.tar解压到/usr/local/目录下
2、修改配置文件
vi res/conf/users.properties
# 密码(管理员:用户名:admin,密码:admin)
ftpserver.user.admin.userpassword=admin
# 该用户的根目录
ftpserver.user.admin.homedirectory=./res/home/secret
# 设置当前用户可用
ftpserver.user.admin.enableflag=true
# 设置当前用户具有上传权限
ftpserver.user.admin.writepermission=true
# 最大登录用户数为20
ftpserver.user.admin.maxloginnumber=20
# 同IP登录的用户数为2
ftpserver.user.admin.maxloginperip=2
# 空闲时间为300秒
ftpserver.user.admin.idletime=300
# 上传速率限制为48000000字节每秒
ftpserver.user.admin.uploadrate=48000000
# 下载速率限制为48000000字节每秒
ftpserver.user.admin.downloadrate=48000000
ftpserver.user.anonymous.userpassword=
ftpserver.user.anonymous.homedirectory=./res/home/any
ftpserver.user.anonymous.enableflag=true
ftpserver.user.anonymous.writepermission=false
ftpserver.user.anonymous.maxloginnumber=20
ftpserver.user.anonymous.maxloginperip=2
ftpserver.user.anonymous.idletime=300
ftpserver.user.anonymous.uploadrate=4800
ftpserver.user.anonymous.downloadrate=4800
ftpserver.user.qin.userpassword=qin
ftpserver.user.qin.homedirectory=./res/home/any
ftpserver.user.qin.enableflag=true
ftpserver.user.qin.writepermission=true
ftpserver.user.qin.maxloginnumber=20
ftpserver.user.qin.maxloginperip=2
ftpserver.user.qin.idletime=300
ftpserver.user.qin.uploadrate=4800
ftpserver.user.qin.downloadrate=4800
vi res/conf/ftpd-typical.xml
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://mina.apache.org/ftpserver/spring/v1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd
"
id="myServer"
max-logins="5"
anon-enabled="true"
max-anon-logins="5"
max-login-failures="3" >
<listeners>
<nio-listener name="default" port="21"><!-- ftp服务端口-->
<ssl>
<keystore file="./res/ftpserver.jks" password="password" />
</ssl>
</nio-listener>
</listeners>
<!-- 添加encrypt-password="clear",将MD5密码加密方式修改为clear,也就是明文密码-->
<file-user-manager file="./res/conf/users.properties" encrypt-passwords="clear" />
</server>
3、开启防火墙21端口
参考:Linux:centos7防火墙开放端口_秦毅翔的专栏-CSDN博客
4、启动服务器
cd /usr/src/java/ftp/apache-ftpserver-1.1.1
方式1:临时启动(窗口关闭服务停止)
sh bin/ftpd.sh /res/conf/ftpd-typical.xml
方式2:后台启动(窗口关闭服务仍然运行)
nohup ./bin/ftpd.sh res/conf/ftpd-typical.xml &
二、SpringBoot+FtpClientHelper
2.1、创建Maven项目
参考:SpringBoot第 1 讲:HelloWorld_秦毅翔的专栏-CSDN博客
2.2、修改pom.xm
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- SpringBoot支持01、parent:Begin -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<!-- SpringBoot支持01、parent:End -->
<groupId>org.personal.qin.demos</groupId>
<artifactId>apache_ftp_server_demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- java版本 -->
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SpringBoot:Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot:End -->
<!-- SpringMVC:Begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- SpringMVC:End -->
<!-- apache_ftp:Begin -->
<!-- httpclient begin -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- httpclient end -->
<!-- ftp begin -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.5</version>
</dependency>
<!-- ftp end -->
<!-- apache_ftp:End -->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 资源文件拷贝插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- SpringBoot支持03、添加SpringBoot的插件支持:Begin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- SpringBoot支持03、添加SpringBoot的插件支持:End -->
</plugins>
</build>
</project>
2.3、添加配置文件ftp.properties
#ftp服务器的地址
ftp.host=10.211.55.7
#ftp服务器的端口号(连接端口号)
ftp.port=21
#ftp的用户名
ftp.username=admin
#ftp的密码
ftp.password=admin
#被动模式:回显地址
#ftp.httpPath=http://192.168.134.139
2.4、自定义FTP客户端工具类,用于操作ApacheFTPServer
package demo.ftp.utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import demo.ftp.entity.FileInfo;
@Configuration
@PropertySource(value = { "classpath:ftp.properties" }, ignoreResourceNotFound = false)
public class FtpClientHelper {
@Value("${ftp.host}")
private String host;
@Value("${ftp.port}")
private int port;
@Value("${ftp.username}")
private String username;
@Value("${ftp.password}")
private String password;
/**
* 上传文件(加密空间)
*
* @param relativeFilePath:路径
* @param remoteFileName:文件名
* @param input:输入流
* @return
*/
public FileInfo uploadSecretFile(String relativeFilePath, String remoteFileName, InputStream input) {
boolean isOk = uploadFile(username, password, relativeFilePath, remoteFileName, input);
if(isOk) {
return new FileInfo(remoteFileName, true, "ftp://"+host+":"+port+relativeFilePath+File.separator+remoteFileName, relativeFilePath+File.separator+remoteFileName);
}
return null;
}
/**
* 上传文件(普通空间)
*
* @param relativeFilePath:路径
* @param remoteFileName:文件名
* @param input:输入流
* @return
*/
public FileInfo uploadSimpleFile(String relativeFilePath, String remoteFileName, InputStream input) {
boolean isOk = uploadFile("qin", "qin", relativeFilePath, remoteFileName, input);
if(isOk) {
return new FileInfo(remoteFileName, false, "ftp://"+host+":"+port+relativeFilePath+File.separator+remoteFileName, relativeFilePath+File.separator+remoteFileName);
}
return null;
}
/**
* 下载文件(加密空间)
* http://127.0.0.1:8080/ftp/download?filePath=20211009&fileName=1633763634571336
* ftp://10.211.55.7/20211009/1633763634571336
*
* @param response
* @param relativeFilePath
* @param filename
*/
public void downloadSecretFile(HttpServletResponse response, String relativeFilePath, String filename) {
// jdk7以后的语法,执行之后,jdk会自动关流,无需提供finally手动关流
try (OutputStream os = response.getOutputStream();) {
response.setContentType("application/x-download");
response.addHeader("Content-Disposition", "attachment;filename=" + filename);
boolean isOk = downloadFile(username, password, os, relativeFilePath, filename);
if (isOk) {
Log.i(getClass(), relativeFilePath + "/" + filename + "下载成功");
}
os.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 下载文件(普通空间)
* http://127.0.0.1:8080/ftp/download?filePath=20211009&fileName=1633763634571336
* ftp://10.211.55.7/20211009/1633763634571336
*
* @param response
* @param relativeFilePath
* @param filename
*/
public void downloadSimpleFile(HttpServletResponse response, String relativeFilePath, String filename) {
// jdk7以后的语法,执行之后,jdk会自动关流,无需提供finally手动关流
try (OutputStream os = response.getOutputStream();) {
response.setContentType("application/x-download");
response.addHeader("Content-Disposition", "attachment;filename=" + filename);
boolean isOk = downloadFile("qin", "qin", os, relativeFilePath, filename);
if (isOk) {
Log.i(getClass(), relativeFilePath + "/" + filename + "下载成功");
}
os.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 删除文件(加密空间)
* http://127.0.0.1:8080/ftp/delete?filePath=20211009&fileName=1633764242506557
*
* @param relativeFilePath
* @param filename
* @return
*/
public boolean deleteSecretFile(String relativeFilePath, String filename) {
return deleteFile(username, password, relativeFilePath, filename);
}
/**
* 删除文件(普通空间)
* http://127.0.0.1:8080/ftp/delete?filePath=20211009&fileName=1633764242506557
*
* @param relativeFilePath
* @param filename
* @return
*/
public boolean deleteSimpleFile(String relativeFilePath, String filename) {
return deleteFile("qin", "qin", relativeFilePath, filename);
}
private boolean uploadFile(String username, String password, String relativeFilePath, String remoteFileName,
InputStream input) {
boolean flag = false;
FTPClient ftp = new FTPClient();
ftp.setControlEncoding("UTF-8");
try {
ftp.enterLocalPassiveMode(); //开启被动模式
int reply;
ftp.connect(host, port);// 连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
Log.i(getClass(), "登录ftp服务返回状态码为:" + reply);
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return flag;
}
// 8、changWorkingDirectory(linux上的文件夹):检测所传入的目录是否存在,如果存在返回true,否则返回false
// basePath+filePath-->home/ftp/www/2019/09/02
try {
setWorkingDirectory(ftp, relativeFilePath);
} catch (Exception e) {
e.printStackTrace();
}
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
// 设置为被动模式
// originFilePath就是上传文件的文件名,建议使用生成的唯一命名,中文命名最好做转码
flag = ftp.storeFile(remoteFileName, input);
// flag = ftp.storeFile(new
// String(remoteFileName.getBytes(),"iso-8859-1"),input);
Log.i(getClass(), "要上传的原始文件名为:" + remoteFileName + ", 上传结果:" + flag);
input.close();
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return flag;
}
private boolean downloadFile(String username, String password, OutputStream os, String relativeFilePath,
String filename) {
boolean flag = false;
// FTPSClient ftpClient = new FTPSClient("TLS", true);
FTPClient ftpClient = new FTPClient();
try {
// 连接FTP服务器
ftpClient.connect(host, port);
// 登录FTP服务器
ftpClient.login(username, password);
// 验证FTP服务器是否登录成功
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
return flag;
}
// 切换FTP目录
setWorkingDirectory(ftpClient, relativeFilePath);
// 此处为demo方法,正常应该到数据库中查询fileName
FTPFile[] ftpFiles = ftpClient.listFiles();
for (FTPFile file : ftpFiles) {
String name = file.getName();
if (filename.equalsIgnoreCase(name)) {
flag = ftpClient.retrieveFile(name, os);
Log.i(getClass(), "下载文件:" + relativeFilePath + "/" + name);
os.close();
}
}
ftpClient.logout();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.logout();
} catch (IOException e) {
}
}
}
return flag;
}
private boolean deleteFile(String username, String password, String relativeFilePath, String filename) {
boolean flag = false;
FTPClient ftpClient = new FTPClient();
try {
// 连接FTP服务器
ftpClient.connect(host, port);
// 登录FTP服务器
ftpClient.login(username, password);
// 验证FTP服务器是否登录成功
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
return flag;
}
// 切换FTP目录
// ftpClient.changeWorkingDirectory(basePath);
setWorkingDirectory(ftpClient, relativeFilePath);
int i = ftpClient.dele(filename);
if (i == 250) {// 删除成功状态码
flag = true;
}
ftpClient.logout();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.logout();
} catch (IOException e) {
}
}
}
return flag;
}
/**
* 设置ftp服务器文件上传的路径
*
* @param ftp
* @param relativeFilePath
* @throws Exception
*/
private void setWorkingDirectory(FTPClient ftp, String relativeFilePath) throws Exception {
// 判断工作路径是否存在
boolean exisit = ftp.changeWorkingDirectory(relativeFilePath);
if (!exisit) {
ftp.makeDirectory(relativeFilePath); // 不存在就创建
ftp.changeWorkingDirectory(relativeFilePath); // 设置工作路径
}
Log.i(getClass(), "切换ftp文件路径:" + relativeFilePath);
}
}
Ps:
FTP协议有两种工作方式,Port和Pasv方式,主动和被动式。
(1) PORT(主动模式)
工作的原理: FTP客户端连接到FTP服务器的21端口,发送用户名和密码登录,登录成功后要list列表或者读取数据时,客户端随机开放一个端口(1024以上),发送 PORT命令到FTP服务器,告诉服务器客户端采用主动模式并开放端口;FTP服务器收到PORT主动模式命令和端口号后,通过服务器的20端口和客户端开放的端口连接,发送数据
(2) PASV(被动模式) 这就是上面方法的作用。
工作的原理:FTP客户端连接到FTP服务器的21端口,发送用户名和密码登录,登录成功后要list列表或者读取数据时,发送PASV命令到FTP服务器, 服务器在本地随机开放一个端口(1024以上),然后把开放的端口告诉客户端, 客户端再连接到服务器开放的端口进行数据传输
2.5、启动BootApplication测试
package demo.ftp;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import demo.ftp.entity.FileInfo;
import demo.ftp.utils.FileNameUtil;
import demo.ftp.utils.FtpClientHelper;
@SpringBootApplication
@Controller
@RequestMapping("ftp")
public class FtpBootApplication {
@Autowired
private FtpClientHelper util;
@RequestMapping(value = "upload")
@ResponseBody
public FileInfo upload(MultipartFile file) {
// 根据id调用工具类生成新文件名
String newFileName = FileNameUtil.getFileName(3);
// 生成路径
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String filePath = sdf.format(new Date());
try(InputStream ins = file.getInputStream();){
return util.uploadSecretFile(filePath, newFileName, ins); // 上传到加密空间
// return util.uploadSimpleFile(filePath, newFileName, fis); //上传到普通空间
}catch (Exception e) {
e.printStackTrace();
}
return new FileInfo("", false, "", "");
}
@RequestMapping("delete")
@ResponseBody
public String delete(String filePath, String fileName) {
boolean isok = util.deleteSecretFile(filePath, fileName); // 删除加密空间
// boolean isok = util.deleteSimpleFile(filePath, fileName); //删除普通空间
if (isok) {
return "删除成功";
} else {
return "删除失败";
}
}
@RequestMapping("download")
@ResponseBody
public String download(HttpServletResponse response, String filePath, String fileName) {
// util.downloadSimpleFile(response, filePath, fileName); //下载普通空间
util.downloadSecretFile(response, filePath, fileName); // 下载加密空间
return "";
}
public static void main(String[] args) {
SpringApplication.run(FtpBootApplication.class, args);
}
}