Java操作FTP实现上传下载功能
FTP简介
文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层,tcp 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。
FTP也是一个应用程序,基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。可以使用FTP进行下载和上传。
文件传送协议FTP(File Transfer Protocol)是Internet上使用比较广泛的文件传送协议。FTP提供交互式的访问,允许客户指明文件的类型与格式,并允许文件具有存取权限。FTP屏蔽了各种计算机系统的细节,因此适用于在异构网络中任意计算机之间传送文件。它的基本应用就是将文件从一台计算机复制到另一台计算机中。它要存取一个文件,就必须先获得一个本地文件的副本,如果修改文件,也只能对文件的副本进行修改,然后再将修改后的文件副本传回到原节点。
您只要记住几个关键词:交互式、存取权限和副本。
简单文件传送协议TFTP(Trivial File Transfer Protocol)是一个小而易于实现的文件传送协议。TFTP是基于UDP数据报,需要有自己的差错改正措施。TFTP只支持文件传输,不支持交互,没有庞大的命令集。也没有目录列表功能,以及不能对用户进行身份鉴别。但它的代码所占内存较小,不需要硬盘就可以固化TFTP代码,很适合较小的计算机和特殊用途的设备。
您会发现TFTP和FTP一个主要的区别就是它没有交互式,且不进行身份验证。
FTP的客户可以是任意平台
FTP标准端口:
- 20 数据接口
- 21 指令接口
FTP架构
FTP监听于TCP的21号端口,是一种C/S架构的应用程序。在linux中常用的服务端软件和客户端软件一般如下表所示:
FTP数据连接模式
FTP有2种数据连接模式:命令连接和数据连接
- 命令连接:是指文件管理类命令,始终在线的持久性连接,直到用户退出登录为止(可以简单的理解为建立连接)
- 数据连接:是指数据传输,按需创建及关闭的连接(可以简单的理解为传输数据)
数据连接需要关注的点有:
1.数据传输格式
- 文件传输
- 二进制传输
2.数据传输模式
- 主动模式:由服务器端创建数据连接
- 被动模式:由客户端创建数据连接
两种数据传输模式的建立过程:
1.主动模式
命令连接(建立连接通道)
Client端以一个1024以上的随机端口(端口号大于1023小于65535)来连接Server端的21号端口
数据连接(传输数据)
Server端以20号端口去连接Client创建命令连接时使用的随机端口+1的端口
示例:
命令连接:Client(9527)–> Server(21),数据连接:Server(20/tcp) --> Client(9527+1)
2.被动模式
命令连接
Client以一个1024以上的随机端口号(端口号大于1023小于65535)来连接Server端的21号端口,命令连接建立完毕后,Server端会告知Client端用于创建数据连接的随机端口号(端口号大于1023小于65535)
数据连接
Client以创建命令连接时使用的随机端口号+1去连服务器端通过命令连接告知自己的一个随机端口号来创建数据连接
示例:
命令连接:Client(9527) --> Server(21),数据连接:Client(9527+1) --> Server(随机端口)
注意:
主动模式存在弊端,因为客户端的端口是随机的,客户端如果开了防火墙,则服务器端去连客户端创建数据连接时可能会被拒绝
用户认证
Ftp的用户认证主要有三种:
1.虚拟用户:仅用于访问某特定服务中的资源
虚拟用户通过ftp访问的资源位置为给虚拟用户指定的映射成为的系统用户的家目录
2.系统用户:可以登录系统的真实用户(出于对安全性的考虑,这种认证很少使用)
系统用户通过ftp访问的资源位置为用户的家目录
3.匿名用户
匿名用户(映射为ftp用户)的共享资源位置是/var/ftp
服务端
- /etc/pam.d/vsftpd #vsftpd用户认证配置文件
- /etc/vsftpd/ #配置文件目录
- /etc/vsftpd/vsftpd.conf #主配置文件
- vsftpd常见的配置参数
客户端
lftp工具 支持指定用户名和密码登录
Vsftp安装与配置
在linux环境下,使用最多的FTP服务端软件就是Vsftpd!
安装Vsftpd,运行一下命令需要在root用户下进行!
yum install vsftpd 安装
yum remove vsftpd 卸载
启动服务
启动服务 service vsftpd start 或者 systemctl start vsftpd.service
重启服务 systemctl restart vsftpd.service
停止服务 systemctl stop vsftpd.service
设置开机自启动 systemctl enable vsftpd.service
配置文件说明
/etc/vsftpd/vsftpd.conf 这个文件是vsftpd服务的核心配置文件!
我们在修改配置文件的时候,最好先备份一份!
cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak
/etc/vsftpd/ftpusers这个文件是禁止使用vsftpd的用户列表文件。记录不允许访问FTP服务器的用户名单,管理员可以把一些对系统安全有威胁的用户账号记录在此文件中,以免用户从FTP登录后获得大于上传下载操作的权利,而对系统造成损坏。
/etc/vsftpd/user_list这个文件禁止或允许使用vsftpd的用户列表文件。这个文件中指定的用户缺省情况(即在/etc/vsftpd/vsftpd.conf中设置userlist_deny=YES)下也不能访问FTP服务器,在设置了userlist_deny=NO时,仅允许user_list中指定的用户访问FTP服务器。
注意配置文件中的值一定不要有空格
# Example config file /etc/vsftpd/vsftpd.conf
#
#
# 禁止匿名登录
anonymous_enable=NO
# 是否允许本地用户(即linux系统中的用户帐号)登录FTP服务器,默认设置为YES允许
# 本地用户登录后会进入用户主目录,而匿名用户登录后进入匿名用户的下载目录/var/ftp/pub
# 若只允许匿名用户访问,前面加上 #注释掉即可阻止本地用户访问FTP服务器
local_enable=YES
#
# 是否允许本地用户对FTP服务器文件具有写权限,默认设置为YES允许
write_enable=YES
#
# 掩码,本地用户默认掩码为077
# 你可以设置本地用户的文件掩码为缺省022,也可根据个人喜好将其设置为其他值
local_umask=022
#
# 是否允许匿名用户上传文件,须将全局的write_enable=YES。默认为YES
#anon_upload_enable=YES
#
#是否允许匿名用户创建新文件夹
#anon_mkdir_write_enable=YES
# 是否激活目录欢迎信息功能
# 当用户用CMD模式首次访问服务器上某个目录时,FTP服务器将显示欢迎信息
# 默认情况下,欢迎信息是通过该目录下的.message文件获得的
# 此文件保存自定义的欢迎信息,由用户自己建立
dirmessage_enable=YES
#
# 是否让系统自动维护上传和下载的日志文件
# 默认情况该日志文件为/var/log/vsftpd.log,也可以通过下面的xferlog_file选项对其进行设定
# 默认值为NO
xferlog_enable=YES
#
# 是否设定FTP服务器将启用FTP数据端口的连接请求
# ftp-data数据传输,21为连接控制端口
connect_from_port_20=YES
#
# 设定是否允许改变上传文件的属主,与下面一个设定项配合使用
# 注意,不推荐使用root用户上传文件
#chown_uploads=YES
# 设置想要改变的上传文件的属主,如果需要,则输入一个系统用户名
# 可以把上传的文件都改成root属主。whoever:任何人
#chown_username=whoever
#
# 设定系统维护记录FTP服务器上传和下载情况的日志文件
#xferlog_file=/var/log/xferlog
#
#如果你想,你可以有一个标准的ftpd xferlog格式的日志文件。
#注意默认的日志文件位置是/var/log/xferlog。
xferlog_std_fORMat=YES
#
#您可以更改默认值,以使空闲会话超时。
#idle_session_timeout=600
#
# #您可以更改数据连接超时的默认值。
#data_connection_timeout=120
#
#运行vsftpd需要的非特权系统用户,缺省是nobody
#nopriv_user=ftpsecure
#
# 是否识别异步ABOR请求。
# 如果FTP client会下达“async ABOR”这个指令时,这个设定才需要启用
# 而一般此设定并不安全,所以通常将其取消
#async_abor_enable=YES
#
# 是否以ASCII方式传输数据。默认情况下,服务器会忽略ASCII方式的请求。
# 启用此选项将允许服务器以ASCII方式传输数据
# 不过,这样可能会导致由"SIZE /big/file"方式引起的DoS攻击
#ascii_upload_enable=YES
#ascii_download_enable=YES
# 登录FTP服务器时显示的欢迎信息
# 如有需要,可在更改目录欢迎信息的目录下创建名为.message的文件,并写入欢迎信息保存后
#ftpd_banner=Welcome to blah FTP service.
# 黑名单设置。如果很讨厌某些email address,就可以使用此设定来取消他的登录权限
# 可以将某些特殊的email address抵挡住。
#deny_email_enable=YES
# 当上面的deny_email_enable=YES时,可以利用这个设定项来规定哪些邮件地址不可登录vsftpd服务器
# 此文件需用户自己创建,一行一个email address即可
#banned_email_file=/etc/vsftpd/banned_emails
# You may specify an explicit list of local users to chroot() to their home
# directory. If chroot_local_user is YES, then this list becomes a list of
# users to NOT chroot().
# (Warning! chroot'ing can be very dangerous. If using chroot, make sure that
# the user does not have write access to the top level directory within the
# chroot)
chroot_local_user=YES
# 允许 chroot 用户具备写权限
allow_writeable_chroot=YES
#当chroot_list_enable=YES,chroot_local_user=YES时,在/etc/vsftpd.chroot_list文件中列出的用户,可以切换到其他目录;未在文件中列出的用户,不能切换到其他目录。
#当chroot_list_enable=YES,chroot_local_user=NO时,在/etc/vsftpd.chroot_list文件中列出的用户,不能切换到其他目录;未在文件中列出的用户,可以切换到其他目录。
#当chroot_list_enable=NO,chroot_local_user=YES时,所有的用户均不能切换到其他目录。
#当chroot_list_enable=NO,chroot_local_user=NO时,所有的用户均可以切换到其他目录。
chroot_list_enable=YES
# 被列入此文件的用户,在登录后将不能切换到自己目录以外的其他目录
# 从而有利于FTP服务器的安全管理和隐私保护。此文件需自己建立
#chroot_list_file=/etc/vsftpd/chroot_list
#
# 是否允许递归查询。默认为关闭,以防止远程用户造成过量的I/O
#ls_recurse_enable=YES
#
# 是否允许监听。
# 如果设置为YES,则vsftpd将以独立模式运行,由vsftpd自己监听和处理IPv4端口的连接请求
listen=YES
#
# 设定是否支持IPV6。如要同时监听IPv4和IPv6端口,
# 则必须运行两套vsftpd,采用两套配置文件
# 同时确保其中有一个监听选项是被注释掉的
# listen_ipv6=NO
# 设置是否阻扯user_list文件中的用户登录FTP服务器,默认为YES
#userlist_deny=YES/NO
# 设置PAM外挂模块提供的认证服务所使用的配置文件名,即/etc/pam.d/vsftpd文件
# 此文件中file=/etc/vsftpd/ftpusers字段,说明了PAM模块能抵挡的帐号内容来自文件/etc/vsftpd/ftpusers中
pam_service_name=vsftpd
# 是否允许ftpusers文件中的用户登录FTP服务器,默认为NO
# 若此项设为YES,则user_list文件中的用户允许登录FTP服务器
# 而如果同时设置了userlist_deny=YES,则user_list文件中的用户将不允许登录FTP服务器,甚至连输入密码提示信息都没有
userlist_enable=YES
# 是否使用tcp_wrappers作为主机访问控制方式。
# tcp_wrappers可以实现linux系统中网络服务的基于主机地址的访问控制
# 在/etc目录中的hosts.allow和hosts.deny两个文件用于设置tcp_wrappers的访问控制
# 前者设置允许访问记录,后者设置拒绝访问记录。
# 如想限制某些主机对FTP服务器192.168.57.2的匿名访问,编缉/etc/hosts.allow文件,如在下面增加两行命令:
# vsftpd:192.168.57.1:DENY 和vsftpd:192.168.57.9:DENY
# 表明限制IP为192.168.57.1/192.168.57.9主机访问IP为192.168.57.2的FTP服务器
# 此时FTP服务器虽可以PING通,但无法连接
tcp_wrappers=YES
#开启被动模式
pasv_enable=YES
# 传输文件的端口范围,如果是云服务器记得开放端口,linux服务器本身也要开放端口
pasv_min_port=9000
pasv_max_port=9005
传输模式配置
开启被动模式:
connect_from_port_20=NO(默认为YES) #设置是否允许主动模式
pasv_enable=YES(默认为YES) #设置是否允许被动模式
pasv_min_port=50000(default:0(use any port))
pasv_max_port=60000(default:0(use any port))
开启主动模式:
connect_from_port_20=YES
pasv_enable=NO
系统用户配置
linux创建用户:
useradd -d /home/ftp/compass -s /sbin/nologin compass
passwd compass
为虚拟用户创建家目录
mkdir -p /home/ftp/compass
设置权限
chown -R compass:compass /home/ftp/compass
配置selinux允许ftp访问home和外网访问
[root@master ~]# setsebool -P allow_ftpd_full_access on
[root@master ~]# setsebool -P tftp_home_dir on
重启vsftpd服务
[root@master ~]# systemctl restart vsftpd
无法登录的情况 在/etc/vsftpd/vsftpd.conf配置文件中添加了以下两句
chroot_local_user=YES #原本就有,取掉注释就好
allow_writeable_chroot=YES #添加
chown compass:compass -R /home/ftp/compass
java操作ftp文件服务器
有时候,我们需要接收用户上传的文件存储到服务器,然后保存起来,下次用户可以直接下载,或者是保存为档案,我这里提供三个方法,一个是上传文件到ftp,还有就是从ftp下载文件,还有就是删除ftp服务器删的文件
1.引入依赖
<!-- ftp上传下载-->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.7</version>
</dependency>
2.提供接口
import javax.servlet.Http.httpservletResponse;
import java.io.InputStream;
import java.io.OutputStream;
public interface FtpService {
Boolean uploadFile(InputStream inputStream, String fileName, String filePath);
byte[] downloadFileBytes(String ftpFilePath, HttpServletResponse response);
Boolean deleteFile(String ftpFilePath);
}
3.对提供操作ftp接口进行实现
import java.io.*;
import compass.token.pocket.com.common.ajaxResult;
import compass.token.pocket.com.entity.FtpInstanceEntity;
import compass.token.pocket.com.service.FtpService;
import compass.token.pocket.com.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Service
public class FtpServiceImpl implements FtpService {
@Resource
FtpInstanceEntity ftpInstanceEntity;
@Override
public Boolean uploadFile(InputStream inputStream, String fileName, String filePath) {
// 定义保存结果
boolean isSuccess = false;
// 初始化连接
FTPClient ftp = connectFtpServer();
if (ftp != null) {
try {
// 设置文件传输模式为二进制,可以保证传输的内容不会被改变
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//注:上传文件都为0字节,设置为被动模式即可
ftp.enterLocalPassiveMode();
// 跳转到指定路径,逐级跳转,不存在的话就创建再进入
toPathOrCreateDir(ftp, filePath);
// 上传文件 参数:上传后的文件名,输入流,,返回Boolean类型,上传成功返回true
isSuccess = ftp.storeFile(fileName, inputStream);
// 关闭输入流
inputStream.close();
// 退出ftp
ftp.loGout();
} catch (IOException e) {
log.error(e.toString());
} finally {
if (ftp.isConnected()) {
try {
// 断开ftp的连接
ftp.disconnect();
} catch (IOException ioe) {
log.error(ioe.toString());
}
}
}
}
return isSuccess;
}
@Override
public byte[] downloadFileBytes(String ftpFilePath , HttpServletResponse response) {
FTPClient ftp = connectFtpServer();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
ftp.enterLocalPassiveMode();
boolean retrieveFile = ftp.retrieveFile(ftpFilePath, stream);
if (!retrieveFile){
throw new RuntimeException("FTP文件下载失败");
}
ftp.logout();
return stream.toByteArray();
} catch (Exception e) {
log.error("FTP文件下载失败!" + e.toString());
ResponseUtil.response(response,AjaxResult.businessError(e.getMessage(), "-1"));
throw new RuntimeException(e.getMessage());
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
log.error(ioe.toString());
}
}
}
}
@Override
public Boolean deleteFile(String ftpFilePath ) {
FTPClient ftp = connectFtpServer();
boolean result = false;
try {
result = ftp.deleteFile(ftpFilePath);
ftp.logout();
return result;
} catch (Exception e) {
log.error("FTP文件删除失败!" + e.toString());
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
log.error(ioe.toString());
}
}
}
return result;
}
private FTPClient connectFtpServer() {
// 创建FTPClient对象(对于连接ftp服务器,以及上传和上传都必须要用到一个对象)
FTPClient ftpClient = new FTPClient();
// 设置连接超时时间
ftpClient.setConnectTimeout(1000 * 10);
// 设置ftp字符集
ftpClient.setControlEncoding("utf-8");
// 设置被动模式,文件传输端口设置,否则文件上传不成功,也不报错
ftpClient.enterLocalPassiveMode();
try {
// 定义返回的状态码
int replyCode;
// 连接ftp(当前项目所部署的服务器和ftp服务器之间可以相互通讯,表示连接成功)
ftpClient.connect(ftpInstanceEntity.getHost());
// 输入账号和密码进行登录
ftpClient.login(ftpInstanceEntity.getUsername(), ftpInstanceEntity.getPassword());
// 接受状态码(如果成功,返回230,如果失败返回503)
replyCode = ftpClient.getReplyCode();
// 根据状态码检测ftp的连接,调用isPositiveCompletion(reply)-->如果连接成功返回true,否则返回false
if (!FTPReply.isPositiveCompletion(replyCode)) {
log.info("connect ftp {} failed", ftpInstanceEntity.getHost());
// 说明连接失败,需要断开连接
ftpClient.disconnect();
throw new RuntimeException(String.format("connect ftp %s failed", ftpInstanceEntity.getHost()));
}
log.info("连接状态码:" + replyCode);
} catch (IOException e) {
log.error("connect fail:" + e.toString());
throw new RuntimeException(String.format("connect ftp %s error", ftpInstanceEntity.getHost()));
}
return ftpClient;
}
private void toPathOrCreateDir(FTPClient ftp, String filePath) throws IOException {
String[] dirs = filePath.split("/");
for (String dir : dirs) {
if (StringUtils.isEmpty(dir)) {
continue;
}
if (!ftp.changeWorkingDirectory(dir)) {
ftp.makeDirectory(dir);
ftp.changeWorkingDirectory(dir);
}
}
}
}
4.配置ftp相关参数
在配置文件中配置连接ftp服务器的配置
ftp:
#ftp服务器的地址
host: 127.0.0.1
#ftp服务器的端口号(连接端口号)
port: 21
#ftp的用户名
username: compass
#ftp的密码
passWord: 6317738ef8324199a24602308f3a9a18
#ftp上传的根目录
basePath: /home/ftp/compass
#回显地址
httpPath: ftp://127.0.0.1
然后将配置文件的内容读取到一个bean里面
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "ftp")
public class FtpInstanceEntity {
private String host;
private String port;
private String username;
private String password;
private String basePath;
private String httpPath;
}
5.写测试controller
package compass.token.pocket.com.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import compass.token.pocket.com.common.AjaxResult;
import compass.token.pocket.com.entity.FtpInstanceEntity;
import compass.token.pocket.com.service.FtpService;
import compass.token.pocket.com.utils.FileUtils;
import compass.token.pocket.com.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.WEB.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/fileTestController")
public class FileTestController {
@Resource
private FtpService ftpService;
@Resource
FtpInstanceEntity ftpInstanceEntity;
@PostMapping(value = "/pdfUpload")
public AjaxResult<String> pdfUpload(@RequestParam("file") MultipartFile file) {
try {
String fileDir = DateUtil.format(new Date(), "yyyy-MM-dd");
boolean upload = ftpService.uploadFile(file.getInputStream(), file.getOriginalFilename(), fileDir);
if (upload) {
String savePath = ftpInstanceEntity.getBasePath() + "/" + fileDir + "/" + file.getOriginalFilename();
return AjaxResult.success("上传成功", savePath);
} else {
return new AjaxResult<>("file upload error");
}
} catch (IOException e) {
e.printStackTrace();
}
return new AjaxResult<>("file upload error");
}
@GetMapping(value = "/pdfDownload", produces = "application/octet-stream")
public void downloadFile(@RequestParam("ftpPath") String ftpPath, HttpServletRequest request, HttpServletResponse response) {
String userAgent = request.getHeader("User-Agent");
String fileName = ftpPath.substring(ftpPath.lastIndexOf('/') + 1);
try {
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
//IE浏览器处理
fileName = URLEncoder.encode(fileName, "UTF-8");
} else {
// 非IE浏览器的处理:
fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
}
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/octet-stream; charset=UTF-8");
byte[] bytes = ftpService.downloadFileBytes( ftpPath,response);
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
@PostMapping("ftpDeleteFile")
public AjaxResult<Boolean> ftpDeleteFile(@RequestParam("ftpPath") String ftpPath){
Boolean isSuccess = ftpService.deleteFile(ftpPath);
if (isSuccess){
return AjaxResult.success("删除成功", true);
}else {
return AjaxResult.businessError("删除失败", false);
}
}
}
到这里操作ftp文件服务器的基本操作差不多结束了,讲的不是特别详细,但是基本来说,够用,如果遇到不能解决的问题请自行百度。
以上就是Java操作FTP实现上传下载功能的详细内容,更多关于Java FTP上传下载的资料请关注其它相关文章!
相关文章