springboot上傳下載檔案(2)---搭建獨立的檔案伺服器
哇!chrome的自動網頁翻譯,把這篇文章英文單詞全部弄亂了,難受啊= =
上一篇說道隨著業務不斷髮展,將程式碼和檔案放在同一伺服器的弊端就會越來越明顯。
為了解決上面的問題引入獨立圖片伺服器,工作流程如下:專案上傳檔案時,首先通過FTP或者SSH將檔案上傳到圖片伺服器的某個目錄下,再通過ngnix或者阿帕奇來訪問此目錄下的檔案,返回一個獨立域名的圖片URL地址,前端使用檔案時就通過這個URL地址讀取。
優點:圖片訪問是很消耗伺服器資源的(因為會涉及到作業系統的上下文切換和磁碟I / O操作),分離出來後,網路/應用伺服器可以更專注發揮動態處理的能力;獨立儲存,更方便做擴充套件,容災和資料遷移;方便做圖片訪問請求的負載均衡,方便應用各種快取策略(HTTP Header,Proxy Cache等),也更加方便遷移到CDN。
缺點:單機存在效能瓶頸,容災,垂直擴充套件性稍差即檔案伺服器掛了怎麼辦?
前提:
centos7
目錄
1、安裝FTP元件,配置FTP伺服器
1.1、安裝yum
以root使用者登入,接下來省掉許多麻煩
$apt install yum
如果報錯,就apt-get update一下
1.2、通過yum安裝FTP
$yum -y install vsftpd
1.3、修改配置
使用者訪問模式配置
vsftpd的的服務訪問模式有三種:匿名使用者模式,系統使用者模式和虛擬使用者模式!
我這裡採用系統- -使用者
(1)/etc/vsftpd/vsftpd.conf這個檔案是vsftpd的的服務的核心配置檔案!
我們在修改配置檔案的時候,最好先備份一份!
$cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.older
(2)瞭解配置的大致內容:https ://blog.csdn.net/aiynmimi/article/details/77012507
(3)測試
Vsftpd 預設以匿名使用者訪問,匿名使用者預設訪問的FTP 伺服器端路徑為:/var/ftp/pub
,匿名使用者只有檢視許可權,無法建立,刪除,修改。
這種模式下,不需改動配置檔案
$service vsftpd start
//或者
$systemctl start vsftpd.service
重啟服務使用restart
測試1:
首先將防火牆關掉
$service firewalld stop
在cmd中輸入ftp + ip地址
預設使用者的FTP,密碼為空
測試2:在瀏覽器輸入FTP:// IP地址
PS:如果有藍燈,要關掉藍燈,否則會報錯
另外預設的Vsftpd匿名使用者有兩個:匿名FTP,所以匿名使用者如果需要上傳檔案,刪除及修改等許可權,需要FTP使用者對在/ var / ftp / pub下目錄有寫入許可權,使用如下CHOWN和CHMOD任意一種即可,設定命令如下:
chown -R ftp pub /
1.4、修改配置檔案(系統使用者模式)
匿名模式可以讓任何人使用ftp 服務,比較公開!多適用於共享檔案!如果我們想要特定使用者使用,就需要使用系統使用者登入訪問!這種模式,需要我們新建不同使用者,linux 建立使用者:
(1)建立系統使用者
useradd ftpuser(新的使用者名稱)
passwd ftpuser(新的使用者名稱)
(2)修改配置檔案 /etc/vsftpd/vsftpd.conf
修改:
anonymous_enable=NO #禁止匿名使用者登入,把預設YES改成NO
#開啟被動模式
#這樣遠端連線才可以進行傳輸資料
#預設是開啟的,但是要指定一個埠範圍,開啟vsftpd.conf檔案,在後面加上
pasv_min_port=30000
pasv_max_port=30999
重新啟動:
systemctl restart vsftpd
(3)關閉防火牆
service firewalld stop
(4)測試
測試1
測試2
測試3:
連線成功:
發現傳檔案過去是報錯
修改檔案許可權:
$cd / home / ftpuser
$chmod 777upload
PS:名為ftpuser 不能為777,但他的子目錄可以為777
成功
這種模式下,登入訪問的目錄就是/家庭/新建使用者/
自此成功!
參考:
centos7:
nginx的的我之前有一篇文章,不過Ubuntu的的英文的,但兩者差不多
下載好了,移到虛擬機器中
如圖1所示,需要安裝GCC的環境。
$yum install gcc-c++
2,第三方的開發包。
PCRE
PCRE(Perl的相容正則表示式)是一個Perl的庫,包括perl的相容的正則表示式庫.nginx的HTTP模組使用PCRE來解析正則表示式,所以需要在Linux的上安裝PCRE庫。
$yum install -y pcre pcre-devel
注:PCRE-devel的的是使用PCRE開發的一個二次開發庫.nginx也需要此庫。
zlib的的
zlib的的庫提供了很多種壓縮和解壓縮的方式,Nginx的的使用zlib的的對HTTP包的內容進行的gzip的,所以需要在Linux的的上安裝的zlib的庫。
yum install -y zlib zlib-devel
OpenSSL的的
OpenSSL的是一個強大的安全套接字層密碼庫,囊括主要的密碼演算法,常用的金鑰和證書封裝管理功能及SSL協議,並提供豐富的應用程式供測試或其它目的使用。
nginx的的不僅支援HTTP協議,還支援HTTPS(即在SSL協議上傳輸HTTP),所以需要在Linux的的安裝的OpenSSL的庫。
yum install -y openssl openssl-devel
1,把nginx的的的原始碼包上傳到的Linux的系統
2,解壓縮
tar zxf nginx-1.14.0.tar.gz
3,使用配置命令建立一Makefile檔案
進入解壓檔案中可以看到有配置
然後進行配置生成的Makefile檔案
./configure --prefix=/usr/local/nginx --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_gzip_static_module --http-client-body-temp-path=/var/temp/nginx/client --http-proxy-temp-path=/var/temp/nginx/proxy --http-fastcgi-temp-path=/var/temp/nginx/fastcgi --http-uwsgi-temp-path=/var/temp/nginx/uwsgi --http-scgi-temp-path=/var/temp/nginx/scgi
複製上面的,貼上在命令列,然後回車
表示成功
生成了Makefile檔案
如圖4所示,執行安裝命令:
$make &&make install
安裝成功後:
進入/ usr / local / nginx / sbin
有個nginx的的可執行檔案
./nginx 及開啟了伺服器。
一個錯誤,啟動是缺少資料夾:所以nginx的的的需要的資料夾必須存在,即使沒有檔案在其中
建立:
上邊將臨時檔案目錄指定為/ var / temp / nginx ,需要在/ var 下建立temp 及nginx 目錄
[root @ localhost sbin]
$mkdir / var / temp / nginx / client -p
注意:-p
表示級聯建立資料夾
自此nginx的的安裝成功
最後的想要的結果是:
圖片通過ftp服務上傳到/ home / ftpuser / upload /目錄下,我想通過訪問Nginx伺服器來訪問ftp目錄下的圖片檔案,該url為http:// 192.168.23.130 /picture/x.jpg。即使用HTTP請求訪問原本需要使用FTP請求才能訪問到的資原始檔。
user root;//解決403 forbidden
server {
listen 80;
server_name 192.168.23.130;//虛擬機器或伺服器的ip地址
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#zj add
location /picture/ { #location指定了我們的圖片伺服器存放圖片的地>址
root /home/ftpuser/upload; #指定圖片/視訊存放的地址
access_log on;
autoindex on;//開啟瀏覽功能
}
注意:
圖片是放在root / home / ftpuser / upload / picture
名為ftpuser目錄許可權不能為777
上傳圖片目錄許可權為777
參考:
Nginx出現403 Forbidden最終解決方法_nginx_指令碼之家
配置完後需要重啟的nginx的伺服器:
./nginx -s reload
測試1:
注意最後一個“/”
測試2:
第二天開啟報錯:
CentOS啟動nginx出現nginx:[emerg] open()“/ var / run / nginx / nginx.pid”失敗(2:沒有這樣的檔案或導演) - CSDN部落格
但又出現新的問題:
linux作業系統重啟後解決nginx的pid消失問題 - 陳欽 - 部落格園
最終解決方案:
1. 首先你要先確保關閉nginx 程序了(可以通過pkill -9 nginx 關閉程序)
2. 進入你的nginx 的安裝目錄裡面CD` 路徑/ nginx的/ sbin` 裡面 然後輸入側
> nginx / sbin / nginx -c nginx / conf / nginx.conf 就可以了(sbin 目錄下面有個nginx 為啟動程式,-c 為以什麼配置啟動,後面接著nginx 配置檔案的路徑)
$pkill -9 nginx
$cd / usr / local / nginx / sbin
$./nginx -c conf / nginx.conf
cd / usr / local / nginx / sbin
mkdir / var / run / nginx
cd / var / run / nginx
touch nginx.conf
cd -
./nginx -c conf / nginx.conf
PS:
為什麼要這樣,因為每次虛擬機器重啟後,VAR /執行/ nginx的,nginx的這個資料夾都會被刪除,搞得每一次都要去建立nginx的這個資料夾。也可以把上面寫成一個SH指令碼(推薦)
startingNginx.sh
#!/bin/sh
cd /usr/local/nginx/sbin
mkdir /var/run/nginx
cd /var/run/nginx
touch nginx.conf
cd -
./nginx -c conf/nginx.conf
$bash startingNginx.sh
就可以了
如果你不想像上面每次啟動那麼麻煩請參考:(我沒有做)
如果修改配置檔案後,需要重啟nginx的的伺服器:
./nginx -s reload
<!-- 加入上傳檔案元件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
2.3.1、修改application. properties
#ftp相關配置
FTP_ADDRESS=192.168.23.130
FTP_PORT=21
FTP_USERNAME=ftpuser
FTP_PASSWORD=123456
FTP_BASEPATH=/home/ftpuser/upload/picture
#圖片伺服器相關配置
IMAGE_BASE_URL=http://192.168.23.1303/picture
後來發現該配置沒有起作用,故刪去(其實有用,只不過後面程式碼我寫死了)
/**
* @Auther: zj
* @Date: 2018/8/24 17:28
* @Description: 配置ftp伺服器的相關引數
*/
@Component
public class FtpConfig {
/**
* 獲取ip地址
*/
@Value("${FTP_ADDRESS}")
private String FTP_ADDRESS="192.168.23.130";
/**
* 埠號
*/
@Value("${FTP_PORT}")
private String FTP_PORT="21";
/**
* 使用者名稱
*/
@Value("${FTP_USERNAME}")
private String FTP_USERNAME="ftpuser";
/**
* 密碼
*/
@Value("${FTP_PASSWORD}")
private String FTP_PASSWORD="123456";
/**基本路徑
*/
@Value("${FTP_BASEPATH}")
private String FTP_BASEPATH="/home/ftpuser/upload/picture";
/**
* 下載地址地基礎url
*/
@Value("${IMAGE_BASE_URL}")
private String IMAGE_BASE_URL="http://192.168.23.130/picture";
public FtpConfig() {
}
public String getFTP_ADDRESS() {
return FTP_ADDRESS;
}
public void setFTP_ADDRESS(String FTP_ADDRESS) {
this.FTP_ADDRESS = FTP_ADDRESS;
}
public String getFTP_PORT() {
return FTP_PORT;
}
public void setFTP_PORT(String FTP_PORT) {
this.FTP_PORT = FTP_PORT;
}
public String getFTP_USERNAME() {
return FTP_USERNAME;
}
public void setFTP_USERNAME(String FTP_USERNAME) {
this.FTP_USERNAME = FTP_USERNAME;
}
public String getFTP_PASSWORD() {
return FTP_PASSWORD;
}
public void setFTP_PASSWORD(String FTP_PASSWORD) {
this.FTP_PASSWORD = FTP_PASSWORD;
}
public String getFTP_BASEPATH() {
return FTP_BASEPATH;
}
public void setFTP_BASEPATH(String FTP_BASEPATH) {
this.FTP_BASEPATH = FTP_BASEPATH;
}
public String getIMAGE_BASE_URL() {
return IMAGE_BASE_URL;
}
public void setIMAGE_BASE_URL(String IMAGE_BASE_URL) {
this.IMAGE_BASE_URL = IMAGE_BASE_URL;
}
}
/**
* @Auther: zj
* @Date: 2018/8/24 17:33
* @Description: 上傳的工具類FtpUtil
*/
public class FtpUtil {
/**
* ftp上傳檔案方法
*title:pictureUpload
*@param ftpConfig 由spring管理的FtpConfig配置,在呼叫本方法時,可以在使用此方法的類中通過@AutoWared注入該屬性。由於本方法是靜態方法,所以不能在此注入該屬性
*@param picNewName 圖片新名稱--防止重名 例如:"1.jpg"
*@param picSavePath 圖片儲存路徑。注:最後訪問路徑是 ftpConfig.getFTP_ADDRESS()+"/images"+picSavePath
*@param inputStream 要上傳的檔案(圖片)
*@return 若上傳成功,返回圖片的訪問路徑,若上傳失敗,返回null
* @throws IOException
*/
public static String pictureUploadByConfig(FtpConfig ftpConfig, String picNewName, String picSavePath, InputStream inputStream) throws IOException {
String picHttpPath = null;
//String picHttpPath = "";
boolean flag = uploadFile(ftpConfig.getFTP_ADDRESS(), ftpConfig.getFTP_PORT(), ftpConfig.getFTP_USERNAME(),
ftpConfig.getFTP_PASSWORD(), ftpConfig.getFTP_BASEPATH(), picSavePath, picNewName, inputStream);
if(!flag){
System.out.println( "上傳失敗" );
return picHttpPath;
}
//picHttpPath = ftpConfig.getFTP_ADDRESS()+"/images"+picSavePath+"/"+picNewName;
picHttpPath = ftpConfig.getIMAGE_BASE_URL()+picSavePath+"/"+picNewName;
System.out.println("上傳成功==="+picHttpPath);
return picHttpPath;
}
/**
* ftp下載檔案的方法
* *@param ftpConfig 由spring管理的FtpConfig配置,在呼叫本方法時,可以在使用此方法的類中通過@AutoWared注入該屬性。由於本方法是靜態方法,所以不能在此注入該屬性
*@param remotePath FTP伺服器上的相對路徑
* @param fileName 要下載的檔名
* @param localPath 下載後儲存到本地的路徑
*@return 若上傳成功,返回
* @throws IOException
*/
public static String pictureDowmloadByConfig(FtpConfig ftpConfig, String remotePath, String fileName, String localPath) throws IOException {
boolean flag = downloadFile(ftpConfig.getFTP_ADDRESS(),Integer.parseInt(ftpConfig.getFTP_PORT()),ftpConfig.getFTP_USERNAME(),
ftpConfig.getFTP_PASSWORD(),remotePath,fileName,localPath);
if(!flag){
System.out.println( "下載失敗" );
return null;
}
System.out.println( "下載成功" );
return null;
}
/**
* Description: 向FTP伺服器上傳檔案
* @param host FTP伺服器hostname
* @param ftpPort FTP伺服器埠
* @param username FTP登入賬號
* @param password FTP登入密碼
* @param basePath FTP伺服器基礎目錄
* @param filePath FTP伺服器檔案存放路徑。例如分日期存放:/2015/01/01。檔案的路徑為basePath+filePath
* @param filename 上傳到FTP伺服器上的檔名
* @param input 輸入流
* @return 成功返回true,否則返回false
*/
public static boolean uploadFile(String host, String ftpPort, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
int port = Integer.parseInt(ftpPort);
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 連線FTP伺服器
// 如果採用預設埠,可以使用ftp.connect(host)的方式直接連線FTP伺服器
ftp.login(username, password);// 登入
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切換到上傳目錄
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目錄不存在建立目錄
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
//設定上傳檔案的型別為二進位制型別
ftp.setFileType( FTP.BINARY_FILE_TYPE);
ftp.enterLocalPassiveMode();//這個設定允許被動連線--訪問遠端ftp時需要
//上傳檔案
if (!ftp.storeFile(filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
//下載檔案方法不用看,可能日後有用,先留在這裡==========================================
/**
* Description: 從FTP伺服器下載檔案
* @param host FTP伺服器hostname
* @param port FTP伺服器埠
* @param username FTP登入賬號
* @param password FTP登入密碼
* @param remotePath FTP伺服器上的相對路徑
* @param fileName 要下載的檔名
* @param localPath 下載後儲存到本地的路徑
* @return
*/
public static boolean downloadFile(String host, int port, String username, String password, String remotePath,
String fileName, String localPath) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);
// 如果採用預設埠,可以使用ftp.connect(host)的方式直接連線FTP伺服器
ftp.login(username, password);// 登入
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
ftp.changeWorkingDirectory(remotePath);// 轉移到FTP伺服器目錄
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(fileName)) {
File localFile = new File(localPath + "/" + ff.getName());
OutputStream is = new FileOutputStream(localFile);
ftp.retrieveFile(ff.getName(), is);
is.close();
}
}
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
}
/**
* @Auther: zj
* @Date: 2018/8/24 17:40
* @Description: 上傳工具類 UploadUtils
*/
public class UploadUtils {
/**
* 得到真實檔名
* @param fileName
* @return
*/
public static String subFileName(String fileName){
//查詢最後一個 \ (檔案分隔符)位置
int index = fileName.lastIndexOf( File.separator);
if(index == -1){
//沒有分隔符,說明是真實名稱
return fileName;
}else {
return fileName.substring(index+1);
}
}
/**
* 獲得隨機UUID檔名
* @param fileName
* @return
*/
public static String generateRandonFileName(String fileName){
//首相獲得副檔名,然後生成一個UUID碼作為名稱,然後加上副檔名
String ext = fileName.substring(fileName.lastIndexOf("."));
return UUID.randomUUID().toString()+ext;
}
/**
* 獲得hashcode 生成二級目錄
* @param uuidFileName
* @return
*/
public static String generateRandomDir(String uuidFileName){
int hashCode = uuidFileName.hashCode();//得到它的hashcode編碼
//一級目錄
int d1 = hashCode & 0xf;
//二級目錄
int d2 = (hashCode >> 4) & 0xf;
return "/"+d1+"/"+d2;
}
}
//上傳頭像
@PostMapping("/headImg")
public Object uploadHeadimg(@RequestParam("file") MultipartFile file) {
String username = SecurityContextHolder.getContext().getAuthentication().getName();//獲取當前登入使用者
System.out.println( username );
User user = userService.getUserByUsername(username);
if (!file.isEmpty()) { //檔案不是空檔案
try {
BufferedOutputStream out = new BufferedOutputStream(
//C:\IDEA_mode_project\agriculture\src\main
new FileOutputStream(new File(filepath + username + ".jpg")));//儲存圖片到目錄下,建立儲存檔案的輸入流
out.write(file.getBytes());
out.flush();
out.close();
String filename = filepath + username + ".jpg";
user.setAvater(filename); //設定頭像路徑
userService.saveOrUpdate(user);//修改使用者資訊
} catch (FileNotFoundException e) {
e.printStackTrace();
return new Reponse(false,"上傳失敗," + e.getMessage());
//return "上傳失敗," + e.getMessage(); //檔案路徑錯誤
} catch (IOException e) {
e.printStackTrace();
return new Reponse(false,"上傳失敗," + e.getMessage());
//return "上傳失敗," + e.getMessage(); //檔案IO錯誤
}
return new Reponse(true,"上傳頭像成功",user);//返回使用者資訊
} else {
return new Reponse(false,"上傳失敗,因為檔案是空的");
}
}
//上傳檔案(ftp + nginx)(多檔案上傳或單檔案上傳)
@PostMapping("/ftp/upload")
public Object uploadHeadImgNew(@RequestParam("file") MultipartFile[] files) throws IOException {
String username = SecurityContextHolder.getContext().getAuthentication().getName();//獲取當前登入使用者
System.out.println( username );
//User user = userService.getUserByUsername(username);
FtpConfig ftpConfig = new FtpConfig();
//System.out.println( ftpConfig.getFTP_ADDRESS() );
for (MultipartFile file : files) {
//Photo photo = new Photo();
String oldName = file.getOriginalFilename();// 獲取圖片原來的名字
System.out.println( oldName );
String picNewName = UploadUtils.generateRandonFileName(oldName);// 通過工具類產生新圖片名稱,防止重名
System.out.println( picNewName );
//String picSavePath = UploadUtils.generateRandomDir(picNewName);// 通過工具類把圖片目錄分級
String picSavePath = "/"+username;//以使用者名稱為目錄
System.out.println( picSavePath );
/*
* photo.setPhotoUrl(picSavePath + "/");//
* 設定圖片的url--》就是儲存到資料庫的字串url photo.setAlbumId(albumId);//
* 設定圖片所屬相簿id photo.setUser_id("wk");
* photo.setPhoteName(picNewName);
*/
//photoList.add(photo);
FtpUtil.pictureUploadByConfig(ftpConfig, picNewName, picSavePath, file.getInputStream());// 上傳到圖片伺服器的操作
// 新增到資料庫
}
return new Reponse( true,"上傳頭像成功" );
}
/**
* 下載檔案(ftp + nginx)(單個檔案下載)
* @param remotePath 絕對路徑或相對路徑
* @param fileName 檔名
* @param localPath 下載後儲存到本地的路徑
* @throws IOException
* for example:
remotePath="/home/ftpuser/upload/picture/18083764688"+
fileName="33691e23-b864-4069-a83c-2427c27cdd96.jpg"+
localPath="C:\Users\zj\Desktop"
*/
@PostMapping("/ftp/download")
public void downloadHeadImgNew(@RequestParam("remotePath") String remotePath,
@RequestParam("fileName") String fileName,
@RequestParam("localPath") String localPath) throws IOException {
String username = SecurityContextHolder.getContext().getAuthentication().getName();//獲取當前登入使用者
System.out.println( username );
//User user = userService.getUserByUsername(username);
FtpConfig ftpConfig = new FtpConfig();
FtpUtil.pictureDowmloadByConfig( ftpConfig,remotePath,fileName,localPath );
System.out.println( "檔案下載成功" );
}
用postman親測可用
參考:
獨立檔案伺服器就好了,但如果這個伺服器掛了,怎麼辦?
下期我們來用HDFS分散式檔案系統來解決這個問題