diff --git a/README.md b/README.md
index 6185a04..4bdf3f8 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,10 @@

+#### 8.2 ftp 服务工具
+
+
+
## 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 @@
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/static/redame/img_31.png b/src/main/resources/static/redame/img_31.png
new file mode 100644
index 0000000..cdf163a
Binary files /dev/null and b/src/main/resources/static/redame/img_31.png differ