docker&springboot-快速搭建FTP-Image圖片服務器

本章目的是講解圖片服務器及其運用的使用,主講解個別要點,細節沒有全列出來,如果是未搭建過相關服務的小白白,建議先去參考下其他健全的講解內容,你所希望的搭建一套完整體系流程,本篇可能不太適合。

事先安裝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 兩個只是回調參數封裝,還有表單功能,這裏不做展示。

本文是個人在摸索實踐,歡迎留言探討。

參考文章:

鏡像官網:https://hub.docker.com/

Linux FTP 命令全集

Java實現把圖片上傳到圖片服務器(nginx+vsftp)

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