本章目的是講解圖片服務器及其運用的使用,主講解個別要點,細節沒有全列出來,如果是未搭建過相關服務的小白白,建議先去參考下其他健全的講解內容,你所希望的搭建一套完整體系流程,本篇可能不太適合。
事先安裝docker和docker-compose這裏就不說了,
我們希望在nginx下能直接拉取圖片,所以ftp的上傳的映射路徑要和nginx的映射路徑一起,方便取圖
直接展示拉取配置如下:
vsftpd:
restart: always
image: fauria/vsftpd
volumes:
- /root/docker-data/vsftpd:/home/vsftpd
ports:
- "20:20"
- "21:21"
- "21100-21110:21100-21110"
environment:
FTP_USER: XXX
FTP_PASS: XXX
PASV_MIN_PORT: 21100
PASV_MAX_PORT: 21110
nginx:
restart: always
image: jwilder/nginx-proxy:latest
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- /root/docker-data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/docker-data/nginx/site-enabled:/etc/nginx/site-enabled
- /root/docker-data/vsftpd/XXX:/usr/local/images:ro
以上鏡像配置是個人配置,鏡像的掛載請根據個人情況修改配置。
注:/root/docker-data/vsftpd/XXX ,XXX是ftp鏡像裏面配置的登陸用戶名。下面會說到爲什麼用這個。
鏡像拉取後,使用啓動命令
docker-compose up -d
然後在服務器測試下鏈接
ftp xxx.xxx.xxx.xxx
出現 bash: ftp: command not found 是因爲linux還沒安裝ftp服務,運行如下命令就行
yum install -y ftp
再次鏈接後,會要求輸入用戶名和密碼,正確後會顯示 Login successful. 算是搭建完成。
現在開始外網訪問測試下,如果是自己搭建的虛擬機,記得要關閉防火牆或是開放對應的訪問端口,不然遠程鏈接不上。
我這裏使用的是阿里雲的ESC,需要到安全組配置端口,端口範圍就是我們的20,21和浮動端口21100-21110。
我是配置了多個端口限制,如果服務太多又想全部鏈接的話,直接創建端口範圍從 1024/65535 可一次搞定,但感覺不安全。
這裏使用網頁版登陸刷新下
表示配置訪問成功。
注:現在看到的這個文件目錄,雖然是“/” , 但在服務器上存在的位置如下
容器的位置:/home/vsftpd/xxx
宿主主機的位置:/root/docker-data/vsftpd/xxx
xxx是登錄用戶名,鏡像掛載配置,ftp默認把登錄用戶所在的目錄設爲主目錄
然後我們再配置nginx.conf,添加如下配置
location /images/ {
root /usr/local;
autoindex_exact_size off;
autoindex_localtime on;
charset utf-8;
}
參數說明:
(1) root /usr/local;: 添加圖片目錄映射,映射目錄爲/usr/local/images/
(2) autoindex on;:在Nginx下默認是不允許列出整個目錄的。如需此功能,將該項設置爲on
(3) autoindex_exact_size off;:默認爲on,顯示出文件的確切大小,單位是bytes,改爲off後,顯示出文件的大概大小,單位是kB或者MB或者GB
(4) autoindex_localtime on;:默認爲off,顯示的文件時間爲GMT時間, 注意:改爲on後,顯示的文件時間爲文件的服務器時間
(5) charset utf-8,gbk;:設置編碼(防止中文亂碼),可以設置對全局生效或者部分路徑生效
因爲ftp上次的圖片文件映射到宿主機,隨後又掛載到nginx內,所以當你訪問http://XXX.XXX.XXX.XX/images/123.jpg時,等同於訪問 /usr/local/images/123.jpg
重啓動nginx鏡像,讓服務生效
docker restart nginx
圖片服務器就搭好了。
接下來就是用代碼上傳下我們的圖片,先建立一個基礎的springboot服務,這裏也直接跳過。
引入主要依賴:
<!-- 圖片縮放 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<!-- net -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- ftp -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ftp</artifactId>
<version>2.24.2</version>
</dependency>
編寫兩個工具類:一個圖片工具類,一個FTP工具類
/**
* 圖片工具類
* @author leopard
*
*/
public class ImageUtil {
/**
* 簡單上傳圖片,不做任何處理
*
* @param imagePath 目標文件夾路徑
* @param imageName 圖片名稱
* @param inputStream 輸出流
* @return
*/
public static ImageResult uploadImage(String imagePath, String imageName, InputStream inputStream) {
BufferedInputStream inBuff = null;
BufferedOutputStream outBuff = null;
if (!"/".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))
&& !"\\".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))) {
imagePath += "/";
}
File realFile = new File(imagePath);
// 不存在文件夾則創建
if (!realFile.exists())
realFile.mkdirs();
try {
// 新建文件輸入流並對它進行緩衝
inBuff = new BufferedInputStream(inputStream);
// 新建文件輸出流並對它進行緩衝
outBuff = new BufferedOutputStream(new FileOutputStream(imagePath + "/" + imageName));
// 緩衝數組
byte[] b = new byte[1024 * 5];
int len;
while ((len = inBuff.read(b)) != -1) {
outBuff.write(b, 0, len);
}
// 刷新此緩衝的輸出流
outBuff.flush();
} catch (FileNotFoundException a) {
a.printStackTrace();
return ImageResult.error("目標文件夾找不到");
} catch (IOException e) {
e.printStackTrace();
return ImageResult.error("操作流異常!");
} finally {
// 關閉流
try {
if (inBuff != null)
inBuff.close();
if (outBuff != null)
outBuff.close();
} catch (IOException c) {
c.printStackTrace();
}
}
StringBuffer result = new StringBuffer(imagePath);
result.append(imageName);
return ImageResult.success(result.toString(), null);
}
/**
* 圖片名生成
*/
public static String genImageName() {
// 取當前時間的長整形值包含毫秒
long millis = System.currentTimeMillis();
// 加上三位隨機數
int end3 = MathUtil.getNumRandom(3);
// 如果不足三位前面補0
String str = millis + String.format("%03d", end3);
return str;
}
/**
* 保存圖片並且生成縮略圖
*
* @param imagePath 目標文件夾路徑
* @param imageName 圖片名稱
* @param inputStream 輸出流
* @param fileSize 文件大小
* @param width 期望寬度
* @param height 期望高度
* @return
*/
public static ImageResult uploadFileAndCreateThumbnail(String imagePath, String imageName, InputStream inputStream,
long fileSize, int width, int height) {
if (width == 0 || height == 0) {
return ImageResult.error("請設置縮略圖寬高!");
}
// 保存源文件
ImageResult uploadImage = uploadImage(imagePath, imageName, inputStream);
if (!SystemCodeAndMsg.SUCCESS.code().equals(uploadImage.getCode())) {
return uploadImage;
}
/*** 縮略圖begin */
// 源圖片路徑
StringBuffer imagePathBuffer = new StringBuffer(imagePath);
// 如果最後不存在斜杆,則自行添加
if (!"/".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))
&& !"\\".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))) {
imagePathBuffer.append("/");
}
String filePathName = imagePathBuffer.append(imageName).toString();
// 目標圖片(移除後綴 並改名 xxx.jpg
// ->xxxx-small)不含後綴路徑,目的在下面的outputFormat(jpg),轉化爲固定的jpg格式文件
StringBuffer thumbnailFilePathName = new StringBuffer(filePathName.substring(0, filePathName.lastIndexOf(".")))
.append("-small");
thumbnailFilePathName = thumbnailFilePathName.append(".jpg");
String disposeUrl = thumbnailFilePathName.toString();
try {
Builder<File> builderFile = Thumbnails.of(filePathName);
// 壓縮200KB
if (fileSize > 200 * 1024) {
builderFile.outputQuality((200 * 1024f) / fileSize);
}
// 按自定義大小比例縮放
builderFile.forceSize(width, height).toFile(disposeUrl);
} catch (Exception e1) {
return ImageResult.error(e1.getMessage());
}
/*** 縮略圖end */
return ImageResult.success(uploadImage.getUrl(), disposeUrl);
}
}
/**
* ftp工具類
*
* @author leopard
*
*/
public class FtpUtil {
/**
* Description: 向FTP服務器上傳文件
*
* @param host FTP服務器ip
* @param port FTP服務器端口
* @param username FTP登錄賬號
* @param password FTP登錄密碼
* @param basePath FTP服務器基礎目錄,/images --> /home/vsftpd/${FTP登錄賬號 }/images
* @param filePath FTP服務器文件存放路徑。例如分日期存放:/2018/05/28。文件的路徑爲basePath+filePath
* @param filename 上傳到FTP服務器上的文件名
* @param input 輸入流
* @return
*/
public static FtpResult uploadFileToImage(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream inputStream, long fileSize, int width, int height) {
String imageTempPath = ClassUtils.getDefaultClassLoader().getResource("").getPath() + "/imageTemp";
ImageResult uploadFileAndCreateThumbnail = ImageUtil.uploadFileAndCreateThumbnail(imageTempPath, filename,
inputStream, fileSize, width, height);
if (!SystemCodeAndMsg.SUCCESS.code().equals(uploadFileAndCreateThumbnail.getCode())) {
return new FtpResult(uploadFileAndCreateThumbnail);
}
FTPClient ftp = new FTPClient();
String imagePath = basePath + filePath;
StringBuffer initUrl = new StringBuffer(imagePath); // 原文件上傳路徑
StringBuffer disposeUrl = new StringBuffer(imagePath); // 處理後文件上傳路徑
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 FtpResult.error("ftp遠程登錄失敗!");
}
// 切換到上傳目錄--changeWorkingDirectory()方法類似linux的cd命令
if (!ftp.changeWorkingDirectory(imagePath)) {
// 如果目錄不存在創建目錄
String[] dirs = imagePath.split("/");
String tempPath = "/";
for (String dir : dirs) {
if (null == dir || "".equals(dir)) {
continue;
}
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
//makeDirectory()方法類似linux的mkdir命令
if (!ftp.makeDirectory(tempPath)) {
return FtpResult.error("ftp遠程創建文件夾失敗!");
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
// 設置爲被動模式
ftp.enterLocalPassiveMode();
// 設置上傳文件的類型爲二進制類型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
String initImageFileUrl = uploadFileAndCreateThumbnail.getUrl();
String disposeImageFileUrl = uploadFileAndCreateThumbnail.getDisposeUrl();
// 上傳原文件
if (StringUtils.isNotBlank(initImageFileUrl)) {
File initImageFile = new File(initImageFileUrl);
if (!ftp.storeFile(filename, new FileInputStream(initImageFile))) {
return FtpResult.error("ftp遠程上傳原文件失敗!");
}
initUrl.append(filename);
// initImageFile.delete();//所在進程運行中,刪除不了
}
// 上傳壓縮文件
if (StringUtils.isNotBlank(disposeImageFileUrl)) {
File disposeImageFile = new File(disposeImageFileUrl);
String disposeFileName = disposeImageFileUrl.substring(disposeImageFileUrl.lastIndexOf("/") + 1,
disposeImageFileUrl.length());
if (!ftp.storeFile(disposeFileName, new FileInputStream(disposeImageFile))) {
return FtpResult.error("ftp遠程上傳壓縮文件失敗!");
}
disposeUrl.append(disposeFileName);
// disposeImageFile.delete();//所在進程運行中,刪除不了
}
inputStream.close();
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return FtpResult.success(initUrl.toString(), disposeUrl.toString());
}
}
FTP工具類問題檢測,一般都看reply的數值,200<= reply < 300 ,屬正常可操作FTP數值,如果不在此範圍,需要查看下是否鏈接登陸失敗,或是文件夾爲創建。
如:一開始,工具類裏得局部參數tempPath不是從“ / ”,而是從“/temp”,開始,那拼接出入的“/images”,就變成“/temp/images”,
makeDirectory() 方法也無法一次性創建兩層目錄。進入源碼或是打印出來,可以看到reply 的數組爲550.
注:本章主要是運用搭建圖片服務器和上傳圖片爲主,所以FTP工具類被個人改動爲主爲圖片服務,如要上傳其他類型文件,可自行修改。
測試代碼塊:
@RequestMapping(value = "/savePic", method = RequestMethod.POST)
@ResponseBody
public JSONObject sendSimple(MultipartFile uploadFile) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", SystemCodeAndMsg.WEB_SUCCESS.code());
jsonObject.put("msg", SystemCodeAndMsg.WEB_SUCCESS.msg());
// 判斷上傳圖片是否爲空
if (null == uploadFile || uploadFile.isEmpty()) {
jsonObject.put("msg", "文件不能爲空");
return jsonObject;
}
if (uploadFile.getSize() >= 5 * 1024 * 1024) {
jsonObject.put("msg", "文件不能大於5M");
return jsonObject;
}
// 取文件擴展名
String ext = FilenameUtils.getExtension(uploadFile.getOriginalFilename());
String imageName = ImageUtil.genImageName() + "." + ext;
// 文件在服務器的存放路徑,應該使用日期分隔的目錄結構
DateTime dateTime = new DateTime();
String filePath = dateTime.toString("yyyy/MM/dd");
String IMAGE_BASE_URL = "H:";
String imagePath = IMAGE_BASE_URL + "/images/" + filePath;
try {
//本地圖片上傳方式
// ImageResult uploadFileAndCreateThumbnail = ImageUtil.uploadFileAndCreateThumbnail(imagePath, imageName,
// uploadFile.getInputStream(), uploadFile.getSize(), 400, 400);
// jsonObject.put("msg", uploadFileAndCreateThumbnail.getUrl());
//遠程FTP圖片服務器上傳
FtpResult uploadFile2 = FtpUtil.uploadFileToImage("xxx.leopardxxx.com", 21, "xxx", "xxx", "/images/",
filePath, imageName, uploadFile.getInputStream() ,uploadFile.getSize() , 400 , 400);
jsonObject.put("msg", uploadFile2.getDisposeUrl());
} catch (Exception e) {
e.printStackTrace();
return jsonObject;
}
// 返回結果,生成一個可以訪問到圖片的url返回
return jsonObject;
}
文章內的 FtpResult 和 ImageResult 兩個只是回調參數封裝,還有表單功能,這裏不做展示。
本文是個人在摸索實踐,歡迎留言探討。
參考文章: