项目中开始使用http协议进行传输文件,但是终端提出传输的字节超过限度,要使用FTP文件服务器进行断点续传,每次1k的进行传输. 老大让我在开发环境中搭建vsftp服务器,一起没操作过,现在记录下来留做以后的回顾学习;
vsftp简介
vsftpd是一款在
Linux发行版中最受推崇的FTP服务器程序。特点是小巧轻快,安全易用。ftp作为文件服务器,断点续传是和http传输的一个区别;
CentOS6.5安装vsftp
以管理员(root)身份进行一下操作:
1.检测是否安装了vsftp
[root@dev04 ~]# rpm -qa |grep vsftpd
vsftpd-2.2.2-14.el6.x86_64
我是安装过的, 命令下方返回的是我的vsftp版本
检测到安装过后如果想卸载执行:
rpm -e vsftpd
再执行
rpm -qa |grep vsftpd
2.安装vsftp
yum install vsftpd* -y
3.设置开机自启vsftp ftp服务
chkconfig vsftpd on
4.启动ftp服务
[root@dev04 vsftpd]# service vsftpd start
关闭服务:
[root@dev04 vsftpd]# service vsftpd stop
重启服务:
[root@dev04 vsftpd]# service vsftpd restart
5.配置vsftp的配置文件vsftpd.conf
在 /etc/vsftpd目录下有三个文件和一个执行命令?-rw------- 1 root root 125 Jul 24 2015 ftpusers
-rw------- 1 root root 371 Apr 6 14:11 user_list
-rw------- 1 root root 4737 Apr 1 15:51 vsftpd.conf
-rwxr--r-- 1 root root 338 Jul 24 2015 vsftpd_conf_migrate.sh
要修改的配置信息都在vsftpd.conf配置文件中,修改配置文件之前最好进行备份
[root@dev04 vsftpd]# cp vsftpd.conf vsftpd.conf.bak
再进行编辑修改配置文件
[root@dev04 vsftpd]# vim /etc/vsftpd/vsftpd.conf
主要的就是匿名用户的访问权限和 新增本地用户设置访问权限
因为要提供给终端使用FTP服务器映射的公网进行访问,又不行暴露服务器文件所在的目录,
必须支持匿名用户的访问, ftp匿名用户默认目录为 /var/ftp/pub目录下
vsftpd.conf配置文件主要修改
anonymous_enable=YES // 支持匿名登录,如果改为NO不支持匿名登录
write_enable=YES //支持读
anon_upload_enable=YES //支持匿名改写
anon_mkdir_write_enable=YES //支持匿名用户创建子目录
6.创建用户ftpuser,指定目录/home/ftpuser,禁止SSH登录权限
useradd -d /home/ftpuser -g ftp -s /sbin/nologin ftpuser
给帐号设置密码
passwd ftpuer
注意: 在设置密码时会提示什么密码过于简单,或者密码为单词词组等等,不用管,它已经进行设置了,要确认两次输入的密码都相同;输入两次密码后出现successful.....就代表设置密码成功了;
创建用户时遇到的问题:
1.我开始是关闭匿名用户的登录权限的,设置了ftpuser, 终端访问时必须要输入 ftp://username:password@IP地址:port/访问路径 这样会暴露文件存放的路径,不合理(我觉得可能是我设置ftpuser用户时没有在配置文件中配置默认目录什么的,因为按照前面这样访问时候默认进入默认目录/ftpuser的,这点没怎么清楚==)
2.项目中上传文件原本是使用fastDFS文件服务器的,现在改为FTP,将固件上传到匿名用户目录下,提供终端下载
新建了anon用户将默认目录设置为匿名访问的目录 /var/ftp/pub
useradd -d /var/ftp -g ftp -s /sbin/nologin anon
在使用java进行上传文件时发现anon用户不能连接上该目录,查资料说是没有对目录进行设置权限
注意的事项是的:
1、pub目录的权限不能全开,全开会报错,chmod 755 -R pub
2、但是这样也会带来一个问题,当匿名用户登录后,在pub下的newftp目录下想上传一个文件,会报错,因为匿名用户(ftp)无法在该目录下(该目录属于root用户)创建一个新文件的,因为该目录下他没有权限,所以要加入其他用户(o)的写权限,chmod o+w /var/ftp/pub/.
现在权限变为:
drwxr-xrwx 2 root root 4096 06-20 16:55 pub
其实就是757的权限,此时匿名用户(ftp)就可以在该目录下上传文件了。
最后重启服务: service vsftpd restart
附上MultipartFile使用ftp上传文件
1.maven项目中添加pom文件
<!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
2.ftp配置文件 ftp_client.properties
#公网IP映射地址: 218.205.115.XXX 2121
ftp_public_network_add = 218.205.115.XXX
#FTP公网端口
ftp_public_network_port = 2121
#FTP服务器地址172.23.28.XXX
ftp_host = 172.23.28.XXX
#21
ftp_port = 21
##匿名用户 anonymous 创建一个用户名为ftpuser,目录在pub下,具有增删改查/新建子目录权限
ftp_user_name = ftpuser
ftp_user_pwd = ftpuser
#匿名登录上传目录 /var/ftp/pub
remoteFile_addr = /var/ftp/pub/
3.自定义配置文件的pojo类,提高代码扩展性
@Component
public class FTPConfig {
/** FTP服务器IP地址 */
@Value("#{configProperties['ftp_host']}")
private String fTPhost = "localhost";
/** FTP服务器端口 */
@Value("#{configProperties['ftp_port']}")
private int ftpPort = 21;
/** FTP服务器用户名 */
@Value("#{configProperties['ftp_user_name']}")
private String ftpUserName;
/** FTP用户密码 */
@Value("#{configProperties['ftp_user_pwd']}")
private String ftpUserPasswd;
/** 用户远程上传文件存储目录 */
@Value("#{configProperties['remoteFile_addr']}")
private String remoteFileAddr;
/** FTP公网映射地址 */
@Value("#{configProperties['ftp_public_network_add']}")
private String ftpNetAdd;
/** FTP公网端口 */
@Value("#{configProperties['ftp_public_network_port']}")
private int ftpNetPort = 2121;
}
4.FTP上传下载的工具类FTPFileManager
/**
* @Type ftpClientFileManager.java
* @Desc
* @author 123
* @date 2017年3月31日 下午2:50:58
* @version
*/
@Service
public class FTPFileManager {
private static final Logger logger = LoggerFactory.getLogger(FTPFileManager.class);
private FTPClient ftpClient = new FTPClient();
/** 路径分隔符 */
private static final String SEPARATOR = "/";
/** FTP访问路径前缀 */
private static final String FTP_PREFIX = "ftp://";
@Autowired
private FTPConfig config;
/**
* @Title: upload
*
* @Description: 文件上传
*
* @param uploadFile
* MultipartFile
* @return
* 返回保存路径 + 文件名
* @throws Exception
*
*/
public String upload(MultipartFile uploadFile) throws Exception {
ftpClient = new FTPClient();
ftpClient.connect(config.getfTPhost(), config.getFtpPort());
ftpClient.login(config.getFtpUserName(), config.getFtpUserPasswd());
//设置ftp字节流
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
int reply;
reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect();
return null;
}
ftpClient.enterLocalPassiveMode();
String basePath = config.getRemoteFileAddr();
ftpClient.changeWorkingDirectory(basePath);
//为当天日期文件创建追加目录
Date today = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
String appendFile = sdf.format(today);
//切换到上传目录
if (!ftpClient.changeWorkingDirectory(basePath + appendFile)) {
//如果目录不存在创建目录
String[] dirs = appendFile.split(SEPARATOR);
String tempTargetPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir))
continue;
tempTargetPath += SEPARATOR + dir;
if (!ftpClient.changeWorkingDirectory(tempTargetPath)) {
if (!ftpClient.makeDirectory(tempTargetPath)) {
logger.error("ftp文件目录创建失败!");
return null;
} else {
ftpClient.changeWorkingDirectory(tempTargetPath);
}
}
}
}
String fileName = new String(uploadFile.getOriginalFilename().getBytes("utf-8"),
"iso-8859-1");
/**
* 避免文件过多后出现重名,设置30位随机数为文件名称
*/
// 获取文件扩展名 形如: .txt
String fileExt = fileName.substring(fileName.lastIndexOf("."));
String newFileName = RandomUtils.randomAlphanumericStrictly(30) + fileExt;
InputStream is = uploadFile.getInputStream();
if (!ftpClient.storeFile(newFileName, is)) {
logger.error("文件{}存储过程出错", fileName);
return null;
}
is.close();
ftpClient.disconnect();
/* String finalFile = basePath + SEPARATOR + appendFile + SEPARATOR + fileName;*/
//直接存储为匿名用户可直接访问下载的地址
String finalFile = FTP_PREFIX + config.getFtpNetAdd() + ":" + config.getFtpNetPort()
+ SEPARATOR + "pub" + SEPARATOR + appendFile + SEPARATOR + newFileName;
logger.info("文件上传FTP服务器成功, 访问文件路径为 : " + finalFile);
return finalFile;
}
/**
* 从FTP服务器上下载文件
* @param remote 远程文件路径
* @param local 本地文件路径
* @return 是否成功
* @throws Exception
*/
public boolean download(String remote, String local) throws Exception {
ftpClient = new FTPClient();
ftpClient.connect(config.getfTPhost(), config.getFtpPort());
ftpClient.login(config.getFtpUserName(), config.getFtpUserPasswd());
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
int reply;
reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect();
return false;
}
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
boolean result;
File f = new File(local);
FTPFile[] files = ftpClient.listFiles(remote);
if (files.length != 1) {
System.out.println("远程文件不唯一");
ftpClient.disconnect();
return false;
}
long lRemoteSize = files[0].getSize();
if (f.exists()) {
OutputStream out = new FileOutputStream(f, true);
System.out.println("本地文件大小为:" + f.length());
if (f.length() >= lRemoteSize) {
logger.error("本地文件大小大于远程文件大小,下载中止");
out.close();
ftpClient.disconnect();
return false;
}
ftpClient.setRestartOffset(f.length());
result = ftpClient.retrieveFile(remote, out);
out.close();
} else {
OutputStream out = new FileOutputStream(f);
result = ftpClient.retrieveFile(remote, out);
out.close();
}
ftpClient.disconnect();
return result;
}
}
/**
* Revision history
* -------------------------------------------------------------------------
*
* Date Author Note
* -------------------------------------------------------------------------
* 2017年3月31日 123 creat
*/
由于项目中没使用到文件下载,我只在本地进行了简单的下载centOS中FTP文件测试.
简单的记录.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
支持断点续传的上传方法
/**
* 上传文件到FTP服务器,支持断点续传
* @param local 本地文件名称,绝对路径
* @param remote 远程文件路径,使用/home/directory1/subdirectory/file.ext 按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构
* @return 上传结果
* @throws IOException
*/
public String upload(String local, String remote) throws IOException {
//设置PassiveMode传输
ftpClient.enterLocalPassiveMode();
//设置以二进制流的方式传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
String uploadStatus;
//对远程目录的处理
String remoteFileName = remote;
if (remote.contains("/")) {
remoteFileName = remote.substring(remote.lastIndexOf("/") + 1);
String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(directory)) {
//如果远程目录不存在,则递归创建远程服务器目录
int start = 0;
int end = 0;
if (directory.startsWith("/")) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf("/", start);
while (true) {
String subDirectory = remote.substring(start, end);
if (!ftpClient.changeWorkingDirectory(subDirectory)) {
if (ftpClient.makeDirectory(subDirectory)) {
ftpClient.changeWorkingDirectory(subDirectory);
} else {
System.out.println("创建目录失败");
return UploadStatus.Create_Directory_Fail;
}
}
start = end + 1;
end = directory.indexOf("/", start);
//检查所有目录是否创建完毕
if (end <= start) {
break;
}
}
}
}
//检查远程是否存在文件
FTPFile[] files = ftpClient.listFiles(remoteFileName);
if (files.length == 1) {
long remoteSize = files[0].getSize();
File f = new File(local);
long localSize = f.length();
if (remoteSize == localSize) {
return UploadStatus.File_Exits;
} else if (remoteSize > localSize) {
return UploadStatus.Remote_Bigger_Local;
}
//尝试移动文件内读取指针,实现断点续传
InputStream is = new FileInputStream(f);
if (is.skip(remoteSize) == remoteSize) {
//设置文件开始
ftpClient.setRestartOffset(remoteSize);
if (ftpClient.storeFile(remote, is)) {
is.close();
return UploadStatus.Upload_From_Break_Success;
}
}
//如果断点续传没有成功,则删除服务器上文件,重新上传
if (!ftpClient.deleteFile(remoteFileName)) {
return UploadStatus.Delete_Remote_Faild;
}
is = new FileInputStream(f);
if (ftpClient.storeFile(remote, is)) {
uploadStatus = UploadStatus.Upload_New_File_Success;
} else {
uploadStatus = UploadStatus.Upload_New_File_Failed;
}
is.close();
} else {
InputStream is = new FileInputStream(local);
if (ftpClient.storeFile(remoteFileName, is)) {
uploadStatus = UploadStatus.Upload_New_File_Success;
} else {
uploadStatus = UploadStatus.Upload_New_File_Failed;
}
is.close();
}
return uploadStatus;
}