springboot整合apache ftpserver详细教程(推荐)
一、Apacheftpserver相关简介
ApacheFtpServer是100%纯JavaFTP服务器。它被设计为基于当前可用的开放协议的完整且可移植的FTP服务器引擎解决方案。FtpServer可以作为Windows服务或Unix/Linux守护程序独立运行,也可以嵌入Java应用程序中。我们还提供对Spring应用程序内集成的支持,并以OSGi捆绑软件的形式提供我们的发行版。默认的网络支持基于高性能异步IO库ApacheMINA。使用MINA,FtpServer可以扩展到大量并发用户。
二、Apacheftpserver相关特性
- 100%纯Java,免费的开源可恢复FTP服务器
- 多平台支持和多线程设计。
- 用户虚拟目录,写入权限,空闲超时和上载/下载带宽限制支持。
- 匿名登录支持。
- 上传和下载文件都是可恢复的。
- 处理ASCII和二进制数据传输。
- 支持IP限制以禁止IP。
- 数据库和文件可用于存储用户数据。
- 所有FTP消息都是可定制的。
- 隐式/显式SSL/TLS支持。
- MDTM支持-您的用户可以更改文件的日期时间戳。
- “模式Z”支持更快地上传/下载数据。
- 可以轻松添加自定义用户管理器,IP限制器,记录器。
- 可以添加用户事件通知(Ftplet)。
三、Apacheftpserver简单部署使用(基于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文件配置
127.0.0.1
3、启动并访问
首先启动服务,打开cmd并cd到bin路径执行.\ftpd.batres/conf/ftpd-typical.xml,看到如下状态说明启动成功
测试访问,打开浏览器输入:ftp://localhost:2121/就会看到你的文件目录了,如果没有配置匿名用户,则会要求你输入用户名密码,正是你在user.properties中配置的
四、Springboot整合Apacheftpserver(重点)
方式一:独立部署ftpserver服务
这种方式比较简单,只要把服务部署好即可,然后通过FtpClien来完成相关操作,同jedis访问redis服务一个道理,没啥可说的。主要注意一下ftpserver的访问模式,如果要支持外网连接,需要使用被动模式passive。
方式二:将ftpserver服务内嵌到springboot服务中
这种方式需要和springboot整合在一起,相对比较复杂,但这种方式下ftpserver会随着springboot服务启动或关闭而开启或销毁。具体使用哪种方式就看自己的业务需求了。
简单说一下我的实现的方案,ftpserver支持配置文件和db两种方式来保存账号信息和其它相关配置,如果我们的业务系统需要将用户信息和ftp的账号信息打通,并且还有相关的业务统计,比如统计系统中每个人上传文件的时间、个数等等,那么使用数据库来保存ftp账号信息还是比较方便灵活的。我这里就选择使用mysql了。
开始整合
1、项目添加依赖
//这些只是apacheftpserver相关的依赖,springboot项目本身的依赖大家自己添加即可org.slf4j slf4j-log4j12 1.7.25 org.apache.ftpserver ftpserver-core 1.1.1 org.apache.ftpserver ftplet-api 1.1.1 org.apache.mina mina-core 2.0.16
2、数据库建表用来保存相关的账户信息(大家可以手动添加几条用来测试),具体字段意思参考users.properties文件配置(可以想象一下以后我们的系统每注册一个用户都可以为其添加一条ftp_user信息,用来指定保存用户的上传数据等等)
CREATETABLEFTP_USER( useridVARCHAR(64)NOTNULLPRIMARYKEY, userpasswordVARCHAR(64), homedirectoryVARCHAR(128)NOTNULL, enableflagBOOLEANDEFAULTTRUE, writepermissionBOOLEANDEFAULTFALSE, idletimeINTDEFAULT0, uploadrateINTDEFAULT0, downloadrateINTDEFAULT0, maxloginnumberINTDEFAULT0, maxloginperipINTDEFAULT0 );
3、配置ftpserver,提供ftpserver的init()、start()、stop()方法
importcom.mysql.cj.jdbc.MysqlDataSource; importcom.talkingdata.tds.ftpserver.plets.MyFtpPlet; importorg.apache.commons.io.IOUtils; importorg.apache.ftpserver.DataConnectionConfigurationFactory; importorg.apache.ftpserver.FtpServer; importorg.apache.ftpserver.FtpServerFactory; importorg.apache.ftpserver.ftplet.FtpException; importorg.apache.ftpserver.ftplet.Ftplet; importorg.apache.ftpserver.listener.Listener; importorg.apache.ftpserver.listener.ListenerFactory; importorg.apache.ftpserver.ssl.SslConfigurationFactory; importorg.apache.ftpserver.usermanager.ClearTextPasswordEncryptor; importorg.apache.ftpserver.usermanager.DbUserManagerFactory; importorg.apache.ftpserver.usermanager.PropertiesUserManagerFactory; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.core.io.ClassPathResource; importorg.springframework.stereotype.Component; importjavax.sql.DataSource; importjava.io.File; importjava.io.FileOutputStream; importjava.io.IOException; importjava.util.HashMap; importjava.util.Map; /** *注意:被@Configuration标记的类会被加入ioc容器中,而且类中所有带@Bean注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。 *ftp服务访问地址: *ftp://localhost:3131/ */ @Configuration("MyFtp") publicclassMyFtpServer{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MyFtpServer.class); //springboot配置好数据源直接注入即可 @Autowired privateDataSourcedataSource; protectedFtpServerserver; //我们这里利用spring加载@Configuration的特性来完成ftpserver的初始化 publicMyFtpServer(DataSourcedataSource){ this.dataSource=dataSource; initFtp(); logger.info("Apacheftpserverisalreadyinstantiationcomplete!"); } /** *ftpserverinit *@throwsIOException */ publicvoidinitFtp(){ FtpServerFactoryserverFactory=newFtpServerFactory(); ListenerFactorylistenerFactory=newListenerFactory(); //1、设置服务端口 listenerFactory.setPort(3131); //2、设置被动模式数据上传的接口范围,云服务器需要开放对应区间的端口给客户端 DataConnectionConfigurationFactorydataConnectionConfFactory=newDataConnectionConfigurationFactory(); dataConnectionConfFactory.setPassivePorts("10000-10500"); listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration()); //3、增加SSL安全配置 //SslConfigurationFactoryssl=newSslConfigurationFactory(); //ssl.setKeystoreFile(newFile("src/main/resources/ftpserver.jks")); //ssl.setKeystorePassword("password"); //ssl.setSslProtocol("SSL"); //settheSSLconfigurationforthelistener //listenerFactory.setSslConfiguration(ssl.createSslConfiguration()); //listenerFactory.setImplicitSsl(true); //4、替换默认的监听器 Listenerlistener=listenerFactory.createListener(); serverFactory.addListener("default",listener); //5、配置自定义用户事件 MapftpLets=newHashMap(); ftpLets.put("ftpService",newMyFtpPlet()); serverFactory.setFtplets(ftpLets); //6、读取用户的配置信息 //注意:配置文件位于resources目录下,如果项目使用内置容器打成jar包发布,FTPServer无法直接直接读取Jar包中的配置文件。 //解决办法:将文件复制到指定目录(本文指定到根目录)下然后FTPServer才能读取到。 //PropertiesUserManagerFactoryuserManagerFactory=newPropertiesUserManagerFactory(); //StringtempPath=System.getProperty("java.io.tmpdir")+System.currentTimeMillis()+".properties"; //FiletempConfig=newFile(tempPath); //ClassPathResourceresource=newClassPathResource("users.properties"); //IOUtils.copy(resource.getInputStream(),newFileOutputStream(tempConfig)); //userManagerFactory.setFile(tempConfig); //userManagerFactory.setPasswordEncryptor(newClearTextPasswordEncryptor());//密码以明文的方式 //serverFactory.setUserManager(userManagerFactory.createUserManager()); //6.2、基于数据库来存储用户实例 DbUserManagerFactorydbUserManagerFactory=newDbUserManagerFactory(); //todo.... dbUserManagerFactory.setDataSource(dataSource); dbUserManagerFactory.setAdminName("admin"); dbUserManagerFactory.setSqlUserAdmin("SELECTuseridFROMFTP_USERWHEREuserid='{userid}'ANDuserid='admin'"); dbUserManagerFactory.setSqlUserInsert("INSERTINTOFTP_USER(userid,userpassword,homedirectory,"+ "enableflag,writepermission,idletime,uploadrate,downloadrate)VALUES"+ "('{userid}','{userpassword}','{homedirectory}',{enableflag},"+ "{writepermission},{idletime},uploadrate},{downloadrate})"); dbUserManagerFactory.setSqlUserDelete("DELETEFROMFTP_USERWHEREuserid='{userid}'"); dbUserManagerFactory.setSqlUserUpdate("UPDATEFTP_USERSETuserpassword='{userpassword}',homedirectory='{homedirectory}',enableflag={enableflag},writepermission={writepermission},idletime={idletime},uploadrate={uploadrate},downloadrate={downloadrate},maxloginnumber={maxloginnumber},maxloginperip={maxloginperip}WHEREuserid='{userid}'"); dbUserManagerFactory.setSqlUserSelect("SELECT*FROMFTP_USERWHEREuserid='{userid}'"); dbUserManagerFactory.setSqlUserSelectAll("SELECTuseridFROMFTP_USERORDERBYuserid"); dbUserManagerFactory.setSqlUserAuthenticate("SELECTuserid,userpasswordFROMFTP_USERWHEREuserid='{userid}'"); dbUserManagerFactory.setPasswordEncryptor(newClearTextPasswordEncryptor()); serverFactory.setUserManager(dbUserManagerFactory.createUserManager()); //7、实例化FTPServer server=serverFactory.createServer(); } /** *ftpserverstart */ publicvoidstart(){ try{ server.start(); logger.info("ApacheFtpserverisstarting!"); }catch(FtpExceptione){ e.printStackTrace(); } } /** *ftpserverstop */ publicvoidstop(){ server.stop(); logger.info("ApacheFtpserverisstoping!"); } }
4、配置监听器,使spring容器启动时启动ftpserver,在spring容器销毁时停止ftpserver
importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.context.WebApplicationContext; importorg.springframework.web.context.support.WebApplicationContextUtils; importjavax.servlet.ServletContextEvent; importjavax.servlet.ServletContextListener; importjavax.servlet.annotation.WebListener; @WebListener publicclassFtpServerListenerimplementsServletContextListener{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MyFtpServer.class); privatestaticfinalStringSERVER_NAME="FTP-SERVER"; @Autowired privateMyFtpServerserver; //容器关闭时调用方法stopftpServer publicvoidcontextDestroyed(ServletContextEventsce){ //WebApplicationContextctx=WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); //MyFtpServerserver=(MyFtpServer)ctx.getServletContext().getAttribute(SERVER_NAME); server.stop(); sce.getServletContext().removeAttribute(SERVER_NAME); logger.info("ApacheFtpserverisstoped!"); } //容器初始化调用方法startftpServer publicvoidcontextInitialized(ServletContextEventsce){ //WebApplicationContextctx=WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); //MyFtpServerserver=(MyFtpServer)ctx.getBean("MyFtp"); sce.getServletContext().setAttribute(SERVER_NAME,server); try{ //项目启动时已经加载好了 server.start(); logger.info("ApacheFtpserverisstarted!"); }catch(Exceptione){ e.printStackTrace(); thrownewRuntimeException("ApacheFtpserverstartfailed!",e); } } }
5、通过继承DefaultFtplet抽象类来实现一些自定义用户事件(我这里只是举例)
importorg.apache.ftpserver.ftplet.*; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importjava.io.IOException; publicclassMyFtpPletextendsDefaultFtplet{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MyFtpPlet.class); @Override publicFtpletResultonUploadStart(FtpSessionsession,FtpRequestrequest) throwsFtpException,IOException{ //获取上传文件的上传路径 Stringpath=session.getUser().getHomeDirectory(); //获取上传用户 Stringname=session.getUser().getName(); //获取上传文件名 Stringfilename=request.getArgument(); logger.info("用户:'{}',上传文件到目录:'{}',文件名称为:'{}',状态:开始上传~",name,path,filename); returnsuper.onUploadStart(session,request); } @Override publicFtpletResultonUploadEnd(FtpSessionsession,FtpRequestrequest) throwsFtpException,IOException{ //获取上传文件的上传路径 Stringpath=session.getUser().getHomeDirectory(); //获取上传用户 Stringname=session.getUser().getName(); //获取上传文件名 Stringfilename=request.getArgument(); logger.info("用户:'{}',上传文件到目录:'{}',文件名称为:'{},状态:成功!'",name,path,filename); returnsuper.onUploadEnd(session,request); } @Override publicFtpletResultonDownloadStart(FtpSessionsession,FtpRequestrequest)throwsFtpException,IOException{ //todoservies... returnsuper.onDownloadStart(session,request); } @Override publicFtpletResultonDownloadEnd(FtpSessionsession,FtpRequestrequest)throwsFtpException,IOException{ //todoservies... returnsuper.onDownloadEnd(session,request); } }
6、配置springboot静态资源的访问
importorg.springframework.context.annotation.Configuration; importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration publicclassFtpConfigimplementsWebMvcConfigurer{ @Override publicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){ //可以通过os来判断 Stringos=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,大家可以自行封装一下即可。
importorg.apache.commons.net.ftp.FTPClient; importorg.apache.commons.net.ftp.FTPFile; importorg.apache.commons.net.ftp.FTPReply; importorg.apache.commons.net.ftp.FTPSClient; importjava.io.*; publicclassFtpClientUtil{ //ftp服务器ip地址 privatestaticStringFTP_ADDRESS="localhost"; //端口号 privatestaticintFTP_PORT=3131; //用户名 privatestaticStringFTP_USERNAME="bxl"; //密码 privatestaticStringFTP_PASSWORD="123456"; //相对路径 privatestaticStringFTP_BASEPATH=""; publicstaticbooleanuploadFile(StringremoteFileName,InputStreaminput){ booleanflag=false; FTPClientftp=newFTPClient(); ftp.setControlEncoding("UTF-8"); try{ intreply; 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(); returnflag; } ftp.setFileType(FTPClient.BINARY_FILE_TYPE); //设置为被动模式 ftp.enterLocalPassiveMode(); ftp.makeDirectory(FTP_BASEPATH); ftp.changeWorkingDirectory(FTP_BASEPATH); //originFilePath就是上传文件的文件名,建议使用生成的唯一命名,中文命名最好做转码 booleana=ftp.storeFile(remoteFileName,input); //booleana=ftp.storeFile(newString(remoteFileName.getBytes(),"iso-8859-1"),input); System.out.println("要上传的原始文件名为:"+remoteFileName+",上传结果:"+a); input.close(); ftp.logout(); flag=true; }catch(IOExceptione){ e.printStackTrace(); }finally{ if(ftp.isConnected()){ try{ ftp.disconnect(); }catch(IOExceptionioe){ } } } returnflag; } //publicstaticBooleanuploadFile(StringremoteFileName,InputStreaminputStream,StringftpAddress,intftpPort, //StringftpName,StringftpPassWord,StringftpBasePath){ //FTP_ADDRESS=ftpAddress; //FTP_PORT=ftpPort; //FTP_USERNAME=ftpName; //FTP_PASSWORD=ftpPassWord; //FTP_BASEPATH=ftpBasePath; //uploadFile(remoteFileName,inputStream); //returntrue; //} publicstaticbooleandeleteFile(Stringfilename){ booleanflag=false; FTPClientftpClient=newFTPClient(); try{ //连接FTP服务器 ftpClient.connect(FTP_ADDRESS,FTP_PORT); //登录FTP服务器 ftpClient.login(FTP_USERNAME,FTP_PASSWORD); //验证FTP服务器是否登录成功 intreplyCode=ftpClient.getReplyCode(); if(!FTPReply.isPositiveCompletion(replyCode)){ returnflag; } //切换FTP目录 ftpClient.changeWorkingDirectory(FTP_BASEPATH); ftpClient.dele(filename); ftpClient.logout(); flag=true; }catch(Exceptione){ e.printStackTrace(); }finally{ if(ftpClient.isConnected()){ try{ ftpClient.logout(); }catch(IOExceptione){ } } } returnflag; } publicstaticbooleandownloadFile(Stringfilename,StringlocalPath){ booleanflag=false; //FTPSClientftpClient=newFTPSClient("TLS",true); FTPClientftpClient=newFTPClient(); try{ //连接FTP服务器 ftpClient.connect(FTP_ADDRESS,FTP_PORT); //登录FTP服务器 ftpClient.login(FTP_USERNAME,FTP_PASSWORD); //验证FTP服务器是否登录成功 intreplyCode=ftpClient.getReplyCode(); if(!FTPReply.isPositiveCompletion(replyCode)){ returnflag; } //切换FTP目录 ftpClient.changeWorkingDirectory(FTP_BASEPATH); //此处为demo方法,正常应该到数据库中查询fileName FTPFile[]ftpFiles=ftpClient.listFiles(); for(FTPFilefile:ftpFiles){ if(filename.equalsIgnoreCase(file.getName())){ FilelocalFile=newFile(localPath+"/"+file.getName()); OutputStreamos=newFileOutputStream(localFile); ftpClient.retrieveFile(file.getName(),os); os.close(); } } ftpClient.logout(); flag=true; System.out.println("文件下载完成!!!"); }catch(Exceptione){ e.printStackTrace(); }finally{ if(ftpClient.isConnected()){ try{ ftpClient.logout(); }catch(IOExceptione){ } } } returnflag; } }
五、总结
到此,所有的配置已经完成,我们的业务系统也同时也承担了一个角色,那就是ftp服务器,整个配置是没有加入SSL/TLS安全机制的,大家如果感兴趣可以自行研究下。我代码中注释那那部分,只是注意下通过客户端访问时,需要使用FtpsCliet,而非FtpCliet。当然还需要配置你自己的ftpserver.jks文件,也就是javakeystore。百度下一下如何生成,很简单哦!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。