diff --git a/README.md b/README.md index 6185a04..4bdf3f8 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,10 @@ ![](./src/main/resources/static/redame/img_29.png) +#### 8.2 ftp 服务工具 + +![](./src/main/resources/static/redame/img_31.png) + ## 2. 开源项目总览 | 项目名称 |地址| diff --git a/pom.xml b/pom.xml index 9c32679..885624a 100644 --- a/pom.xml +++ b/pom.xml @@ -276,6 +276,12 @@ javax.mail 1.6.2 + + + org.apache.ftpserver + ftpserver-core + 1.1.1 + diff --git a/src/main/java/com/zhangmeng/tools/controller/FtpServerController.java b/src/main/java/com/zhangmeng/tools/controller/FtpServerController.java index aba8c4a..6ae916a 100644 --- a/src/main/java/com/zhangmeng/tools/controller/FtpServerController.java +++ b/src/main/java/com/zhangmeng/tools/controller/FtpServerController.java @@ -1,6 +1,30 @@ package com.zhangmeng.tools.controller; +import com.zhangmeng.tools.server.SimpleFtpServer; +import com.zhangmeng.tools.utils.AlertUtils; +import com.zhangmeng.tools.utils.ImagePath; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import javafx.util.Callback; import lombok.extern.slf4j.Slf4j; +import org.apache.ftpserver.ftplet.Authority; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import org.apache.ftpserver.usermanager.impl.WritePermission; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; /** * @author : 芊芊墨客 @@ -10,5 +34,205 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class FtpServerController { + public enum Status{ + init("未启动"), + start("已启动"), + stop("已停止"), + ; + private String desc; + + Status(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + } + + @FXML + public Button server_start; + + @FXML + public Button server_stop; + + @FXML + public TextField user_name; + + @FXML + public PasswordField password; + + @FXML + public TextField resoureces_dir; + + @FXML + public Text server_status; + + @FXML + private Button add_user; + + @FXML + private ListView server_list; + + @FXML + private Button choose_file_dir; + + private ObservableList list = FXCollections.observableArrayList(); + private SimpleObjectProperty server_status_info = new SimpleObjectProperty<>(); + + private SimpleFtpServer simpleFtpServer = null; + + @FXML + public void initialize() { + + initStatus(); + server_list.setPlaceholder(new Label("暂无服务信息!")); + server_list.setItems(list); + server_list.setCellFactory(new Callback<>() { + @Override + public ListCell call(ListView param) { + return new ListCell<>() { + @Override + protected void updateItem(Data data, boolean b) { + super.updateItem(data, b); + if (!b) { + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER); + hBox.getChildren().add(new Label("username:" + data.username + ",password:" + data.password + ",rootDir:" + data.rootDir)); + this.setGraphic(hBox); + } + } + }; + } + }); + server_list.setFixedCellSize(40); + add_user.setOnAction(event -> { + if (user_name.getText().length() == 0 ){ + AlertUtils.alert_warning("用户名不能为空!"); + return; + } + if (password.getText().length() == 0 ){ + AlertUtils.alert_warning("密码不能为空!"); + return; + } + + if (resoureces_dir.getText().length() == 0 ){ + AlertUtils.alert_warning("资源文件夹不能为空!"); + return; + } + Data data = new Data(); + data.setUsername(user_name.getText()); + data.setPassword(password.getText()); + data.setRootDir(resoureces_dir.getText()); + + list.add(data); + + //清楚输入框 + user_name.setText(null); + password.setText(null); + resoureces_dir.setText(null); + }); + + server_start.setOnAction(event -> { + + simpleFtpServer = SimpleFtpServer.create(); + + for (Data item : server_list.getItems()) { + BaseUser user = new BaseUser(); + List list = new ArrayList<>(); + list.add(new WritePermission()); + user.setAuthorities(list); + user.setName(item.username); + user.setPassword(item.password); + user.setHomeDirectory(item.rootDir); + simpleFtpServer.addUser(user); + } + simpleFtpServer.start(); + startStatus(); + }); + + server_stop.setOnAction(event -> { + simpleFtpServer.stop(); + stopStatus(); + }); + + choose_file_dir.setText(null); + ImageView iv = new ImageView(new Image(ImagePath.path(ImagePath.ImagePathType.IMAGE_FILE))); + iv.setPreserveRatio(true); + iv.setFitWidth(18); + choose_file_dir.setGraphic(iv); + choose_file_dir.setOnAction(event -> { + Stage stage = new Stage(); + DirectoryChooser dc = new DirectoryChooser(); + dc.setTitle("文件夹选择器"); + File file = dc.showDialog(stage); + if (file == null){ + return; + } + resoureces_dir.setText(file.getPath()); + }); + } + + private void stopStatus(){ + server_status.setFill(Paint.valueOf("#ccffccff")); + server_status.setText(Status.stop.desc); + server_status_info.set(Status.stop); + } + + private void startStatus(){ + server_status.setFill(Paint.valueOf("#ccffccff")); + server_status.setText(Status.start.desc); + server_status_info.set(Status.start); + } + + private void initStatus(){ + server_status.setFill(Paint.valueOf("#ccffccff")); + server_status.setText(Status.init.desc); + server_status_info.set(Status.init); + } + + public static class Data{ + + private String username; + private String password; + private String rootDir; + + public Data() { + } + + public Data(String username, String password, String rootDir) { + this.username = username; + this.password = password; + this.rootDir = rootDir; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRootDir() { + return rootDir; + } + + public void setRootDir(String rootDir) { + this.rootDir = rootDir; + } + } } diff --git a/src/main/java/com/zhangmeng/tools/server/SimpleFtpServer.java b/src/main/java/com/zhangmeng/tools/server/SimpleFtpServer.java new file mode 100644 index 0000000..a489632 --- /dev/null +++ b/src/main/java/com/zhangmeng/tools/server/SimpleFtpServer.java @@ -0,0 +1,229 @@ +package com.zhangmeng.tools.server; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.net.NetUtil; +import cn.hutool.extra.ftp.FtpException; +import org.apache.ftpserver.ConnectionConfig; +import org.apache.ftpserver.FtpServer; +import org.apache.ftpserver.FtpServerFactory; +import org.apache.ftpserver.ftplet.Authority; +import org.apache.ftpserver.ftplet.Ftplet; +import org.apache.ftpserver.ftplet.User; +import org.apache.ftpserver.ftplet.UserManager; +import org.apache.ftpserver.listener.ListenerFactory; +import org.apache.ftpserver.ssl.SslConfiguration; +import org.apache.ftpserver.ssl.SslConfigurationFactory; +import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import org.apache.ftpserver.usermanager.impl.WritePermission; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author : 芊芊墨客 + * @version : 1.0 + * @date : 2023-03-11 15:05 + */ +public class SimpleFtpServer { + + /** + * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 + * + * @return SimpleFtpServer + */ + public static SimpleFtpServer create() { + return new SimpleFtpServer(); + } + + FtpServerFactory serverFactory; + ListenerFactory listenerFactory; + FtpServer ftpServer; + + /** + * 构造 + */ + public SimpleFtpServer() { + serverFactory = new FtpServerFactory(); + listenerFactory = new ListenerFactory(); + } + + /** + * 获取 {@link FtpServerFactory},用于设置FTP服务器相关信息 + * + * @return {@link FtpServerFactory} + */ + public FtpServerFactory getServerFactory() { + return this.serverFactory; + } + + /** + * 设置连接相关配置,使用ConnectionConfigFactory创建{@link ConnectionConfig}对象 + * + * @param connectionConfig 连接配置 + * @return this + */ + public SimpleFtpServer setConnectionConfig(ConnectionConfig connectionConfig) { + this.serverFactory.setConnectionConfig(connectionConfig); + return this; + } + + /** + * 获取{@link ListenerFactory},用于设置端口、用户、SSL等信息 + * + * @return {@link ListenerFactory} + */ + public ListenerFactory getListenerFactory() { + return this.listenerFactory; + } + + /** + * 自定义默认端口,如果不设置,使用默认端口:21 + * + * @param port 端口 + * @return this + */ + public SimpleFtpServer setPort(int port) { + Assert.isTrue(NetUtil.isValidPort(port), "Invalid port!"); + this.listenerFactory.setPort(port); + return this; + } + + /** + * 获取用户管理器,用于新增、查找和删除用户信息 + * + * @return 用户管理器 + */ + public UserManager getUserManager() { + return this.serverFactory.getUserManager(); + } + + /** + * 增加FTP用户 + * + * @param user FTP用户信息 + * @return this + */ + public SimpleFtpServer addUser(User user) { + try { + getUserManager().save(user); + } catch (org.apache.ftpserver.ftplet.FtpException e) { + throw new FtpException(e); + } + return this; + } + + /** + * 添加匿名用户 + * + * @param homePath 用户路径,匿名用户对此路径有读写权限 + * @return this + */ + public SimpleFtpServer addAnonymous(String homePath) { + BaseUser user = new BaseUser(); + user.setName("anonymous"); + user.setHomeDirectory(homePath); + List authorities = new ArrayList<>(); + // 添加用户读写权限 + authorities.add(new WritePermission()); + user.setAuthorities(authorities); + return addUser(user); + } + + /** + * 删除用户 + * + * @param userName 用户名 + * @return this + */ + public SimpleFtpServer delUser(String userName) { + try { + getUserManager().delete(userName); + } catch (org.apache.ftpserver.ftplet.FtpException e) { + throw new FtpException(e); + } + return this; + } + + /** + * 使用SSL安全连接,可以使用SslConfigurationFactory创建{@link SslConfiguration} + * + * @param ssl {@link SslConfiguration} + * @return this + */ + public SimpleFtpServer setSsl(SslConfiguration ssl) { + this.listenerFactory.setSslConfiguration(ssl); + listenerFactory.setImplicitSsl(true); + return this; + } + + /** + * 使用SSL安全连接 + * + * @param keystoreFile 密钥文件 + * @param password 密钥文件密码 + * @return this + */ + public SimpleFtpServer setSsl(File keystoreFile, String password) { + SslConfigurationFactory sslFactory = new SslConfigurationFactory(); + sslFactory.setKeystoreFile(keystoreFile); + sslFactory.setKeystorePassword(password); + return setSsl(sslFactory.createSslConfiguration()); + } + + /** + * 自定义用户管理器,一般用于使用配置文件配置用户信息 + * + * @param userManager {@link UserManager} + * @return this + */ + public SimpleFtpServer setUserManager(UserManager userManager) { + this.serverFactory.setUserManager(userManager); + return this; + } + + /** + * 自定义用户信息配置文件,此方法会重置用户管理器 + * + * @param propertiesFile 配置文件 + * @return this + */ + public SimpleFtpServer setUsersConfig(File propertiesFile) { + final PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory(); + userManagerFactory.setFile(propertiesFile); + return this.setUserManager(userManagerFactory.createUserManager()); + } + + /** + * 增加FTP动作行为监听处理器,通过实现{@link Ftplet},可以对用户的行为监听并执行相应动作 + * + * @param name 名称 + * @param ftplet {@link Ftplet},用户自定义监听规则 + * @return this + */ + public SimpleFtpServer addFtplet(String name, Ftplet ftplet) { + this.serverFactory.getFtplets().put(name, ftplet); + return this; + } + + /** + * 启动FTP服务,阻塞当前线程 + */ + public void start() { + // 一个Listener对应一个监听端口 + // 可以创建多个监听,此处默认只监听一个 + serverFactory.addListener("default", listenerFactory.createListener()); + try { + ftpServer = serverFactory.createServer(); + ftpServer.start(); + } catch (org.apache.ftpserver.ftplet.FtpException e) { + throw new FtpException(e); + } + } + + public void stop() { + ftpServer.stop(); + } + +} diff --git a/src/main/resources/fxml/ftp-server.fxml b/src/main/resources/fxml/ftp-server.fxml index 269db3e..07737d2 100644 --- a/src/main/resources/fxml/ftp-server.fxml +++ b/src/main/resources/fxml/ftp-server.fxml @@ -1,6 +1,28 @@ + + + + + + - - + + +