springboot整合apache ftpserver詳細教程


springboot整合apache ftpserver詳細教程

一、Apache ftpserver相關簡介

Apache FtpServer是100%純Java FTP服務器。它被設計為基於當前可用的開放協議的完整且可移植的FTP服務器引擎解決方案。FtpServer可以作為Windows服務或Unix / Linux守護程序獨立運行,也可以嵌入Java應用程序中。我們還提供對Spring應用程序內集成的支持,並以OSGi捆綁軟件的形式提供我們的發行版。默認的網絡支持基於高性能異步IO庫Apache MINA。使用MINA,FtpServer可以擴展到大量併發用戶。

二、Apache ftpserver相關特性

  • 100%純Java,免費的開源可恢復FTP服務器
  • 多平臺支持和多線程設計。
  • 用戶虛擬目錄,寫入權限,空閒超時和上載/下載帶寬限制支持。
  • 匿名登錄支持。
  • 上傳和下載文件都是可恢復的。
  • 處理ASCII和二進制數據傳輸。
  • 支持IP限制以禁止IP。
  • 數據庫和文件可用於存儲用戶數據。
  • 所有FTP消息都是可定製的。
  • 隱式/顯式SSL / TLS支持。
  • MDTM支持-您的用戶可以更改文件的日期時間戳。
  • “模式Z”支持更快地上傳/下載數據。
  • 可以輕鬆添加自定義用戶管理器,IP限制器,記錄器。
  • 可以添加用戶事件通知(Ftplet)。

三、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文件配置

<code>例如配置一個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
/<code>

ftpd-typical.xml文件配置

<code><server>        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>

<keystore>


<data-connection>
<active>

/<data-connection>

<blacklist>127.0.0.1/<blacklist>
/<nio-listener>
/<listeners>


<file-user-manager>
/<server>
/<code>
  • 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、項目添加依賴
<code>//這些只是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>
/<code>
  • 2、數據庫建表用來保存相關的賬戶信息(大家可以手動添加幾條用來測試),具體字段意思參考users.properties文件配置(可以想象一下以後我們的系統每註冊一個用戶都可以為其添加一條ftp_user信息,用來指定保存用戶的上傳數據等等)
<code>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
);
/<code>
  • 3、配置ftpserver,提供ftpserver的init()、start()、stop()方法。
<code>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> 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!");
}

}
/<string>/<code>
  • 4、配置監聽器,使spring容器啟動時啟動ftpserver,在spring容器銷燬時停止ftpserver
<code>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);
}
}

}
/<code>
  • 5、通過繼承DefaultFtplet抽象類來實現一些自定義用戶事件(我這裡只是舉例)
<code>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);
}

}
/<code>
  • 6、 配置springboot靜態資源的訪問
<code>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/");
}

}
/<code>
  • 7、以上6步已經完成ftpserver的配置,隨著springboot項目的啟動就會開啟ftpserver服務,下面在給大家貼一下客戶端的訪問的util,大家可以自行封裝一下即可。
<code>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;
}
}
/<code>

五、總結

到此,所有的配置已經完成,我們的業務系統也同時也承擔了一個角色,那就是ftp服務器,整個配置是沒有加入SSL/TLS安全機制的,大家如果感興趣可以自行研究下。我代碼中註釋那那部分,只是注意下通過客戶端訪問時,需要使用FtpsCliet,而非FtpCliet。當然還需要配置你自己的ftpserver.jks文件,也就是java key store。百度下一下如何生成,很簡單哦!


springboot整合apache ftpserver詳細教程

關注頭條號JAVA 後端架構 》 ,話癆技術,職場,招聘,在線面試,進階提升。每天一篇技術分享

沒有做不到的,只有想不到的。


springboot整合apache ftpserver詳細教程


分享到:


相關文章: