注意:本文只適合小文本文件的上傳下載,因為post請求是有大小限制的,。默認(rèn)大小是2m,,雖然具體數(shù)值可以調(diào)節(jié),,但不適合做大文件的傳輸
最近公司有這么個需求:以后所有的項目開發(fā)中需要使用ftp服務(wù)器的地方都不能直接操作ftp服務(wù)器,,而是通過調(diào)用一個統(tǒng)一的接口去操作文件的上傳下載等功能,。 其實就是針對ftp服務(wù)器封裝一個項目,,所有的針對ftp的操作都通過這個項目來實現(xiàn)。 技術(shù)點:接口調(diào)用必然存在各種參數(shù),、錯誤信息、成功信息等,。那么我們的文件要通過什么形式放到參數(shù)中在網(wǎng)絡(luò)傳遞呢,? 文件可以轉(zhuǎn)換成字節(jié)流在網(wǎng)絡(luò)中傳輸沒毛病,,但是轉(zhuǎn)換成二進(jìn)制流之后放到我們封裝的對象中再次序列化在網(wǎng)絡(luò)中傳輸就有問題了。 所以關(guān)鍵是將我們的文件以何種數(shù)據(jù)結(jié)構(gòu)封裝在我們的參數(shù)對象中在網(wǎng)絡(luò)里傳輸,。答案是 字節(jié)數(shù)組,; 目錄結(jié)構(gòu): 入?yún)ο螅?nbsp;Vsftpd package com.ch.vsftpd.common.pojo; /** * @Auther: 011336 * @Date: 2019/4/24 11:04 */ public class Vsftpd { /** * 請求類型[必填] * upload: 上傳 文件已存在上傳失敗 * replace:上傳 文件已存在則覆蓋 * download: 下載 * display: 查看文件列表 * delete: 刪除文件 */ private String optionType; /** * 請求者在接口管理系統(tǒng)中維護(hù)的項目編碼[必填] */ private String projectCode; /** * 上傳/下載 文件名[非必填] */ private String fileName; /** * 上傳/下載 文件的字節(jié)數(shù)組[非必填] */ private byte[] byteArry; ... } 返回對象: VsftpResult package com.ch.vsftpd.common.pojo; import java.util.List; /** * @Auther: 011336 * @Date: 2019/4/24 11:03 */ public class VsftpResult { private boolean status; private byte[] byteArry; private String[] fileNames; private List<ErrorInfo> errors; } 錯誤信息: ErrorInfo package com.ch.vsftpd.common.pojo; /** * @Auther: 011336 * @Date: 2019/3/28 11:32 */ public class ErrorInfo { private String errorCode; private String errorMsg; public ErrorInfo() { } public ErrorInfo(String errorCode) { this.errorCode = errorCode; } public ErrorInfo(String errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } } 對外提供接口: FtpController /** * ftp服務(wù),,對外提供統(tǒng)一接口地址,,通過控制傳遞的參數(shù)實現(xiàn) 上傳,、覆蓋上傳,、獲取文件列表和下載4個功能 * 具體參數(shù)參考【Vsftpd.java, FtpConstants】 * @before AuthorityInterceptor.java 攔截器做權(quán)限校驗 * @Auther: 011336 * @Date: 2019/4/24 11:21 */ @Controller @RequestMapping("/vsftpdService") public class ftpController { private static Logger LOGGER = LoggerFactory.getLogger(ftpController.class); @ResponseBody @RequestMapping(path = "/vsftpd", method = RequestMethod.POST) public VsftpResult getAuthInfo(@RequestBody Vsftpd vsftpd){ LOGGER.info("ftpController.getAuthInfo start"); IFtpUploadStrategy strategy = null; List<ErrorInfo> errors = new ArrayList<>(); VsftpResult result = new VsftpResult(); //第一步校驗參數(shù)是否合法 if (StringUtils.isEmpty(vsftpd.getOptionType())) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","調(diào)用參數(shù)[type]不能為空!"); errors.add(errorInfo); } if (StringUtils.isEmpty(vsftpd.getProjectCode())) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","參數(shù)[projectCode]不能為空,!"); errors.add(errorInfo); } //根據(jù)請求類型使用不同策略 if(FtpConstants.UP_LOAD.equals(vsftpd.getOptionType())){ strategy = new FtpUploadStrategy(); }else if(FtpConstants.REPLACE.equals(vsftpd.getOptionType())){ strategy = new FtpUploadStrategy(); }else if(FtpConstants.DOWAN_LOAD.equals(vsftpd.getOptionType())){ strategy = new FtpDownLoadStrategy(); }else if(FtpConstants.DISPLAY.equals(vsftpd.getOptionType())){ strategy = new FtpDisplayStrategy(); }else if(FtpConstants.DELETE.equals(vsftpd.getOptionType())){ strategy = new FtpDeleteStrategy(); }else { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","調(diào)用參數(shù)[type]錯誤,!"); errors.add(errorInfo); } if (CollectionUtils.isEmpty(errors)) { result = strategy.vsftpMethod(vsftpd); }else{ result.setStatus(false); result.setErrors(errors); } return result; } } 四種策略:文件上傳: FtpUploadStrategy /** * 文件上傳 * 通過參數(shù)vsftpd.getOptionType()分為普通上傳 和 覆蓋上傳兩種 * 第一種若文件已存在則返回錯誤信息提示文件已存在 * 第二種則直接覆蓋 * @Auther: 011336 * @Date: 2019/4/24 10:59 */ public class FtpUploadStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpUploadStrategy.class); @Override public VsftpResult vsftpMethod(Vsftpd vsftpd){ LOGGER.info("FtpUploadStrategy.vsftpMethod start"); VsftpResult result = new VsftpResult(); List<ErrorInfo> errors = new ArrayList<>(); if (StringUtils.isEmpty(vsftpd.getFileName())) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","參數(shù)[fileName]不能為空,!"); errors.add(errorInfo); } if (vsftpd.getByteArry()==null) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","參數(shù)[byteArry]不能為空,!"); errors.add(errorInfo); } //當(dāng)不強制上傳的時候 文件若已存在則上傳失敗 boolean flag = false; try { if(FtpConstants.UP_LOAD.equals(vsftpd.getOptionType())) { //判斷文件是否存在 boolean b = FtpUtil.fileExist(vsftpd.getProjectCode(), vsftpd.getFileName()); if (b) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL", "文件[" + vsftpd.getFileName() + "]已存在!"); errors.add(errorInfo); } } //上傳文件(文件若存在則覆蓋) if(CollectionUtils.isEmpty(errors)){ flag = FtpUtil.uploadFile(vsftpd.getFileName(),vsftpd.getByteArry(),vsftpd.getOptionType()); } } catch (Exception e) { e.printStackTrace(); ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","下載失??!服務(wù)端異常,!"); errors.add(errorInfo); } if(!flag){ ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","上傳失??!系統(tǒng)異常,!"); errors.add(errorInfo); } if(CollectionUtils.isEmpty(errors)){ result.setStatus(true); }else{ result.setStatus(false); result.setErrors(errors); } LOGGER.info("FtpUploadStrategy.vsftpMethod end"); return result; } } 文件下載: FtpDownLoadStrategy
View Code
文件刪除: FtpDeleteStrategy /** * 文件刪除 * 文件若不存在則返回刪除成功 * @Auther: 011336 * @Date: 2019/4/24 10:59 */ public class FtpDeleteStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpDeleteStrategy.class); @Override public VsftpResult vsftpMethod(Vsftpd vsftpd){ LOGGER.info("FtpDeleteStrategy.vsftpMethod start"); VsftpResult result = new VsftpResult(); List<ErrorInfo> errors = new ArrayList<>(); if (StringUtils.isEmpty(vsftpd.getFileName())) { ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","參數(shù)[fileName]不能為空!"); errors.add(errorInfo); } //刪除文件 try { if(CollectionUtils.isEmpty(errors)){ FtpUtil.deleteFile(vsftpd.getProjectCode(),vsftpd.getFileName()); } } catch (Exception e) { e.printStackTrace(); ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","刪除失??!服務(wù)端異常,!"); errors.add(errorInfo); } if(CollectionUtils.isEmpty(errors)){ result.setStatus(true); }else{ result.setStatus(false); result.setErrors(errors); } LOGGER.info("FtpDeleteStrategy.vsftpMethod end"); return result; } } 獲取文件列表:FtpDisplayStrategy
View Code
提供一個策略接口:
View Code
最主要是我們的 FtpUtil 工具類 /** * @author 王未011336 * @date 2018/11/04 * ftp服務(wù)器文件上傳下載 */ public class FtpUtil { private static Logger LOGGER = LoggerFactory.getLogger(FtpUtil.class); private static String LOCAL_CHARSET = "GBK"; private static String SERVER_CHARSET = "ISO-8859-1"; private static String host; private static String port; private static String username; private static String password; private static String basePath; private static String filePath; private static String localPath; /** *讀取配置文件信息 * @return */ public static void getPropertity(){ Properties properties = new Properties(); ClassLoader load = FtpUtil.class.getClassLoader(); InputStream is = load.getResourceAsStream("conf/vsftpd.properties"); try { properties.load(is); host=properties.getProperty("vsftpd.ip"); port=properties.getProperty("vsftpd.port"); username=properties.getProperty("vsftpd.user"); password=properties.getProperty("vsftpd.pwd"); //服務(wù)器端 基路徑 basePath=properties.getProperty("vsftpd.remote.base.path"); //服務(wù)器端 文件路徑 filePath=properties.getProperty("vsftpd.remote.file.path"); //本地 下載到本地的目錄 localPath=properties.getProperty("vsftpd.local.file.path"); } catch (IOException e) { e.printStackTrace(); } } /** * 上傳重載 * @param filename 上傳到服務(wù)器端口重命名 * @param buffer byte[] 文件流 * @return */ public static boolean uploadFile(String filename, byte[] buffer, String optionType) throws Exception{ getPropertity(); return uploadFile( host, port, username, password, basePath, filePath, filename, buffer, optionType); } /** * 獲取文件列表 * @param filePath * @return * @throws Exception */ public static String[] displayFile(String filePath) throws Exception{ getPropertity(); return displayFile(host, port, username, password, basePath, filePath); } /** * 刪除文件 * @param filePath * @return */ public static boolean deleteFile(String filePath, String fileName) throws Exception{ getPropertity(); return deleteFile(host, port, username, password, basePath, filePath, fileName); } /** * 判斷文件是否存在 * @param filePath * @return * @throws Exception */ public static boolean fileExist(String filePath,String filename) throws Exception{ if(StringUtils.isEmpty(filename)){ return false; } getPropertity(); String[] names = displayFile(filePath); for (String name : names) { if(filename.equals(name)){ return true; } } return false; } /** *下載重載 * @param filePath 要下載的文件所在服務(wù)器的相對路徑 * @param fileName 要下載的文件名 * @return */ public static byte[] downloadFile(String filePath,String fileName) throws Exception{ getPropertity(); return downloadFile( host, port, username, password, basePath, filePath, fileName); } /** * Description: 向FTP服務(wù)器上傳文件 * @param host FTP服務(wù)器hostname * @param port FTP服務(wù)器端口 * @param username FTP登錄賬號 * @param password FTP登錄密碼 * @param basePath FTP服務(wù)器基礎(chǔ)目錄 * @param filePath FTP服務(wù)器文件存放路徑,。例如分日期存放:/2015/01/01,。文件的路徑為basePath+filePath * @param fileName 上傳到FTP服務(wù)器上的文件名 * @return 成功返回true,否則返回false */ public static boolean uploadFile(String host, String port, String username, String password, String basePath, String filePath, String fileName, byte[] buffer, String optionType) throws Exception{ FTPClient ftp = new FTPClient(); try { fileName = new String(fileName.getBytes(LOCAL_CHARSET)); boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath); if(!result){ return result; } //為了加大上傳文件速度,,將InputStream轉(zhuǎn)成BufferInputStream , InputStream input InputStream inputStream = new ByteArrayInputStream(buffer); //加大緩存區(qū) ftp.setBufferSize(1024*1024); //設(shè)置上傳文件的類型為二進(jìn)制類型 ftp.setFileType(FTP.BINARY_FILE_TYPE); if(FtpConstants.REPLACE.equals(optionType)){ ftp.deleteFile(fileName); } //上傳文件 if (!ftp.storeFile(fileName, inputStream)) { return false; } inputStream.close(); ftp.logout(); } catch (IOException e) { LOGGER.info(e.getMessage()); e.printStackTrace(); throw e; } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } return true; } /** * Description: 從FTP服務(wù)器下載文件 * @param host FTP服務(wù)器hostname * @param port FTP服務(wù)器端口 * @param username FTP登錄賬號 * @param password FTP登錄密碼 * @param basePath FTP服務(wù)器上的相對路徑 * @param fileName 要下載的文件名 * @return */ public static byte[] downloadFile(String host, String port, String username, String password, String basePath, String filePath, String fileName) throws Exception{ FTPClient ftp = new FTPClient(); try { fileName = new String(fileName.getBytes(LOCAL_CHARSET)); boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath); if(!result){ return null; } FTPFile[] fs = ftp.listFiles(); boolean flag = true; for (FTPFile ff : fs) { if (ff.getName().equals(fileName)) { InputStream input = ftp.retrieveFileStream(ff.getName()); BufferedInputStream in = new BufferedInputStream(input); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = -1; while((len = in.read(buffer)) != -1){ outStream.write(buffer, 0, len); } outStream.close(); in.close(); byte[] arryArry = outStream.toByteArray(); return arryArry; } } if(flag) { LOGGER.info("服務(wù)器端文件不存在..."); return null; } ftp.logout(); } catch (IOException e) { LOGGER.info(e.getMessage()); e.printStackTrace(); throw e; } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } return null; } /** * 獲取服務(wù)器文件名列表 * @param host * @param port * @param username * @param password * @param basePath * @param filePath * @return * @throws Exception */ public static String[] displayFile(String host, String port, String username, String password, String basePath, String filePath) throws Exception{ FTPClient ftp = new FTPClient(); try { boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath); if(!result){ return null; } String[] names = ftp.listNames(); ftp.logout(); return names; } catch (IOException e) { LOGGER.info(e.getMessage()); e.printStackTrace(); throw e; } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } } /** * 刪除文件 * @param host * @param port * @param username * @param password * @param basePath * @param filePath * @return */ public static boolean deleteFile(String host, String port, String username, String password, String basePath, String filePath,String fileName) throws Exception{ FTPClient ftp = new FTPClient(); boolean b = false; try { boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath); if(!result){ return b; } b = ftp.deleteFile(fileName); ftp.logout(); } catch (IOException e) { LOGGER.info(e.getMessage()); e.printStackTrace(); throw e; } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } return b; } /** * 連接ftp服務(wù)器并切換到目的目錄 * 調(diào)用此方法需手動關(guān)閉ftp連接 * @param ftp * @param host * @param port * @param username * @param password * @param basePath * @param filePath * @return */ private static boolean connectFtp( FTPClient ftp,String host, String port, String username, String password, String basePath, String filePath) throws Exception{ boolean result = false; try { int portNum = Integer.parseInt(port); int reply; // 連接FTP服務(wù)器 ftp.connect(host, portNum); // 如果采用默認(rèn)端口,可以使用ftp.connect(host)的方式直接連接FTP服務(wù)器 ftp.login(username, password); reply = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { ftp.disconnect(); return result; } // 開啟服務(wù)器對UTF-8的支持,,如果服務(wù)器支持就用UTF-8編碼,否則就使用本地編碼(GBK). if (FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))) { LOCAL_CHARSET = "UTF-8"; } ftp.setControlEncoding(LOCAL_CHARSET); //切換到上傳目錄 if (!ftp.changeWorkingDirectory(basePath+filePath)) { //如果目錄不存在創(chuàng)建目錄 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); } } } } result = true; } catch (IOException e) { LOGGER.info(e.getMessage()); e.printStackTrace(); throw e; } return result; } } 攔截器權(quán)限校驗這邊涉及到太多業(yè)務(wù),,讀者可止步于此,但我還是要貼一下代碼 配置攔截器: FilterManager /** * @Auther: 011336 * @Date: 2019/3/28 19:17 */ @Configuration public class FilterManager implements WebMvcConfigurer { @Autowired private AuthorityInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginInterceptor) .addPathPatterns("/**"); } } 攔截器具體實現(xiàn): AuthorityInterceptor /** * 攔截器做權(quán)限驗證 * 權(quán)限信息接SI接口管理系統(tǒng),,調(diào)用者需提供白名單用戶名,和白名單密碼(MD5加密) * 本服務(wù)根據(jù)調(diào)用者在SI中的項目編碼projectCode為調(diào)用者創(chuàng)建獨立目錄地址 * requestUri 接口地址 * onlineInterfaceCode 接口線上編碼 * 從SI同步而來的白名單信息包含此接口的線上接口編碼onlineInterfaceCode,,本項目維護(hù)了onlineInterfaceCode與requestUri之間的對應(yīng)關(guān)系 * 目的:使系統(tǒng)兼容提供多個接口的情況 * @Auther: 011336 * @Date: 2019/4/28 19:11 */ @Component public class AuthorityInterceptor implements HandlerInterceptor { private static Logger LOGGER = LoggerFactory.getLogger(AuthorityInterceptor.class); @Autowired private IWhiteListService whiteListService; /** * 攔截器做權(quán)限驗證 * @param request * @param response * @param handler * @return * @author 011336 * @date 2019/04/24 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ String whiteListUsername = request.getHeader("whiteListUsername"); String whiteListPassword = request.getHeader("whiteListPassword"); String projectCode = request.getHeader("projectCode"); if(StringUtils.isNotEmpty(projectCode)){ Optional<String> userName = Optional.ofNullable(whiteListUsername); Optional<String> passWord = Optional.ofNullable(whiteListPassword); String requestUri = request.getRequestURI(); Map<String,Object> condition = new HashMap<>(); condition.put("requestUri",requestUri); condition.put("projectCode",projectCode); WhiteList whiteList = whiteListService.query(condition); Optional<WhiteList> whiteOptional = Optional.ofNullable(whiteList); WhiteList white = whiteOptional.orElse(new WhiteList()); if(userName.orElse(UUID.randomUUID().toString()).equals(white.getWhiteListUsername()) && passWord.orElse(UUID.randomUUID().toString()).equals(white.getWhiteListPassword())) { LOGGER.info("["+projectCode+"]權(quán)限認(rèn)證成功!"); return true; } } //重置response response.reset(); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); PrintWriter pw = response.getWriter(); VsftpResult result = new VsftpResult(); result.setStatus(false); ErrorInfo errorInfo = new ErrorInfo("AUTHORITY.FAIL", "權(quán)限驗證失?。?); List<ErrorInfo> errors = new ArrayList<>(); errors.add(errorInfo); result.setErrors(errors); pw.write(JsonUtils.objectToJson(result)); pw.flush(); pw.close(); LOGGER.info("["+projectCode+"]權(quán)限認(rèn)證失?。?+ JsonUtils.objectToJson(result)); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView model) throws Exception{ } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception{ } } 現(xiàn)這樣吧...心情不好
|
|