最近做項目需要文件上傳下載的功能模塊,但是考慮客戶的因素沒有選擇阿里雲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();//被動模式配置