您好,登錄后才能下訂單哦!
一、Apache ftpserver相關簡介
Apache FtpServer是100%純Java FTP服務器。它被設計為基于當前可用的開放協議的完整且可移植的FTP服務器引擎解決方案。FtpServer可以作為Windows服務或Unix / Linux守護程序獨立運行,也可以嵌入Java應用程序中。我們還提供對Spring應用程序內集成的支持,并以OSGi捆綁軟件的形式提供我們的發行版。默認的網絡支持基于高性能異步IO庫Apache MINA。使用MINA,FtpServer可以擴展到大量并發用戶。
二、Apache ftpserver相關特性
三、Apache ftpserver簡單部署使用(基于windows下,linux大同小異)
1、根據需要下載對應版本的部署包:https://mina.apache.org/ftpserver-project/downloads.html
2、解壓部署包并調整.\res\conf\users.properties和.\res\conf\ftpd-typical.xml配置文件
users.properties文件配置
例如配置一個bxl用戶: #密碼 配置新的用戶 ftpserver.user.bxl.userpassword=123456 #主目錄,這里可以自定義自己的主目錄 ftpserver.user.bxl.homedirectory=./res/bxl-home #當前用戶可用 ftpserver.user.bxl.enableflag=true #具有上傳權限 ftpserver.user.bxl.writepermission=true #最大登陸用戶數為20 ftpserver.user.bxl.maxloginnumber=20 #同IP登陸用戶數為2 ftpserver.user.bxl.maxloginperip=2 #空閑時間為300秒 ftpserver.user.bxl.idletime=300 #上傳速率限制為480000字節每秒 ftpserver.user.bxl.uploadrate=48000000 #下載速率限制為480000字節每秒 ftpserver.user.bxl.downloadrate=48000000
ftpd-typical.xml文件配置
<server xmlns="http://mina.apache.org/ftpserver/spring/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd" id="myServer"> <listeners> <nio-listener name="default" port="2121"> <ssl> <keystore file="./res/ftpserver.jks" password="password" /> </ssl> <!--注意:如果要支持外網連接,需要使用被動模式passive,默認開啟主動模式--> <data-connection idle-timeout="60"> <active enabled="true" ip-check="true" /> <!-- <passive ports="2000-2222" address="0.0.0.0" external-address="xxx.xxx.xxx.xxx" /> --> </data-connection> <!--添加ip黑名單--> <blacklist>127.0.0.1</blacklist> </nio-listener> </listeners> <!--這里添加encrypt-passwords="clear",去掉密碼加密--> <file-user-manager file="./res/conf/users.properties" encrypt-passwords="clear" /> </server>
3、啟動并訪問
首先啟動服務,打開cmd并cd到bin路徑執行.\ftpd.bat res/conf/ftpd-typical.xml,看到如下狀態說明啟動成功
測試訪問,打開瀏覽器輸入:ftp://localhost:2121/就會看到你的文件目錄了,如果沒有配置匿名用戶,則會要求你輸入用戶名密碼,正是你在user.properties中配置的
四、Springboot整合Apache ftpserver(重點)
方式一:獨立部署ftpserver服務
這種方式比較簡單,只要把服務部署好即可,然后通過FtpClien來完成相關操作,同jedis訪問redis服務一個道理,沒啥可說的。主要注意一下ftpserver的訪問模式,如果要支持外網連接,需要使用被動模式passive。
方式二:將ftpserver服務內嵌到springboot服務中
這種方式需要和springboot整合在一起,相對比較復雜,但這種方式下ftpserver會 隨著springboot服務啟動或關閉而開啟或銷毀。具體使用哪種方式就看自己的業務需求了。
簡單說一下我的實現的方案,ftpserver支持配置文件和db兩種方式來保存賬號信息和其它相關配置,如果我們的業務系統需要將用戶信息和ftp的賬號信息打通,并且還有相關的業務統計,比如統計系統中每個人上傳文件的時間、個數等等,那么使用數據庫來保存ftp賬號信息還是比較方便靈活的。我這里就選擇使用mysql了。
開始整合
1、項目添加依賴
//這些只是apache ftpserver相關的依賴,springboot項目本身的依賴大家自己添加即可 <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25 </version> </dependency> <dependency> <groupId>org.apache.ftpserver</groupId> <artifactId>ftpserver-core</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.ftpserver</groupId> <artifactId>ftplet-api</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.mina</groupId> <artifactId>mina-core</artifactId> <version>2.0.16</version> </dependency>
2、數據庫建表用來保存相關的賬戶信息(大家可以手動添加幾條用來測試),具體字段意思參考users.properties文件配置(可以想象一下以后我們的系統每注冊一個用戶都可以為其添加一條ftp_user信息,用來指定保存用戶的上傳數據等等)
CREATE TABLE FTP_USER ( userid VARCHAR(64) NOT NULL PRIMARY KEY, userpassword VARCHAR(64), homedirectory VARCHAR(128) NOT NULL, enableflag BOOLEAN DEFAULT TRUE, writepermission BOOLEAN DEFAULT FALSE, idletime INT DEFAULT 0, uploadrate INT DEFAULT 0, downloadrate INT DEFAULT 0, maxloginnumber INT DEFAULT 0, maxloginperip INT DEFAULT 0 );
3、配置ftpserver,提供ftpserver的init()、start()、stop()方法
import com.mysql.cj.jdbc.MysqlDataSource; import com.talkingdata.tds.ftpserver.plets.MyFtpPlet; import org.apache.commons.io.IOUtils; import org.apache.ftpserver.DataConnectionConfigurationFactory; import org.apache.ftpserver.FtpServer; import org.apache.ftpserver.FtpServerFactory; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.Ftplet; import org.apache.ftpserver.listener.Listener; import org.apache.ftpserver.listener.ListenerFactory; import org.apache.ftpserver.ssl.SslConfigurationFactory; import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor; import org.apache.ftpserver.usermanager.DbUserManagerFactory; import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 注意:被@Configuration標記的類會被加入ioc容器中,而且類中所有帶 @Bean注解的方法都會被動態代理,因此調用該方法返回的都是同一個實例。 * ftp服務訪問地址: * ftp://localhost:3131/ */ @Configuration("MyFtp") public class MyFtpServer { private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class); //springboot配置好數據源直接注入即可 @Autowired private DataSource dataSource; protected FtpServer server; //我們這里利用spring加載@Configuration的特性來完成ftp server的初始化 public MyFtpServer(DataSource dataSource) { this.dataSource = dataSource; initFtp(); logger.info("Apache ftp server is already instantiation complete!"); } /** * ftp server init * @throws IOException */ public void initFtp() { FtpServerFactory serverFactory = new FtpServerFactory(); ListenerFactory listenerFactory = new ListenerFactory(); //1、設置服務端口 listenerFactory.setPort(3131); //2、設置被動模式數據上傳的接口范圍,云服務器需要開放對應區間的端口給客戶端 DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory(); dataConnectionConfFactory.setPassivePorts("10000-10500"); listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration()); //3、增加SSL安全配置 // SslConfigurationFactory ssl = new SslConfigurationFactory(); // ssl.setKeystoreFile(new File("src/main/resources/ftpserver.jks")); // ssl.setKeystorePassword("password"); //ssl.setSslProtocol("SSL"); // set the SSL configuration for the listener // listenerFactory.setSslConfiguration(ssl.createSslConfiguration()); // listenerFactory.setImplicitSsl(true); //4、替換默認的監聽器 Listener listener = listenerFactory.createListener(); serverFactory.addListener("default", listener); //5、配置自定義用戶事件 Map<String, Ftplet> ftpLets = new HashMap(); ftpLets.put("ftpService", new MyFtpPlet()); serverFactory.setFtplets(ftpLets); //6、讀取用戶的配置信息 //注意:配置文件位于resources目錄下,如果項目使用內置容器打成jar包發布,FTPServer無法直接直接讀取Jar包中的配置文件。 //解決辦法:將文件復制到指定目錄(本文指定到根目錄)下然后FTPServer才能讀取到。 // PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory(); // String tempPath = System.getProperty("java.io.tmpdir") + System.currentTimeMillis() + ".properties"; // File tempConfig = new File(tempPath); // ClassPathResource resource = new ClassPathResource("users.properties"); // IOUtils.copy(resource.getInputStream(), new FileOutputStream(tempConfig)); // userManagerFactory.setFile(tempConfig); // userManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor()); //密碼以明文的方式 // serverFactory.setUserManager(userManagerFactory.createUserManager()); //6.2、基于數據庫來存儲用戶實例 DbUserManagerFactory dbUserManagerFactory = new DbUserManagerFactory(); //todo.... dbUserManagerFactory.setDataSource(dataSource); dbUserManagerFactory.setAdminName("admin"); dbUserManagerFactory.setSqlUserAdmin("SELECT userid FROM FTP_USER WHERE userid='{userid}' AND userid='admin'"); dbUserManagerFactory.setSqlUserInsert("INSERT INTO FTP_USER (userid, userpassword, homedirectory, " + "enableflag, writepermission, idletime, uploadrate, downloadrate) VALUES " + "('{userid}', '{userpassword}', '{homedirectory}', {enableflag}, " + "{writepermission}, {idletime}, uploadrate}, {downloadrate})"); dbUserManagerFactory.setSqlUserDelete("DELETE FROM FTP_USER WHERE userid = '{userid}'"); dbUserManagerFactory.setSqlUserUpdate("UPDATE FTP_USER SET userpassword='{userpassword}',homedirectory='{homedirectory}',enableflag={enableflag},writepermission={writepermission},idletime={idletime},uploadrate={uploadrate},downloadrate={downloadrate},maxloginnumber={maxloginnumber}, maxloginperip={maxloginperip} WHERE userid='{userid}'"); dbUserManagerFactory.setSqlUserSelect("SELECT * FROM FTP_USER WHERE userid = '{userid}'"); dbUserManagerFactory.setSqlUserSelectAll("SELECT userid FROM FTP_USER ORDER BY userid"); dbUserManagerFactory.setSqlUserAuthenticate("SELECT userid, userpassword FROM FTP_USER WHERE userid='{userid}'"); dbUserManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor()); serverFactory.setUserManager(dbUserManagerFactory.createUserManager()); //7、實例化FTP Server server = serverFactory.createServer(); } /** * ftp server start */ public void start(){ try { server.start(); logger.info("Apache Ftp server is starting!"); }catch(FtpException e) { e.printStackTrace(); } } /** * ftp server stop */ public void stop() { server.stop(); logger.info("Apache Ftp server is stoping!"); } }
4、配置監聽器,使spring容器啟動時啟動ftpserver,在spring容器銷毀時停止ftpserver
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @WebListener public class FtpServerListener implements ServletContextListener { private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class); private static final String SERVER_NAME="FTP-SERVER"; @Autowired private MyFtpServer server; //容器關閉時調用方法stop ftpServer public void contextDestroyed(ServletContextEvent sce) { // WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); // MyFtpServer server=(MyFtpServer)ctx.getServletContext().getAttribute(SERVER_NAME); server.stop(); sce.getServletContext().removeAttribute(SERVER_NAME); logger.info("Apache Ftp server is stoped!"); } //容器初始化調用方法start ftpServer public void contextInitialized(ServletContextEvent sce) { // WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); // MyFtpServer server=(MyFtpServer) ctx.getBean("MyFtp"); sce.getServletContext().setAttribute(SERVER_NAME,server); try { //項目啟動時已經加載好了 server.start(); logger.info("Apache Ftp server is started!"); } catch (Exception e){ e.printStackTrace(); throw new RuntimeException("Apache Ftp server start failed!", e); } } }
5、通過繼承DefaultFtplet抽象類來實現一些自定義用戶事件(我這里只是舉例)
import org.apache.ftpserver.ftplet.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; public class MyFtpPlet extends DefaultFtplet { private static final Logger logger = LoggerFactory.getLogger(MyFtpPlet.class); @Override public FtpletResult onUploadStart(FtpSession session, FtpRequest request) throws FtpException, IOException { //獲取上傳文件的上傳路徑 String path = session.getUser().getHomeDirectory(); //獲取上傳用戶 String name = session.getUser().getName(); //獲取上傳文件名 String filename = request.getArgument(); logger.info("用戶:'{}',上傳文件到目錄:'{}',文件名稱為:'{}',狀態:開始上傳~", name, path, filename); return super.onUploadStart(session, request); } @Override public FtpletResult onUploadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { //獲取上傳文件的上傳路徑 String path = session.getUser().getHomeDirectory(); //獲取上傳用戶 String name = session.getUser().getName(); //獲取上傳文件名 String filename = request.getArgument(); logger.info("用戶:'{}',上傳文件到目錄:'{}',文件名稱為:'{},狀態:成功!'", name, path, filename); return super.onUploadEnd(session, request); } @Override public FtpletResult onDownloadStart(FtpSession session, FtpRequest request) throws FtpException, IOException { //todo servies... return super.onDownloadStart(session, request); } @Override public FtpletResult onDownloadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { //todo servies... return super.onDownloadEnd(session, request); } }
6、 配置springboot靜態資源的訪問
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class FtpConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //可以通過os來判斷 String os = System.getProperty("os.name"); //linux設置 // registry.addResourceHandler("/ftp/**").addResourceLocations("file:/home/pic/"); //windows設置 //第一個方法設置訪問路徑前綴,第二個方法設置資源路徑,既可以指定項目classpath路徑,也可以指定其它非項目路徑 registry.addResourceHandler("/ftp/**").addResourceLocations("file:D:\\apache-ftpserver-1.1.1\\res\\bxl-home\\"); registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); } }
7、以上6步已經完成ftpserver的配置,隨著springboot項目的啟動就會開啟ftpserver服務,下面在給大家貼一下客戶端的訪問的util,大家可以自行封裝一下即可。
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPSClient; import java.io.*; public class FtpClientUtil { // ftp服務器ip地址 private static String FTP_ADDRESS = "localhost"; // 端口號 private static int FTP_PORT = 3131; // 用戶名 private static String FTP_USERNAME = "bxl"; // 密碼 private static String FTP_PASSWORD = "123456"; // 相對路徑 private static String FTP_BASEPATH = ""; public static boolean uploadFile(String remoteFileName, InputStream input) { boolean flag = false; FTPClient ftp = new FTPClient(); ftp.setControlEncoding("UTF-8"); try { int reply; ftp.connect(FTP_ADDRESS, FTP_PORT);// 連接FTP服務器 ftp.login(FTP_USERNAME, FTP_PASSWORD);// 登錄 reply = ftp.getReplyCode(); System.out.println("登錄ftp服務返回狀態碼為:" + reply); if (!FTPReply.isPositiveCompletion(reply)) { ftp.disconnect(); return flag; } ftp.setFileType(FTPClient.BINARY_FILE_TYPE); //設置為被動模式 ftp.enterLocalPassiveMode(); ftp.makeDirectory(FTP_BASEPATH); ftp.changeWorkingDirectory(FTP_BASEPATH); //originFilePath就是上傳文件的文件名,建議使用生成的唯一命名,中文命名最好做轉碼 boolean a = ftp.storeFile(remoteFileName, input); // boolean a = ftp.storeFile(new String(remoteFileName.getBytes(),"iso-8859-1"),input); System.out.println("要上傳的原始文件名為:" + remoteFileName + ", 上傳結果:" + a); input.close(); ftp.logout(); flag = true; } catch (IOException e) { e.printStackTrace(); } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } return flag; } // public static Boolean uploadFile(String remoteFileName, InputStream inputStream, String ftpAddress, int ftpPort, // String ftpName, String ftpPassWord, String ftpBasePath) { // FTP_ADDRESS = ftpAddress; // FTP_PORT = ftpPort; // FTP_USERNAME = ftpName; // FTP_PASSWORD = ftpPassWord; // FTP_BASEPATH = ftpBasePath; // uploadFile(remoteFileName,inputStream); // return true; // } public static boolean deleteFile(String filename) { boolean flag = false; FTPClient ftpClient = new FTPClient(); try { // 連接FTP服務器 ftpClient.connect(FTP_ADDRESS, FTP_PORT); // 登錄FTP服務器 ftpClient.login(FTP_USERNAME, FTP_PASSWORD); // 驗證FTP服務器是否登錄成功 int replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { return flag; } // 切換FTP目錄 ftpClient.changeWorkingDirectory(FTP_BASEPATH); ftpClient.dele(filename); ftpClient.logout(); flag = true; } catch (Exception e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { ftpClient.logout(); } catch (IOException e) { } } } return flag; } public static boolean downloadFile(String filename, String localPath) { boolean flag = false; // FTPSClient ftpClient = new FTPSClient("TLS", true); FTPClient ftpClient = new FTPClient(); try { // 連接FTP服務器 ftpClient.connect(FTP_ADDRESS, FTP_PORT); // 登錄FTP服務器 ftpClient.login(FTP_USERNAME, FTP_PASSWORD); // 驗證FTP服務器是否登錄成功 int replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { return flag; } // 切換FTP目錄 ftpClient.changeWorkingDirectory(FTP_BASEPATH); //此處為demo方法,正常應該到數據庫中查詢fileName FTPFile[] ftpFiles = ftpClient.listFiles(); for (FTPFile file : ftpFiles) { if (filename.equalsIgnoreCase(file.getName())) { File localFile = new File(localPath + "/" + file.getName()); OutputStream os = new FileOutputStream(localFile); ftpClient.retrieveFile(file.getName(), os); os.close(); } } ftpClient.logout(); flag = true; System.out.println("文件下載完成!!!"); } catch (Exception e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { ftpClient.logout(); } catch (IOException e) { } } } return flag; } }
五、總結
到此,所有的配置已經完成,我們的業務系統也同時也承擔了一個角色,那就是ftp服務器,整個配置是沒有加入SSL/TLS安全機制的,大家如果感興趣可以自行研究下。我代碼中注釋那那部分,只是注意下通過客戶端訪問時,需要使用FtpsCliet,而非FtpCliet。當然還需要配置你自己的ftpserver.jks文件,也就是java key store。百度下一下如何生成,很簡單哦!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。