电子图片管理系统(JavaFx-整合Springboot-socket服务)实现本地好友分享图片,图片管理等,含源码

q:2077128238(源码)

1. 使用技术+工具

1.1. 技术

主程序客户端:SpringBoot + Mysql + Redis+ javafx + Mybatis-Plus + OSS云存储 + Socket网络编程

服务端:SpringBoot + Mybatis-Plus + Mysql + Socket网络编程

1.2. 技术用处

javafx:实现客户端的UI

springboot:负责提供框架,简化数据层,业务层代码,实现一个小小服务器

Mybatis-plus:负操作数据库实现登录注册校验,存储用户信息,用户添加好友的好友信息,好友的分享图片url,用户创建的收藏标签

redis:主要负责缓存当前用户访问到的非空目录(有文件的),将redis缓存的路径回显到路径框中

OSS云存储:主要实现用户的头像注册/修改头像,云存储到阿里云中返回可访问的url,存放到数据库中,或者好友分享图片,发送消息的信息就是url

socket编程:主要负责和springboot搭配一下,每个用户都会对应一个线程,通过线程去进行socket用户的交互,发送信息,添加好友信息,分享图片信息,退出发送,形成一个微小的服务器

1.3. 工具

IDEA2023-20019 + javafx SceneBuilder2.0 + mysql图形化界面 + Git工具 + gitee

2. 成员分工

2.2. 分工

负责登录界面绘制,除数据库校验登录部分的页面Controller方法属性绑定;主界面的本地电脑展开目录树编写部分;主界面的根据选中目录树后,实时更新路径框的内容(路径框的图形化显示路径层次变化);主界面的鼠标右键显示浮窗的代码编写+浮窗Popup绘制(复制粘贴打开删除分析收藏(回调函数去进行传出选中内容));主界面的删除图片操作的逻辑编写(根据选中的图片集合进行将文件暂时转移,方便后续撤回操作);主界面的轮播上下张图片展示的窗口绘制以及算法实现每10张图片进行一个播放条不断更新播放条的算法,上下张图片切换,图片旋转等功能;用户中心界面的窗口编写;复制粘贴打开删除收藏分享浮窗的编写(带有监听回调功能);

负责注册界面绘制,除数据库校验注册部分的页面Controller方法属性绑定;主界面的分享窗口;主界面的修改头像的窗口;主界面的搜索框的逻辑代码(模糊查询(正则表达式实现));主界面的主题色浮窗Popup的编写(主题色的切换功能(主要是去进行一个回调函数去触发按钮后就可以让主界面的Stage的背景色进行一个切换));主界面的多种类型的排序算法编写(实现切换不同排序效果都可以让我们的图片显示区域能不断更新显示当前排序后的效果);自动播放图片窗口绘制以及编写;头像右键显示相关用户信息操作选择的浮窗绘制(添加好友+修改密码+用户中心+修改头像+退出登录)以及选中后回调函数传出选中内容;

负责主界面绘制(不带属性方法controller绑定);目录树侧边的隐藏显示;详细信息部分的隐藏显示;图片浏览区的三种显示切换算法(图片 + 图片文字 + 文件形式);选中图片后的浏览区域详细信息同步展示;选中多张图片后的图片存储(方便复制粘贴删除等操作);基于孔维朗同学的分享FXML创建stage传入对应数据进行监听new操作;主界面的修改密码窗口(包含验证码);图片总数目和选中数目的数据绑定(SimpleXXXProperty.addLis enter实现即可);修改头像OSS操作+数据库修改;路径框存放先前点击过有图片的路径redis缓存;修改密码数据库的修改逻辑操作;登录后向服务端发送登录消息,添加好友的向服务端发送添加好友消息(分离线在线状态两种),向好友分享图片OSS+数据库+服务端转发消息,好友接收到图片后更新分享窗口,打开分享窗口显示好友分享数据(数据传递),可下载好友分享的图片;主界面的选中图片后,收藏图片的操作;3. 准备工作

3.1. 配置环境

maven + IDEA + JDK8以上 + 导入依赖

3.2. gitee创建

gitee创建好仓库

4. 系统分析

4.1. 系统总体结构设计

4.2. 类与类之间的关系

4.2.1. 包解释-客户端

4.2.2. 包解释-服务端

4.2.3. 类之间的关系

4.3. 界面划分-预计

划分出登录界面+注册界面+主界面

4.3.1. 登录界面

4.3.2. 注册界面

4.3.3. 主界面

4.4. 数据存储的设计

4.4.1. Mysql

主要存储用户相关信息,好友信息,分享图片等信息

4.4.2. 本地

内存存储相关的数据,使用集合存储图片文件

4.4.3. Redis

缓存用户的访问本地的路径(有图片的),会对路径的访问频繁次数进行一个排序,回显示到登录者的文件下拉框中

4.4.4. OSS云存储

主要存储头像的图片以及好友分享的图片,以url形式返回存储到数据库

5. 系统实现

5.1. 登录界面实现

fxml配合javaSceneBulider来绘制页面,对应Controller绑定事件属性,配合数据库去校验登录信息身份,点击注册按钮跳转到注册界面窗口

@FXML
    public void login(){//登录
        //先走我们输入的内容是否都符合要求前的判断(防止又去做无味的数据库查询)
        if (!isLogin){
            //就说明输入内容不不符合要求
            //提示框
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("登录失败");
            alert.setContentText("输入内容不不符合要求");
            alert.show();
            return;
        }
        String username = usernameTextFiled.getText();
        String password = MD5.encrypt(passwordTextFiled.getText());
        //操作数据库
        UserAccounts userAccounts = userAccountsMapper.selectOne(new LambdaQueryWrapper<UserAccounts>().eq(UserAccounts::getUsername, username).eq(UserAccounts::getPassword, password));
        if (userAccounts!=null){
            //如果相等就设置true校验成功
            isLogin = true;
            try {
                //创建用户使用界面+传入登录的信息
                CustomWindow customWindow = new CustomWindow(loginApplicationJavaFX,userAccounts);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            //清空 + 跳转(关闭)
            passwordLabel.setText("");
            usernameLabel.setText("");
            usernameTextFiled.setText("");
            passwordTextFiled.setText("");
//                loginApplicationJavaFX.hideLoginAndRegister();
            loginApplicationJavaFX.closeLoginAndRegister();
            return;
        }else {
            //登录失败,不存在该用户
            //提示框
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("登录失败");
            alert.setContentText("账号密码不正确");
            alert.show();
            passwordLabel.setText("账号密码不正确");
            usernameLabel.setText("账号密码不正确");
            return;
        }

    }
    //点击注册按钮跳转到注册界面
    @FXML
    public void register() throws IOException {
        //跳转到注册界面
        loginApplicationJavaFX.showRegister();
    }

5.2. 注册界面

校验输入框的内容是否存在该用户,上传图片(打开本地文件选择框),oss云存储头像返回url,存储用户信息,提示注册成功

@FXML
public void register() throws Exception {
    //进行一个校验
    String password = passwordTextFiled.getText();
    String passwordConfirm = passwordTextFiledConfirm.getText();
    String username = usernameTextFiled.getText();
    //。。。。。。。。校验
    //判断是否上传的文件
    if (selectedFile == null) {
        //说明没有上传文件头像 使用默认头像
        userAccounts.setImgUrl(defaultHeadUrl);
    } else {
        MultipartFile customMultipartFile = new CustomMultipartFile(selectedFile);
        if (fileUploadServiceImpl != null) {
            String url = fileUploadServiceImpl.fileUpload(customMultipartFile);
            userAccounts.setImgUrl(url);
        }
    }
}

5.3. 主界面

点击目录树获取当前路径,文件流对象获取所有文件信息,展示到展示区域,监听文件集合,进行动态显示数据

路径的展示都是去用监听属性,不断监听他的变化状态来动态绘制

倒退前进返回路径,使用栈存储形式实现

复制粘贴图片,监听鼠标键盘事件+使用粘贴板实现

好友分享图片使用socket网络编程发送信息,离线存放数据库,在线发送信息图片的url

好友添加使用socket网络编程发送消息,在线则受到添加信息,离线则存放到数据库,登录后接受到好友请求信息

收藏图片,数据库设置好用户拥有的标签和图片对应标签,利用云存储存放到阿里云中,返回的url存放到数据库,查看收藏的图片,则从数据库中拿到后的url直接image展示

多选和单选图片,主要用多个变量去判断是否选中,存放/移除选中集合中,方便后续的增删改分享等操作

6. 系统测试

6.1. 登录测试

输入不正确的格式会有提示,不正确也有提示!、

正确则在登陆成功

进入主界面

6.2. 注册测试

输入账户,输入密码(密码要求大小写数字),上传头像,不上传给默认头像

上传头像,不上传默认一个头像,输入正确的格式密码后,可以注册成功

6.3. 好友添加测试

运行两个程序,一个进行搜索好友输入好友账户,进行搜索输入,然后进行添加,对方可以收到好友申请信息

添加好友

显示查询结果

添加好友

对方查看是否收到信息

同意它成为好友

查看个人中心

测试完毕,能够实现添加的好友在线的话可以去进行一个消息推送

6.4. 点击目录树显示图片测试

6.5. 排序测试

当切换文件文件类型排序

切换大小排序

测试完毕

6.6. 好友分享图片测试

分享然后选择还好友

好友查看是否分享成功

测试完毕

6.7. 添加收藏标签

我们需要点击收藏选择标签

显示rap标签内容

7. 系统运行界面截图

7.1. 登录

7.2. 注册

7.3. 主界面

7.3.1. 图片形式

7.3.2. 文件信息形式

7.3.3. 文件名+图片形式

7.4. 分享

7.5. 修改头像

7.6. 修改密码

7.7. 用户中心

7.8. 通知

7.9. 播放图片

7.10. 自动播放

7.11. 历史路径框

7.12. 详细内容展示区

7.13. 主题色

切换主题色

在 Java 中实现实时监听数据库数据到来的功能,通常可以通过使用数据库的触发器(Triggers)结合 Java 编程来实现。具体步骤如下:

  1. 创建触发器: 首先,在数据库中创建一个触发器,用于在新数据插入时发送通知。例如,在 MySQL 数据库中可以创建一个类似如下的触发器:
sql
CREATE TRIGGER new_data_trigger AFTER INSERT ON your_table
FOR EACH ROW
BEGIN
    SELECT 'New data inserted' FROM dual;
END;

在这个触发器中,当 your_table 表中有新数据插入时,会执行 SELECT 'New data inserted' FROM dual; 这条语句。

  • Java 实时监听: 在 Java 程序中使用 JDBC 连接数据库,并实时监听触发器的通知。以下是一个简单的示例代码,演示了如何实现在 Java 中实时监听数据库数据到来的功能:
java
import java.sql.*;

public class DatabaseListener {
    public static void main(String[] args) {
        try {
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/yourdatabase", "username", "password");
            Statement statement = conn.createStatement();

            // 设置触发器通知
            statement.execute("CREATE TRIGGER new_data_trigger AFTER INSERT ON your_table FOR EACH ROW CALL notifyJavaApplication();");

            // 监听触发器通知
            statement.execute("CREATE FUNCTION notifyJavaApplication() RETURNS BOOLEAN LANGUAGE JAVA EXTERNAL NAME 'com.yourpackage.YourClassName.notifyJavaApplication'");

            // 开始监听数据库变化
            while (true) {
                Thread.sleep(1000); // 每秒检查一次
                // 在 notifyJavaApplication 方法中处理新数据到来的逻辑
            }

            // 关闭连接
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,您需要在 notifyJavaApplication 方法中编写处理新数据到来的逻辑。这个方法应该在 Java 应用程序的某个类中实现,您需要替换 com.yourpackage.YourClassName 为实际的类名。

请注意,以上示例仅作为概念性示例,实际生产环境中可能需要更复杂的逻辑和错误处理机制。同时,不同的数据库系统触发器和通知机制可能会有所不同,您需要根据您使用的数据库系统的文档进行相应的调整。

// 设置触发器通知 statement.execute("CREATE TRIGGER new_data_trigger AFTER INSERT ON your_table FOR EACH ROW CALL notifyJavaApplication();"); // 监听触发器通知 statement.execute("CREATE FUNCTION notifyJavaApplication() RETURNS BOOLEAN LANGUAGE JAVA EXTERNAL NAME 'com.yourpackage.YourClassName.notifyJavaApplication'");这是什么逻辑?能否细细讲解

2024/3/22 16:46:14

这段代码片段中包含了两个关键部分,分别用于设置触发器通知和监听触发器通知。让我详细解释一下这两部分的逻辑:

  1. 设置触发器通知:
java
statement.execute("CREATE TRIGGER new_data_trigger AFTER INSERT ON your_table FOR EACH ROW CALL notifyJavaApplication();");

这行代码是在数据库中创建一个触发器。这个触发器的名称是 new_data_trigger,它会在 your_table 表中有新数据插入时触发。AFTER INSERT 表示在插入操作之后触发,FOR EACH ROW 表示对每一行数据都会触发一次。CALL notifyJavaApplication(); 表示当触发器被触发时,会调用名为 notifyJavaApplication 的方法。

  • 监听触发器通知:
java
statement.execute("CREATE FUNCTION notifyJavaApplication() RETURNS BOOLEAN LANGUAGE JAVA EXTERNAL NAME 'com.yourpackage.YourClassName.notifyJavaApplication'");

这行代码是在数据库中创建一个函数,函数的名称是 notifyJavaApplication。这个函数定义为返回 BOOLEAN 类型。LANGUAGE JAVA 表示这是一个使用 Java 编写的函数,EXTERNAL NAME 'com.yourpackage.YourClassName.notifyJavaApplication' 则指定了实际 Java 类中的方法名,用于处理触发器通知时的逻辑。

综合起来,上述代码片段的作用是在数据库中创建一个触发器,当指定表中插入新数据时触发,同时调用 Java 中指定类的指定方法进行处理。这样就实现了在 Java 中监听数据库数据到来的功能,并且能够在触发器被触发时执行特定的 Java 逻辑。请注意,实际使用时需要根据具体需求和数据库系统的要求进行适当的调整和修改。

当创建一个简单的账户密码表时,可以使用以下 SQL 语句:

sql
CREATE TABLE user_accounts (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户id',
    `username` VARCHAR(64) NOT NULL COMMENT '账户',
    `password` VARCHAR(64) NOT NULL COMMENT '密码',
    `name` VARCHAR(64)DEFAULT NULL COMMENT '自定义名称',
    `img_url` varchar(255) NOT NULL DEFAULT '' COMMENT '头像图片url',
    `is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)'
);

这个 SQL 语句创建了一个名为 user_accounts 的表,包含三个字段:

  • id:作为主键,自增长。
  • username:存储用户名,类型为 VARCHAR(64),且不能为空。
  • password:存储密码,类型为 VARCHAR(64),且不能为空。

接下来,让我们创建一个用户之间的关联表,假设是一个简单的好友关系表。可以使用以下 SQL 语句:

sql

CREATE TABLE user_friends (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'id',
    user_id INT NOT NULL COMMENT '用户id',
    friend_id INT NOT NULL COMMENT '朋友id',
    `is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
    CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES user_accounts(id),
    CONSTRAINT fk_friend_id FOREIGN KEY (friend_id) REFERENCES user_accounts(id)
);

这个 SQL 语句创建了一个名为 user_friends 的表,包含四个字段:

  • id:作为主键,自增长。
  • user_id:存储用户 ID,不能为空。
  • friend_id:存储好友 ID,不能为空。
  • fk_user_id 和 fk_friend_id 是外键约束,确保 user_id 和 friend_id 分别引用 user_accounts 表中的 id 字段,实现了用户之间的关联。

通过以上 SQL 语句,您可以在数据库中创建一个简单的账户密码表和用户之间的关联表。如果您有其他需求或者需要进一步的帮助,请随时告诉我!

在上面提供的 SQL 中,删除 user_accounts 表中的一个用户后,user_friends 表中相关的关联并不会自动被删除。如果需要实现级联删除(即删除主表记录时同时删除关联表中对应的记录),可以使用外键约束中的 ON DELETE CASCADE 选项。

以下是修改后的 user_friends 表的 SQL,添加了 ON DELETE CASCADE 来实现级联删除:

sql
CREATE TABLE user_friends (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    friend_id INT NOT NULL,
    CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES user_accounts(id) ON DELETE CASCADE,
    CONSTRAINT fk_friend_id FOREIGN KEY (friend_id) REFERENCES user_accounts(id) ON DELETE CASCADE
);

通过设置外键约束的 ON DELETE CASCADE 选项,当 user_accounts 表中的用户记录被删除时,与这些用户相关的 user_friends 表中的关联也会被自动删除。这样可以实现级联删除的效果。

请注意,使用级联删除要谨慎,确保您了解删除操作的影响,以免意外删除重要数据。如果您有其他问题或需要进一步帮助,请随时告诉我!

8. 线程使用

8.1. new Thread

直接启动一个子线程

8.2. Platform.runlater()

主要是用来加载页面用的(在使用线程时我们必须要使用该逻辑来加载javafx组件!否则报错)

8.3. @Async异步线程

springboot开启异步线程(主程序@EnableAsync),方法上加上注解@Async

使用在界面更新,异步线程去对数据更新,保证性能快速

8.4. 多线程使用

使用多线程来同时完成多个页面加载

CountDownLatch latch = new CountDownLatch(2);//计数 

Thread thread1 = new Thread(() -> {
    Platform.runLater(() -> {
        latch.countDown(); // 页面加载完成,计数器减一
    });
});
thread1.start();


Thread thread2 = new Thread(() -> {
    Platform.runLater(() -> {
        latch.countDown(); // 页面加载完成,计数器减一
    });
});
thread2.start();

 // 等待所有页面加载完成
new Thread(() -> {
    try {
        latch.await(); // 等待所有页面加载完成
        Platform.runLater(() -> {
            // 所有页面加载完成后弹出对话框Alert
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("页面加载完成");
            alert.setHeaderText(null);
            alert.setContentText("所有页面加载完成!");
            alert.showAndWait();
        });
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

9. 部分实现解决

9.1. 如何实现更改某些内容可以实时监听

我们会给每个可能产生变化的属性,添加一个监听属性,修改的同时也会去修改对应的监听属性,监听属性被修改后,会会触发监听事件获取旧新值,在这个逻辑中去进行一系列你想要的更新操作,比如图片集合更新,就去更新展示区的展示的图片,如果文件路径修改了,就去修改对应的绘制等等

9.2. 如何让轮播图片的十个为一条不断更新

主要传入当前图片集合,传参获取后,对这个集合转换成队列,因为要以十张为一个图片条,要对传过来的图片进行判断,判断是否有10张,不够就进行copy并截取形成10张的队列,当点击上下张按钮时就去进行切换,队列的元素会进行对头放到队尾,队尾插到对头的操作,实现不断循环更新图片条

queue.offer(poll);//添加到队尾

queue.remove(lastFile);//添加到队头 ((LinkedList<File>) queue).addFirst(lastFile)

9.3. 如何让好友之间通信(在线离线状态)

配合socket编程,创建一个本地的服务器,去实时接受客户端的连接,每个客户端用户登录都会和服务端进行一个连接,连接后发送信息到服务端,服务端去根据现有的用户判断是离线还是在线状态,在线状态就直接转发消息给用户,离线状态则进行存储数据库相关信息,等到它登录后,首先会去数据库中获取离线的消息

9.4. 如何实现多种排序

用回调函数(事件绑定),获取到排序的类型以及升序降序,修改监听属性,来监听排序,一旦切换了当前排序状态,就能监听到,然后获取你想要排序的方式,进行排序,主要使用集合的排序方法

9.5. 如何实现查看路径历史记录(存在图片的目录)

当点击某个目录的时候,如果该目录路径有图片的,就会进行添加路径,其中用户为key,路径为value,用zset存放到redis中,方便判断用户常常点击那个那个路径更多进行排序,最终回显到路径下拉框中

9.6. 图片加载如何实现

点击目录如果有图片就进行线程展示,但是会根据你展示的类型,用监听属性存储三种类型(图片,图片+文件名,文件详细信息)当直接根据路径寻找时,路径找时直接启动多线程进行将相关的页面都进行更新,加快加载速度图片加载是加载完一个就去进行展示出来的,不会等待全部加载才能显示

10. 出现错误及解决

10.1. 错误一

图片路径问题

<Button layoutX="259.0" layoutY="14.0" maxHeight="150.0" maxWidth="150.0" mnemonicParsing="false" onMouseClicked="#upIconToOOS" style="-fx-background-color: white; /* 设置背景颜色#007bff */-fx-text-fill: white; /* 设置文字颜色 */-fx-background-radius: 75; /* 设置圆角半径为按钮宽度的一半,实现圆形效果 */-fx-min-width: 150px; /* 设置按钮最小宽度 */                                               -fx-min-height: 150px; /* 设置按钮最小高度,保证按钮是一个正方形 */">
   <graphic>
      <ImageView fx:id="lodeImageView" fitHeight="142.0" fitWidth="139.0" style="-fx-pref-height: 10px; -fx-pref-width: 10px;">
         <image>
            <Image url="@../images/upLoderImage.png" />
         </image>
      </ImageView>
   </graphic>
</Button>
@FXML
public void upIconToOOS(){
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("选择文件");
    selectedFile = fileChooser.showOpenDialog(primaryStage);

    if (selectedFile != null) {
        System.out.println("已选择文件: " + selectedFile.getAbsolutePath());
        // 清除原有内容
        lodeImageView.setImage(null);

        // 在这里可以处理选定的文件,比如上传等操作
        try {
            String absolutePath = selectedFile.getAbsolutePath();
            Image image = new Image("file:" + absolutePath);//可以直接file.uri 
            lodeImageView.setImage(image);
            lodeImageView.setPreserveRatio(true);//保持缩放比例
            lodeImageView.setFitHeight(120);
            lodeImageView.setFitWidth(120);
        } catch (Exception e) {
            System.out.println("加载图片时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
    }

当我去修改Image时,必须要去选择一个有效文件(不能带中文路径的!!!)

修改为:

<StackPane layoutX="259.0" layoutY="14.0" maxHeight="150.0" maxWidth="150.0" prefHeight="145.0" prefWidth="137.0">
   <children>
      <Button maxHeight="150.0" maxWidth="150.0" mnemonicParsing="false" onMouseClicked="#upIconToOOS" style="-fx-background-color: white; /* 设置背景颜色#007bff */-fx-text-fill: white; /* 设置文字颜色 */-fx-background-radius: 75; /* 设置圆角半径为按钮宽度的一半,实现圆形效果 */-fx-min-width: 150px; /* 设置按钮最小宽度 */                                               -fx-min-height: 150px; /* 设置按钮最小高度,保证按钮是一个正方形 */" />
      <ImageView onMouseClicked="#upIconToOOS" fx:id="lodeImageView" fitHeight="143.0" fitWidth="132.0" pickOnBounds="true" preserveRatio="true">
         <image>
            <Image url="@../images/upLoderImage.png" />
         </image>
      </ImageView>
   </children>
</StackPane>
@FXML
public void upIconToOOS(){
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("选择文件");
    selectedFile = fileChooser.showOpenDialog(primaryStage);

    if (selectedFile != null) {
        System.out.println("已选择文件: " + selectedFile.getAbsolutePath());
        // 清除原有内容
        lodeImageView.setImage(null);

        // 在这里可以处理选定的文件,比如上传等操作
        try {
            String absolutePath = selectedFile.getAbsolutePath();
            Image image = new Image("file:" + absolutePath);
            lodeImageView.setImage(image);
            lodeImageView.setPreserveRatio(true);//保持缩放比例
            lodeImageView.setFitHeight(120);
            lodeImageView.setFitWidth(120);
        } catch (Exception e) {
            System.out.println("加载图片时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

10.2. 错误二

图片的Image显示不了

本地文件上传要用new File后,去用file.getURI().toString()来作为路径

oss存储时,直接url即可,但是注意路径不能错,当时发现上传生成的路径错了!

10.3. 错误三

分享图片的时候会出现异常,也就是存发送消息时我们分享的图片存放在oss的url太长了数据库存不了(得修改字段大一点)

varchar(64) ==>varchar(256)

成功发送!

发送后

10.4. 错误四

消息不同步,就是好友分享图片时,出现对方收到消息但没有同步信息,debug发现时时间格式转换出现问题了!修改后,又出现一个线程绘制问题,就是new 好友分享图片的窗口需要用到Platform.runlater去加载

10.5. 错误五

打包的时候出现错误出现两个类,一个application类,一个springboot主启动类

解决:我们直接进行一个整合到一个类,init()方法解决

    public static void main(String[] args) {
        //SpringApplication.run(ProductApplication.class, args);
        //多个舞台并存
        launch(args);
    }


    @Override
    public void init() throws Exception {
        context = SpringApplication.run(LoginApplicationJavaFX.class);
    }

10.6. 错误六

注入获取springIOC容器bean对象错误,出现名字冲突

解决:直接context.getBean()获取

11. 分工具体明细

11.1.1. 登录

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.image.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane fx:id="loginRoot" prefHeight="448.0" prefWidth="654.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.managerImage.controller.LoginController">
   <children>
      <StackPane layoutY="-3.0" prefHeight="428.0" prefWidth="654.0">
         <children>
            <ImageView fitHeight="457.0" fitWidth="654.0">
               <image>
                  <Image url="@../images/imageManagerLoginBackGroud.jpg" />
               </image>
            </ImageView>
            <AnchorPane prefHeight="440.0" prefWidth="599.0">
               <children>
                  <Label layoutX="205.0" layoutY="50.0" prefHeight="65.0" prefWidth="232.0" text="图览之光">
                     <font>
                        <Font name="System Bold Italic" size="55.0" />
                     </font>
                  </Label>
                  <Label layoutX="154.0" layoutY="147.0" text="账户:">
                     <font>
                        <Font size="24.0" />
                     </font>
                  </Label>
                  <TextField fx:id="usernameTextFiled" layoutX="228.0" layoutY="147.0" prefHeight="32.0" prefWidth="260.0" promptText="请输入账户" />
                  <PasswordField fx:id="passwordTextFiled" layoutX="228.0" layoutY="213.0" prefHeight="32.0" prefWidth="260.0" promptText="请输入密码" />
                  <Label layoutX="154.0" layoutY="213.0" text="密码:">
                     <font>
                        <Font size="24.0" />
                     </font>
                  </Label>

                  <Button layoutX="152.0" layoutY="284.0" mnemonicParsing="false" onAction="#login" prefHeight="58.0" prefWidth="130.0" text="登录" style="-fx-background-color: #e68f8d;-fx-border-radius: 10" textFill="WHITE">
                     <font>
                        <Font name="System Bold Italic" size="22.0" />
                     </font>
                  </Button>

                  <Button layoutX="360.0" layoutY="284.0" mnemonicParsing="false" onAction="#register" prefHeight="58.0" prefWidth="130.0" text="注册" style="-fx-background-color: #00aa63;-fx-border-radius: 10" textFill="WHITE">
                     <font>
                        <Font name="System Bold Italic" size="22.0" />
                     </font>
                  </Button>
                  <Label fx:id="usernameLabel" layoutX="425.0" layoutY="179.0" prefHeight="20.0" prefWidth="176.0" textFill="RED" />
                  <Label fx:id="passwordLabel" layoutX="425.0" layoutY="245.0" prefHeight="20.0" prefWidth="176.0" textFill="RED" />
               </children>
            </AnchorPane>
         </children>
      </StackPane>
   </children>
</AnchorPane>
package org.example.managerImage.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import org.example.managerImage.LoginApplicationJavaFX;
import org.example.managerImage.SpringBootCommandLineRunnerApplication;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.mapper.UserAccountsMapper;
import org.example.managerImage.mystage.CustomWindow;
import org.example.managerImage.util.MD5;
import org.springframework.context.ApplicationContext;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 张 志 豪
 * @version 1.0
 * 登录Controller
 */

public class LoginController implements Initializable {

    private LoginApplicationJavaFX loginApplicationJavaFX;

    public void setMain(LoginApplicationJavaFX main) {
        this.loginApplicationJavaFX = main;
    }


    private UserAccountsMapper userAccountsMapper;//数据库操作不用管,zzh完成

    private boolean isLogin = false;//能否允许登录

    private boolean isA0 = false;//是否包含数字字母的密码

    //属性
    @FXML
    private TextField usernameTextFiled;

    @FXML
    private PasswordField passwordTextFiled;

    @FXML
    private Label usernameLabel;

    @FXML
    private Label passwordLabel;



    //初始化
    @Override
    public void initialize(URL location, ResourceBundle resources) {
//        ApplicationContext context = SpringBootCommandLineRunnerApplication.getContext();
        userAccountsMapper = LoginApplicationJavaFX.context.getBean(UserAccountsMapper.class);
        //监听输入框的内容(失去焦点时)
        usernameTextFiled.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) { // 失去焦点时
                String inputText = usernameTextFiled.getText();
                // 在这里进行输入内容的判断和处理
                if (inputText.isEmpty()) {
                    usernameLabel.setText("内容不能为空");
                    isLogin = false;
                } else {
                    usernameLabel.setText("");
                    isLogin = true;
                }
            }
        });

        passwordTextFiled.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) { // 失去焦点时
                String inputText = passwordTextFiled.getText();
                // 在这里进行输入内容的判断和处理
                if (inputText.isEmpty()) {
                    passwordLabel.setText("内容不能为空");
                    isLogin = false;
                } else if (isA0Method(inputText)) {//是否包含数字字母
                    //包含大小写和数字
                    passwordLabel.setText("");
                    isLogin = true;
                    isA0 = true;
                } else {
                    passwordLabel.setText("需要大小写字母和数字");
                    isLogin = false;
                    isA0 = false;
                }
            }
        });

    }

    @FXML
    public void login(){//登录
        //先走我们输入的内容是否都符合要求前的判断(防止又去做无味的数据库查询)
        if (!isLogin){
            //就说明输入内容不不符合要求
            //提示框
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("登录失败");
            alert.setContentText("输入内容不不符合要求");
            alert.show();
            return;
        }
        String username = usernameTextFiled.getText();
        String password = MD5.encrypt(passwordTextFiled.getText());
        //操作数据库
        UserAccounts userAccounts = userAccountsMapper.selectOne(new LambdaQueryWrapper<UserAccounts>().eq(UserAccounts::getUsername, username).eq(UserAccounts::getPassword, password));
        if (userAccounts!=null){
            //如果相等就设置true校验成功
            isLogin = true;
            try {
                //创建用户使用界面+传入登录的信息
                CustomWindow customWindow = new CustomWindow(loginApplicationJavaFX,userAccounts);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            //清空 + 跳转(关闭)
            passwordLabel.setText("");
            usernameLabel.setText("");
            usernameTextFiled.setText("");
            passwordTextFiled.setText("");
//                loginApplicationJavaFX.hideLoginAndRegister();
            loginApplicationJavaFX.closeLoginAndRegister();
            return;
        }else {
            //登录失败,不存在该用户
            //提示框
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("登录失败");
            alert.setContentText("账号密码不正确");
            alert.show();
            passwordLabel.setText("账号密码不正确");
            usernameLabel.setText("账号密码不正确");
            return;
        }

    }

    //点击注册按钮跳转到注册界面
    @FXML
    public void register() throws IOException {
        //跳转到注册界面
//        RegisterStage myRegisterStage = new RegisterStage(new LoginApplicationJavaFX());
//        myRegisterStage.show();
        loginApplicationJavaFX.showRegister();
    }

    //判断字符串是否有大小写和数字
    public boolean isA0Method(String input) {
        // 使用正则表达式进行匹配
        if (input.matches(".*[a-z].*") && input.matches(".*[A-Z].*") && input.matches(".*\\d.*")) {
            return true;
        } else {
            return false;
        }
    }

    //判断目录是否有中文
    public boolean containsChinese(String str) {
        Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]");
        Matcher matcher = pattern.matcher(str);
        return matcher.find();
    }


}

登录的窗口Stage,前面就是页面,将页面放到Stage中的意思!!

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.example.managerImage.LoginApplicationJavaFX;
import org.example.managerImage.SpringBootCommandLineRunnerApplication;
import org.example.managerImage.controller.LoginController;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;

/**
 * @author 张 志 豪
 * @version 1.0
 */
public class MyLoginStage extends Stage {

    private double xOffset = 0;
    private double yOffset = 0;

    public MyLoginStage(LoginApplicationJavaFX loginApplicationJavaFX) throws IOException {
        // 创建根布局
        AnchorPane root = new AnchorPane();
        root.setStyle("-fx-background-color: #333;");

        // 1创建自定义标题栏(顶部)
        BorderPane titleBar = createTitleBar(this);
        AnchorPane.setTopAnchor(titleBar, 0.0);//这些方法就是设置外边距
        AnchorPane.setLeftAnchor(titleBar, 0.0);
        AnchorPane.setRightAnchor(titleBar, 0.0);

        //中间区域(就是写的登录界面fxml文件注入)
        // 获取 Maven 项目 resources/fxml/content.fxml 注意,无法识别横向
        URL resource = getClass().getResource("/fxml/login.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        AnchorPane anchorPane = loader.load();
        LoginController controller = loader.getController();
        controller.setMain(loginApplicationJavaFX);

        AnchorPane.setTopAnchor(anchorPane, 45.0); // Adjust the top position as needed
        AnchorPane.setLeftAnchor(anchorPane, 8.0);
        AnchorPane.setRightAnchor(anchorPane, 8.0);
        AnchorPane.setBottomAnchor(anchorPane, 37.0);

        root.getChildren().addAll(titleBar, anchorPane);

        //Scene scene = new Scene(root, 654, 448);
        Scene scene = new Scene(root, 670, 510);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add(getClass().getResource("/css/myStyles.css").toExternalForm());

        this.setScene(scene);
        this.initStyle(StageStyle.TRANSPARENT);
        this.show();
        this.getIcons().add(new Image("/images/icon.png"));
    }

    //=======下面可以不用管,都是固定方法,就当做工具类直接使用,不用细究 

    //创建标题栏(自定义)
    private BorderPane createTitleBar(Stage primaryStage) throws FileNotFoundException {
        // 创建标题栏
        BorderPane titleBar = new BorderPane();
        titleBar.getStyleClass().add("title-bar");

        // 创建图标
        ImageView iconImageView = new ImageView(new Image(getClass().getResourceAsStream("/images/icon.png")));
        iconImageView.setFitWidth(32);
        iconImageView.setFitHeight(32);

        // 创建标题
        Label titleLabel = new Label("图览之光登录界面");
        titleLabel.setFont(Font.font("楷体", FontWeight.BOLD,18));
        titleLabel.setTextFill(Color.WHITE);
        titleLabel.setPadding(new Insets(0,0,0,6));//设置一定的边距

        // 将图标和标题放在一个水平布局中
        HBox iconTitleBox = new HBox();
        iconTitleBox.setAlignment(Pos.CENTER_LEFT);
        iconTitleBox.getChildren().addAll(iconImageView, titleLabel);

        // 创建按钮
        Button closeButton = createWindowControlButton(primaryStage, "close.png",32 );
        Button maximizeButton = createWindowControlButton(primaryStage, "maximize1.png",32);
        Button minimizeButton = createWindowControlButton(primaryStage, "minimize.png",30);

        //整合三个按钮放在一个水平布局中
        HBox buttonHBox = new HBox(5);
        buttonHBox.getChildren().addAll(minimizeButton, maximizeButton, closeButton);


        // 添加组件到标题栏
        titleBar.setLeft(iconTitleBox);
        titleBar.setRight(buttonHBox);

        // 设置标题栏样式
        //titleBar.setAlignment(Pos.CENTER_LEFT);
        titleBar.setPadding(new Insets(5, 10, 5, 10));
        titleBar.setStyle("-fx-background-color: #333;");

        // 窗口拖动事件处理
        titleBar.setOnMousePressed((MouseEvent event) -> {
            xOffset = event.getSceneX();
            yOffset = event.getSceneY();
        });
        titleBar.setOnMouseDragged((MouseEvent event) -> {
            primaryStage.setX(event.getScreenX() - xOffset);
            primaryStage.setY(event.getScreenY() - yOffset);
        });

        // 调整按钮的样式和位置
        HBox.setMargin(closeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(maximizeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(minimizeButton, new Insets(0, 0, 0, 5));
        closeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        maximizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        minimizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");

        return titleBar;
    }
    //生成关闭、放大、缩放按钮方法
    private Button createWindowControlButton(Stage primaryStage, String imageUrl,double size) throws FileNotFoundException {
        String path = "F:\\java2Code\\addressBook\\src\\main\\resources\\images\\";
        //自定义Image!为了获取URL的名称
        Image image = new Image(new FileInputStream(path + imageUrl));
        ImageView imageView = new ImageView(image);
        imageView.setFitWidth(size);
        imageView.setFitHeight(size);

        Button button = new Button();
        button.setGraphic(imageView);
        button.getStyleClass().add("window-control-button");

        if (imageUrl.contains("close")) {
            button.setOnAction(event -> {
                //窗口,springboot,application程序
                primaryStage.close();
                SpringBootCommandLineRunnerApplication.shoutDown();
                LoginApplicationJavaFX.stopApplication();
            });
        } else if (imageUrl.contains("maximize")) {
            button.setOnAction(event -> {
                System.out.println(!primaryStage.isMaximized());
                if (!primaryStage.isMaximized()) {//非全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize2.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                //点击后发现(如果点击的是属于两个框的话,就切换一个框的图片)
                if (primaryStage.isMaximized()) {//全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize1.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }

                primaryStage.setMaximized(!primaryStage.isMaximized());//切换放大放小
            });
        } else if (imageUrl.contains("minimize")) {
            button.setOnAction(event -> primaryStage.setIconified(true));
        }

        return button;
    }

}

11.1.2. 主界面的目录树

@FXML
private TreeView<String> fileTreeView;//目录树


//计算机图标
ImageView computerIcon = new ImageView(new Image(getClass().getResourceAsStream("/images/computer.png")));
computerIcon.setFitHeight(32);
computerIcon.setFitWidth(32);

//桌面图标
ImageView tableIcon = new ImageView(new Image(getClass().getResourceAsStream("/images/table.png")));
tableIcon.setFitHeight(30);
tableIcon.setFitWidth(30);

//文档图标
ImageView textIcon = new ImageView(new Image(getClass().getResourceAsStream("/images/text.png")));
textIcon.setFitHeight(30);
textIcon.setFitWidth(30);

//桌面树
TreeItem<String> rootItemTable = new TreeItem<>("桌面", tableIcon);
TreeItem<String> item = createTreeItem(new File(System.getProperty("user.home") + "/Desktop"));//递归!
rootItemTable.getChildren().add(item);
//盘符树
TreeItem<String> rootItemComputer = new TreeItem<>("计算机", computerIcon);
//遍历盘符集
for (File file : File.listRoots()) {
    TreeItem<String> driveItem = createTreeItem(file);//递归!
    rootItemComputer.getChildren().add(driveItem);
}
//文档假树
TreeItem<String> rootItemText = new TreeItem<>("文档", textIcon);
TreeItem<String> itemText = createTreeItem(new File("text"));//递归!
rootItemText.getChildren().add(itemText);

//合并所有树
TreeItem<String> root = new TreeItem<>();
root.getChildren().addAll(rootItemText, rootItemTable, rootItemComputer);

fileTreeView.setRoot(root);
fileTreeView.setShowRoot(false);//不显示根树
// 监听fileTreeView的选中变化,记录先前选中的子项
fileTreeView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue != null) {
        previouslySelectedItem = newValue;
    }
});

//====================================绑定目录树点击一个选项后的逻辑操作

private ListProperty<File> fileObservableList = new SimpleListProperty<>(FXCollections.observableArrayList());//用来监听文件的集合变化

 //绑定点击目录树(本地电脑文件的目录树)的事件
    @FXML
    private void getSelectTreeItemFile() {
        if (oldHaveFilesPath.isEmpty()) {

        }
        //点击的时候要记得清空选中图片的展示区域的图片(切换目录)
        stackPaneOneImage.getChildren().clear();
        //同时隐藏我们的浏览去的详细信息的部分
        detailImageNameHBox.setVisible(false);
        detailImageInformationVBox.setVisible(false);
        //获取当前选中的目录树选项 
        TreeItem<String> selectedItem = fileTreeView.getSelectionModel().getSelectedItem();
        if (selectedItem != null) {
            TreeItem<String> current = selectedItem;
            String trimPath = createFilePath(current);//获取该目录树的子项的路径
            this.currentPath = new String(trimPath);//更新当前路径
            currentPathProperty.setValue(this.currentPath);
            if (trimPath.equals("")) {//如果空字符串,就表示没有图片的路径
                this.imageFiles = null;//设置更新
                this.imageFilesBackUp = null;

            } else {
                //判断旧的路径和新的路径是否一致,一致就是重复点击了
                if (oldHaveFilesPath.equals(trimPath)) {
                    return;
                }
                this.oldHaveFilesPath = new String(currentPath);//保存旧的
                System.out.println("文件路径: " + trimPath);
                this.imageFiles = getImageFiles(trimPath);//从这个文件路径获取出所有该文件(图片!不同格式)
                imageFilesBackUp = new ArrayList<>(imageFiles);//备份一个
                //更新图片文件!!
                fileObservableList.clear();
                fileObservableList.addAll(imageFiles);
                if (imageFiles != null && imageFiles.size() != 0) {//有图片
                    //有图片=》就说明就去添加目录记录(redis缓存csy不用管)
                    JedisUtil.addScoreAndAddPath(trimPath);
                    // 将当前的newPath值保存到oldPath/TreeItem (有图片的)
                    this.oldHaveFilesItem = selectedItem;
                    //如果oldHaveFilesPath不为空就压入栈中
                    if (oldHaveFilesItem != null) {
                        if (!previouslyHaveImagesTreeItemSatck.contains(oldHaveFilesItem)) {
                            previouslyHaveImagesTreeItemSatck.push(oldHaveFilesItem);
                            System.out.println("item" + this.previouslyHaveImagesTreeItemSatck);
                        }
                    }
                }
            }
        }
    }

11.1.3. 路径框创建

//这个对象可以监听该字符串的改变(切换目录的时候就修改这个内容,就可以监听到变化 ) 
private SimpleStringProperty currentPathProperty = new SimpleStringProperty(null);

//监听路径path的变化
currentPathProperty.addListener((observable, oldValue, newValue) -> {
    // 创建 HBox 容器来存放路径信息
    Platform.runLater(() -> {
        this.pathDisplayOrTextFiledBox = createPathDisplayBoxAndTextFiled(newValue);
        this.textFiledOrpathDisplayHBoxOut.getChildren().clear();
        this.textFiledOrpathDisplayHBoxOut.getChildren().add(pathDisplayOrTextFiledBox);
    });
});



//one更新路径框方法(根据当前路径来创建出一个带图标的路径框)/也是更新的方法 
    private HBox createPathDisplayBoxAndTextFiled(String path) {
        HBox hboxOut = new HBox();
        hBoxIn = createPathDisPlayHBox(path);

        hboxOut.setAlignment(Pos.CENTER_LEFT);

        pathTextFiled = new TextField(path);
        //设置大小.....
        pathTextFiled.setPrefWidth(650);
        pathTextFiled.setPrefHeight(40);
        pathTextFiled.setEditable(true);

        hboxOut.getChildren().add(hBoxIn);
        hboxOut.setOnMouseClicked(event -> {
            if (event.getClickCount() == 2) {
                hboxOut.getChildren().remove(hBoxIn);
                hboxOut.getChildren().add(pathTextFiled);
            }
        });
        pathTextFiled.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ENTER) {
                hboxOut.getChildren().remove(pathTextFiled);
                String textPath = pathTextFiled.getText();
                System.out.println(textPath);
                //创建文件(如果存在且为目录的话)
                File file = new File(textPath);
                if (file.exists() && file.isDirectory()) {
                    // 1存在就跳转并且修改
                    //还要更新我们的选中状态treeItem,以及面板()
                    this.pathDisplayOrTextFiledBox = createPathDisplayBoxAndTextFiled(textPath);
                    this.textFiledOrpathDisplayHBoxOut.getChildren().clear();
                    this.textFiledOrpathDisplayHBoxOut.getChildren().add(pathDisplayOrTextFiledBox);
                    //更新treeItem
                    TreeItem<String> stringTreeItem = new TreeItem<>(textPath);
                    fileTreeView.getSelectionModel().select(stringTreeItem);
                    //更新面板 + 总数
                    this.imageFiles = getImageFiles(textPath);
                    if (isUpSort) {
                        sortByUp(sortTypeName);//升序 + 名称/大小/时间
                    } else if (isDownSort) {
                        sortByDown(sortTypeName);//降序 + 名称/大小/时间
                    }
                    fileObservableList.clear();
                    fileObservableList.addAll(this.imageFiles);
                    stackPaneOneImage.getChildren().clear();
                    //同时隐藏我们的浏览去的详细信息的部分
                    detailImageNameHBox.setVisible(false);
                    detailImageInformationVBox.setVisible(false);
                } else {
                    //2如果没有结果就恢复原来的并提示并没有alert,(单单修改我们的路径框就好了)
                    this.pathDisplayOrTextFiledBox = createPathDisplayBoxAndTextFiled(path);
                    this.textFiledOrpathDisplayHBoxOut.getChildren().clear();
                    this.textFiledOrpathDisplayHBoxOut.getChildren().add(pathDisplayOrTextFiledBox);
                    //弹出提示框
                    MyPathExistAlert myPathExistAlert = new MyPathExistAlert(textPath);
                    myPathExistAlert.show();
                }
                //到时候可以去查找修改后的
                //是否存在,存在就更新面板,不存在弹出一个提示框
                /**
                 * 查询???怎么样查
                 */
                //1如果路径存在就去进行跳转(起一个线程进行查询)=》可以加一个旋转加载

            }
        });


        hboxOut.setPrefHeight(40);
        hboxOut.setPrefWidth(650);

        return hboxOut;
    }

11.1.4. 主界面右键显示浮窗代码编写

package org.example.managerImage.component.popup;

import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;

import java.io.IOException;

/**
 * @author 张 志 豪
 * @version 1.0
 * 弹窗(鼠标右键)显示复制粘贴打开删除
 */
public class CopyPasteRenameOpenDeleteCollectPopupShare extends ContextMenu {

    private Label labelLeftOpen;
    private Label labelLeftPaste;
    private Label labelLeftRename;
    private Label labelLeftCopy;
    private Label labelLeftDelete;
    private Label labelLeftCollect;
    private Label labelLeftShare;

    public Label getLabelLeftDelete() {
        return labelLeftDelete;
    }

    public void setLabelLeftDelete(Label labelLeftDelete) {
        this.labelLeftDelete = labelLeftDelete;
    }

    public String getSelectText() {
        return selectText;
    }

    public void setSelectText(String selectText) {
        this.selectText = selectText;
    }

    private String selectText;

    public Label getLabelLeftOpen() {
        return labelLeftOpen;
    }

    public void setLabelLeftOpen(Label labelLeftOpen) {
        this.labelLeftOpen = labelLeftOpen;
    }

    public Label getLabelLeftPaste() {
        return labelLeftPaste;
    }

    public void setLabelLeftPaste(Label labelLeftPaste) {
        this.labelLeftPaste = labelLeftPaste;
    }

    public Label getLabelLeftRename() {
        return labelLeftRename;
    }

    public void setLabelLeftRename(Label labelLeftRename) {
        this.labelLeftRename = labelLeftRename;
    }

    public Label getLabelLeftCopy() {
        return labelLeftCopy;
    }

    public void setLabelLeftCopy(Label labelLeftCopy) {
        this.labelLeftCopy = labelLeftCopy;
    }

    public Label getLabelLeftCollect() {
        return labelLeftCollect;
    }

    public void setLabelLeftCollect(Label labelLeftCollect) {
        this.labelLeftCollect = labelLeftCollect;
    }

    public Label getLabelLeftShare() {
        return labelLeftShare;
    }

    public void setLabelLeftShare(Label labelLeftShare) {
        this.labelLeftShare = labelLeftShare;
    }

    //=================== 回调接口(目的是为了让主界面具体实现该方法,)
    public interface CallbackOpenPopGetText {
        //获取选中的内容
        void onClickGetText(String typeText) throws IOException;
    }

    private CallbackOpenPopGetText callbackOpenPopGetText;

    public void setCallbackOpenPopGetText(CallbackOpenPopGetText callback) {
        this.callbackOpenPopGetText = callback;
    }
    //=================== 回调接口
    public CopyPasteRenameOpenDeleteCollectPopupShare(){
        //创建菜单项
        MenuItem menuItemCopy = new MenuItem("ctrl+c");
        MenuItem menuItemPaste = new MenuItem("ctrl+v");
        MenuItem menuItemRename = new MenuItem("rename");//#d4e2ff
        MenuItem menuItemOpen = new MenuItem("open");
        MenuItem menuItemDelete = new MenuItem("delete");
        MenuItem menuItemCollect = new MenuItem("collect");
        MenuItem menuItemShare = new MenuItem("share");
        //监听选中(handleMenuItemSelected在下面)
        menuItemCopy.setOnAction(event -> handleMenuItemSelected(menuItemCopy));
        menuItemPaste.setOnAction(event -> handleMenuItemSelected(menuItemPaste));
        menuItemRename.setOnAction(event -> handleMenuItemSelected(menuItemRename));
        menuItemOpen.setOnAction(event -> handleMenuItemSelected(menuItemOpen));
        menuItemDelete.setOnAction(event -> handleMenuItemSelected(menuItemDelete));
        menuItemCollect.setOnAction(event -> handleMenuItemSelected(menuItemCollect));
        menuItemShare.setOnAction(event -> handleMenuItemSelected(menuItemShare));


        // 创建左侧菜单项标签并设置样式
        labelLeftCopy = new Label("复制");
        labelLeftCopy.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        // 创建左侧菜单项标签并设置样式
        labelLeftPaste = new Label("粘贴");
        labelLeftPaste.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        // 创建左侧菜单项标签并设置样式
        labelLeftRename = new Label("重命名");
        labelLeftRename.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        // 创建左侧菜单项标签并设置样式
        labelLeftOpen = new Label("打开");
        labelLeftOpen.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        labelLeftDelete = new Label("删除");
        labelLeftDelete.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        labelLeftCollect = new Label("收藏★");
        labelLeftCollect.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        labelLeftShare = new Label("分享♥");
        labelLeftShare.setStyle("-fx-padding: 0 20 0 5;"); // 设置左侧标签的右边距

        menuItemCopy.setGraphic(labelLeftCopy);
        menuItemPaste.setGraphic(labelLeftPaste);
        menuItemRename.setGraphic(labelLeftRename);
        menuItemOpen.setGraphic(labelLeftOpen);
        menuItemDelete.setGraphic(labelLeftDelete);
        menuItemCollect.setGraphic(labelLeftCollect);
        menuItemShare.setGraphic(labelLeftShare);


        // 设置菜单的样式//背景色,边角圆润
        this.setStyle("-fx-background-color: #f0f0f0;-fx-background-radius: 5px;");
        this.getItems().addAll(menuItemCopy,menuItemPaste,menuItemRename,menuItemOpen,menuItemDelete,menuItemCollect,menuItemShare);

    }

    private void handleMenuItemSelected(MenuItem menuItem) {
        String selectedContent = menuItem.getText();
        selectText = selectedContent;
        if (callbackOpenPopGetText!=null){
            try {
                callbackOpenPopGetText.onClickGetText(selectText);//调用回调函数
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

主界面右键显示复制粘贴删除收藏等浮窗

copyPasteRenameOpenDeleteCollectPopupShare.show(threeHBox, event.getScreenX(), event.getScreenY());

//three的右键弹出复制粘贴删除打开重命名的浮窗
    @FXML
    public void rightMouseCVROD(MouseEvent event) {
        if (event.getButton() == MouseButton.SECONDARY) {//鼠标右键
            copyPasteRenameOpenDeleteCollectPopupShare.show(threeHBox, event.getScreenX(), event.getScreenY());
            //这个时候一定要去使用回调函数,直接获取不行(就是上面copyPasteRenameOpenDeleteCollectPopupShare类的方法)
            copyPasteRenameOpenDeleteCollectPopupShare.setCallbackOpenPopGetText((typeText) -> {
                //逻辑操作调用方法实现(就是可以获取到你点击某个选项后的数据)
                methodRightCVRODS(typeText);//传入typeText后 进行什么复制粘贴...操作 
            });
        } else if (event.getButton() == MouseButton.PRIMARY) {//左键隐藏
            copyPasteRenameOpenDeleteCollectPopupShare.hide();
        }
    }
 //实现具体rightMouseCVROD的方法
    public void methodRightCVRODS(String typeText) {
        if (typeText.equals("ctrl+c")) {
            ctrlC();
        } else if (typeText.equals("ctrl+v")) {
            ctrlV();
        } else if (typeText.equals("rename")) {
            rename();
        } else if (typeText.equals("delete")) {
            delete();//这个方法是你负者实现 ===>分类到下面主界面删除图片
        } else if (typeText.equals("open")) {
            open();
        } else if (typeText.equals("collect")) {
            //收藏
            collect();
        }else if (typeText.equals("share")) {
            //分享
            share();
        }
    }

11.1.5. 主界面删除图片

具体就是(从选中的文件集合中获取),将这些文件进行移动到其他目录,目的是为了方便撤回操作(就是把他们移回来)

    //删除
    public void delete() {
        //重命名
        System.out.println("删除");
        deleteImage();
    }

    public void deleteImage() {
        //判断是否有选中删除的图片
        if (selectFiles.size() == 0) {
            //没有选中的照片
            //弹出提示框
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
            alert.setTitle("删除图片");
            alert.setHeaderText(null);
            alert.setContentText("请先选中您想要删除的图片");
            alert.showAndWait();
        } else { //有选中想要删除的图片
            //显示一个提示框
            // 创建一个确认提示框
            MyDeleteAlert myDeleteAlert = new MyDeleteAlert();

            // 等待用户点击确定按钮
            ButtonType result = myDeleteAlert.showAndWait().orElse(ButtonType.CANCEL);

            if (result == ButtonType.OK) {
                // 用户点击了确定按钮,执行下面的逻辑操作
                System.out.println("执行删除操作");
                // 执行其他逻辑操作...

                //先去获取被选中的文件selectFiles
                if (selectFiles != null) {
                    Stack<File> deletedFilesStack = new Stack<>();
                    Iterator<File> iterator = selectFiles.iterator();
                    String backUpDirectoryPath = "C:\\backUp";//创建备份目录 -->改成云存储(zzh以后更新)
                    //创建备份目录
                    File fileBackUpDirectory = new File(backUpDirectoryPath);
                    if (!fileBackUpDirectory.exists()) {
                        fileBackUpDirectory.mkdirs();
                    }

                    while (iterator.hasNext()) {//使用迭代器进行遍历和操作(不能for循环直接删除)
                        File selectFile = iterator.next();
                        //删除成功(备份)
                        File backUpFile = new File(fileBackUpDirectory, selectFile.getName());
                        //将源文件移到备份目录中
                        try {
                            Files.move(selectFile.toPath(), backUpFile.toPath());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                        deletedFilesStack.push(backUpFile); // 将被删除的文件(备份后的)添加到 deletedFilesStack 中
                        iterator.remove();
                    }
                    //将删除的文件存放到栈中,目的是为了我们以后撤回一个个pop出来添加回面板,更新 
                    MyDeleteStacks myDeleteStacks = new MyDeleteStacks(currentPath, deletedFilesStack);
                    myDeleteStacksList.add(myDeleteStacks);//添加到集合中

                    //更新面板(将其移除该文件(从该集合中))
                    imageFiles.removeAll(selectFiles);
                    fileObservableList.clear();
                    fileObservableList.addAll(imageFiles);//起到监听作用 
                    //重新绘制(zzh补充)
                    try {
                        createImagePerform(fileObservableList,performType.getValue());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }

            } else {
                // 用户点击了取消按钮,取消删除操作
                System.out.println("取消删除操作");
            }
        }

11.1.6. 主界面的图片播放窗口

就只有一个方法没有fxml文件,都是动态数据

package org.example.managerImage.mystage;

import javafx.animation.RotateTransition;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.example.managerImage.component.alert.MyAlert;
import org.example.managerImage.component.alert.MyDeleteAlert;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;

/**
 * @author 张 志 豪
 * @version 1.0
 */
public class MyVideoStage extends Stage {
    private Integer countShun = 0;//顺时针的计数(正数)
    private Integer countNi = 0;//逆时针的计数(负数)

    private double xOffset = 0;
    private double yOffset = 0;

    private HBox hBoxImageView;//播放下一章图片的
    private HBox hBoxImageName;//图片的名称 + 大小

    private ImageView cuurentImageView;//当前播放图片

    private Integer currentIndex = 0;//存放当前播放图片的索引

    private List<File> imageFiles;//图片文件

    private StackPane stackPane;//放当前图片的组件

    private HBox hBoxButton;//存放组件的(按钮组件)

    private HBox hBoxImageList;//存放多张图片缩略图,能显示多长

    private VBox vBoxImageContent;//整合上面三个部分的地方;

    private Image imageNextOneNo;//下一张图片
    private Image imageNextOneSelect;//下一张图片选中

    private Image imagePreOneNo;//上一张图片
    private Image imagePreOneSelect;//上一张图片选中

    private Image imageShunXZ;//顺时针图片
    private Image imageShunXZSelect;//顺时针图片选中

    private Image imageNiXZ;//逆时针图片
    private Image imageNiXZSelect;//逆时针图片选中

    private Image imageDelete;//删除该图片
    private Image imageDeleteSelect;//删除图片选中

    private Image imageBig;//放大该图片
    private Image imageBigSelect;//放大该图片
    private Image imageSmall;//缩小图片选中
    private Image imageSmallSelect;//缩小图片选中

    private Image returnImageNo;//恢复图片比例

    private Image returnImageSelect;//恢复图片比例选中

    private double size = 30;//放大图片,缩小图片的比例

    private File currentFile = null;

    private Timer timerNextOne;//时间器(下一张)
    private Timer timerPreOne;//时间器(上一张)
    private Timer timerDelete;//时间器(删除)
    private Timer timerShun;//时间器(顺旋转)
    private Timer timerNi;//时间器(逆旋转)
    private Timer timerBig;//时间器(放大)
    private Timer timerSmall;//时间器(缩小)
    private Timer timerReturn;//时间器(缩小)


    private double lastX, lastY;//拖拽
    private boolean dragging = false;

    private ArrayList<File> filesQueue;//循环的文件(复制出来的)
    private Queue<File> queue;//循环的文件(复制出来的)
    private ArrayList<File> file10;//指定只有10张

    private Queue<File> imageFilesQueue;//存放非重复,不断将头放到尾巴的循环形式(用于删除的时候去构建新的图片列表用的)

    //缓存被删除的文件(方便ctrl+z撤回)
    private Stack<File> deletedFilesStack = new Stack<>();//堆来存(到时候一个个pop后撤回)

    // 回调接口(关闭窗口的回调函数后,需要更新three和second,将删除的文件对象传给回调函数,到时候ctrl+z可以撤回)
    public interface CallbackCloseWindowAndDelete {
        void onClickDeleteClose(File backUpFile, List<File> fileList) throws IOException;
    }

    private CallbackCloseWindowAndDelete callbackCloseWindowAndDelete;

    public void setCallbackCloseWindowAndDelete(CallbackCloseWindowAndDelete callback) {
        this.callbackCloseWindowAndDelete = callback;
    }


    //根据你选中的当前图片来开始去播放,如果没选择就从开头开始播放
    public MyVideoStage(List<File> fileArrayList, Integer currentPerformIndex) throws FileNotFoundException {
        this.imageFiles = fileArrayList;
        if (fileArrayList != null) {
            imageFilesQueue = new LinkedList<>(fileArrayList);
        } else {
            imageFilesQueue = null;
        }

        ArrayList<File> objects = null;
        if (imageFilesQueue != null) {
            for (int i = 0; i < currentPerformIndex; i++) {
                //根据传过来选中位置,放在播放条图片集合的首位置 0 1 2 3 4 如果传过来2 =》 2 3 4 0 1
                File poll = imageFilesQueue.poll();
                //放到队尾
                imageFilesQueue.offer(poll);
            }
            this.imageFiles = new ArrayList<>(imageFilesQueue);
            objects = new ArrayList<>(imageFilesQueue);
        }
        createFile10AndFilesQueue(objects);//创建文件的file10和filesQueue和queue(文件条)

        System.out.println(queue);
        System.out.println(this.imageFiles);
        System.out.println(this.file10);
        // 创建根布局
        AnchorPane root = new AnchorPane();
        root.setStyle("-fx-background-color: #333;");
        // 1创建自定义标题栏(顶部)
        BorderPane titleBar = createTitleBar();
        AnchorPane.setTopAnchor(titleBar, 0.0);//这些方法就是设置外边距
        AnchorPane.setLeftAnchor(titleBar, 0.0);
        AnchorPane.setRightAnchor(titleBar, 0.0);


        root.getChildren().addAll(titleBar);

        //创建初始化
        this.imagePreOneNo = new Image("/images/arrowPreone.png");
        this.imagePreOneSelect = new Image("/images/arrowPreoneSelect.png");

        this.imageNextOneNo = new Image("/images/arrowNextone.png");
        this.imageNextOneSelect = new Image("/images/arrowNextoneSelect.png");

        this.imageShunXZ = new Image("/images/shunshizhen.png");
        this.imageShunXZSelect = new Image("/images/shunshizhenSelect.png");

        this.imageNiXZ = new Image("/images/nishizhen.png");
        this.imageNiXZSelect = new Image("/images/nishizhenSelect.png");

        this.imageBig = new Image("/images/fangda.png");
        this.imageBigSelect = new Image("/images/fangdaSelect.png");

        this.imageSmall = new Image("/images/suoxiao.png");
        this.imageSmallSelect = new Image("/images/suoxiaoSelect.png");

        this.returnImageNo = new Image("/images/return.png");
        this.returnImageSelect = new Image("/images/returnSelect.png");

        this.imageDelete = new Image("/images/deleteImage.png");
        this.imageDeleteSelect = new Image("/images/deleteImageSelect.png");

        //图片名称 + 内存大小
        this.hBoxImageName = new HBox();
        createHBoxImageName(null);

        //图片布局
        this.hBoxImageView = new HBox();
        createHBoxImageViewContent();


        //组件按钮布局
        this.hBoxButton = new HBox();
        createHBoxButton();

        //底部图片列表
        this.hBoxImageList = new HBox(10);
        createBottomImageList(this.file10);


        this.vBoxImageContent = new VBox();
        this.vBoxImageContent.getChildren().addAll(hBoxImageView, hBoxImageName, hBoxButton, hBoxImageList);
        this.vBoxImageContent.setPrefWidth(1500);
        this.vBoxImageContent.setPrefHeight(950);
        this.vBoxImageContent.setMaxWidth(1500);
        this.vBoxImageContent.setMaxHeight(950);
        //底部图片列表设置位置
        vBoxImageContent.setMargin(hBoxImageList, new Insets(20, 0, 0, 800));
        vBoxImageContent.setMargin(hBoxButton, new Insets(40, 0, 0, 0));
        vBoxImageContent.setMargin(hBoxImageName, new Insets(5, 0, 0, 0));

        AnchorPane.setTopAnchor(vBoxImageContent, 50.0);//这些方法就是设置外边距
        AnchorPane.setLeftAnchor(vBoxImageContent, 0.0);
        AnchorPane.setRightAnchor(vBoxImageContent, 0.0);

        root.getChildren().add(vBoxImageContent);
        Scene scene = new Scene(root, 1500, 1000);
        scene.setFill(Color.TRANSPARENT);
        this.setScene(scene);
        this.initStyle(StageStyle.TRANSPARENT);
    }

    private void createHBoxImageName(File file) {
        if (file != null) {
            String imageName = file.getName();//图片名
            long fileSize = file.length();//图片大小
            Label labelNameSize = new Label(imageName + " ( " + fileSize + " bytes )");
            labelNameSize.setTextFill(Color.WHITE);//白色字体(粗体)
            labelNameSize.setFont(Font.font("楷体", FontWeight.BOLD, 22));
            this.hBoxImageName.getChildren().add(labelNameSize);
            this.hBoxImageName.setAlignment(Pos.CENTER);
        }
        this.hBoxImageName.setPrefHeight(32);
        this.hBoxImageName.setPrefWidth(1500);
    }

    //创建底部的图片列表
    private void createBottomImageList(ArrayList<File> fileArrayList) {
        if (fileArrayList == null) {
            return;
        }
        for (int i = 0; i < fileArrayList.size(); i++) {
            HBox hBox = new HBox();
            if (i == 0) {
                //第一个就要有放大
                ImageView imageView = new ImageView(new Image(fileArrayList.get(i).toURI().toString()));
                imageView.setFitWidth(80);
                imageView.setFitHeight(80);
                hBox.getChildren().add(imageView);
                hBox.setAlignment(Pos.CENTER);
                hBox.setStyle("-fx-border-color: GRAY; -fx-border-width: 10px;");
                DropShadow dropShadow = new DropShadow();
                dropShadow.setOffsetX(5);
                dropShadow.setOffsetY(5);
                dropShadow.setColor(Color.GRAY);
                hBox.setEffect(dropShadow);
            } else {
                ImageView imageView = new ImageView(new Image(fileArrayList.get(i).toURI().toString()));
                imageView.setFitWidth(60);
                imageView.setFitHeight(60);
                hBox.setAlignment(Pos.CENTER);
                hBox.getChildren().add(imageView);
            }
            this.hBoxImageList.getChildren().add(hBox);
        }
        this.hBoxImageList.setPrefWidth(400);
        this.hBoxImageList.setPrefHeight(100);
        this.hBoxImageList.setAlignment(Pos.CENTER_LEFT);
        this.hBoxImageList.setStyle("-fx-background-color: white");//背景色为白色
        this.hBoxImageList.setSpacing(5);
    }

    //创建图片浏览区
    private void createHBoxImageViewContent() {
        if (this.imageFiles != null && this.imageFiles.size() != 0) {
            File file = this.imageFiles.get(0);
            currentFile = file;//当前图片
            //创建文件名的HBox
            this.hBoxImageName = new HBox();
            createHBoxImageName(file);
            //创建第一张图片区域
            this.cuurentImageView = new ImageView(new Image(file.toURI().toString()));
            this.cuurentImageView.setFitWidth(400);
            this.cuurentImageView.setFitHeight(400);
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setPrefWidth(1500);
            scrollPane.setPrefHeight(680);
            //背景色 + 滚动条背景色 + 隐藏背景色
            scrollPane.setStyle("-fx-background-color: #333;-fx-control-inner-background: #333;-fx-hbar-policy: never;-fx-vbar-policy: never;");


            stackPane = new StackPane(cuurentImageView);
            stackPane.setStyle("-fx-background-color: #333;");
            stackPane.setPrefWidth(1500);
            stackPane.setPrefHeight(680);
            stackPane.setAlignment(Pos.CENTER);

            scrollPane.setContent(stackPane);//scrollpane背景色会被覆盖(上层内容(即便为null))
            scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
            this.hBoxImageView.getChildren().add(scrollPane);
        } else {
            //空的话
            //如果文件没有,就提示没有图片
            Label label = new Label("没 有 图 片 !");
            //加粗字体
            label.setTextFill(Color.WHITE);//白色字体(粗体)
            label.setFont(Font.font("楷体", FontWeight.BOLD, 50));
            this.hBoxImageView.setPrefWidth(1500);
            this.hBoxImageView.setPrefHeight(680);
            this.hBoxImageView.setAlignment(Pos.CENTER);
            this.hBoxImageView.getChildren().add(label);
        }


        hBoxImageView.setStyle("-fx-background-color: #333;");
        this.hBoxImageView.setPrefWidth(1500);
        this.hBoxImageView.setPrefHeight(680);
        this.hBoxImageView.setAlignment(Pos.CENTER);

        if (cuurentImageView != null) {
            // 设置鼠标拖动监听器
            this.cuurentImageView.setOnMousePressed(e -> {
                lastX = e.getX();
                lastY = e.getY();
                dragging = true;
            });

            this.cuurentImageView.setOnMouseDragged(e -> {
                if (dragging) {
                    double deltaX = e.getX() - lastX;
                    double deltaY = e.getY() - lastY;
                    cuurentImageView.setTranslateX(cuurentImageView.getTranslateX() + deltaX);
                    cuurentImageView.setTranslateY(cuurentImageView.getTranslateY() + deltaY);
                    lastX = e.getX();
                    lastY = e.getY();
                }
            });

            this.cuurentImageView.setOnMouseReleased(e -> {
                dragging = false;
            });
        }

    }

    //创建按钮控件的布局
    private void createHBoxButton() {
        //组装
        //恢复
        ImageView imageReturnimageView = new ImageView(returnImageNo);
        imageReturnimageView.setFitHeight(50);
        imageReturnimageView.setFitWidth(50);
        HBox imageReturnimageViewhBox = new HBox(imageReturnimageView);

        //放大缩小
        ImageView imageBigimageView = new ImageView(imageBig);
        imageBigimageView.setFitHeight(50);
        imageBigimageView.setFitWidth(50);
        HBox imageBigimageViewhBox = new HBox(imageBigimageView);


        ImageView imageSmallimageView = new ImageView(imageSmall);
        imageSmallimageView.setFitHeight(50);
        imageSmallimageView.setFitWidth(50);
        HBox imageSmallimageViewhBox = new HBox(imageSmallimageView);

        //虚线
        Line leftLine = createDashedLine(52, 5);

        //上下一张
        ImageView imagePreOneNoimageView = new ImageView(imagePreOneNo);
        imagePreOneNoimageView.setFitHeight(50);
        imagePreOneNoimageView.setFitWidth(50);
        HBox imagePreOneNoimageViewhBox = new HBox(imagePreOneNoimageView);

        ImageView imageNextOneNoView = new ImageView(imageNextOneNo);
        imageNextOneNoView.setFitHeight(50);
        imageNextOneNoView.setFitWidth(50);
        HBox imageNextOneNoViewhBox = new HBox(imageNextOneNoView);

        //虚线
        Line rightLine = createDashedLine(52, 5);

        //左旋右旋删除
        ImageView imageNiXZimageView = new ImageView(imageNiXZ);
        imageNiXZimageView.setFitHeight(50);
        imageNiXZimageView.setFitWidth(50);
        HBox imageNiXZimageViewhBox = new HBox(imageNiXZimageView);

        ImageView imageShunXZimageView = new ImageView(imageShunXZ);
        imageShunXZimageView.setFitHeight(50);
        imageShunXZimageView.setFitWidth(50);
        HBox imageShunXZimageViewhBox = new HBox(imageShunXZimageView);

        ImageView imageDeleteimageView = new ImageView(imageDelete);
        imageDeleteimageView.setFitHeight(50);
        imageDeleteimageView.setFitWidth(50);
        HBox imageDeleteimageViewhBox = new HBox(imageDeleteimageView);

        //监听点击事件
        //恢复
        imageReturnimageViewhBox.setOnMouseClicked(event -> {
            imageReturnimageView.setImage(returnImageSelect);//切换选中状态
            startTimer(imageReturnimageView, returnImageNo, timerReturn);//自动2s回复原图
            if (cuurentImageView != null) {
                //恢复
                cuurentImageView.setFitWidth(400);
                cuurentImageView.setFitHeight(400);
            }
        });

        // 放大
        imageBigimageViewhBox.setOnMouseClicked(event -> {
            imageBigimageView.setImage(imageBigSelect);//切换选中状态
            startTimer(imageBigimageView, imageBig, timerBig);//自动2s回复原图
            if (cuurentImageView != null) {
                //放大
                cuurentImageView.setFitWidth(cuurentImageView.getFitWidth() + size);
                cuurentImageView.setFitHeight(cuurentImageView.getFitHeight() + size);
            }

        });
        //缩小
        imageSmallimageViewhBox.setOnMouseClicked(event -> {
            imageSmallimageView.setImage(imageSmallSelect);//切换选中状态
            startTimer(imageSmallimageView, imageSmall, timerSmall);//自动2s回复原图
            //缩小//400-30*X => x
            if (cuurentImageView != null && cuurentImageView.getFitHeight() == 10) {
                return;//不能再缩小了
            }
            if (cuurentImageView != null) {
                cuurentImageView.setFitWidth(cuurentImageView.getFitWidth() - size);
                cuurentImageView.setFitHeight(cuurentImageView.getFitHeight() - size);
            }
        });


        //上一张
        imagePreOneNoimageViewhBox.setOnMouseClicked(event -> {
            imagePreOneNoimageView.setImage(imagePreOneSelect);//切换选中状态
            startTimer(imagePreOneNoimageView, imagePreOneNo, timerPreOne);//自动2s回复原图
            showPreviousImage();//上一张

            if (queue != null) {
                File lastFile = ((LinkedList<File>) queue).getLast(); //获取队尾
                queue.remove(lastFile);
                ((LinkedList<File>) queue).addFirst(lastFile);//添加到队头

                //变化队头队尾(方便删除的时候和面板内容相同)
                File lastFile1 = ((LinkedList<File>) imageFilesQueue).getLast(); //获取队尾
                imageFilesQueue.remove(lastFile1);
                ((LinkedList<File>) imageFilesQueue).addFirst(lastFile1);//添加到队头

                // 获取从队头开始数到队尾的方向数10个元素构成一个ArrayList
                ArrayList<File> eightFiles = new ArrayList<>();
                int count = 0;
                for (File file : queue) {
                    if (count < 10) {
                        eightFiles.add(file);
                        count++;
                    } else {
                        break;
                    }
                }
                this.file10 = eightFiles;
                //更新图片列表
                this.vBoxImageContent.getChildren().remove(this.hBoxImageList);
                this.hBoxImageList = new HBox();
                createBottomImageList(file10);//重新创建图片列表(刷新)
                this.vBoxImageContent.getChildren().add(hBoxImageList);
                this.vBoxImageContent.setMargin(hBoxImageList, new Insets(20, 0, 0, 800));
            }
        });
        //下一张
        imageNextOneNoViewhBox.setOnMouseClicked(event -> {
            imageNextOneNoView.setImage(imageNextOneSelect);//切换选中状态
            startTimer(imageNextOneNoView, imageNextOneNo, timerNextOne);//自动2s回复原图
            showNextImage();//下一张
            if (queue != null) {
                File poll = queue.poll();//取出对头并向后移动
                queue.offer(poll);//添加到队尾

                //变化队头队尾(方便删除的时候和面板内容相同)
                File poll1 = imageFilesQueue.poll();//取出对头并向后移动
                imageFilesQueue.offer(poll1);//添加到队尾

                // 获取从队头开始数到队尾的方向数10个元素构成一个ArrayList
                ArrayList<File> eightFiles = new ArrayList<>();
                int count = 0;
                for (File file : queue) {
                    if (count < 10) {
                        eightFiles.add(file);
                        count++;
                    } else {
                        break;
                    }
                }
                this.file10 = eightFiles;
                //更新图片列表
                this.vBoxImageContent.getChildren().remove(this.hBoxImageList);
                this.hBoxImageList = new HBox();
                createBottomImageList(file10);//重新创建图片列表(刷新)
                this.vBoxImageContent.getChildren().add(hBoxImageList);
                this.vBoxImageContent.setMargin(hBoxImageList, new Insets(20, 0, 0, 800));
            }
        });
        //顺时针
        imageShunXZimageViewhBox.setOnMouseClicked(event -> {
            countNi = 0;
            imageShunXZimageView.setImage(imageShunXZSelect);//切换选中状态
            startTimer(imageShunXZimageView, imageShunXZ, timerShun);//自动2s回复原图
            if (cuurentImageView != null) {
                if (countShun % 360 == 0) {
                    countShun = 0;
                }
                countShun += 45;
                RotateTransition rotateTransition = new RotateTransition(Duration.seconds(0.4), cuurentImageView);
                rotateTransition.setByAngle(45); // 顺时针旋转 360 度
                //rotateTransition.setCycleCount(RotateTransition.INDEFINITE); // 设置无限循环 -1
                rotateTransition.setCycleCount(1); // 设置动画只播放一次
                rotateTransition.play(); // 开始动画
                // 监听动画播放完成事件
                rotateTransition.setOnFinished(event1 -> {
                    cuurentImageView.setRotate(cuurentImageView.getRotate()); // 设置图片最终旋转到的角度,这里设置为 90 度
                });
            }

        });

        //逆时针
        imageNiXZimageViewhBox.setOnMouseClicked(event -> {
            countShun = 0;
            imageNiXZimageView.setImage(imageNiXZSelect);//切换选中状态
            startTimer(imageNiXZimageView, imageNiXZ, timerNi);//自动2s回复原图
            if (cuurentImageView != null) {
                if (countNi % (-360) == 0) {
                    countNi = 0;
                }
                countNi -= 45;
                //创建旋转动画
                RotateTransition rotateTransition = new RotateTransition(Duration.seconds(0.4), cuurentImageView);
                rotateTransition.setByAngle(-45); // 顺时针旋转 360 度
                //rotateTransition.setCycleCount(RotateTransition.INDEFINITE); // 设置无限循环 -1
                rotateTransition.setCycleCount(1); // 设置动画只播放一次
                rotateTransition.play(); // 开始动画
                // 监听动画播放完成事件
                rotateTransition.setOnFinished(event1 -> {
                    cuurentImageView.setRotate(cuurentImageView.getRotate()); // 设置图片最终旋转到的角度,这里设置为 90 度
                });
            }
        });

        //删除
        imageDeleteimageViewhBox.setOnMouseClicked(event -> {
            imageDeleteimageView.setImage(imageDeleteSelect);//切换选中状态
            startTimer(imageDeleteimageView, imageDelete, timerDelete);//自动2s回复原图

            //先去判断是否要删除
            if (currentFile == null){
                //如果删除的文件为null,那么我们就弹出提示框
                MyAlert myAlert = new MyAlert(Alert.AlertType.CONFIRMATION, "删除图片", "没有图片可删除");
                myAlert.show();
            }else {
                MyDeleteAlert myDeleteAlert = new MyDeleteAlert();

                // 等待用户点击确定按钮
                ButtonType result = myDeleteAlert.showAndWait().orElse(ButtonType.CANCEL);

                if (result == ButtonType.OK) {
                    File currentRemoveFile = currentFile;

                    //删除当前的文件
                    this.imageFiles.remove(currentRemoveFile);
                    //图片浏览去要更新(往后)
                    showCurrentImage();

                    //图片列表也要更新
                    imageFilesQueue.poll();//去掉当前图片
                    ArrayList<File> objects = new ArrayList<>(imageFilesQueue);
                    createFile10AndFilesQueue(objects);//重新创建出file10和queue
                    //更新图片列表
                    this.vBoxImageContent.getChildren().remove(this.hBoxImageList);
                    this.hBoxImageList = new HBox();
                    createBottomImageList(file10);//重新创建图片列表(刷新)
                    this.vBoxImageContent.getChildren().add(hBoxImageList);
                    this.vBoxImageContent.setMargin(hBoxImageList, new Insets(20, 0, 0, 800));

                    System.out.println(this.imageFiles.size());
                    //删除文件移动到备份里
                    if (currentRemoveFile != null) {
                        String backUpDirectoryPath = "G:\\backUp";//创建备份目录
                        //创建备份目录
                        File fileBackUpDirectory = new File(backUpDirectoryPath);
                        if (!fileBackUpDirectory.exists()) {
                            fileBackUpDirectory.mkdirs();
                        }


                        File backUpFile = new File(fileBackUpDirectory, currentRemoveFile.getName());
                        //将源文件移到备份目录中
                        try {
                            Files.move(currentRemoveFile.toPath(), backUpFile.toPath());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }

                        //启动回调函数(主窗口去监听回调函数实现 更新 图片面板)
                        if (callbackCloseWindowAndDelete != null) {
                            try {
                                System.out.println("nihao");
                                callbackCloseWindowAndDelete.onClickDeleteClose(backUpFile, this.imageFiles);//传入删除的文件集合
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }

                    }

                }else {
                    //不删除
                }
            }

        });


        //组合
        hBoxButton.getChildren().addAll(imageReturnimageViewhBox, imageBigimageViewhBox, imageSmallimageViewhBox,
                leftLine, imagePreOneNoimageViewhBox, imageNextOneNoViewhBox, rightLine, imageNiXZimageViewhBox
                , imageShunXZimageViewhBox, imageDeleteimageViewhBox);
        hBoxButton.setSpacing(25);//组件间的间距

        hBoxButton.setAlignment(Pos.CENTER);
    }


    private BorderPane createTitleBar() throws FileNotFoundException {
        // 创建标题栏
        BorderPane titleBar = new BorderPane();
        titleBar.getStyleClass().add("title-bar");

        // 创建图标
        ImageView iconImageView = new ImageView(new Image(getClass().getResourceAsStream("/images/icon.png")));
        iconImageView.setFitWidth(32);
        iconImageView.setFitHeight(32);

        // 创建标题
        Label titleLabel = new Label("图览之光");
        titleLabel.setFont(Font.font("楷体", FontWeight.BOLD, 18));
        titleLabel.setTextFill(Color.WHITE);
        titleLabel.setPadding(new Insets(0, 0, 0, 6));//设置一定的边距

        // 将图标和标题放在一个水平布局中
        HBox iconTitleBox = new HBox();
        iconTitleBox.setAlignment(Pos.CENTER_LEFT);
        iconTitleBox.getChildren().addAll(iconImageView, titleLabel);

        // 创建按钮
        Button closeButton = createWindowControlButton(this, "close.png", 32);
        Button maximizeButton = createWindowControlButton(this, "maximize1.png", 32);
        Button minimizeButton = createWindowControlButton(this, "minimize.png", 30);

        //整合三个按钮放在一个水平布局中
        HBox buttonHBox = new HBox(5);
        buttonHBox.getChildren().addAll(minimizeButton, maximizeButton, closeButton);


        // 添加组件到标题栏
        titleBar.setLeft(iconTitleBox);
        titleBar.setRight(buttonHBox);

        // 设置标题栏样式
        //titleBar.setAlignment(Pos.CENTER_LEFT);
        titleBar.setPadding(new Insets(5, 10, 5, 10));
        titleBar.setStyle("-fx-background-color: #333;");

        // 窗口拖动事件处理
        titleBar.setOnMousePressed((MouseEvent event) -> {
            xOffset = event.getSceneX();
            yOffset = event.getSceneY();
        });
        titleBar.setOnMouseDragged((MouseEvent event) -> {
            this.setX(event.getScreenX() - xOffset);
            this.setY(event.getScreenY() - yOffset);
        });

        // 调整按钮的样式和位置
        HBox.setMargin(closeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(maximizeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(minimizeButton, new Insets(0, 0, 0, 5));
        closeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        maximizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        minimizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");

        return titleBar;
    }

    //生成关闭、放大、缩放按钮方法
    private Button createWindowControlButton(Stage primaryStage, String imageUrl, double size) throws FileNotFoundException {
        String path = "F:\\java2Code\\addressBook\\src\\main\\resources\\images\\";
        //自定义Image!为了获取URL的名称
        Image image = new Image(new FileInputStream(path + imageUrl));
        ImageView imageView = new ImageView(image);
        imageView.setFitWidth(size);
        imageView.setFitHeight(size);

        Button button = new Button();
        button.setGraphic(imageView);
        button.getStyleClass().add("window-control-button");

        if (imageUrl.contains("close")) {
            button.setOnAction(event -> primaryStage.close());
        } else if (imageUrl.contains("maximize")) {
            button.setOnAction(event -> {
                System.out.println(!primaryStage.isMaximized());
                if (!primaryStage.isMaximized()) {//非全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize2.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                //点击后发现(如果点击的是属于两个框的话,就切换一个框的图片)
                if (primaryStage.isMaximized()) {//全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize1.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }

                primaryStage.setMaximized(!primaryStage.isMaximized());//切换放大放小
            });
        } else if (imageUrl.contains("minimize")) {
            button.setOnAction(event -> primaryStage.setIconified(true));
        }

        return button;
    }

    private void startTimer(ImageView imageView, Image imageNo, Timer timer) {
        if (timer != null) {
            timer.cancel();//如果有任务的话就终止(防止冲突要重新计算2s的)
        }

        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                imageView.setImage(imageNo);
            }
        }, 2000); // 2秒后恢复原始图片

        //
    }

    //创建虚线gap为虚线大小每一根
    private Line createDashedLine(double length, double gap) {
        Line line = new Line(0, 0, 0, length);
        line.setStroke(Color.WHITE);
        line.setStrokeType(StrokeType.OUTSIDE);
        line.getStrokeDashArray().addAll(gap, gap);
        return line;
    }

    //上下一张图片
    private void showCurrentImage() {
        if (imageFiles != null) {
            if (currentIndex >= 0 && currentIndex < imageFiles.size()) {
                currentFile = imageFiles.get(currentIndex);
                Image image = new Image(currentFile.toURI().toString());
                //刷新文件名 + 更新面板
                this.vBoxImageContent.getChildren().remove(this.hBoxImageName);
                this.hBoxImageName = new HBox();
                createHBoxImageName(currentFile);
                this.vBoxImageContent.getChildren().add(1, this.hBoxImageName);


                this.cuurentImageView.setImage(image);
                this.cuurentImageView.setFitWidth(400);
                this.cuurentImageView.setFitHeight(400);
                this.stackPane.getChildren().clear();
                this.stackPane.getChildren().add(this.cuurentImageView);//重新添加(刷新定位居中)
            }
        }
    }

    private void showPreviousImage() {
        if (currentIndex > 0) {
            currentIndex--;
            showCurrentImage();
        } else if (currentIndex == 0) {
            //循环到最后一个图片
            if (imageFiles != null) {
                currentIndex = imageFiles.size() - 1;
                showCurrentImage();
            }
        }
    }

    private void showNextImage() {
        if (imageFiles != null) {
            if (currentIndex < imageFiles.size() - 1) {
                currentIndex++;
                showCurrentImage();
            } else if (currentIndex == imageFiles.size() - 1) {
                //重新回到第一张
                currentIndex = 0;
                showCurrentImage();
            }
        }
    }

    private void createFile10AndFilesQueue(ArrayList<File> fileArrayList) {//根据传入的文件对象来生成file10的十张图片
        //构建出一个循环队列(或者一个)
        if (fileArrayList == null || fileArrayList.size() == 0) {//没有一张图片
            filesQueue = null;
            this.file10 = null;
        } else if (fileArrayList.size() == 1) {//就一张图片的话
            filesQueue = null;//没有必要构建底部图片列表
            this.file10 = null;
        } else if (fileArrayList.size() == 2) {//重复5次-> 2 * (3+1) =>取出10个
            // 复制原始 ArrayList<File> 到新的 ArrayList<File>
            ArrayList<File> copiedList1 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList2 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList3 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList4 = new ArrayList<>(fileArrayList);
            // 合并四个 ArrayList<File>
            filesQueue = new ArrayList<>();
            filesQueue.addAll(fileArrayList);
            filesQueue.addAll(copiedList1);
            filesQueue.addAll(copiedList2);
            filesQueue.addAll(copiedList3);
            filesQueue.addAll(copiedList4);
            this.file10 = new ArrayList<>(filesQueue);//获取到指定10个
        } else if (fileArrayList.size() == 3) {//3*4 =>取出10个
            // 复制原始 ArrayList<File> 到新的 ArrayList<File>
            ArrayList<File> copiedList1 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList2 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList3 = new ArrayList<>(fileArrayList);
            // 合并四个 ArrayList<File>
            filesQueue = new ArrayList<>();
            filesQueue.addAll(fileArrayList);
            filesQueue.addAll(copiedList1);
            filesQueue.addAll(copiedList2);
            filesQueue.addAll(copiedList3);
            this.file10 = new ArrayList<>(filesQueue.subList(0, 10));//第1个到第10个
        } else if (fileArrayList.size() == 4) {//4*3 =>取出10个
            // 复制原始 ArrayList<File> 到新的 ArrayList<File>
            ArrayList<File> copiedList1 = new ArrayList<>(fileArrayList);
            ArrayList<File> copiedList2 = new ArrayList<>(fileArrayList);
            // 合并四个 ArrayList<File>
            filesQueue = new ArrayList<>();
            filesQueue.addAll(fileArrayList);
            filesQueue.addAll(copiedList1);
            filesQueue.addAll(copiedList2);
            this.file10 = new ArrayList<>(filesQueue.subList(0, 10));//获取到指定8八个
        } else if (fileArrayList.size() == 5
                || fileArrayList.size() == 6
                || fileArrayList.size() == 7
                || fileArrayList.size() == 8
                || fileArrayList.size() == 9) {//5*2 =>取10个 //6*2 =>取10个 //7*2 =>取10个 //8*2 =>取10个 //9*2 =>取10个
            //复制原始 ArrayList<File> 到新的 ArrayList<File>
            ArrayList<File> copiedList1 = new ArrayList<>(fileArrayList);
            // 合并四个 ArrayList<File>
            filesQueue = new ArrayList<>();
            filesQueue.addAll(fileArrayList);
            filesQueue.addAll(copiedList1);
            this.file10 = new ArrayList<>(filesQueue.subList(0, 10));//获取到指定8八个
        } else if (this.imageFiles.size() == 10) {
            filesQueue = new ArrayList<>(fileArrayList);
            this.file10 = new ArrayList<>(fileArrayList);//获取到指定10个
        } else {
            filesQueue = new ArrayList<>(fileArrayList);
            this.file10 = new ArrayList<>(filesQueue.subList(0, 10));//获取到指定8八个
        }
        //filesQueue转成队列
        if (filesQueue != null) {
            queue = new LinkedList<>(filesQueue);
        } else {
            queue = null;
        }
    }

}

11.1.7. 用户中心界面窗口编写

窗口代码

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import org.example.managerImage.controller.UpdatePasswordController;
import org.example.managerImage.controller.UserCenterController;
import org.example.managerImage.entity.UserAccounts;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 张 志 豪
 * @version 1.0
 * 修改密码的窗口
 */
public class UserCenterStage extends Stage {

    //传入userAccountsList用户好友列表,UserAccount当前用户
    public UserCenterStage(List<UserAccounts> userAccountsList,UserAccounts userAccounts) throws IOException {
        URL resource = getClass().getResource("/fxml/userCenter.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        UserCenterController.userAccountsCurrent = userAccounts;
        UserCenterController.userAccountsFriendList = new ArrayList<UserAccounts>(userAccountsList);

        AnchorPane anchorPane = loader.load();
        Scene scene = new Scene(anchorPane);
        this.setScene(scene);
        this.setHeight(680);
        this.setWidth(420);
    }
}

fxml页面编写

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="635.0" prefWidth="378.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="org.example.managerImage.controller.UserCenterController">
    <children>
        <HBox alignment="CENTER" layoutX="10.0" layoutY="159.0" prefHeight="72.0" prefWidth="384.0" spacing="16.0"
              style="-fx-background-radius: 30; -fx-background-color: red;">
            <children>
                <HBox alignment="CENTER" prefHeight="72.0" prefWidth="112.0" spacing="4.0">
                    <children>
                        <ImageView fitHeight="44.0" fitWidth="56.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/grade.png"/>
                            </image>
                        </ImageView>
                        <VBox alignment="CENTER_LEFT" prefHeight="72.0" prefWidth="65.0">
                            <children>
                                <Label text="1">
                                    <font>
                                        <Font size="25.0"/>
                                    </font>
                                </Label>
                                <Label prefHeight="22.0" prefWidth="64.0" text="Grade">
                                    <font>
                                        <Font size="16.0"/>
                                    </font>
                                </Label>
                            </children>
                        </VBox>
                    </children>
                </HBox>
                <HBox alignment="CENTER" prefHeight="72.0" prefWidth="112.0" spacing="4.0">
                    <children>
                        <ImageView fitHeight="44.0" fitWidth="56.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/friend.png"/>
                            </image>
                        </ImageView>
                        <VBox alignment="CENTER_LEFT" prefHeight="72.0" prefWidth="65.0">
                            <children>
                                <Label fx:id="friendNumLabel" text="12">
                                    <font>
                                        <Font size="25.0"/>
                                    </font>
                                </Label>
                                <Label prefHeight="22.0" prefWidth="64.0" text="Friends">
                                    <font>
                                        <Font size="16.0"/>
                                    </font>
                                </Label>
                            </children>
                        </VBox>
                    </children>
                </HBox>
                <HBox alignment="CENTER" prefHeight="72.0" prefWidth="112.0" spacing="4.0">
                    <children>
                        <ImageView fitHeight="44.0" fitWidth="56.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/collect2.png"/>
                            </image>
                        </ImageView>
                        <VBox alignment="CENTER_LEFT" prefHeight="72.0" prefWidth="65.0">
                            <children>
                                <Label text="11">
                                    <font>
                                        <Font size="25.0"/>
                                    </font>
                                </Label>
                                <Label prefHeight="22.0" prefWidth="64.0" text="Collects">
                                    <font>
                                        <Font size="16.0"/>
                                    </font>
                                </Label>
                            </children>
                        </VBox>
                    </children>
                </HBox>
            </children>
        </HBox>
        <VBox layoutY="242.0" prefHeight="393.0" prefWidth="404.0">
            <children>
                <ScrollPane prefHeight="403.0" prefWidth="404.0">
                    <content>
                        <VBox fx:id="friendVbox" prefHeight="390.0" prefWidth="402.0" spacing="5">
                            <children>
                            </children>
                        </VBox>
                    </content>
                </ScrollPane>
            </children>
        </VBox>
        <StackPane prefHeight="146.0" prefWidth="404.0">
            <children>
                <ImageView fitHeight="148.0" fitWidth="404.0">
                    <image>
                        <Image url="@../images/imageManagerLoginBackGroud.jpg"/>
                    </image>
                </ImageView>
<!--                <ImageView fitHeight="32.0" fitWidth="35.0" pickOnBounds="true" preserveRatio="true">-->
<!--                    <image>-->
<!--                        <Image url="@../images/close.png"/>-->
<!--                    </image>-->
<!--                    <StackPane.margin>-->
<!--                        <Insets bottom="100.0" left="350.0"/>-->
<!--                    </StackPane.margin>-->
<!--                </ImageView>-->
                <VBox alignment="CENTER_LEFT" prefHeight="141.0" prefWidth="404.0" spacing="11.0"
                      style="-fx-background-radius: 30;">
                    <children>
                        <HBox prefHeight="100.0" prefWidth="200.0">
                            <children>
                                <ImageView fx:id="currentImageView" fitHeight="96.0" fitWidth="92.0" pickOnBounds="true"
                                           preserveRatio="true">
                                    <image>
                                        <Image url="@../images/icon.png"/>
                                    </image>
                                    <HBox.margin>
                                        <Insets left="7.0"/>
                                    </HBox.margin>
                                </ImageView>
                                <VBox alignment="CENTER_LEFT" prefHeight="120.0" prefWidth="313.0">
                                    <children>
                                        <Label fx:id="currentNameLabel" prefHeight="33.0" prefWidth="115.0"
                                               text="用户名称" textFill="WHITE">
                                            <font>
                                                <Font name="System Bold" size="25.0"/>
                                            </font>
                                        </Label>
                                        <HBox prefHeight="32.0" prefWidth="304.0">
                                            <children>
                                                <Label prefHeight="22.0" prefWidth="67.0" text="小小号:">
                                                    <font>
                                                        <Font size="16.0"/>
                                                    </font>
                                                </Label>
                                                <Label fx:id="currentUserNameLabel" text="1231242115251">
                                                    <font>
                                                        <Font size="16.0"/>
                                                    </font>
                                                    <HBox.margin>
                                                        <Insets left="8.0"/>
                                                    </HBox.margin>
                                                </Label>
                                            </children>
                                        </HBox>
                                    </children>
                                </VBox>
                            </children>
                        </HBox>
                    </children>
                </VBox>
            </children>
        </StackPane>
    </children>
</AnchorPane>

controller(数据和事件)

package org.example.managerImage.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.entity.UserFriends;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;

/**
 * @author 张 志 豪
 * @version 1.0
 * 用户中心界面的Controller
 */
public class UserCenterController implements Initializable {

    public static List<UserAccounts> userAccountsFriendList;//好友列表

    public static UserAccounts userAccountsCurrent;//当前用户

    @FXML
    private Label friendNumLabel;

    @FXML
    private Label currentUserNameLabel;
    @FXML
    private Label currentNameLabel;

    @FXML
    private ImageView currentImageView;

    @FXML
    private VBox friendVbox;//好友列表VBox

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        //初始化当前用户信息
        currentUserNameLabel.setText(userAccountsCurrent.getUsername());
        currentNameLabel.setText(userAccountsCurrent.getName());
        currentImageView.setImage(new Image(userAccountsCurrent.getImgUrl()));
        int size = userAccountsFriendList.size();
        friendNumLabel.setText(size+"");
        for (UserAccounts userAccounts : userAccountsFriendList) {
            String username = userAccounts.getUsername();
            String imgUrl = userAccounts.getImgUrl();
            String name = userAccounts.getName();
            Platform.runLater(()->{
                HBox showUserAccountAgreeInfo = createShowUserAccountAgreeInfo(username, name, imgUrl,userAccounts);
                friendVbox.getChildren().add(showUserAccountAgreeInfo);
            });
        }
    }

    public HBox createShowUserAccountAgreeInfo(String username,String name,String imgUrl,UserAccounts userAccounts){
        HBox hBox = new HBox();
        ImageView imageView = new ImageView(new Image(imgUrl));
        imageView.setFitWidth(80);
        imageView.setFitHeight(80);
        HBox.setMargin(imageView,new Insets(0,0,0,10));

        // Labels
        Label nameLabel = new Label(name);
        nameLabel.setFont(new Font(28));

        Label idLabel = new Label(username);

        Label requestLabel = new Label("好友");
        requestLabel.setTextFill(Color.web("#f28181"));

        // VBox
        VBox vBox = new VBox(nameLabel, idLabel, requestLabel);
        vBox.setAlignment(Pos.CENTER_LEFT);
        vBox.setPrefSize(206, 100);
        HBox.setMargin(vBox, new Insets(0, 0, 0, 2));

        // Button
        Button agreeButton = new Button("查看");
        agreeButton.setPrefWidth(74.0);
        agreeButton.setPrefHeight(32.0);
        agreeButton.setTextFill(Color.WHITE);
        agreeButton.setStyle("-fx-background-color: #00aa63;-fx-border-radius: 10");
        agreeButton.setFont(Font.font("System", FontWeight.BOLD,16));

        agreeButton.setOnAction((event)->agreeFriend(agreeButton,userAccounts));//点击时间

        // Add children to HBox
        hBox.getChildren().addAll(imageView, vBox, agreeButton);
        hBox.setPrefSize(378, 80);
        hBox.setAlignment(Pos.CENTER_LEFT);
        hBox.setStyle("-fx-background-color: #ededed; -fx-background-radius: 20;");

        return hBox;
    }

    //同意成为好友
    public void agreeFriend(Button agreeBut,UserAccounts userAccounts) {
        //关闭浮窗回调函数!
        agreeBut.setText("已同意");
    }

}

11.2.1. 注册界面

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.example.managerImage.LoginApplicationJavaFX;
import org.example.managerImage.SpringBootCommandLineRunnerApplication;
import org.example.managerImage.controller.LoginController;
import org.example.managerImage.controller.RegisterController;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;

/**
 * @author 张 志 豪
 * @version 1.0
 */
public class MyRegisterStage extends Stage {

    private double xOffset = 0;
    private double yOffset = 0;

    public MyRegisterStage(LoginApplicationJavaFX loginApplicationJavaFX) throws IOException {
        // 创建根布局
        AnchorPane root = new AnchorPane();
        root.setStyle("-fx-background-color: #333;");

        // 1创建自定义标题栏(顶部)
        BorderPane titleBar = createTitleBar(this);
        AnchorPane.setTopAnchor(titleBar, 0.0);//这些方法就是设置外边距
        AnchorPane.setLeftAnchor(titleBar, 0.0);
        AnchorPane.setRightAnchor(titleBar, 0.0);

        //中间区域
        // 获取 Maven 项目 resources/fxml/content.fxml 注意,无法识别横向
        URL resource = getClass().getResource("/fxml/register.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        AnchorPane anchorPane = loader.load();
        RegisterController controller = loader.getController();
        controller.setMain(loginApplicationJavaFX);

        AnchorPane.setTopAnchor(anchorPane, 45.0); // Adjust the top position as needed
        AnchorPane.setLeftAnchor(anchorPane, 8.0);
        AnchorPane.setRightAnchor(anchorPane, 8.0);
        AnchorPane.setBottomAnchor(anchorPane, 37.0);

        root.getChildren().addAll(titleBar, anchorPane);

        //Scene scene = new Scene(root, 654, 448);448.0  654.0
        Scene scene = new Scene(root, 670, 520);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add(getClass().getResource("/css/myStyles.css").toExternalForm());

        this.setScene(scene);
        this.initStyle(StageStyle.TRANSPARENT);
        this.show();
        this.getIcons().add(new Image("/images/icon.png"));
    }

    //==============================后面可以不用看 

    //创建标题栏(自定义)
    private BorderPane createTitleBar(Stage primaryStage) throws FileNotFoundException {
        // 创建标题栏
        BorderPane titleBar = new BorderPane();
        titleBar.getStyleClass().add("title-bar");

        // 创建图标
        ImageView iconImageView = new ImageView(new Image(getClass().getResourceAsStream("/images/icon.png")));
        iconImageView.setFitWidth(32);
        iconImageView.setFitHeight(32);

        // 创建标题
        Label titleLabel = new Label("图览之光注册界面");
        titleLabel.setFont(Font.font("楷体", FontWeight.BOLD,18));
        titleLabel.setTextFill(Color.WHITE);
        titleLabel.setPadding(new Insets(0,0,0,6));//设置一定的边距

        // 将图标和标题放在一个水平布局中
        HBox iconTitleBox = new HBox();
        iconTitleBox.setAlignment(Pos.CENTER_LEFT);
        iconTitleBox.getChildren().addAll(iconImageView, titleLabel);

        // 创建按钮
        Button closeButton = createWindowControlButton(primaryStage, "close.png",32 );
        Button maximizeButton = createWindowControlButton(primaryStage, "maximize1.png",32);
        Button minimizeButton = createWindowControlButton(primaryStage, "minimize.png",30);

        //整合三个按钮放在一个水平布局中
        HBox buttonHBox = new HBox(5);
        buttonHBox.getChildren().addAll(minimizeButton, maximizeButton, closeButton);


        // 添加组件到标题栏
        titleBar.setLeft(iconTitleBox);
        titleBar.setRight(buttonHBox);

        // 设置标题栏样式
        //titleBar.setAlignment(Pos.CENTER_LEFT);
        titleBar.setPadding(new Insets(5, 10, 5, 10));
        titleBar.setStyle("-fx-background-color: #333;");

        // 窗口拖动事件处理
        titleBar.setOnMousePressed((MouseEvent event) -> {
            xOffset = event.getSceneX();
            yOffset = event.getSceneY();
        });
        titleBar.setOnMouseDragged((MouseEvent event) -> {
            primaryStage.setX(event.getScreenX() - xOffset);
            primaryStage.setY(event.getScreenY() - yOffset);
        });

        // 调整按钮的样式和位置
        HBox.setMargin(closeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(maximizeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(minimizeButton, new Insets(0, 0, 0, 5));
        closeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        maximizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        minimizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");

        return titleBar;
    }
    //生成关闭、放大、缩放按钮方法
    private Button createWindowControlButton(Stage primaryStage, String imageUrl,double size) throws FileNotFoundException {
        String path = "F:\\java2Code\\addressBook\\src\\main\\resources\\images\\";
        //自定义Image!为了获取URL的名称
        Image image = new Image(new FileInputStream(path + imageUrl));
        ImageView imageView = new ImageView(image);
        imageView.setFitWidth(size);
        imageView.setFitHeight(size);

        Button button = new Button();
        button.setGraphic(imageView);
        button.getStyleClass().add("window-control-button");

        if (imageUrl.contains("close")) {
            button.setOnAction(event -> {
                //窗口,springboot,application程序
                primaryStage.close();
                SpringBootCommandLineRunnerApplication.shoutDown();
                LoginApplicationJavaFX.stopApplication();
            });
        } else if (imageUrl.contains("maximize")) {
            button.setOnAction(event -> {
                System.out.println(!primaryStage.isMaximized());
                if (!primaryStage.isMaximized()) {//非全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize2.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                //点击后发现(如果点击的是属于两个框的话,就切换一个框的图片)
                if (primaryStage.isMaximized()) {//全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize1.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }

                primaryStage.setMaximized(!primaryStage.isMaximized());//切换放大放小
            });
        } else if (imageUrl.contains("minimize")) {
            button.setOnAction(event -> primaryStage.setIconified(true));
        }

        return button;
    }

}

注册窗口对应的fxml文件(界面绘制)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.image.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane fx:id="registerRoot" prefHeight="448.0" prefWidth="654.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.managerImage.controller.RegisterController">
   <children>
      <StackPane layoutY="-3.0" prefHeight="428.0" prefWidth="654.0">
         <children>
            <ImageView fitHeight="457.0" fitWidth="654.0">
               <image>
                  <Image url="@../images/imageManagerLoginBackGroud.jpg" />
               </image>
            </ImageView>
            <AnchorPane prefHeight="440.0" prefWidth="599.0">
               <children>
                  <Label layoutX="156.0" layoutY="185.0" text="账户:">
                     <font>
                        <Font size="24.0" />
                     </font>
                  </Label>
                  <TextField fx:id="usernameTextFiled" layoutX="230.0" layoutY="185.0" prefHeight="32.0" prefWidth="260.0" promptText="请输入账户" />
                  <PasswordField fx:id="passwordTextFiled" layoutX="230.0" layoutY="237.0" prefHeight="32.0" prefWidth="260.0" promptText="请输入密码 包含大小写字母+数字" />
                  <Label layoutX="156.0" layoutY="237.0" text="密码:">
                     <font>
                        <Font size="24.0" />
                     </font>
                  </Label>

                  <Button layoutX="259.0" layoutY="350.0" mnemonicParsing="false" onAction="#register" prefHeight="54.0" prefWidth="137.0" text="注册" style="-fx-background-color: #00aa63;-fx-border-radius: 10" textFill="WHITE">
                     <font>
                        <Font name="System Bold Italic" size="23.0" />
                     </font>
                  </Button>


                  <Label fx:id="confirmUserNameLabel" layoutX="425.0" layoutY="217.0" prefHeight="20.0" prefWidth="150.0" textFill="RED" />
                  <Label fx:id="confirmPasswordLabel" layoutX="425.0" layoutY="269.0" prefHeight="20.0" prefWidth="150.0" textFill="RED" />
                  <Label layoutX="108.0" layoutY="289.0" text="确认密码:">
                     <font>
                        <Font size="24.0" />
                     </font>
                  </Label>
                  <PasswordField fx:id="passwordTextFiledConfirm" layoutX="230.0" layoutY="289.0" prefHeight="32.0" prefWidth="260.0" promptText="请输入密码 包含大小写字母+数字" />
                  <Label layoutX="456.0" layoutY="383.0" text="返回" onMouseClicked="#showLogin" />
                  <Line endX="-6.0" layoutX="495.0" layoutY="403.0" startX="-40.0" />

                  <StackPane layoutX="259.0" layoutY="14.0" maxHeight="150.0" maxWidth="150.0" prefHeight="145.0" prefWidth="137.0">
                     <children>
                        <Button maxHeight="150.0" maxWidth="150.0" mnemonicParsing="false" onMouseClicked="#upIconToOOS" style="-fx-background-color: white; /* 设置背景颜色#007bff */-fx-text-fill: white; /* 设置文字颜色 */-fx-background-radius: 75; /* 设置圆角半径为按钮宽度的一半,实现圆形效果 */-fx-min-width: 150px; /* 设置按钮最小宽度 */                                               -fx-min-height: 150px; /* 设置按钮最小高度,保证按钮是一个正方形 */" />
                        <ImageView onMouseClicked="#upIconToOOS" fx:id="lodeImageView" fitHeight="143.0" fitWidth="132.0" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../images/upLoderImage.png" />
                           </image>
                        </ImageView>
                     </children>
                  </StackPane>
                  <Label fx:id="confirmNewPasswordLabel" layoutX="425.0" layoutY="321.0" prefHeight="20.0" prefWidth="150.0" textFill="RED" />
               </children>
            </AnchorPane>
         </children>
      </StackPane>
   </children>
</AnchorPane>

界面对应的controller

package org.example.managerImage.controller;

import de.felixroske.jfxsupport.FXMLController;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.example.managerImage.LoginApplicationJavaFX;
import org.example.managerImage.SpringBootCommandLineRunnerApplication;
import org.example.managerImage.component.pojo.CustomMultipartFile;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.mapper.UserAccountsMapper;
import org.example.managerImage.service.FileUploadService;
import org.example.managerImage.service.UserAccountsService;
import org.example.managerImage.service.UserFriendsService;
import org.example.managerImage.service.impl.FileUploadServiceImpl;
import org.example.managerImage.service.impl.UserAccountsServiceImpl;
import org.example.managerImage.util.MD5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 张 志 豪
 * @version 1.0
 * 注册Controller
 */

public class RegisterController implements Initializable {

    private LoginApplicationJavaFX loginApplicationJavaFX;

    public void setMain(LoginApplicationJavaFX main) {
        this.loginApplicationJavaFX = main;
    }

    private UserAccountsMapper userAccountsMapper;

    private FileUploadServiceImpl fileUploadServiceImpl;//注入OOS操作类

    private Stage primaryStage;

    private File selectedFile;

    private boolean isA0 = false;//是否大小写+字母的密码
    private boolean isA0Confirm = false;//是否大小写+字母的密码

    private String defaultHeadUrl = "https://imagemanagerzkc.oss-cn-beijing.aliyuncs.com/headIcon/97527e350dbe45b5b5c7fdce2acbb691defaultHead.png";

    public void setPrimaryStage(Stage primaryStage) {
        this.primaryStage = primaryStage;
    }


    //属性
    @FXML
    private TextField usernameTextFiled;

    @FXML
    private PasswordField passwordTextFiled;
    @FXML
    private PasswordField passwordTextFiledConfirm;

    @FXML
    private Label confirmUserNameLabel;

    @FXML
    private Label confirmPasswordLabel;

    @FXML
    private Label confirmNewPasswordLabel;

    @FXML
    private ImageView lodeImageView;//加载头像图片

    @Override
    public void initialize(URL location, ResourceBundle resources) {
//        ApplicationContext context = SpringBootCommandLineRunnerApplication.context;
//        fileUploadServiceImpl = context.getBean(FileUploadServiceImpl.class);
//        userAccountsMapper = context.getBean(UserAccountsMapper.class);
        fileUploadServiceImpl = LoginApplicationJavaFX.context.getBean(FileUploadServiceImpl.class);
        userAccountsMapper = LoginApplicationJavaFX.context.getBean(UserAccountsMapper.class);
        //监听输入框的内容(失去焦点时)
        usernameTextFiled.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) { // 失去焦点时
                String inputText = usernameTextFiled.getText();
                // 在这里进行输入内容的判断和处理
                if (inputText.isEmpty()) {
                    confirmUserNameLabel.setText("内容不能为空");
                } else {
                    confirmUserNameLabel.setText("");
                }
            }
        });

        passwordTextFiled.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) { // 失去焦点时
                String inputText = passwordTextFiled.getText();
                // 在这里进行输入内容的判断和处理
                if (inputText.isEmpty()) {
                    confirmPasswordLabel.setText("内容不能为空");
                } else if (isA0(inputText)) {
                    //包含大小写和数字
                    confirmPasswordLabel.setText("");
                    isA0 = true;
                } else {
                    confirmPasswordLabel.setText("需要大小写字母和数字");
                    isA0 = false;
                }
            }
        });

        passwordTextFiledConfirm.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) { // 失去焦点时
                String inputText = passwordTextFiledConfirm.getText();
                // 在这里进行输入内容的判断和处理
                if (inputText.isEmpty()) {
                    confirmNewPasswordLabel.setText("内容不能为空");
                } else if (isA0(inputText)) {
                    //包含大小写和数字
                    confirmNewPasswordLabel.setText("");
                    isA0Confirm = true;
                } else {
                    confirmNewPasswordLabel.setText("需要大小写字母和数字");
                    isA0Confirm = false;
                }
            }
        });
    }

    //判断字符串是否有大小写和数字
    public boolean isA0(String input) {
        // 使用正则表达式进行匹配
        if (input.matches(".*[a-z].*") && input.matches(".*[A-Z].*") && input.matches(".*\\d.*")) {
            return true;
        } else {
            return false;
        }
    }

    //判断目录是否有中文
    public boolean containsChinese(String str) {
        Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]");
        Matcher matcher = pattern.matcher(str);
        return matcher.find();
    }


    //注册按钮逻辑实现(不需要看 ) =》zzh实现 
    @FXML
    public void register() throws Exception {
        //进行一个校验
        String password = passwordTextFiled.getText();
        String passwordConfirm = passwordTextFiledConfirm.getText();
        String username = usernameTextFiled.getText();
        if (username.equals("") || password.equals("") || passwordConfirm.equals("")){
            //有没填写完整
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("注册失败,请将内容填写完整");
            alert.setTitle("注册");
            alert.show();
            return;
        }

        //密码是否相同
        if (password.equals(passwordConfirm)) {
            //相同则进行一个数据库保存信息
            UserAccounts userAccounts = new UserAccounts();
            userAccounts.setUsername(username);
            //密码加密
            String encryptPassword = MD5.encrypt(passwordTextFiled.getText());
            userAccounts.setPassword(encryptPassword);

            //判断是否上传的文件
            if (selectedFile == null) {
                //说明没有上传文件头像 使用默认头像
                userAccounts.setImgUrl(defaultHeadUrl);
            } else {
                MultipartFile customMultipartFile = new CustomMultipartFile(selectedFile);
                if (fileUploadServiceImpl != null) {
                    String url = fileUploadServiceImpl.fileUpload(customMultipartFile);
                    userAccounts.setImgUrl(url);
                }
            }

            //最后操作数据库
            int insert = userAccountsMapper.insert(userAccounts);
            //注册成功后提示注册成功并回到登录界面同时清空数据
            if (insert==1){
                confirmUserNameLabel.setText("");
                confirmPasswordLabel.setText("");
                confirmNewPasswordLabel.setText("");
                usernameTextFiled.setText("");
                passwordTextFiled.setText("");
                passwordTextFiledConfirm.setText("");
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setContentText("注册成功");
                alert.setTitle("注册");
                Optional<ButtonType> result = alert.showAndWait();
                if (result.isPresent() && result.get() == ButtonType.OK) {
                    // 在这里添加您想要执行的相关逻辑=>切换回登录界面
                    loginApplicationJavaFX.showLogin();
                } else {
                    // 用户点击了取消或关闭按钮的逻辑
                    System.out.println("用户取消或关闭了对话框");
                }
            }

        } else if (isA0 && isA0Confirm) {//输入都符合要求了
            //提示两个密码不一致
            confirmPasswordLabel.setText("密码不一致");
            confirmNewPasswordLabel.setText("密码不一致");
            //弹框
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setContentText("密码不一致");
            alert.show();
            return;
        } else {//输入都不符合要求
            confirmPasswordLabel.setText("需要大小写字母和数字");
            confirmNewPasswordLabel.setText("需要大小写字母和数字");
            //弹框
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setContentText("需要大小写字母和数字");
            alert.show();
            return;
        }

    }

    @FXML
    public void upIconToOOS() {
        //点击头像位置去打开本地选择文件的文件框 
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("选择文件");
        selectedFile = fileChooser.showOpenDialog(primaryStage);

        if (selectedFile != null) {
            System.out.println("已选择文件: " + selectedFile.getAbsolutePath());
            //判断是否有中文目录
            String absolutePath1 = selectedFile.getAbsolutePath();
            if (containsChinese(absolutePath1)) {
                //包含就提示(包含中文目录!!)
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setContentText("不能包含中文目录!请重新选择");
                alert.show();
                selectedFile = null;//置空
                return;
            }

            // 清除原有内容
            lodeImageView.setImage(null);

            // 在这里可以处理选定的文件,比如上传等操作
            try {
                String absolutePath = selectedFile.getAbsolutePath();
                Image image = new Image("file:" + absolutePath);
                lodeImageView.setImage(image);
                lodeImageView.setPreserveRatio(true);//保持缩放比例
                lodeImageView.setFitHeight(120);
                lodeImageView.setFitWidth(120);
            } catch (Exception e) {
                System.out.println("加载图片时出现异常:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    @FXML
    public void showLogin(){
        //回到登录界面
        loginApplicationJavaFX.showLogin();
    }
}

11.2.2. 主界面分享窗口

就是好友分享图片给你,你可以看到哪个好友分享了什么图片的窗口

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import org.example.managerImage.controller.UserCenterController;
import org.example.managerImage.controller.UserToMeShareImageCenterController;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.entity.UserToFriendsImg;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author 张 志 豪
 * @version 1.0
 * 修改密码的窗口
 */
public class UserToMeShareImageCenterStage extends Stage {

    public UserToMeShareImageCenterStage(Map<UserAccounts, List<UserToFriendsImg>> userAccountsToShareImgsMap) throws IOException {
        URL resource = getClass().getResource("/fxml/userToMeShareImageCenter.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        UserToMeShareImageCenterController.userAccountsToShareImgsMap = userAccountsToShareImgsMap;
        AnchorPane anchorPane = loader.load();
        Scene scene = new Scene(anchorPane);
        this.setScene(scene);
        this.sizeToScene();
    }
}

fxml文件界面编写

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Line?>

<!-- 获取好友分享图片的窗口! -->

<AnchorPane prefHeight="649.0" prefWidth="981.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.managerImage.controller.UserToMeShareImageCenterController">
    <children>
        <Label layoutX="387.0" layoutY="18.0" text="好友分享图片中心">
            <font>
                <Font name="System Bold" size="26.0" />
            </font>
        </Label>

        <Line endX="981.0" endY="80.0" layoutY="-9.0" startY="80.0" stroke="#dadce0" strokeType="OUTSIDE" />
        <ScrollPane hbarPolicy="NEVER" layoutX="13.0" layoutY="87.0" prefHeight="496.0" prefWidth="955.0" style="-fx-background-color: white;">
            <content>
                <VBox fx:id="userToMeVBox" prefHeight="493.0" prefWidth="955.0" spacing="18.0" style="-fx-background-color: white;">
                    <children>
                        <!--这里由controller来根据传入不同的数据动态去创建javafx组件来显示内容-->
                    </children>
                </VBox>
            </content>
        </ScrollPane>
      <Button layoutX="441.0" layoutY="598.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="100.0" style="-fx-background-color: black;" text="下 载" textFill="WHITE">
         <font>
            <Font name="System Bold" size="19.0" />
         </font>
      </Button>
    </children>
</AnchorPane>

controller(界面的属性绑定+数据展示)

package org.example.managerImage.controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.entity.UserToFriendsImg;

import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author 张 志 豪
 * @version 1.0
 */
public class UserToMeShareImageCenterController implements Initializable {

    @FXML
    private VBox userToMeVBox;

    private String[] minShareTimeSplit;
    private String[] maxShareTimeSplit;

    //存储哪个好友分享什么图片的Map集合
    public static Map<UserAccounts, List<UserToFriendsImg>> userAccountsToShareImgsMap;

    private boolean isCtrlDown;//是否按下ctrl(以后添加新功能)



    @Override
    public void initialize(URL location, ResourceBundle resources) {
        if (userAccountsToShareImgsMap != null && userAccountsToShareImgsMap.size() != 0) {
            Set<UserAccounts> userAccounts = userAccountsToShareImgsMap.keySet();
            for (UserAccounts userAccount : userAccounts) {
                List<UserToFriendsImg> userToFriendsImgList = userAccountsToShareImgsMap.get(userAccount);
                HBox oneFriendShareImageHBox = createOneFriendShareImageHBox(userAccount, userToFriendsImgList);
                userToMeVBox.getChildren().addAll(oneFriendShareImageHBox);
            }
        } else {
            userToMeVBox.setAlignment(Pos.CENTER);
            Label label = new Label("没有好友分享图片给你");
            label.setFont(Font.font("System", FontWeight.BOLD, 28.0));
            label.setTextFill(Color.web("#dadce0"));
            userToMeVBox.getChildren().add(label);
        }

//        for (int i = 0; i < 2; i++) {
//            HBox oneFriendShareImageHBox1 = createOneFriendShareImageHBox1();
//            userToMeVBox.getChildren().add(oneFriendShareImageHBox1);
//        }
    }

    //动态根据传入的数据去创建HBox组件(显示数据)
    private HBox createOneFriendShareImageHBox(UserAccounts userAccounts, List<UserToFriendsImg> userToFriendsImgList) {
        HBox hBox = new HBox(5);
        hBox.setPrefHeight(119);
        hBox.setPrefWidth(955);
        hBox.setAlignment(Pos.CENTER_LEFT);

        //1.用户信息
        HBox hBoxUser = new HBox(7);
        hBoxUser.setAlignment(Pos.CENTER_LEFT);
        hBoxUser.setPrefHeight(119);
        hBoxUser.setPrefWidth(263);
        hBoxUser.setStyle("-fx-background-color: #fdf4e5; -fx-background-radius: 20");
        ImageView imageView = new ImageView(new Image(userAccounts.getImgUrl()));
        imageView.setFitHeight(80);
        imageView.setFitWidth(80);
        imageView.setPreserveRatio(true);
        HBox.setMargin(imageView, new Insets(0, 0, 0, 5));

        VBox vBox = new VBox(6);
        vBox.setAlignment(Pos.CENTER_LEFT);
        vBox.setPrefHeight(119);
        vBox.setPrefWidth(193);
        Label name = new Label(userAccounts.getName());
        name.setFont(Font.font("System", 28));
        Label username = new Label(userAccounts.getUsername());
        username.setFont(Font.font("System", 16));
        vBox.getChildren().addAll(name, username);
        hBoxUser.getChildren().addAll(imageView, vBox);
        HBox.setMargin(hBoxUser, new Insets(0, 0, 0, 5));

        //2.时间(获取最大最小的时间)
        // 获取最小时间
        Optional<LocalDateTime> minShareTime = userToFriendsImgList.stream()
                .map(UserToFriendsImg::getShareTime)
                .min(Comparator.naturalOrder());

        // 获取最大时间
        Optional<LocalDateTime> maxShareTime = userToFriendsImgList.stream()
                .map(UserToFriendsImg::getShareTime)
                .max(Comparator.naturalOrder());

        // 输出结果
        //maxShareTime/minShareTime =>Optional[2024-04-04T00:20:34]
        minShareTime.ifPresent(minTime -> {
            System.out.println("最小时间: " + minTime.toString());
            System.out.println("最小时间1: " + minShareTime.toString());
        });
        maxShareTime.ifPresent(maxTime -> {
            System.out.println("最大时间: " + maxTime.toString());
            System.out.println("最大时间1: " + maxShareTime.toString());
        });
        StringBuilder minShareTimeStringBuilder =  new StringBuilder(minShareTime.toString());
        StringBuilder maxShareTimeStringBuilder =  new StringBuilder( maxShareTime.toString());
        int lengthMin = minShareTimeStringBuilder.length();
        int lengthMax = maxShareTimeStringBuilder.length();
        //删除最后一个]
        minShareTimeStringBuilder.deleteCharAt(lengthMin-1);
        maxShareTimeStringBuilder.deleteCharAt(lengthMax-1);
        //根据[分割
        String stringMin = minShareTimeStringBuilder.toString();
        String stringMax = maxShareTimeStringBuilder.toString();
        String[] splitMin = stringMin.split("\\[");
        String[] splitMax = stringMax.split("\\[");
        //根据T分割年月时分
        minShareTimeSplit = splitMax[1].split("T");
        maxShareTimeSplit = splitMax[1].split("T");


        //开始创建标签
        VBox vBoxTime = new VBox();
        vBoxTime.setAlignment(Pos.CENTER);
        vBoxTime.setPrefHeight(100);
        vBoxTime.setPrefWidth(119.0);//123-41
        vBoxTime.setStyle("-fx-background-color: #fdf4e5; -fx-background-radius: 20");
        Label labelMinYear = new Label(minShareTimeSplit[0]);
        labelMinYear.setFont(Font.font("System",16));
        Label labelMinTime = new Label(minShareTimeSplit[1]);
        labelMinTime.setFont(Font.font("System",16));
        Label label = new Label("|");
        Label labelMaxYear = new Label(maxShareTimeSplit[0]);
        labelMaxYear.setFont(Font.font("System",16));
        Label labelMaxTime = new Label(maxShareTimeSplit[1]);
        labelMaxTime.setFont(Font.font("System",16));
        vBoxTime.getChildren().addAll(labelMinYear,labelMinTime,label,labelMaxYear,labelMaxTime);

        //3."分享"标签
        VBox vboxShare = new VBox();
        vboxShare.setAlignment(javafx.geometry.Pos.CENTER);
        vboxShare.setPrefHeight(119.0);
        vboxShare.setPrefWidth(41);//123-41
        vboxShare.setSpacing(4.0);
        // 创建两个Label,并设置字体样式
        Label label1 = new Label("分");
        label1.setFont(Font.font("System", FontWeight.BOLD, 28.0));
        Label label2 = new Label("享");
        label2.setFont(Font.font("System", FontWeight.BOLD, 28.0));
        // 将Label添加到VBox中
        vboxShare.getChildren().addAll(label1, label2);

        //3.图片列表
        // 创建一个水平布局HBox
        HBox hbox = new HBox();
        hbox.setPrefHeight(100.0);
        hbox.setPrefWidth(559.0);
        hbox.setSpacing(8.0);
        hbox.setStyle("-fx-background-color: #fdf4e4");

        // 添加ImageView到HBox中
        for (UserToFriendsImg userToFriendsImg : userToFriendsImgList) {
            ImageView imageView1 = new ImageView(new Image(userToFriendsImg.getImgUrl()));
            imageView1.setFitHeight(99.0);
            imageView1.setFitWidth(99.0);
            imageView1.setPreserveRatio(true);
            imageView1.setPickOnBounds(true);

            HBox hBox1 = new HBox();
            hBox1.setAlignment(Pos.CENTER);
            hBox1.setPrefHeight(99);
            hBox1.setPrefWidth(99);
            hBox1.getChildren().add(imageView1);
            hBox1.setStyle("-fx-background-color: white");
            hBox1.setUserData(false);//设置为未选中
            hBox1.setOnKeyPressed(keyEvent->{
                if (keyEvent.isControlDown()){//ctrl
                    isCtrlDown = true;
                }else {//没有点击ctrl
                    isCtrlDown = false;
                }
            });
            //鼠标进入
            hBox1.setOnMouseEntered(event -> {
                hBox1.setStyle("-fx-background-color: #fdf4e4");
            });
            //鼠标离开
            hBox1.setOnMouseExited(event -> {
                if (!(boolean)hBox1.getUserData()){//未选中时
                    hBox1.setStyle("-fx-background-color: #ffffff");
                }
            });
            hBox1.setOnMouseClicked(event -> {
                hBox1.setUserData(!(boolean)hBox1.getUserData());//切换
                if (isCtrlDown){
                    //ToDo 有待开发
                }else {
                    if ((boolean)hBox1.getUserData()){//选中则设置选中
                        hBox1.setStyle("-fx-background-color: #fdf4e4");
                        //添加到选中集合中
                    }else {
                        hBox1.setStyle("-fx-background-color: #ffffff");
                        //从选中列表中清除

                    }
                }
            });


            hbox.getChildren().add(hBox1);
        }

        // 创建ScrollPane,并将HBox添加为内容
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setPrefHeight(114.0);
        scrollPane.setPrefWidth(558.0);
        scrollPane.setStyle("-fx-background-color: white; -fx-border-color: white; -fx-border-width: 0;");
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.setContent(hbox);

        hBox.getChildren().addAll(hBoxUser, vBoxTime,vboxShare, scrollPane);
        return hBox;
    }


    private HBox createOneFriendShareImageHBox1() {
        HBox hBox = new HBox(5);
        hBox.setPrefHeight(119);
        hBox.setPrefWidth(955);
        hBox.setAlignment(Pos.CENTER_LEFT);

        //1.用户信息
        HBox hBoxUser = new HBox(7);
        hBoxUser.setAlignment(Pos.CENTER_LEFT);
        hBoxUser.setPrefHeight(119);
        hBoxUser.setPrefWidth(263);
        hBoxUser.setStyle("-fx-background-color: #fdf4e5; -fx-background-radius: 20");
        ImageView imageView = new ImageView(new Image("/images/defaultHead.png"));
        imageView.setFitHeight(80);
        imageView.setFitWidth(80);
        imageView.setPreserveRatio(true);
        HBox.setMargin(imageView, new Insets(0, 0, 0, 5));

        VBox vBox = new VBox(6);
        vBox.setAlignment(Pos.CENTER_LEFT);
        vBox.setPrefHeight(119);
        vBox.setPrefWidth(193);
        Label name = new Label("路过");
        name.setFont(Font.font("System", 28));
        Label username = new Label("131234213123");
        username.setFont(Font.font("System", 16));
        vBox.getChildren().addAll(name, username);
        hBoxUser.getChildren().addAll(imageView, vBox);
        HBox.setMargin(hBoxUser, new Insets(0, 0, 0, 5));

        //2.时间(获取最大最小的时间)
        StringBuilder minShareTimeStringBuilder =  new StringBuilder("Optional[2024-04-04T00:20:34]");
        StringBuilder maxShareTimeStringBuilder =  new StringBuilder("Optional[2024-04-04T00:20:34]");
        int lengthMin = minShareTimeStringBuilder.length();
        int lengthMax = maxShareTimeStringBuilder.length();
        //删除最后一个]
        minShareTimeStringBuilder.deleteCharAt(lengthMin-1);
        maxShareTimeStringBuilder.deleteCharAt(lengthMax-1);
        //根据[分割
        String stringMin = minShareTimeStringBuilder.toString();
        String stringMax = maxShareTimeStringBuilder.toString();
        String[] splitMin = stringMin.split("\\[");
        String[] splitMax = stringMax.split("\\[");
        //根据T分割年月时分
        minShareTimeSplit = splitMax[1].split("T");
        maxShareTimeSplit = splitMax[1].split("T");


        //开始创建标签
        VBox vBoxTime = new VBox();
        vBoxTime.setAlignment(Pos.CENTER);
        vBoxTime.setPrefHeight(100);
        vBoxTime.setPrefWidth(119.0);//123-41
        vBoxTime.setStyle("-fx-background-color: #fdf4e5; -fx-background-radius: 20");
        Label labelMinYear = new Label(minShareTimeSplit[0]);
        labelMinYear.setFont(Font.font("System",16));
        Label labelMinTime = new Label(minShareTimeSplit[1]);
        labelMinTime.setFont(Font.font("System",16));
        Label label = new Label("|");
        Label labelMaxYear = new Label(maxShareTimeSplit[0]);
        labelMaxYear.setFont(Font.font("System",16));
        Label labelMaxTime = new Label(maxShareTimeSplit[1]);
        labelMaxTime.setFont(Font.font("System",16));
        vBoxTime.getChildren().addAll(labelMinYear,labelMinTime,label,labelMaxYear,labelMaxTime);

        //3."分享"标签
        VBox vboxShare = new VBox();
        vboxShare.setAlignment(javafx.geometry.Pos.CENTER);
        vboxShare.setPrefHeight(119.0);
        vboxShare.setPrefWidth(41);//123-41
        vboxShare.setSpacing(4.0);
        // 创建两个Label,并设置字体样式
        Label label1 = new Label("分");
        label1.setFont(Font.font("System", FontWeight.BOLD, 28.0));
        Label label2 = new Label("享");
        label2.setFont(Font.font("System", FontWeight.BOLD, 28.0));
        // 将Label添加到VBox中
        vboxShare.getChildren().addAll(label1, label2);

        //3.图片列表
        // 创建一个水平布局HBox
        HBox hbox = new HBox();
        hbox.setStyle("-fx-background-color: #ffffff");
        hbox.setPrefHeight(100.0);
        hbox.setPrefWidth(559.0);
        hbox.setSpacing(8.0);

        // 添加ImageView到HBox中
        for (int i = 0; i < 7; i++) {

            ImageView imageView1 = new ImageView(new Image("/images/defaultHead.png"));
            imageView1.setFitHeight(80.0);
            imageView1.setFitWidth(80.0);
            imageView1.setPreserveRatio(true);
            imageView1.setPickOnBounds(true);
            HBox hBox1 = new HBox();
            hBox1.setAlignment(Pos.CENTER);
            hBox1.setPrefHeight(99);
            hBox1.setPrefWidth(99);
            hBox1.getChildren().add(imageView1);
            hBox1.setStyle("-fx-background-color: white");
            hBox1.setUserData(false);//设置为未选中
            hBox1.setOnKeyPressed(keyEvent->{
                if (keyEvent.isControlDown()){//ctrl
                    isCtrlDown = true;
                }else {//没有点击ctrl
                    isCtrlDown = false;
                }
            });
            //鼠标进入
            hBox1.setOnMouseEntered(event -> {
                hBox1.setStyle("-fx-background-color: #fdf4e4");
            });
            //鼠标离开
            hBox1.setOnMouseExited(event -> {
                if (!(boolean)hBox1.getUserData()){//未选中时
                    hBox1.setStyle("-fx-background-color: #ffffff");
                }
            });
            hBox1.setOnMouseClicked(event -> {
                hBox1.setUserData(!(boolean)hBox1.getUserData());//切换
                if (isCtrlDown){

                }else {
                    if ((boolean)hBox1.getUserData()){//选中则设置选中
                        hBox1.setStyle("-fx-background-color: #fdf4e4");
                        //添加到选中集合中
                    }else {
                        hBox1.setStyle("-fx-background-color: #ffffff");
                        //从选中列表中清除

                    }
                }
            });


            hbox.getChildren().add(hBox1);
        }

        // 创建ScrollPane,并将HBox添加为内容
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setPrefHeight(114.0);
        scrollPane.setPrefWidth(558.0);
        scrollPane.setStyle("-fx-background-color: white; -fx-border-color: white; -fx-border-width: 0;");
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.setContent(hbox);

        hBox.getChildren().addAll(hBoxUser, vBoxTime,vboxShare, scrollPane);
        return hBox;
    }

}

11.2.3. 主界面搜索框的算法逻辑(模糊搜索)

//searchTextFiled是主界面的收缩框的组件 
//存储当前的展示在浏览区的图片集合(监听他的变化) 
private ListProperty<File> fileObservableList = new SimpleListProperty<>(FXCollections.observableArrayList());//用来监听文件的集合变化


    @FXML
    public void searchImagesNameLike() {
        //首先获取textFiled的内容
        String searchText = searchTextFiled.getText();
        //对这个备份数据进行模糊查询
        if (searchText == null || searchText.equals("")) {
            //如果我们输入的是个空串或者是个null//就不去更新面板//弹出一个提示框,提示要输入
            this.imageFiles = new ArrayList<>(this.imageFilesBackUp);
            this.fileObservableList.clear();
            this.fileObservableList.addAll(this.imageFiles);

            Alert alert = new MyAlert(Alert.AlertType.INFORMATION, "搜索结果", "请您输入想要搜索的图片!");
            alert.showAndWait();
            this.isSearchType = 1;//设置处于非搜索状态
        } else {//如果不是空,就去判断输入的内容进行模糊查询,同时更新面板和更新我们的图片总数
            //根据searchText进行模糊查询文件名//正则匹配
            String pattern = ".*" + searchText + ".*";
            List<File> resultSearchFiles = new ArrayList<>();//存放模糊查询到的文件数据
            if (this.imageFiles != null) {
                for (File imageFile : this.imageFiles) {
                    if (Pattern.matches(pattern, imageFile.getName())) {
                        //匹配成功//添加到里面去
                        resultSearchFiles.add(imageFile);
                    }
                }
            }
            //更新面板
            if (resultSearchFiles.size() == 0) {
                //表示没有找到数据
                this.imageFiles = new ArrayList<>(resultSearchFiles);
                fileObservableList.clear();//监听重新绘制three

                Alert alert = new MyAlert(Alert.AlertType.INFORMATION, "搜索结果", "没有您想要的图片");
                alert.showAndWait();
                this.isSearchType = 1;//设置处于非搜索状态
            } else {
                //表示有数据
                this.imageFiles = new ArrayList<>(resultSearchFiles);
                this.imageFilesBackUpSearch = new ArrayList<>(resultSearchFiles);//备份一个查找的
                fileObservableList.clear();
                fileObservableList.addAll(imageFiles);//监听重新绘制three
                Alert alert = new MyAlert(Alert.AlertType.INFORMATION, "搜索结果", "查询到可能是您想要的图片数:" + resultSearchFiles.size());
                alert.showAndWait();
                this.isSearchType = 2;//设置处于搜索状态
            }
        }

    }

11.2.4. 主题色浮窗

把界面fxml放到ThemeColorPopup

package org.example.managerImage.component.popup;

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Popup;
import org.example.managerImage.controller.InformPopupController;
import org.example.managerImage.entity.UserAccounts;

import java.io.IOException;
import java.net.URL;
import java.util.List;

/**
 * @author 张 志 豪
 * @version 1.0
 * 主题色窗口
 */
public class ThemeColorPopup extends Popup {
    public ThemeColorPopup() {
        URL resource = getClass().getResource("/fxml/themeColor.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        AnchorPane anchorPane = null;
        try {
            anchorPane = loader.load();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        AnchorPane finalAnchorPane = anchorPane;
        this.getContent().add(finalAnchorPane);
    }
}

主题色的页面编写

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.effect.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="402.0" prefWidth="530.0"
            style="-fx-background-color: #dadce0; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 10, 0, 0, 0);"
            xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="org.example.managerImage.controller.ThemeColorController">
    <children>
        <Label layoutX="9.0" layoutY="7.0" prefHeight="35.0" prefWidth="77.0" text="主题色" textAlignment="CENTER"
               textFill="#2b2b2b">
            <font>
                <Font name="System Bold" size="23.0"/>
            </font>
        </Label>
        <FlowPane hgap="22.0" layoutX="3.0" layoutY="51.0" prefHeight="284.0" prefWidth="550.0" vgap="20.0">
            <children>
                <VBox fx:id="themeColor1VBox" onMouseClicked="#themeColor1VBoxCEE" onMouseExited="#themeColor1VBoxCEE"
                      onMouseEntered="#themeColor1VBoxCEE" alignment="CENTER" prefHeight="127.0" prefWidth="160.0"
                      spacing="8.0" style="-fx-background-color: white;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor1.png"/>
                            </image>
                            <VBox.margin>
                                <Insets/>
                            </VBox.margin>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="海岛度假">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                    <FlowPane.margin>
                        <Insets left="8.0"/>
                    </FlowPane.margin>
                </VBox>
                <VBox fx:id="themeColor2VBox" onMouseClicked="#themeColor2VBoxCEE" onMouseExited="#themeColor2VBoxCEE" onMouseEntered="#themeColor2VBoxCEE" alignment="CENTER" prefHeight="120.0" prefWidth="161.0" spacing="8.0"
                      style="-fx-background-color: #fdf4e5;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor2.png"/>
                            </image>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="雨夜">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                </VBox>
                <VBox fx:id="themeColor3VBox" onMouseClicked="#themeColor3VBoxCEE" onMouseExited="#themeColor3VBoxCEE" onMouseEntered="#themeColor3VBoxCEE" alignment="CENTER" prefHeight="120.0" prefWidth="161.0" spacing="8.0"
                      style="-fx-background-color: white;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor3.png"/>
                            </image>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="冷色石板">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                </VBox>
                <VBox fx:id="themeColor4VBox" onMouseClicked="#themeColor4VBoxCEE" onMouseExited="#themeColor4VBoxCEE" onMouseEntered="#themeColor4VBoxCEE" alignment="CENTER" prefHeight="120.0" prefWidth="161.0" spacing="8.0"
                      style="-fx-background-color: white;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor4.png"/>
                            </image>
                            <VBox.margin>
                                <Insets/>
                            </VBox.margin>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="柔和的粉色">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                    <FlowPane.margin>
                        <Insets left="7.0"/>
                    </FlowPane.margin>
                </VBox>
                <VBox fx:id="themeColor5VBox" onMouseClicked="#themeColor5VBoxCEE" onMouseExited="#themeColor5VBoxCEE" onMouseEntered="#themeColor5VBoxCEE" alignment="CENTER" prefHeight="120.0" prefWidth="161.0" spacing="8.0"
                      style="-fx-background-color: white;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor5.png"/>
                            </image>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="晨雾">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                </VBox>
                <VBox fx:id="themeColor6VBox" onMouseClicked="#themeColor6VBoxCEE" onMouseExited="#themeColor6VBoxCEE" onMouseEntered="#themeColor6VBoxCEE" alignment="CENTER" prefHeight="120.0" prefWidth="161.0" spacing="8.0"
                      style="-fx-background-color: white;">
                    <children>
                        <ImageView fitHeight="88.0" fitWidth="140.0" pickOnBounds="true" preserveRatio="true">
                            <image>
                                <Image url="@../images/themeColor6.png"/>
                            </image>
                        </ImageView>
                        <Label style="-fx-background-color: white;" text="辣红色">
                            <font>
                                <Font name="System Bold Italic" size="15.0"/>
                            </font>
                        </Label>
                    </children>
                </VBox>
            </children>
        </FlowPane>
        <Button fx:id="returnHideBut" layoutX="136.0" layoutY="344.0" mnemonicParsing="false"
                onMouseClicked="#returnHidePopup" prefHeight="43.0" prefWidth="93.0"
                style="-fx-background-color: #00aa63;-fx-border-radius: 10" text="返回" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0"/>
            </font>
        </Button>
        <Button onAction="#changeBackground" layoutX="320.0" layoutY="344.0" mnemonicParsing="false" prefHeight="43.0" prefWidth="93.0"
                style="-fx-background-color: #e68f8d;-fx-border-radius: 10" text="确定" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0"/>
            </font>
        </Button>
    </children>
</AnchorPane>

controller(简单就是一个回调函数,当点击一个主题色,确定后,传给主界面一个值(让主界面去修改主题色))

package org.example.managerImage.controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.zip.Inflater;

/**
 * @author 张 志 豪
 * @version 1.0
 * 主题色窗口的方法
 */
public class ThemeColorController implements Initializable {

    //返回按钮
    @FXML
    private Button returnHideBut;

    //确定按钮
    @FXML
    private Button confirmHidBut;


    @FXML
    private VBox themeColor1VBox;
    @FXML
    private VBox themeColor2VBox;
    @FXML
    private VBox themeColor3VBox;
    @FXML
    private VBox themeColor4VBox;
    @FXML
    private VBox themeColor5VBox;
    @FXML
    private VBox themeColor6VBox;

    //定义是否选中的Vbox
    private boolean themeColor1IsSelect;
    private boolean themeColor2IsSelect = true;
    private boolean themeColor3IsSelect;
    private boolean themeColor4IsSelect;
    private boolean themeColor5IsSelect;
    private boolean themeColor6IsSelect;


    //定义6个主题色的web
    private final String themeColor1 = "#73d1d3";
    private final String themeColor2 = "#333333";
    private final String themeColor3 = "#46546a";
    private final String themeColor4 = "#f0e3e1";
    private final String themeColor5 = "#dddfe2";
    private final String themeColor6 = "#a2131d";

    //回调函数给主Custom主界面去实现切换背景色,给主Controller以及隐藏popup
    public static interface CallBackReturnConfirmHide {//这个是去隐藏(返回按钮的)
        void onClickReturnConfirmHide() throws IOException;
    }

    private static CallBackReturnConfirmHide callBackReturnConfirmHide;

    public static void setCallBackReturnConfirmHide(CallBackReturnConfirmHide callback) {
        callBackReturnConfirmHide = callback;
    }

    //回调函数//这个是去修改主题色的!(配合确定按钮使用的)
    public static interface CallBackColorChange {
        void onClickChange(String backgroundColor) throws IOException;
    }

    private static CallBackColorChange callBackColorChange;

    public static void setCallBackColorChange(CallBackColorChange callback) {
        callBackColorChange = callback;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

    }

    //取消按钮,隐藏我们的主题色窗口的回调函数(主界面实现这个方法去调用hide()最终实现隐藏 ) 
    @FXML
    public void returnHidePopup(){
        if (callBackReturnConfirmHide!=null){
            try {
                callBackReturnConfirmHide.onClickReturnConfirmHide();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //确定按钮(判断选中那个主题色!)
    @FXML
    public void changeBackground(){
        if (themeColor1IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor1);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else if (themeColor2IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor2);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else if (themeColor3IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor3);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else if (themeColor4IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor4);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else if (themeColor5IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor5);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else if (themeColor6IsSelect){
            if (callBackColorChange!=null){
                try {
                    callBackColorChange.onClickChange(themeColor6);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }


        //同时隐藏
        if (callBackReturnConfirmHide!=null){
            try {
                callBackReturnConfirmHide.onClickReturnConfirmHide();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    @FXML
    public void themeColor1VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor1IsSelect=true;
            themeColor6IsSelect=false;
            themeColor2IsSelect=false;
            themeColor3IsSelect=false;
            themeColor4IsSelect=false;
            themeColor5IsSelect=false;
            themeColor1VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor1VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor1IsSelect){
                themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }

    }

    @FXML
    public void themeColor2VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor2IsSelect=true;
            themeColor1IsSelect=false;
            themeColor6IsSelect=false;
            themeColor3IsSelect=false;
            themeColor4IsSelect=false;
            themeColor5IsSelect=false;
            themeColor2VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor2VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor2IsSelect){
                themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }
    }


    @FXML
    public void themeColor3VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor3IsSelect=true;
            themeColor6IsSelect=false;
            themeColor1IsSelect=false;
            themeColor2IsSelect=false;
            themeColor4IsSelect=false;
            themeColor5IsSelect=false;
            themeColor3VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor3VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor3IsSelect){
                themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }
    }


    @FXML
    public void themeColor4VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor4IsSelect=true;
            themeColor6IsSelect=false;
            themeColor1IsSelect=false;
            themeColor2IsSelect=false;
            themeColor3IsSelect=false;
            themeColor5IsSelect=false;
            themeColor4VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor4VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor4IsSelect){
                themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }
    }


    @FXML
    public void themeColor5VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor5IsSelect=true;
            themeColor6IsSelect=false;
            themeColor1IsSelect=false;
            themeColor2IsSelect=false;
            themeColor3IsSelect=false;
            themeColor4IsSelect=false;
            themeColor5VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor5VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor5IsSelect){
                themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }
    }


    @FXML
    public void themeColor6VBoxCEE(MouseEvent event) {
        if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {//点击
            themeColor6IsSelect=true;
            themeColor1IsSelect=false;
            themeColor2IsSelect=false;
            themeColor3IsSelect=false;
            themeColor4IsSelect=false;
            themeColor5IsSelect=false;
            themeColor6VBox.setStyle("-fx-background-color: #fdf4e5;");
            themeColor5VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor4VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor3VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor2VBox.setStyle("-fx-background-color: #ffffff;");
            themeColor1VBox.setStyle("-fx-background-color: #ffffff;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {//进入
            themeColor6VBox.setStyle("-fx-background-color: #fdf4e5;");
        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {//离开
            if (!themeColor6IsSelect){
                themeColor6VBox.setStyle("-fx-background-color: #ffffff;");
            }
        }
    }
}

11.2.5. 主界面图片的排序方法

private SimpleBooleanProperty isSort = new SimpleBooleanProperty(false);//用来监听我们的排序按钮以及我们的排序类型选择的监听中间值,一旦有一个发生改变就修改这个值就能实现监听变化,然后去重新绘制

private boolean isUpSort = true;//是否升降序(默认升序+文件名排序)
private boolean isDownSort = false;

//监听进行排序(点击不同排序按钮就会修改排序isSort的值,就能时刻监听到要排序)
isSort.addListener((observable, oldValue, newValue) -> {
    sort();
});


public void sort() {
        //获取升降序 + 排序类型
        if (isUpSort) {
            //升序
            System.out.println("升序");
            //进行排序
            sortByUp(sortTypeName);//升序 + 名称/大小/时间
        } else if (isDownSort) {
            System.out.println("降序");
            //进行排序
            sortByDown(sortTypeName);//降序 + 名称/大小/时间
        } else {
            //不升序也不降序
            System.out.println("不升不降(原来的)");
            //进行排序
            sortByReturn(isSearchType);//不排序
        }
}

//==========================具体实现
    //two升序和降序的方法+恢复不升不降
    //不升不降

    /**
     * @param type type是判断是搜索后去取消排序
     *             还是不处于搜索状态下去取消排序
     */
    private void sortByReturn(Integer type) {
        //恢复之前的状态
        //更新我们的图片集合监听重新绘制
        if (this.imageFiles != null && this.imageFiles.size() != 0) {
            if (type == 1) {//非搜索下
                this.imageFiles = new ArrayList<>(imageFilesBackUp);
                fileObservableList.clear();
                fileObservableList.addAll(imageFiles);
            } else {
                this.imageFiles = new ArrayList<>(imageFilesBackUpSearch);
                fileObservableList.clear();
                fileObservableList.addAll(imageFiles);
            }
        }
    }

    //降序 + 名称/大小/时间
    private void sortByDown(String typeSortBy) {
        if (this.imageFiles != null && this.imageFiles.size() != 0) {
            if (typeSortBy.equals("名称")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {
                        return file2.getName().compareTo(file1.getName());
                    }
                });
            } else if (typeSortBy.equals("大小")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {
                        return Long.compare(file2.length(), file1.length());
                    }
                });
            } else if (typeSortBy.equals("时间")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {
                        return Long.compare(file2.lastModified(), file1.lastModified());
                    }
                });
            }else if (typeSortBy.equals("文件类型")){
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {//根据文件类型
                        //.jpg   .png   ....
                        String name1 = file1.getName();
                        String[] split1 = name1.split("\\.");
                        String s1 = split1[1];

                        String name2 = file2.getName();
                        String[] split2 = name2.split("\\.");
                        String s2 = split2[1];
                        return s2.compareTo(s1);
                    }
                });
            }

            //更新我们的图片集合监听重新绘制
            fileObservableList.clear();
            fileObservableList.addAll(imageFiles);
        }
    }

    //升序 + 名称/大小/时间
    private void sortByUp(String typeSortBy) {
        if (this.imageFiles != null && this.imageFiles.size() != 0) {
            if (typeSortBy.equals("名称")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {
                        return file1.getName().compareTo(file2.getName());
                    }
                });
            } else if (typeSortBy.equals("大小")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {
                        return Long.compare(file1.length(), file2.length());
                    }
                });
            } else if (typeSortBy.equals("时间")) {
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {//修改时间来排序
                        return Long.compare(file1.lastModified(), file2.lastModified());
                    }
                });
            }else if (typeSortBy.equals("文件类型")){
                Collections.sort(this.imageFiles, new Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2) {//根据文件类型
                        //.jpg   .png   ....
                        String name1 = file1.getName();
                        String[] split1 = name1.split("\\.");
                        String s1 = split1[1];

                        String name2 = file2.getName();
                        String[] split2 = name2.split("\\.");
                        String s2 = split2[1];
                        return s1.compareTo(s2);
                    }
                });
            }

            //更新我们的图片集合监听重新绘制
            fileObservableList.clear();
            fileObservableList.addAll(imageFiles);
        }
    }

11.2.6. 自动播放图片窗口功能

按下自动播放后可以去进行自动播放并且切换(按倍速)

package org.example.managerImage.mystage;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author 张 志 豪
 * @version 1.0
 */
public class MyAutoPlayStage extends Stage {
    private double xOffset = 0;
    private double yOffset = 0;

    private HBox hBoxImageView;//轮流播放的图片的盒子

    private HBox hBoxImageName;//图片的名称 + 大小

    private ImageView cuurentImageView;//当前播放图片

    private HBox hBoxButton;//存放组件的(按钮组件)

    private Button startButton;//开始按钮
    private Button stopButton;//暂停按钮

    private HBox hBoxBottomSpeed;//存放组件的(倍速选择)

    private ImageView oneSpeedImageView;//当前播放图片速度(1s切换)

    private ImageView twoSpeedImageView;//当前播放图片速度(2s切换)

    private ImageView fourSpeedImageView;//当前播放图片速度(4s切换)

    private VBox vBoxImageContent;//整合上面三个部分的地方;

    private List<File> imageFiles;
    private Timeline timeline;//时间播放
    private int currentIndex = 0;//当前播放图片的位置
    private boolean isPlaying = false;

    private Integer speed = 1;//播放速度(s)


    //创建图片浏览区
    private void createHBoxImageViewContent() {
        if(this.imageFiles == null){
            //如果文件没有,就提示没有图片
            Label label = new Label("没 有 图 片 !");
            //加粗字体
            label.setTextFill(Color.WHITE);//白色字体(粗体)
            label.setFont(Font.font("楷体",FontWeight.BOLD,50));
            this.hBoxImageView.setPrefWidth(1500);
            this.hBoxImageView.setPrefHeight(670);
            this.hBoxImageView.setAlignment(Pos.CENTER);
            this.hBoxImageView.getChildren().add(label);
        }else {
            File file = this.imageFiles.get(0);
            //创建文件名的HBox
            this.hBoxImageName = new HBox();
            createHBoxImageName(file);
            //第一张图片区域
            this.cuurentImageView = new ImageView(new Image(file.toURI().toString()));
            this.cuurentImageView.setFitWidth(500);
            this.cuurentImageView.setFitHeight(500);
            this.hBoxImageView.setPrefWidth(1500);
            this.hBoxImageView.setPrefHeight(670);
            this.hBoxImageView.setAlignment(Pos.CENTER);
            this.hBoxImageView.getChildren().add(cuurentImageView);
        }
    }

    //创建图片文件名 + 大小
    private void createHBoxImageName(File file) {
        if (file != null){
            String imageName = file.getName();//图片名
            long fileSize = file.length();//图片大小
            Label labelNameSize = new Label( imageName + " ( " + fileSize + " bytes )");
            labelNameSize.setTextFill(Color.WHITE);//白色字体(粗体)
            labelNameSize.setFont(Font.font("楷体",FontWeight.BOLD,22));
            this.hBoxImageName.getChildren().add(labelNameSize);
            this.hBoxImageName.setAlignment(Pos.CENTER);
        }
        this.hBoxImageName.setPrefHeight(35);
        this.hBoxImageName.setPrefWidth(1500);
    }

    private void createHBoxBottomSpeed(){
        //初始化
        Image oneSpeedNo = new Image("/images/oneSpeed.png");
        Image oneSpeedSelect = new Image("/images/oneSpeedSelect.png");
        Image twoSpeedNo = new Image("/images/twoSpeed.png");
        Image twoSpeedSelect = new Image("/images/twoSpeedSelect.png");
        Image fourSpeedNo = new Image("/images/fourSpeed.png");
        Image fourSpeedSelect = new Image("/images/fourSpeedSelect.png");

        this.oneSpeedImageView = new ImageView(oneSpeedSelect);
        this.oneSpeedImageView.setUserData(1);//1倍速
        this.oneSpeedImageView.setFitWidth(50);
        this.oneSpeedImageView.setFitHeight(50);
        HBox oneSpeedImageViewhBox = new HBox(oneSpeedImageView);
        oneSpeedImageViewhBox.setAlignment(Pos.CENTER);

        this.twoSpeedImageView = new ImageView(twoSpeedNo);
        this.twoSpeedImageView.setUserData(2);//1倍速
        this.twoSpeedImageView.setFitWidth(50);
        this.twoSpeedImageView.setFitHeight(50);
        HBox twoSpeedImageViewhBox = new HBox(twoSpeedImageView);
        twoSpeedImageViewhBox.setAlignment(Pos.CENTER);

        this.fourSpeedImageView = new ImageView(fourSpeedNo);
        this.fourSpeedImageView.setUserData(4);//1倍速
        this.fourSpeedImageView.setFitWidth(50);
        this.fourSpeedImageView.setFitHeight(50);
        HBox fourSpeedImageViewhBox = new HBox(fourSpeedImageView);
        fourSpeedImageViewhBox.setAlignment(Pos.CENTER);

        //监听事件
        oneSpeedImageViewhBox.setOnMouseClicked(e->{
            if(this.speed != 1){//不是1倍速(所以可以点击)
                this.oneSpeedImageView.setImage(oneSpeedSelect);//选中
                this.twoSpeedImageView.setImage(twoSpeedNo);//不选中
                this.fourSpeedImageView.setImage(fourSpeedNo);//不选中
                this.speed = 1;//更新
            }
        });

        twoSpeedImageViewhBox.setOnMouseClicked(e->{
            if(this.speed != 2){//不是2倍速(所以可以点击)
                this.twoSpeedImageView.setImage(twoSpeedSelect);//选中
                this.oneSpeedImageView.setImage(oneSpeedNo);//不选中
                this.fourSpeedImageView.setImage(fourSpeedNo);//不选中
                this.speed = 2;//更新
            }
        });

        fourSpeedImageViewhBox.setOnMouseClicked(e->{
            if(this.speed != 4){//不是1倍速(所以可以点击)
                this.fourSpeedImageView.setImage(fourSpeedSelect);//选中
                this.twoSpeedImageView.setImage(twoSpeedNo);//不选中
                this.oneSpeedImageView.setImage(oneSpeedNo);//不选中
                this.speed = 4;//更新
            }
        });


        //底部
        this.hBoxBottomSpeed.setPrefHeight(100);
        this.hBoxBottomSpeed.getChildren().addAll(oneSpeedImageViewhBox,twoSpeedImageViewhBox,fourSpeedImageViewhBox);
        this.hBoxBottomSpeed.setAlignment(Pos.CENTER);
        this.vBoxImageContent.setMargin(this.hBoxBottomSpeed,new Insets(10,0,0,0));

    }

    //创建按钮控件的布局
    private void createHBoxButton(){

        //组装
        this.startButton = new Button("开始播放");
        this.stopButton = new Button("停止播放");

        // 设置按钮的大小
        this.startButton.setPrefSize(180, 80);
        // 设置按钮的背景色和字体颜色 // 设置按钮的边角为圆润(一起写不然会被覆盖)// 设置按钮的字体
        this.startButton.setStyle("-fx-background-color: black; -fx-text-fill: white;" +
                "-fx-background-radius: 10;");

        this.startButton.setFont(Font.font("楷体", FontWeight.BOLD,25));
        this.stopButton.setFont(Font.font("楷体", FontWeight.BOLD,25));

        // 设置按钮的大小
        this.stopButton.setPrefSize(180, 80);
        // 设置按钮的背景色和字体颜色 // 设置按钮的边角为圆润(一起写不然会被覆盖)// 设置按钮的字体
        this.stopButton.setStyle("-fx-background-color: black; -fx-text-fill: white;" +
                "-fx-background-radius: 10;");

        //组合
        hBoxButton.getChildren().addAll(startButton,stopButton);

        //添加点击事件
        startButton.setOnAction(e->{
            if(!isPlaying){
                //开始播放
                startCarousel();
            }
        });

        stopButton.setOnAction(e->{
            if(isPlaying){
                //停止播放
                stopCarousel();
            }
        });


        hBoxButton.setAlignment(Pos.CENTER);
    }

//这里不用看其实(就是去修饰一下标题栏 ) 
    private BorderPane createTitleBar() throws FileNotFoundException {
        // 创建标题栏
        BorderPane titleBar = new BorderPane();
        titleBar.getStyleClass().add("title-bar");

        // 创建图标
        ImageView iconImageView = new ImageView(new Image(getClass().getResourceAsStream("/images/icon.png")));
        iconImageView.setFitWidth(32);
        iconImageView.setFitHeight(32);

        // 创建标题
        Label titleLabel = new Label("图览之光");
        titleLabel.setFont(Font.font("楷体", FontWeight.BOLD,18));
        titleLabel.setTextFill(Color.WHITE);
        titleLabel.setPadding(new Insets(0,0,0,6));//设置一定的边距

        // 将图标和标题放在一个水平布局中
        HBox iconTitleBox = new HBox();
        iconTitleBox.setAlignment(Pos.CENTER_LEFT);
        iconTitleBox.getChildren().addAll(iconImageView, titleLabel);

        // 创建按钮
        Button closeButton = createWindowControlButton(this, "close.png",32 );
        Button maximizeButton = createWindowControlButton(this, "maximize1.png",32);
        Button minimizeButton = createWindowControlButton(this, "minimize.png",30);

        //整合三个按钮放在一个水平布局中
        HBox buttonHBox = new HBox(5);
        buttonHBox.getChildren().addAll(minimizeButton, maximizeButton, closeButton);


        // 添加组件到标题栏
        titleBar.setLeft(iconTitleBox);
        titleBar.setRight(buttonHBox);

        // 设置标题栏样式
        //titleBar.setAlignment(Pos.CENTER_LEFT);
        titleBar.setPadding(new Insets(5, 10, 5, 10));
        titleBar.setStyle("-fx-background-color: #333;");

        // 窗口拖动事件处理
        titleBar.setOnMousePressed((MouseEvent event) -> {
            xOffset = event.getSceneX();
            yOffset = event.getSceneY();
        });
        titleBar.setOnMouseDragged((MouseEvent event) -> {
            this.setX(event.getScreenX() - xOffset);
            this.setY(event.getScreenY() - yOffset);
        });

        // 调整按钮的样式和位置
        HBox.setMargin(closeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(maximizeButton, new Insets(0, 0, 0, 5));
        HBox.setMargin(minimizeButton, new Insets(0, 0, 0, 5));
        closeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        maximizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");
        minimizeButton.setStyle("-fx-font-family: 'Segoe MDL2 Assets'; -fx-font-size: 12pt; -fx-text-fill: #fff; -fx-cursor: hand; -fx-padding: 0; -fx-background-radius: 0; -fx-background-color: transparent;");

        return titleBar;
    }
    //生成关闭、放大、缩放按钮方法
    private Button createWindowControlButton(Stage primaryStage, String imageUrl,double size) throws FileNotFoundException {
        String path = "F:\\java2Code\\addressBook\\src\\main\\resources\\images\\";
        //自定义Image!为了获取URL的名称
        Image image = new Image(new FileInputStream(path + imageUrl));
        ImageView imageView = new ImageView(image);
        imageView.setFitWidth(size);
        imageView.setFitHeight(size);

        Button button = new Button();
        button.setGraphic(imageView);
        button.getStyleClass().add("window-control-button");

        if (imageUrl.contains("close")) {
            button.setOnAction(event -> {
                primaryStage.close();
                //停止播放
                stopCarousel();
            });
        } else if (imageUrl.contains("maximize")) {
            button.setOnAction(event -> {
                System.out.println(!primaryStage.isMaximized());
                if (!primaryStage.isMaximized()) {//非全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize2.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                //点击后发现(如果点击的是属于两个框的话,就切换一个框的图片)
                if (primaryStage.isMaximized()) {//全屏的话
                    //点击后发现(如果点击的是属于一个框的话,就切换两个框的图片)
                    try {
                        imageView.setImage(new Image(new FileInputStream(path + "maximize1.png")));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }

                primaryStage.setMaximized(!primaryStage.isMaximized());//切换放大放小
            });
        } else if (imageUrl.contains("minimize")) {
            button.setOnAction(event -> primaryStage.setIconified(true));
        }

        return button;
    }

    private void startTimer(ImageView imageView,Image imageNo,Timer timer) {
        if (timer != null) {
            timer.cancel();//如果有任务的话就终止(防止冲突要重新计算2s的)
        }

        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                imageView.setImage(imageNo);
            }
        }, 2000); // 2秒后恢复原始图片

        //
    }

    //开始播放的方法
    private void startCarousel() {
        timeline = new Timeline(new KeyFrame(Duration.seconds(this.speed), e -> {
            if(imageFiles != null){
                if (currentIndex < imageFiles.size()) {
                    File file = imageFiles.get(currentIndex);
                    Image image = new Image(file.toURI().toString());
                    this.cuurentImageView.setImage(image);

                    //刷新文件名 + 更新面板
                    this.vBoxImageContent.getChildren().remove(this.hBoxImageName);
                    this.hBoxImageName = new HBox();
                    createHBoxImageName(file);
                    this.vBoxImageContent.getChildren().add(1,this.hBoxImageName);
                    this.vBoxImageContent.setMargin(hBoxImageName,new Insets(5,0,25,0));

                    currentIndex++;
                } else {
                    currentIndex = 0;
                }
            }
        }));
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
        isPlaying = true;
    }

    //停止播放的方法
    private void stopCarousel() {
        if (timeline != null) {
            timeline.stop();
            isPlaying = false;
        }
    }
}

11.2.7. 头像点击显示相关浮窗操作

package org.example.managerImage.component.popup;

import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;

import java.io.IOException;

/**
 * @author 张 志 豪
 * @version 1.0
 * 弹窗(鼠标右键)头像点击后显示相关操作
 */
public class UserInformMenuPopup extends ContextMenu {

    // 回调接口
    public interface CallbackUserInformByHead {
        //获取选中的内容
        void onClickGetText(String typeText) throws IOException;
    }

    private CallbackUserInformByHead callbackUserInformByHead;

    public void setCallbackUserInformByHead(CallbackUserInformByHead callback) {
        this.callbackUserInformByHead = callback;
    }

    public UserInformMenuPopup(){
        //创建菜单项
        MenuItem menuItemUserCenter = new MenuItem("用户中心");

        Menu menuSelectLanguage = new Menu("选择语言");
        MenuItem chineseItem = new MenuItem("中文Chinese");
        MenuItem englishItem = new MenuItem("英语English");
        menuSelectLanguage.getItems().addAll(chineseItem,englishItem);

        MenuItem menuItemReHeadIcon = new MenuItem("修改头像");//#d4e2ff
        MenuItem menuItemPassword = new MenuItem("密码与安全");
        MenuItem menuItemAddFriend = new MenuItem("添加好友");
        MenuItem menuItemReturnLogin = new MenuItem("退出登录");
        //监听选中
        menuItemUserCenter.setOnAction(event -> handleMenuItemSelected(menuItemUserCenter));
        chineseItem.setOnAction(event -> handleMenuItemSelected(chineseItem));
        englishItem.setOnAction(event -> handleMenuItemSelected(englishItem));
        menuItemReHeadIcon.setOnAction(event -> handleMenuItemSelected(menuItemReHeadIcon));
        menuItemPassword.setOnAction(event -> handleMenuItemSelected(menuItemPassword));
        menuItemAddFriend.setOnAction(event -> handleMenuItemSelected(menuItemAddFriend));
        menuItemReturnLogin.setOnAction(event -> handleMenuItemSelected(menuItemReturnLogin));

        this.setAnchorLocation(ContextMenu.AnchorLocation.CONTENT_TOP_LEFT);
        // 设置菜单的样式//背景色,边角圆润
        this.setStyle("-fx-background-color: #ffffff;-fx-background-radius: 5px;");
        this.getItems().addAll(menuItemUserCenter,menuSelectLanguage,menuItemReHeadIcon,menuItemPassword,menuItemAddFriend,menuItemReturnLogin);
    }

    //选中的回调函数
    private void handleMenuItemSelected(MenuItem menuItem) {
        String selectedContent = menuItem.getText();
        if (callbackUserInformByHead!=null){
            try {
                callbackUserInformByHead.onClickGetText(selectedContent);//调用回调函数
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

主界面controller去实现这个选择框的回调函数的伪代码

//这个方法在初始化直接调用
//当我们点击一个选项后,就会触发这个回调函数的方法,可以获取到要选中的操作
//userInformMenuPopup.setCallbackUserInformByHead((selectText)=>{})!!!

    //头像回调函数
    public void headIconMenuCallBack() {
        if (userInformMenuPopup != null) {
            userInformMenuPopup.setCallbackUserInformByHead((selectText) -> {
                System.out.println("选中操作 " + selectText);
                //逻辑判断
                if (selectText.equals("用户中心")) {
                    //创建一个弹窗显示个人信息(数据库实现)
                    userCenterStage.show();
                } else if (selectText.equals("中文Chinese")) {
                    //整个页面的都用中文Chinese显示
                    
                } else if (selectText.equals("英语English")) {
                    //整个页面的都用英语English显示
                    
                } else if (selectText.equals("修改头像")) {
                    //创建窗口 
                    
                } else if (selectText.equals("密码与安全")) {
                    //创建窗口 
                    
                } else if(selectText.equals("添加好友")){
                    //创建窗口 
                    
                }else if (selectText.equals("退出登录")) {
                }
            });
        }
    }

下面就是修改头像,密码安全(修改密码)的窗口编写!

11.2.8. 修改头像,密码安全(修改密码)的窗口

11.2.8.1. 修改头像

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import org.example.managerImage.controller.UpdateHeadController;
import org.example.managerImage.entity.UserAccounts;

import java.io.IOException;
import java.net.URL;

/**
 * @author 张 志 豪
 * @version 1.0
 * 修改密码的窗口
 */
public class UpdateHeadStage extends Stage {

    private UserAccounts userAccounts;//保存用户的信息,以便修改密码的时候好判断

    public UpdateHeadStage(UserAccounts userAccounts) throws IOException {
        this.userAccounts = userAccounts;
        URL resource = getClass().getResource("/fxml/updateHead.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        UpdateHeadController.setUserAccounts(this.userAccounts);
        UpdateHeadController.setPrimaryStage(this);
        AnchorPane anchorPane = loader.load();
        Scene scene = new Scene(anchorPane);
        this.setScene(scene);
        this.setHeight(437);
        this.setWidth(732);
    }

}

fxml页面绘制

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.shape.*?>
<?import javafx.scene.effect.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="402.0" prefWidth="530.0" style="-fx-background-color: #dadce0; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 10, 0, 0, 0);" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.managerImage.controller.UpdateHeadController">
    <children>
        <Label layoutX="9.0" layoutY="7.0" prefHeight="35.0" prefWidth="104.0" text="设置头像" textAlignment="CENTER" textFill="#2b2b2b">
            <font>
                <Font name="System Bold" size="23.0" />
            </font>
        </Label>
        <Button layoutX="199.0" layoutY="328.0" mnemonicParsing="false" onAction="#openLoadImage" prefHeight="43.0" prefWidth="93.0" style="-fx-background-color: #00aa63;-fx-border-radius: 10" text="上传" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0" />
            </font>
        </Button>
        <Button layoutX="438.0" layoutY="328.0" mnemonicParsing="false" onAction="#saveHead" prefHeight="43.0" prefWidth="93.0" style="-fx-background-color: #e68f8d;-fx-border-radius: 10" text="修改" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0" />
            </font>
        </Button>
      <Label layoutX="14.0" layoutY="48.0" prefHeight="20.0" prefWidth="700.0" text="如果您还没有设置自己的头像,系统会显示为默认头像,您需要自己上传一张新照片来作为自己的个人头像" />
      <Label layoutX="14.0" layoutY="72.0" text="点击上传按钮进行设置自己的头像,点击修改头像按钮保存当前头像,即可更换头像" />
      <HBox alignment="CENTER" layoutX="244.0" layoutY="112.0" prefHeight="199.0" prefWidth="240.0">
         <children>
            <ImageView fx:id="headImageView" fitHeight="199.0" fitWidth="242.0" pickOnBounds="true" preserveRatio="true">
               <image>
                  <Image url="@../images/defaultHead.png" />
               </image>
            </ImageView>
         </children>
      </HBox>
      <Label layoutX="14.0" layoutY="99.0" text="!!!注意上传的文件最好不带中文目录!!!" />
    </children>
</AnchorPane>

对应controller类

具体实现zzh来(用到了云存储) 
11.2.8.2. 密码安全

!!点击验证码可以切换不同的验证码的逻辑代码实现!!

package org.example.managerImage.mystage;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import org.example.managerImage.controller.UpdatePasswordController;
import org.example.managerImage.entity.UserAccounts;

import java.io.IOException;
import java.net.URL;

/**
 * @author 张 志 豪
 * @version 1.0
 * 修改密码的窗口
 */
public class UpdatePasswordStage extends Stage {

    private UserAccounts userAccounts;//保存用户的信息,以便修改密码的时候好判断

    public UpdatePasswordStage(UserAccounts userAccounts) throws IOException {
        this.userAccounts = userAccounts;
        URL resource = getClass().getResource("/fxml/updatePassword.fxml");
        if (resource == null) {
            throw new RuntimeException("未找到fxml资源");
        }
        // 此时需要注意, fxml里最外层标签是 AnchorPane 故使用AnchorPane对象获取变量
        FXMLLoader loader = new FXMLLoader(resource);
        AnchorPane anchorPane = loader.load();
        UpdatePasswordController controller = loader.getController();
        controller.setUserAccounts(this.userAccounts);
        Scene scene = new Scene(anchorPane);
        this.setScene(scene);
        this.setHeight(385);
        this.setWidth(499);
    }
}

页面fxml的编写

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.effect.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="357.0" prefWidth="499.0" style="-fx-background-color: #dadce0;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.managerImage.controller.UpdatePasswordController">
    <children>
        <Label layoutX="9.0" layoutY="7.0" prefHeight="35.0" prefWidth="104.0" text="修改密码" textAlignment="CENTER" textFill="#2b2b2b">
            <font>
                <Font name="System Bold" size="23.0" />
            </font>
        </Label>
        <Button onAction="#returnHide"  layoutX="113.0" layoutY="280.0" mnemonicParsing="false" prefHeight="43.0" prefWidth="93.0" style="-fx-background-color: #00aa63;-fx-border-radius: 10" text="返回" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0" />
            </font>
        </Button>
        <Button layoutX="293.0" layoutY="280.0" mnemonicParsing="false" onAction="#confirmUpdate" prefHeight="43.0" prefWidth="93.0" style="-fx-background-color: #e68f8d;-fx-border-radius: 10" text="保存更改" textFill="WHITE">
            <font>
                <Font name="System Bold" size="16.0" />
            </font>
        </Button>
      <Label layoutX="113.0" layoutY="65.0" text="原密码">
         <font>
            <Font name="System Bold Italic" size="19.0" />
         </font>
      </Label>
      <Label layoutX="113.0" layoutY="111.0" text="新密码">
         <font>
            <Font name="System Bold Italic" size="19.0" />
         </font>
      </Label>
      <Label layoutX="75.0" layoutY="161.0" text="确认新密码">
         <font>
            <Font name="System Bold Italic" size="19.0" />
         </font>
      </Label>
      <Label layoutX="113.0" layoutY="213.0" text="验证码">
         <font>
            <Font name="System Bold Italic" size="19.0" />
         </font>
      </Label>
      <Line endX="130.0" layoutX="281.0" layoutY="95.0" startX="-101.0" stroke="WHITE" strokeWidth="2.0" />
      <Line endX="130.0" layoutX="281.0" layoutY="140.0" startX="-100.0" stroke="WHITE" strokeWidth="2.0" />
      <Line endX="130.0" layoutX="281.0" layoutY="190.0" startX="-100.0" stroke="WHITE" strokeWidth="2.0" />
      <Line endX="-25.0" layoutX="281.0" layoutY="240.0" startX="-100.0" stroke="WHITE" strokeWidth="2.0" />
      <Canvas fx:id="testCodeCanvas" height="43.0" layoutX="266.0" layoutY="198.0" onMouseClicked="#flushTestCode" width="146.0" />
      <TextField fx:id="oldPasswordTextFiled" layoutX="180.0" layoutY="62.0" prefHeight="30.0" prefWidth="233.0" promptText="大小字母数字" style="-fx-border-width: 0;" />
      <TextField fx:id="testCodeTextFiled" layoutX="180.0" layoutY="207.0" prefHeight="30.0" prefWidth="77.0" style="-fx-border-width: 0;" />
      <TextField fx:id="newPasswordTextFiled" layoutX="180.0" layoutY="107.0" prefHeight="30.0" prefWidth="233.0" promptText="大小写字母数字" style="-fx-border-width: 0;" />
      <TextField fx:id="newConfirmPasswordTextFiled" layoutX="180.0" layoutY="157.0" prefHeight="30.0" prefWidth="233.0" promptText="大小写字母数字" style="-fx-border-width: 0;" />
    </children>
</AnchorPane>

controller的实现

重点验证码:

package org.example.managerImage.controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import org.example.managerImage.SpringBootCommandLineRunnerApplication;
import org.example.managerImage.entity.UserAccounts;
import org.example.managerImage.mapper.UserAccountsMapper;
import org.example.managerImage.util.MD5;

import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.Random;
import java.util.ResourceBundle;

/**
 * @author 张 志 豪
 * @version 1.0
 * 更改密码界面
 */
public class UpdatePasswordController implements Initializable {
    //用户表操作
    private UserAccountsMapper userAccountsMapper;

    //保存账户信息
    private UserAccounts userAccounts;

    public void setUserAccounts(UserAccounts userAccounts) {
        this.userAccounts = userAccounts;
    }

    private Stage primaryStage;

    public void setStage(Stage stage) {
        this.primaryStage = stage;
    }

    @FXML
    private Canvas testCodeCanvas;//验证码
    private GraphicsContext gc2D;//绘制笔

    @FXML
    private TextField oldPasswordTextFiled;

    @FXML
    private TextField newPasswordTextFiled;


    @FXML
    private TextField newConfirmPasswordTextFiled;

    @FXML
    private TextField testCodeTextFiled;


    // 创建一个随机数生成器
    private Random random = new Random(); // 创建一个随机数生成器

    //记录当前验证码内容
    private String currentTestCode;

    // 回调接口 总数和总大小
    public static interface CallBackUpdatePasswordHide {
        //隐藏同时更新线程变量user
        void onClickUpdatePasswordAccount(UserAccounts userAccounts) throws IOException;
    }

    private static CallBackUpdatePasswordHide callBackUpdatePasswordHide;

    public static void setCallBackUpdatePasswordHide(CallBackUpdatePasswordHide callback) {
        callBackUpdatePasswordHide = callback;
    }


    @Override
    public void initialize(URL location, ResourceBundle resources) {
        userAccountsMapper = SpringBootCommandLineRunnerApplication.context.getBean(UserAccountsMapper.class);


        //先绘制一个验证码
        gc2D = testCodeCanvas.getGraphicsContext2D();
        //绘制 清空 背景色 内容
        gc2D.clearRect(0, 0, testCodeCanvas.getWidth(), testCodeCanvas.getHeight());
        generateCaptcha();

    }

    //刷新验证码
    @FXML
    public void flushTestCode(){//点击刷新
        gc2D.clearRect(0, 0, testCodeCanvas.getWidth(), testCodeCanvas.getHeight());
        //绘制
        generateCaptcha();
    }

    //绘制验证码(!!!!)
    private void generateCaptcha() {
        // 创建线性渐变
        Color randomColor1 = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
        Color randomColor2 = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
        Color randomColor3 = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
        gc2D.setFill(new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,
                new Stop(0, randomColor1), new Stop(0.5, randomColor2),new Stop(1, randomColor2)));
        gc2D.fillRect(0, 0, testCodeCanvas.getWidth(), testCodeCanvas.getHeight());
        // Generate and draw a new captcha
        currentTestCode = generateRandomCaptcha();//随机获取数字字母串 (绘制数字字母)
        gc2D.setFill(Color.WHITE);
        gc2D.setFont(Font.font("Arial", 20)); // Set font size to 20
        gc2D.fillText(currentTestCode, 30, 30);
        //绘制斜线
        for (int i = 0; i < 4; i++) {
            double startX = random.nextDouble() * testCodeCanvas.getWidth();
            double startY = random.nextDouble() * testCodeCanvas.getHeight();
            double endX = random.nextDouble() * testCodeCanvas.getWidth();
            double endY = random.nextDouble() * testCodeCanvas.getHeight();
            double angle = random.nextDouble() * 360; // 随机角度
            gc2D.strokeLine(startX, startY, endX, endY);
        }
    }

    //随机获取数字字母串
    private String generateRandomCaptcha() {
        // 可选的字母和数字
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 5; i++) {
            // 从字符集合中随机选择一个字符
            int randomIndex = random.nextInt(characters.length());
            char randomChar = characters.charAt(randomIndex);
            sb.append(randomChar);
        }
        return sb.toString();

    }

    //返回方法(主界面实现该方法来实现窗口隐藏)
    @FXML
    public void returnHide(){
        //隐藏修改
        if (callBackUpdatePasswordHide!=null){
            try {
                callBackUpdatePasswordHide.onClickUpdatePasswordAccount(null);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    //保存修改密码的方法(zzh实现,你不用看)
    @FXML
    public void confirmUpdate(){
        //判断密码是否错误(原密码)
        if (!MD5.encrypt(oldPasswordTextFiled.getText()).equals(userAccounts.getPassword())){
            //如果不相等就退出
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("原密码不正确!");
            Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
            stage.setX(830); // 设置横坐标
            stage.setY(37); // 设置纵坐标
            alert.show();

            oldPasswordTextFiled.setText("");
            newPasswordTextFiled.setText("");
            newConfirmPasswordTextFiled.setText("");
            testCodeTextFiled.setText("");

            return;
        }

        //判断密码输入是否正确
        if (!newPasswordTextFiled.getText().equals(newConfirmPasswordTextFiled.getText())){
            //如果不相等就退出
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("新密码和确认密码不一致!");
            Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
            stage.setX(830); // 设置横坐标
            stage.setY(37); // 设置纵坐标
            alert.show();

            oldPasswordTextFiled.setText("");
            newPasswordTextFiled.setText("");
            newConfirmPasswordTextFiled.setText("");
            testCodeTextFiled.setText("");

            return;
        }

        //判断验证码
        if (!testCodeTextFiled.getText().equals(currentTestCode)){
            //如果不相等就退出
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("验证码不正确!");
            Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
            stage.setX(830); // 设置横坐标
            stage.setY(37); // 设置纵坐标
            alert.show();

            oldPasswordTextFiled.setText("");
            newPasswordTextFiled.setText("");
            newConfirmPasswordTextFiled.setText("");
            testCodeTextFiled.setText("");
            flushTestCode();//刷新新的验证码
            return;
        }

        //正确就更改数据库
        userAccounts.setPassword(MD5.encrypt(newPasswordTextFiled.getText()));
        int i = userAccountsMapper.updateById(userAccounts);
        if (i!=0){
            //隐藏修改
            if (callBackUpdatePasswordHide!=null){
                try {
                    callBackUpdatePasswordHide.onClickUpdatePasswordAccount(userAccounts);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            oldPasswordTextFiled.setText("");
            newPasswordTextFiled.setText("");
            newConfirmPasswordTextFiled.setText("");
            testCodeTextFiled.setText("");
            //成功
            //如果不相等就退出
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("修改密码成功!");
            Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
            stage.setX(830); // 设置横坐标
            stage.setY(37); // 设置纵坐标
            alert.showAndWait();

        }

    }

}

11.3.1. 服务端发送请求+接收消息

try {
    while (true) {
        // 信息的格式:(add||remove||chat),收件人,...,收件人,发件人,信息体
        //不断地读取客户端发过来的信息
        String msg = user.getBr().readLine();
        System.out.println(msg);
        String[] str = msg.split(",");
        int i=str.length;
        System.out.println(i);
        //转发消息
        switch (str[0]) {
            case "remove":
                remove(user);// 移除用户,此处仅仅只是从list中移除用户
                break;
            case "chat":
                // 转发信息给特定的用户,先单发     或群发
                //比如 chat,原用户,目的用户,消息内容
                fromAccount = str[1];
                toAccount = str[2];
                msg =  new String(str[1] +"说:"+ str[3]);
                sendToClient(str[2], msg);//目的用户去用socket接受数据
                break;
            case "add":
                //比如 add,原用户,目的用户,添加好友
                //有人发送了添加好友消息
                //判断是否好友是否离线
                fromAccount = new String(str[1]);
                toAccount = new String(str[2]);
                boolean isLine = false;//判断是否在线
                for (SocketUser socketUser : list) {
                    if (socketUser.getAccount().equals(toAccount)){
                        //如果相等(说明在线)=>直接转发消息  add,原,想要认识您,目的,添加好友
                        msg =  new String("add" + "," + fromAccount +"," +"想要认识您" + "," + toAccount + "," + str[3]);
                        sendToClient(toAccount, msg);//目的用户去用socket接受数据
                        isLine = true;
                        break;//退出循环
                    }
                }
                //无论离线还是在线都需要去进行插入数据库(userId-》friendId + isAgree=0=>同意后就需要去修改同意
                //想我们userFriend插入数据
                //获取对应用户的id
                //异步操作
                addAgreeNoFriend();
                break;
            case "share":
                //分享图片 服务端 格式:share,时间,usernameCurrent,selectUserName1+selectUserName2+....,imageUrl1+imageUrl2+..
                fromAccount = str[1];
                toAccountLists = str[2];//对这个字符串进行分割获取多个用户
                imagUrls = new String(str[3]);
                touserNameList = new ArrayList<>();
                System.out.println(toAccountLists);
                String[] split1 = toAccountLists.split("\\+");//可能是单个用户注意区分(如果是单个他还是会形成一个的数据的数组!)
                for (String toUserName : split1) {
                    touserNameList.add(toUserName);
                }
    
                //遍历查询该用户是否在线
                for (SocketUser socketUser : list) {
                    for (String userName : touserNameList) {
                        if(socketUser.getAccount().equals(userName)){//在线
                            //发送消息(将imageUrl1+imageUrl2+...=str[3]发给目的用户)
                            msg =  new String("share"+ "," + LocalDateTime.now() +"," + fromAccount + "," +"分享图片给你" + "," + str[3]);
                            sendToClient(socketUser.getAccount(),msg);//发送给指定用户
                            break;
                        }
                    }
                }
                //重新遍历(不在线的!)进行一个保存到数据库中,到时登录查看是否有数据(异步插入)
                shareImageToUserAsync(touserNameList);
                break;
    
            case "agree":
                //同意添加好友
                //接收到的信息 agree,目的,原,同意
                msg = new String("agree" + "," + str[1] + "," + str[3] + "," + str[2]); //封装msg = agree,目的用户,同意,你                        //遍历查询该用户是否在线
                for (SocketUser socketUser : list) {//原来的如果在线的话,就发信息让他去更新好友列表,离线就不用管,因为登录后他会自己去查询一遍的
                    if(socketUser.getAccount().equals(str[2])){//在线
                        sendToClient(str[2],msg);
                        break;
                    }
                }
                break;
            default:
                break;
        }
    }

11.3.2. redis缓存

当有一个目录是有图片,就缓存到redis中,然后如果存在就给这个目录加分,让他能显示得更靠前

if (exists){
    //存在
    // 获取指定 value 的分数
    Double score = jedis.zscore(IMAGEFILESPATHKEY, path);
    if (score != null){
        //说明以前访问过  分数+1

        jedis.zincrby(IMAGEFILESPATHKEY, 1, path);
        System.out.println("Incremented score for " + path);
    }else {
        //key存在但value不存在
        jedis.zadd(IMAGEFILESPATHKEY,1.0,path);
    }
}else {
    //不存在(创建并添加)
    jedis.zadd(IMAGEFILESPATHKEY,1.0,path);
}
if (imageFiles != null && imageFiles.size() != 0) {//有图片
    //有图片=》就说明就去添加目录记录
    JedisUtil.addScoreAndAddPath(trimPath);
}

11.3.3. 数据库相关操作

用springboot-mybatis-plus搭建三层架构来直接操作,springboot启动如何搭配javafx

public static ApplicationContext context;利用静态的这个context(容器实现获取Bean对象)

package org.example.managerImage;

import de.felixroske.jfxsupport.AbstractJavaFxApplicationSupport;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.example.managerImage.mystage.CustomWindow;
import org.example.managerImage.mystage.MyLoginStage;
import org.example.managerImage.mystage.MyRegisterStage;
import org.example.managerImage.view.LoginView;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author 张 志 豪
 * @version 1.0
 */
@SpringBootApplication
@EnableTransactionManagement//开启事务管理
@EnableAsync//开启异步线程管理
public class LoginApplicationJavaFX extends AbstractJavaFxApplicationSupport {
    public static ApplicationContext context;

    private static String[] args;
    private MyLoginStage loginStage;
    private MyRegisterStage registerStage;
    private CustomWindow customWindow;


    public static void main(String[] args) {
        //SpringApplication.run(ProductApplication.class, args);
        //多个舞台并存
        launch(args);
    }
        @Override
    public void init() throws Exception {
//        context = SpringApplication.run(SpringBootCommandLineRunnerApplication.class);
        context = SpringApplication.run(LoginApplicationJavaFX.class);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        loginStage = new MyLoginStage(this);
        registerStage = new MyRegisterStage(this);
        showLogin();
    }
}
//获取Bean对象
fileUploadServiceImpl = LoginApplicationJavaFX.context.getBean(FileUploadServiceImpl.class);
userAccountsMapper = LoginApplicationJavaFX.context.getBean(UserAccountsMapper.class);

11.3.4. OSS操作

编写了一个方法,直接调用,返回一个生成可访问的url地址的图片

12. 配置阿里云存储OSS

application.yml配置相关信息,实现我们的信息配置,好友分享图片,以及头像都用阿里云存储,返回url随时访问

13. 配置阿里云服务器

修改application.yml文件进行配置,保证我们的程序可以在任何电脑使用

14. 打包

14.1. 配置插件

    <!--支持打包插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

             <!--javaFx的相关插件-->
            <plugin>
                <groupId>com.zenjava</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>8.8.3</version>
                <configuration>
                    <!-- 启动类 -->
                    <mainClass>org.example.managerImage.LoginApplicationJavaFX1</mainClass>
                    <!-- 公司名称 -->
                    <vendor>ZKC合作组</vendor>
                    <!-- 应用名称 ${project.build.finalName} = ${project.artifactId}-${project.version} -->
                    <appName>${project.build.finalName}</appName>
                    <!-- 发行版本 -->
                    <nativeReleaseVersion>${project.version}</nativeReleaseVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>

maven打包

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值