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();//被動模式配置

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