最近有一个在集成系统上提供1G以上文件下载的功能,还要提供文件的展示功能和删除的操作,因为常规的文件流速度慢并且容易断掉因此我们采用FTP的方式,系统架构如下图所示,这里我们采用的ftp框架是apache的ftpserver
首先是初始化ftpserver的配置,这里我们采用的是配置文件的方式,另外还可以使用注释掉的代码中的数据库方式
/**
* 注意:被@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() {
//this.dataSource = dataSource;
try{
initFtp();
}catch (Exception e){
e.printStackTrace();
}
logger.info("Apache ftp server is already instantiation complete!");
}
/**
* ftp server init
* @throws IOException
*/
public void initFtp() throws IOException {
FtpServerFactory serverFactory = new FtpServerFactory();
ListenerFactory listenerFactory = new ListenerFactory();
//1、设置服务端口
listenerFactory.setPort(UsersConst.port);
//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("config/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!");
}
}
下面是用于与springboot一同启动和销毁的监听类
@WebListener
public class FtpServerListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(FtpServerListener.class);
private static final String SERVER_NAME="FTP-SERVER";
//tomcat容器关闭时调用方法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);
}
//spring 容器初始化调用方法startFtpServer
public void contextInitialized(ServletContextEvent sce) {
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
//spring boot 启动类中创建bean,命名为MyFtp()
MyFtpServer server = (MyFtpServer) ctx.getBean("MyFtp");
sce.getServletContext().setAttribute(SERVER_NAME, server);
try {
server.initFtp();
server.start();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("FTP启动失败", e);
}
}
}
springboot启动类,在启动时需要注册MyFtpServer的bean以及将监听器注册到ServletContext中
/**
* @Classname Application
* @Description 启动类
* @Date 2020/8/25 2:39 下午
* @Created by eatonsong
*/
@SpringBootApplication
@ComponentScan(basePackages = "com.viewhigh.ftpserver")
@Configuration
public class Application implements ServletContextInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
@Override
public void onStartup(ServletContext servletContext) {
servletContext.addListener(FtpServerListener.class);
}
@Bean
public MyFtpServer MyFtp(){
return new MyFtpServer();
}
}
设置ftp的访问路径以及前缀
@Configuration
public abstract class FtpConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//可以通过os来判断
String os = System.getProperty("os.name");
String homedirectory = UsersConst.homedirectory;
//linux设置
// registry.addResourceHandler("/ftp/**").addResourceLocations("file:/home/pic/");
//windows设置
//第一个方法设置访问路径前缀,第二个方法设置资源路径,既可以指定项目classpath路径,也可以指定其它非项目路径
registry.addResourceHandler("/**").addResourceLocations("file:"+homedirectory);
}
}
users.properties用户配置
#admin用户密码和其他设置
ftpserver.user.admin.userpassword=admin
ftpserver.user.admin.homedirectory=C:\\myftp
#ftpserver.user.admin.homedirectory=/home/pic/
ftpserver.user.admin.enableflag=true
ftpserver.user.admin.writepermission=true
ftpserver.user.admin.maxloginnumber=0
ftpserver.user.admin.maxloginperip=0
ftpserver.user.admin.idletime=0
ftpserver.user.admin.uploadrate=0
ftpserver.user.admin.downloadrate=0
#匿名用户密码和其他设置(本处不设置匿名用户密码)
ftpserver.user.anonymous.userpassword=
#ftpserver.user.anonymous.homedirectory=C:\\myftp
ftpserver.user.anonymous.enableflag=true
ftpserver.user.anonymous.writepermission=true
ftpserver.user.anonymous.maxloginnumber=20
ftpserver.user.anonymous.maxloginperip=2
ftpserver.user.anonymous.idletime=300
ftpserver.user.anonymous.uploadrate=4800
ftpserver.user.anonymous.downloadrate=4800
这样我们的ftpserver就配置好了,启动springboot项目后访问ftp://127.0.0.1:3131可以看到我们的ftp服务界面
这里我们要为集成系统提供可以查询,删除的api
首先需要一个操作ftp的工具类
public class FtpClientUtil {
// ftp服务器ip地址
private static String FTP_ADDRESS = UsersConst.ipaddress;
// 端口号
private static int FTP_PORT = UsersConst.port;
// 用户名
private static String FTP_USERNAME = UsersConst.username;
// 密码
private static String FTP_PASSWORD = UsersConst.userpassword;
// 相对路径
private static String FTP_BASEPATH = "";
private static FTPClient connect() throws IOException{
int reply;
FTPClient ftp = new FTPClient();
ftp.setControlEncoding("UTF-8");
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();
throw new RuntimeException("登录ftp服务返回状态码为:" + reply);
}
return ftp;
}
public static void disconnect(FTPClient ftp){
if (ftp!=null&&ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
public static FTPFile[] listFile(String path) throws Exception{
FTPClient ftp = null;
try {
ftp = connect();
FTPFile []fileList = ftp.listFiles(path);
ftp.logout();
return fileList;
}finally {
disconnect(ftp);
}
}
public static void uploadFile(String remoteFileName, InputStream input) throws Exception {
FTPClient ftp = null;
try {
ftp = connect();
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();
} finally {
disconnect(ftp);
}
}
public static void deleteFile(String filename) throws Exception{
FTPClient ftp = null;
try {
ftp = connect();
// 切换FTP目录
ftp.changeWorkingDirectory(FTP_BASEPATH);
ftp.dele(filename);
ftp.logout();
}finally {
disconnect(ftp);
}
}
public static void downloadFile(String filename, String localPath) throws Exception{
FTPClient ftp = null;
try {
ftp = connect();
// 切换FTP目录
ftp.changeWorkingDirectory(FTP_BASEPATH);
//此处为demo方法,正常应该到数据库中查询fileName
FTPFile[] ftpFiles = ftp.listFiles();
for (FTPFile file : ftpFiles) {
if (filename.equalsIgnoreCase(file.getName())) {
File localFile = new File(localPath + "/" + file.getName());
OutputStream os = new FileOutputStream(localFile);
ftp.retrieveFile(file.getName(), os);
os.close();
}
}
ftp.logout();
System.out.println("文件下载完成!!!");
} finally {
ftp.disconnect();
}
}
}
然后是api
@RestController
public class FtpController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/list")
@ResponseBody
public FTPFile[] getListByPath(String path) throws Exception{
return FtpClientUtil.listFile(path);
}
@GetMapping("/delete")
public boolean deleteFileByPathFileName(String fileName) throws Exception{
FtpClientUtil.deleteFile(fileName);
return true;
}
}
这样我们的功能就基本实现了,集成系统中就是普通的查询和api调用,项目源代码https://github.com/eatonsong/dataiftpserver.git 感谢star