vsftpd+nginx搭建一个文件服务器

最近做项目需要文件上传下载的功能模块,但是考虑客户的因素没有选择阿里云OSS文件服务

如果条件允许的情况下选择云存储的OSS是比较合适的,下面介绍如何自己搭建一个FTP文件服务器:

根据当时项目环境:docker部署spring cloud项目,Linux系统环境

安装vsftpd

Linux执行命令:yum -y install vsftpd

安装完后,有/etc/vsftpd/vsftpd.conf 文件,是vsftp的配置文件;

查看是否安装成功

执行命令:rpm -qa| grep vsftpd

默认配置文件

文件路径:/etc/vsftpd/vsftpd.conf

创建虚拟用户

# 在根目录或者用户目录下创建ftp文件夹,这里选择在根目录(目录为Nginx目录)

mkdir /home/aeccspringcloud/website/nginx/html/ftpfile

# 添加用户

useradd ftpuser -d /home/aeccspringcloud/website/nginx/html/ftpfile -s /sbin/nologin

# 修改ftpfile文件夹权限

chown -R ftpuser.ftpuser /home/aeccspringcloud/website/nginx/html/ftpfile

# 重设ftpuser密码

passwd ftpuser

密码为:#设置你的密码

配置

执行命令:cd /etc/vsftpd

# 创建文件chroot_list

执行命令:vim chroot_list

# 添加内容:ftpuser,保存退出执行:wq

执行命令:vim /etc/selinux/config

# 修改SELINUX=disabled

执行setenforce 0表示关闭selinux防火墙

setenforce 0

修改主配置

修改主配置文件:vim /etc/vsftpd/vsftpd.conf

1. 查找文本信息找到如下注释:

取消ftpd_banner注释,新增加三行配置

2. 继续查找choose_list,取消如下两行配置的注释

chroot_list_enable=YES

# (default follows)

chroot_list_file=/etc/vsftpd/chroot_list

3. 查找anon,将如下的配置项值改为NO

4. 在最底端添加被动传输的端口,最大和最小端口值,在ftp上传文件传输时需要使用的,虽然采用默认的端口范围

也可以,但是防火墙的设置就不能太严格,所以线上环境为了安全考虑建议加上端口配置,方便防火墙配置;

防火墙配置

vim /etc/sysconfig/iptables

# 添加vsftpd的端口配置

-A INPUT -p TCP --dport 61001:62000 -j ACCEPT

-A OUTPUT -p TCP --sport 61001:62000 -j ACCEPT

 

-A INPUT -p TCP --dport 20 -j ACCEPT

-A OUTPUT -p TCP --sport 20 -j ACCEPT

-A INPUT -p TCP --dport 21 -j ACCEPT

-A OUTPUT -p TCP --sport 21 -j ACCEPT

启动服务

重启防火墙:service iptables restart

启动vsftpd:systemctl start vsftpd.service

在/home/aeccspringcloud/ftpfile目录上传一些测试文件及目录,方便验证查看;

查看服务状态

执行命令:systemctl status vsftpd

测试客户端连接状态(只能访问本目录)

配置nginx服务

注:当前的alias标红路径为docker映射路径在非docker环境下为当前系统物理路径

# 文件服务器代理配置

   location /aeccfile {

       alias /usr/share/nginx/html/ftpfile;

       autoindex on;

   }

解决nginx访问404问题

使用Nginx做图片服务器时候,配置之后图片访问一直是 404问题解决

总结:
root响应的路径:配置的路径(root指向的路径)+ 完整访问路径(location的路径)+ 静态文件
alias响应的路径:配置路径+静态文件(去除location中配置的路径)

一般情况下,在location /中配置root,在location /other中配置alias

Java项目中通过FTP连接文件服务器实现上传下载

该项目使用spring boot,所以ftp的相关配置连接写在了配置文件中

#静态资源对外暴露的访问路径
file:
   relativeUrl: /aeccfile/

ftp:
   ipAddress: # ftp服务器地址
   port: 21 # 默认端口
   username: ftpuser # 用户名
   password: # 文件服务设置的密码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/** 
* @author: Dbw
* @date: 2019年8月12日 下午6:36:46 
*/

@Component
public class ConfigBeanValue {
 
    @Value("${file.relativeUrl}")
    public String relativeUrl;
    
    @Value("${ftp.ipAddress}")
    public String ipAddress;
 
    @Value("${ftp.port}")
    public Integer port;
    
    @Value("${ftp.username}")
    public String username;
 
    @Value("${ftp.password}")
    public String password;
    
}

文件上传

/** 
* @author: Dbw
* @date: 2019年8月20日 下午6:16:53 
* @description: ftp文件上传下载工具类
*/

public class FtpUploadUtils {
	
	public static FTPClient getFTPClient(ConfigBeanValue configBeanValue) throws SocketException, IOException {
		FTPClient ftpClient = new FTPClient();
		ftpClient.setControlEncoding("utf-8");
		//设置传输超时时间为60秒
		ftpClient.setDataTimeout(60000);
	    //连接超时为60秒
		ftpClient.setConnectTimeout(60000);
		try {
			String ftpHost = configBeanValue.ipAddress;
			Integer ftpPort = configBeanValue.port;
			String ftpUserName = configBeanValue.username;
			String ftpPassword = configBeanValue.password;
			ftpClient.connect(ftpHost, ftpPort);// 连接FTP服务器
			ftpClient.login(ftpUserName, ftpPassword);// 登陆FTP服务器
			if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
				ftpClient.disconnect();
			}
		} catch (SocketException e) {
			e.printStackTrace();
			throw new SocketException("FTP的IP地址可能错误,请正确配置。");
		} catch (IOException e) {
			e.printStackTrace();
			throw new SocketException("FTP的端口错误,请正确配置。");
		}
		return ftpClient;
	}
	
    /**
     * 	上传文件
     * @param realPathName ftp服务保存地址
     * @param uuidName 上传到ftp的文件名
     * @param inputStream 输入文件流
     * @return
     */
    public static boolean uploadFile(FTPClient ftpClient,String realPathName, String uuidName, InputStream inputStream) throws Exception{
        boolean flag = false;
        try{
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.setFileTransferMode(FTPClient.BINARY_FILE_TYPE);
            flag = createDirecroty(ftpClient, realPathName);
            flag = ftpClient.makeDirectory(realPathName);
            flag = ftpClient.changeWorkingDirectory(realPathName);
            flag = ftpClient.storeFile(uuidName, inputStream);
            inputStream.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    
    //改变目录路径
    public static boolean changeWorkingDirectory(FTPClient ftpClient, String directory) throws Exception {
        boolean flag = true;
        try {
            flag = ftpClient.changeWorkingDirectory(directory);
        } catch (IOException ioe) {
            ioe.printStackTrace();
            throw new Exception("改变目录路径失败");
        }
        return flag;
    }
	
	//创建多层目录文件,如果有ftp服务器已存在该文件,则不创建,如果无,则创建
    public static boolean createDirecroty(FTPClient ftpClient, String remote) throws Exception {
        boolean success = true;
        String directory = remote + "/";
        // 如果远程目录不存在,则递归创建远程服务器目录
        if (!directory.equalsIgnoreCase("/") && !changeWorkingDirectory(ftpClient, new String(directory))) {
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            String path = "";
            String paths = "";
            while (true) {
                String subDirectory = new String(remote.substring(start, end).getBytes("GBK"), "iso-8859-1");
                path = path + "/" + subDirectory;
                //false表示当前文件夹下没有文件
                if (!existFile(ftpClient, path)) {
                    if (makeDirectory(ftpClient, subDirectory)) {
                        changeWorkingDirectory(ftpClient, subDirectory);
                    } else {
                        changeWorkingDirectory(ftpClient, subDirectory);
                    }
                } else {
                    changeWorkingDirectory(ftpClient, subDirectory);
                }
                paths = paths + "/" + subDirectory;
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return success;
    }
 
    //判断ftp服务器文件是否存在
    public static boolean existFile(FTPClient ftpClient, String path) throws IOException {
        boolean flag = false;
        FTPFile[] ftpFileArr = ftpClient.listFiles(path);
        if (ftpFileArr.length > 0) {
            flag = true;
        }
        return flag;
    }

	//创建目录
    public static boolean makeDirectory(FTPClient ftpClient, String dir) {
        boolean flag = true;
        try {
            flag = ftpClient.makeDirectory(dir);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    
    /**
     * 	文件下载 	
     * @param ftpClient
     * @param originname
     * @param path
     * @return
     */
	public static byte[] download(FTPClient ftpClient, String originname, String path) {
    	InputStream inputStream = null;
    	byte[] bytes = null;
    	try {
    		ftpClient.changeWorkingDirectory(path);
			FTPFile[] ftpFiles = ftpClient.listFiles();
			for (FTPFile ftpFile : ftpFiles) {
				//判断文件是否存在
				if(ftpFile.getName().equals(originname)) {
					//返回文件对象
					String filePath = path + originname;
					// 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据,ftp server可能每次开启不同的端口来传输数据,
		            // 但是在Linux上,由于安全限制,可能某些端口没有开启,所以就出现阻塞。
		    		ftpClient.enterLocalPassiveMode();
					inputStream = ftpClient.retrieveFileStream(filePath);
					bytes = IOUtils.toByteArray(inputStream);
				}
			}
	        if (inputStream != null) {
	            inputStream.close();
	        }
		} catch (IOException e) {
			e.printStackTrace();
		}
    	return bytes;
    }
    
}

然后通过文件上传下载的接口调用,实现文件上传下载即可,以下记录下载接口调用:

	/**
	 * 	文件下载
	 */
	public R download(XmWd xmWd, HttpServletRequest request, HttpServletResponse response) throws IOException {
		String path = xmWd.getPath();
		String originname = xmWd.getOriginname();
		String fileName = xmWd.getName();
		//获取下载的文件对象
		FTPClient ftpClient = FtpUploadUtils.getFTPClient(configBeanValue);
		ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        ftpClient.setFileTransferMode(FTPClient.BINARY_FILE_TYPE);
		byte[] buffer = FtpUploadUtils.download(ftpClient, originname, path);
		if(buffer != null && buffer.length > 0) {
			response.setContentType("application/octet-stream");// 设置强制下载不打开
    		response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "utf-8"));  
    		BufferedInputStream bis = null;
    		InputStream inputStream = null;
    		try {
    			inputStream = new ByteArrayInputStream(buffer);
                bis = new BufferedInputStream(inputStream);
                OutputStream os = response.getOutputStream();
                int i = bis.read(buffer);
                while (i != -1) {
                    os.write(buffer, 0, i);
                    i = bis.read(buffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            	if (bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                    	e.printStackTrace();
                    }
            	}
            }
    		return R.ok("下载成功");
		} else {
			return R.ok("下载失败").put("code", 500);
		}
	}	

通过浏览器可下载;

问题整理:

实际应用中会出现文件上传到ftp服务器中文件size=0的情况,造成的原因可能时设置主动模式/被动模式的原因

ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.setFileTransferMode(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
flag = createDirecroty(ftpClient, realPathName);
flag = ftpClient.makeDirectory(realPathName);
flag = ftpClient.changeWorkingDirectory(realPathName);
flag = ftpClient.storeFile(uuidName, inputStream);

添加ftpClient.enterLocalPassiveMode();//被动模式配置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章