搭建基于springboot的FTP服务器

引言

最近有一个在集成系统上提供1G以上文件下载的功能,还要提供文件的展示功能和删除的操作,因为常规的文件流速度慢并且容易断掉因此我们采用FTP的方式,系统架构如下图所示,这里我们采用的ftp框架是apache的ftpserver
搭建基于springboot的FTP服务器_第1张图片

项目实现

配置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服务界面

restApi

这里我们要为集成系统提供可以查询,删除的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

你可能感兴趣的:(java,java,ftp)