# File Distribution System **Repository Path**: aitenry/File-Distribution-System ## Basic Information - **Project Name**: File Distribution System - **Description**: 基于JavaFX开发的文件分发系统【桌面版】,架构:C/S、技术应用:IO、多线程 - **Primary Language**: Java - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-10-18 - **Last Updated**: 2023-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: Javafx, Java, CSS ## README # 文件分发系统【Windows系统】 ## 1. 技术选型 应用选型: - 计算机语言:Java【jdk-11.0.12】 - 数据库:MySQL【8.0以上】 - 图形界面:JavaFX【javafx-sdk-11.0.2】 需求选型: - 架构:C/S - 技术应用:IO、多线程、JDBC【mysql-jdbc-8.0.22】、网络层 ------ ## 2. 功能需求 功能包括: - 普通用户登录功能,查看服务器文件功能,下载服务器文件功能,退出功能; - 服务器管理员的登录功能,用户信息的管理功能、文件的下载功能、公共文件上传、退出功能。 ------ ### 2.1 登录界面 ![](markdown-image/登录界面.png) ------ ### 2.2 用户页面 ![输入图片说明](markdown-image/%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2.png) 上传文件窗口要显示上传按键、当前文件的信息、以及文件进度条,具体实现步骤: - 文件信息的传输要用Server/Client,客户端接收用户提交的文件信息,即文件名,文件大小,文件类型; - 客户端接收用户提交的文件数据,并处理数据,提供给图形界面;服务端接收客户端请求,客户端把当前文件的信息,即文件名,文件大小,文件类型,按批次给服务端发送过去,服务端接收数据,并把数据存储在文件仓库下用户文件夹下对应用户名的文件夹下,即" workhorse/users/user_name/ "。 ![](markdown-image/上传文件窗口【用户】.png) 下载文件窗口要显示服务器里面管理员管理的公共文件仓库和对应用户里面的文件仓库,具体实现步骤: - 文件信息的传输要用Server/Client,客户端向服务端请求服务器文件仓库里面的所有文件的信息,即文件名,文件大小,文件类型; - 服务端接收客户端请求,把公共文件仓库和对应用户里面的所有文件的信息,即文件名,文件大小,文件类型,按批次给客户端发送过去,客户端接收数据,并处理数据,提供给图形界面。 ![](markdown-image/下载文件窗口.png) ------ ### 2.3 管理员页面 ![](markdown-image/管理员界面.png) 用户管理窗口,要显示所有用户的信息,可以删除以及添加用户的功能,当鼠标移动到img属性需要看到用户信息,并且可以看到用户是否在线,还要有一个全局的刷新更新ui的按钮,具体实现步骤: - 用户信息的传输要用Server/Client,客户端向服务端请求服务器的MySQL数据库仓库里面的所有用户的信息,即用户名,密码,在线状态; - 服务端接收客户端请求,把MySQL数据库仓库里面的所有用户的信息,即用户名,密码,在线状态,按批次给客户端发送过去,客户端接收数据,并处理数据,提供给图形界面。 - 把之前的tab删除,重新调用client请求服务器,获取服务器发送的数据渲染到三个tab上。 - 给服务器发送用户名和密码,以及执行命令,给服务器发送过去,服务器接收到命令处理添加用户的命令,把数据添加到数据库。 ![](markdown-image/用户管理窗口.png) 文件查看窗口,要显示所有用户的文件以及公共文件,具体实现: - 文件信息的传输要用Server/Client,客户端向服务端请求服务器文件仓库里面的所有文件的信息,即文件名,上传时间,文件大小,文件类型; - 服务端接收客户端请求,把公共文件仓库和所有用户文件仓库的信息,即文件名,上传时间,文件大小,文件类型,按批次给客户端发送过去,客户端接收数据,并处理数据,提供给图形界面。 ![](markdown-image/文件查看窗口.png) 上传文件窗口要显示上传按键、当前文件的信息、以及文件进度条,具体实现步骤: - 文件信息的传输要用Server/Client,客户端接收用户提交的文件信息,即文件名,文件大小,文件类型; - 客户端接收用户提交的文件数据,并处理数据,提供给图形界面;服务端接收客户端请求,客户端把当前文件的信息,即文件名,文件大小,文件类型,按批次给服务端发送过去,服务端接收数据,并把数据存储在文件仓库下公共文件夹下,即" workhorse/admin/ "。 ![](markdown-image/上传文件窗口【管理员】.png) ------ ## 3. 项目实现 ### 3.1 项目结构 文字说明: - 代码结构:dao、domain、handles【client、server】、ui、utils; - 资源结构:config、img、/warehouse/admin/、/warehouse/users/user_name/; - 下载资源【文件夹】:download; - 数据库创建【sql文件】:file_distribution_sys.sql 。 ------ ### 3.2 实现dao包 #### 3.2.1 AdminDAO.java 该代码中的`AdminDAO`类是一个数据访问对象,提供了管理员(Admin)验证登录相关的方法。 - `verifyAdmin(String username, String password)`:验证管理员用户名和密码是否匹配。通过调用OpnDBUtils工具类的queryForObject方法查询数据库中该管理员信息,如果不存在该管理员则返回false,否则判断输入的用户名和密码与数据库中的是否相同,若相同返回true,否则返回false。其中,参数`username`为需要验证的管理员用户名,`password`为需要验证的管理员密码。 ```java package app.fds.dao; import app.fds.domain.Admin; import app.fds.utils.OpnDBUtils; public class AdminDAO { public boolean verifyAdmin(String username, String password){ Admin admin = OpnDBUtils.queryForObject( Admin.class, "select username, password from admin where username=?;", username); if (admin == null) { return false; } else { if (admin.getUsername().equals(username) && admin.getPassword().equals(password)) { return true; } } return false; } } ``` ------ #### 3.2.2 AdminUploadDAO 该代码中的`AdminUploadDAO`类是一个数据访问对象,提供了与管理员上传文件(UploadFile)相关的方法。 - `insertAdminUploadOverall(List adminUploadList)`:插入多个文件记录到数据库。通过调用OpnDBUtils工具类的insertBatch方法,传入映射表的对象、执行SQL语句、一个对象的List,实现批量插入数据。其中,参数`adminUploadList`为需要插入的文件记录列表,返回值为成功插入的记录数。 ```java package app.fds.dao; import java.sql.SQLException; import java.util.List; import app.fds.domain.UploadFile; import app.fds.utils.OpnDBUtils; public class AdminUploadDAO { public int insertAdminUploadOverall(List adminUploadList) throws SQLException { return OpnDBUtils.insertBatch(UploadFile.class, "insert into admin_upload values(?, ?, ?, ?, ?);", adminUploadList); } } ``` ------ #### 3.2.3 UserDAO 该代码中的UserDAO类是一个数据访问对象,提供了与用户(User)相关的方法。 - `verifyUser(String username, String password)`:验证用户名和密码是否匹配。通过调用OpnDBUtils工具类的queryForObject方法查询数据库中该用户信息,如果不存在该用户则返回false,否则判断输入的用户名和密码与数据库中的是否相同,若相同返回true,否则返回false。 - `verifyUsernameExist(String username)`:验证用户名是否已存在。通过调用OpnDBUtils工具类的queryForObject方法查询数据库中该用户信息,如果不存在该用户则返回true,否则返回false。 - `getUserList()`:获取所有用户列表。通过调用OpnDBUtils工具类的queryForList方法查询数据库中的所有用户信息,返回一个User对象的List。 - `updateUserOnline(String username, int isOnline)`:更新用户在线状态。通过调用OpnDBUtils工具类的createGDBO方法对指定用户名的用户在线状态进行更新。 - `insertUser(String username, String password)`:插入一条新用户。通过调用OpnDBUtils工具类的createGDBO方法向数据库中插入一条新用户的记录。 - `deleteUser(String username)`:删除指定用户名的用户。通过调用OpnDBUtils工具类的createGDBO方法从数据库中删除指定用户名的用户记录。 ```java package app.fds.dao; import java.util.List; import app.fds.domain.User; import app.fds.utils.OpnDBUtils; public class UserDAO { public boolean verifyUser(String username, String password){ User users = OpnDBUtils.queryForObject( User.class, "select username, password from users where username=?;", username); if (users == null) { return false; } else { if (users.getUsername().equals(username) && users.getPassword().equals(password)) { return true; } } return false; } public boolean verifyUsernameExist(String username) { if (OpnDBUtils.queryForObject( User.class, "select username, password from users where username=?;", username) == null) { return true; } return false; } public List getUserList() { return OpnDBUtils.queryForList(User.class, "select * from users;"); } public boolean updateUserOnline(String username, int isOnline) { return OpnDBUtils.createGDBO("update users set online=? where username=?;", isOnline, username); } public boolean insertUser(String username, String password) { return OpnDBUtils.createGDBO("insert into users values (?, ?, ?);", username, password, 0); } public boolean deleteUser(String username) { return OpnDBUtils.createGDBO("delete from users where username=?;", username); } } ``` ------ #### 3.2.4 UserUploadDAO 该代码中的`UserUploadDAO`类是一个数据访问对象,提供了与用户上传文件(UploadFile)相关的方法。 - `insertUserUploadOverall(List userUploadList)`:插入多个文件记录到数据库。通过调用OpnDBUtils工具类的insertBatch方法,传入映射表的对象、执行SQL语句、一个对象的List,实现批量插入数据。其中,参数`userUploadList`为需要插入的文件记录列表,返回值为成功插入的记录数。 ```java package app.fds.dao; import java.sql.SQLException; import java.util.List; import app.fds.domain.UploadFile; import app.fds.utils.OpnDBUtils; public class UserUploadDAO { public int insertUserUploadOverall(List userUploadList) throws SQLException { return OpnDBUtils.insertBatch(UploadFile.class, "insert into users_upload values(?, ?, ?, ?, ?);", userUploadList); } } ``` ------ ### 3.3 实现domain包 #### 3.3.1 Admin.java 该代码中的Admin类是一个数据库映射表对象,必须包含数据库表的所有字段,并且字段名要一致、必须需要实现该类的无参构造方法,否则不能映射数据库表数据。 ```java package app.fds.domain; public class Admin { private String username; private String password; public Admin() {} public Admin(String username, String password) { super(); this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` ------ #### 3.3.2 User.java 该代码中的User类是一个数据库映射表对象,必须包含数据库表的所有字段,并且字段名要一致、必须需要实现该类的无参构造方法,否则不能映射数据库表数据。 ```java package app.fds.domain; public class User { private String username; private String password; private int online; public User() {} public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getOnline() { return online; } public void setOnline(int online) { this.online = online; } @Override public String toString() { return username + "," + password + "," + online; } } ``` ------ #### 3.3.3 UploadFile.java 该代码中的UploadFile类是一个数据库映射表对象,必须包含数据库表的所有字段,并且字段名要一致、必须需要实现该类的无参构造方法,否则不能映射数据库表数据。 ```java package app.fds.domain; import java.util.Date; public class UploadFile { private String username; private String file_name; private Date upload_time; private String address; private long file_size; public UploadFile() {} public UploadFile(String username, String file_name, Date upload_time, String address, long file_size) { super(); this.username = username; this.file_name = file_name; this.upload_time = upload_time; this.address = address; this.file_size = file_size; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getFile_name() { return file_name; } public void setFile_name(String file_name) { this.file_name = file_name; } public Date getUpload_time() { return upload_time; } public void setUpload_time(Date upload_time) { this.upload_time = upload_time; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public long getFile_size() { return file_size; } public void setFile_size(long file_size) { this.file_size = file_size; } @Override public String toString() { return "UserUpload [username=" + username + ", file_name=" + file_name + ", upload_time=" + upload_time + ", address=" + address + ", file_size=" + file_size + "]"; } } ``` ------ #### 3.3.4 UserFile.java & FileInfo.java 该代码中的`UserFile`和`FileInfo`类分别是用户文件(UserFile)和文件信息(FileInfo)的领域模型。 - `UserFile`类包含了用户的用户名和该用户上传的文件列表,在构造方法中初始化用户名和文件列表,提供了获取和设置用户的用户名和文件列表的方法。实现了toString方法打印对象的信息。 - `FileInfo`类包含了文件的名称、大小和创建时间,提供了获取和设置文件名称、大小和创建时间的方法。实现了toString方法打印对象的信息。 - `FileInfo`类主要是为了服务`UserFile`类,实现数据嵌套。注意:这两个实体类不是数据库映射表对象。 ##### 1. UserFile.java ```java package app.fds.domain; import java.util.List; public class UserFile { private String username; private List userFileList; public UserFile() {} public UserFile(String username, List userFileList) { super(); this.username = username; this.userFileList = userFileList; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public List getUserFileList() { return userFileList; } public void setUserFileList(List userFileList) { this.userFileList = userFileList; } @Override public String toString() { return "UserFile [username=" + username + ", userFileList=" + userFileList + "]"; } } ``` ##### 2. FileInfo.java ```java package app.fds.domain; public class FileInfo { private String fileName; private String fileSize; private String creationTime; public FileInfo() {}; public FileInfo(String fileName, String fileSize, String creationTime) { super(); this.fileName = fileName; this.fileSize = fileSize; this.creationTime = creationTime; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getFileSize() { return fileSize; } public void setFileSize(String fileSize) { this.fileSize = fileSize; } public String getCreationTime() { return creationTime; } public void setCreationTime(String creationTime) { this.creationTime = creationTime; } @Override public String toString() { return "FileInfo [fileName=" + fileName + ", creationTime=" + creationTime + ", fileSize=" + fileSize + "]"; } } ``` ------ ### 3.4 实现handles/client包 & handles/server包 #### 3.4.1 CRAllFileInfo.java & SSAllFileInfo.java `CRAllFileInfo`和`SSAllFileInfo`类是客户端和服务器端获取所有文件信息的处理器。 - `CRAllFileInfo`类实现了Runnable接口,通过Socket连接服务器端口获取所有文件信息。在run方法中,建立与服务器的连接,读取服务端传输的所有文件信息并解析为UserFile对象列表,存储到userFiles变量中。getUserFileInfoList方法则是获取解析后的文件信息列表。 - `SSAllFileInfo`类启动了一个服务器,监听客户端请求,并接受来自客户端的Socket连接请求。ServerTask线程处理客户端请求,并返回所有用户和公共文件信息。在run方法中,首先获取所有的用户和公共文件夹,遍历每个文件夹内部的文件,获取文件名称、大小、创建时间,并将其组成字符串格式,最后拼接为一个特定的字符串并通过PrintWriter发送给客户端。 `CRAllFileInfo`和`SSAllFileInfo`类是基于Socket通信实现客户端和服务器端间传输文件信息的处理器,具体实现流程如下: ##### 1. CRAllFileInfo.java - `CRAllFileInfo`类:客户端获取所有文件信息 ​ 1. 创建Socket对象并与指定的IP地址和端口进行连接。 ​ 2. 通过Socket的getInputStream()方法获取输入流。由于要接收到的数据以行为单位,因此使用BufferedReader读取数据。 ​ 3. 将读取到的信息按照特定的字符串格式解析,并将解析后的UserFile对象存储到List中。 ​ 4. 关闭Socket连接。 ```java package app.fds.handles.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import app.fds.domain.FileInfo; import app.fds.domain.UserFile; public class CRAllFileInfo implements Runnable { private static final int SERVER_PORT = 4444; private static final String SERVER_IP = "127.0.0.1"; private List userFiles; private String line; private CountDownLatch latch = new CountDownLatch(1); @Override public void run() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("Connected to server " + SERVER_IP + ":" + SERVER_PORT); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); userFiles = new ArrayList(); StringBuilder responseBuilder = new StringBuilder(); while ((line = reader.readLine()) != null) { responseBuilder.append(line + ","); if (line.equals("]")) { responseBuilder.append(line + ","); String[] strs = responseBuilder.toString().split(" = \\[, |,|\\]"); String username = strs[0].substring(0, strs[0].length() - 4); List fileInfos = new ArrayList(); for (int i = 1; i < strs.length; i += 3) { fileInfos.add(new FileInfo(strs[i], strs[i + 1], strs[i + 2])); } userFiles.add(new UserFile(username, fileInfos)); responseBuilder.setLength(0); } } socket.close(); System.out.println("Disconnected from server " + SERVER_IP + ":" + SERVER_PORT); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } public List getUserFileInfoList() throws InterruptedException { latch.await(); return userFiles; } } ``` ##### 2. SSAllFileInfo.java - `SSAllFileInfo`类:服务器端发送所有文件信息 ​ 1. 创建ServerSocket对象并绑定指定端口。 ​ 2. 通过ServerSocket的accept()方法监听客户端请求,如果有连接请求则建立连接,并返回一个Socket对象。 ​ 3. 服务器通过文件操作获取到所有文件信息,将其组成字符串格式,并转化为特定的字符串。 ​ 4. 使用PrintWriter将JSON字符串发送给客户端。 ​ 5. 关闭Socket连接。 ```java package app.fds.handles.server; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.nio.file.Files; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import app.fds.utils.DataHandleUtils; public class SSAllFileInfo { private static final int SERVER_PORT = 4444; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); File userFolder = new File("src/resource/warehouse/users"); File adminFolder = new File("src/resource/warehouse/admin"); FileTime fileTime = null; if (userFolder.exists() && userFolder.isDirectory()) { List fileInfoList = new ArrayList<>(); List fileUserInfoList = new ArrayList<>(); List fileAdminInfoList = new ArrayList<>(); for (File folder : userFolder.listFiles()) { fileUserInfoList.add(folder.getName() + " = ["); for (File userFile : folder.listFiles()) { try { fileTime = Files.readAttributes(userFile.toPath(), BasicFileAttributes.class).creationTime(); } catch (IOException e) { e.printStackTrace(); } String fileInfo = userFile.getName() + "," + DataHandleUtils.convertFileSize(userFile.length()) + "," + dateFormat.format(fileTime.toMillis()); fileUserInfoList.add(fileInfo); } fileUserInfoList.add("]"); } fileAdminInfoList.add("公共文件 = ["); for (File adminFile : adminFolder.listFiles()) { try { fileTime = Files.readAttributes(adminFile.toPath(), BasicFileAttributes.class).creationTime(); } catch (IOException e) { e.printStackTrace(); } String fileInfo = adminFile.getName() + "," + DataHandleUtils.convertFileSize(adminFile.length()) + "," + dateFormat.format(fileTime.toMillis()); fileAdminInfoList.add(fileInfo); } fileAdminInfoList.add("]"); fileInfoList.addAll(fileUserInfoList); fileInfoList.addAll(fileAdminInfoList); String fileInfoString = String.join("\n", fileInfoList); writer.println(fileInfoString); } else { writer.println("The user directory does not exist!"); } socket.close(); System.out.println("Connection closed: " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.2 CRFileDownload.java & SSFileDownload.java 文件下载功能的实现,其中 `CRFileDownload.java` 是客户端, `SSFileDownload.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并发送下载文件的请求信息,服务端接收到请求后根据请求信息查找对应的文件并发送给客户端 ##### 1. CRFileDownload.java - 构造方法:初始化成员变量 `fileName`、`username`、`type` 和 `downloadFileBar`,其中 `downloadFileBar` 是 `javafx.scene.control.ProgressBar` 对象,用于显示下载进度条。 - `call` 方法:实现了 `javafx.concurrent.Task` 接口中的 `call` 方法,表示该任务执行完成后会返回一个 `Void` 类型值。该方法中建立 `Socket` 连接并向服务端发送下载请求,同时接收服务端传输过来的文件并将其保存至本地磁盘。在整个文件传输过程中,该方法还不断更新下载进度条的显示。 - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,调用 `call` 方法并捕获异常。 ```java package app.fds.handles.client; import java.io.*; import java.net.Socket; import app.fds.utils.FileBufferUtils; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.scene.control.ProgressBar; import app.fds.utils.DataHandleUtils; public class CRFileDownload extends Task implements Runnable { private static final int SERVER_PORT = 6666; private static final String SERVER_IP = "127.0.0.1"; private String fileName; private String username; private String type; private ProgressBar downloadFileBar; public CRFileDownload(String fileName, String username, String type, ProgressBar downloadFileBar) { this.fileName = fileName; this.username = username; this.type = type; this.downloadFileBar = downloadFileBar; updateProgress(0, 1); // 初始化进度条 } @Override public Void call() throws Exception { try (Socket socket = new Socket(SERVER_IP, SERVER_PORT); OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); InputStream is = socket.getInputStream(); DataInputStream din = new DataInputStream(is); BufferedInputStream bis = new BufferedInputStream(is)) { pw.println(DataHandleUtils.getFileRoute(type, username, fileName)); pw.flush(); int exist = din.readInt(); // 读取服务器的响应 long totalSize = 0; if (exist == -1) { System.out.println("The file does not exist!"); } else { FileOutputStream fos = new FileOutputStream("Download File/" + fileName); byte[] buffer = null; long fileSize = din.readLong(); // 读取文件长度 while ((totalSize < fileSize)) { buffer = FileBufferUtils.getFileBuffer(fileSize - totalSize); // 获取缓冲区 int len = bis.read(buffer); if (len > 0) { fos.write(buffer, 0, len); fos.flush(); totalSize += len; double progress = (double) totalSize / fileSize; Platform.runLater(() -> downloadFileBar.setProgress(progress)); } FileBufferUtils.returnFileBuffer(buffer); // 归还缓冲区 } System.out.println("File " + fileName + " download successful."); fos.close(); } } catch (IOException | InterruptedException e) { System.err.println("Error: " + e.getMessage()); } return null; } @Override public void run() { try { call(); } catch (Exception e) { e.printStackTrace(); } } } ``` ##### 2. SSFileDownload.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端下载请求。首先从 `socket` 中获取输入流并读取客户端发送的文件路径,然后判断该文件是否存在。如果不存在,则向客户端发送一个 `-1` 表示文件不存在;如果存在,则发送 `0` 表示文件存在,并将文件内容通过输出流发送给客户端,同时使用缓冲区减少网络传输时间。在整个文件传输过程中,该类还不断申请和归还缓冲区。 ```java package app.fds.handles.server; import java.io.*; import java.net.*; import app.fds.utils.FileBufferUtils; public class SSFileDownload { private static final int SERVER_PORT = 6666; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); // 获取输入流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String fileInfo = br.readLine(); // 读取客户端发送的文件路径 File file = new File(fileInfo); DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); if (!file.exists()) { // 如果文件不存在 dout.writeInt(-1); // 发送-1表示文件不存在 dout.flush(); } else { // 如果文件存在 long fileSize = file.length(); dout.writeInt(0); // 发送0表示文件存在 dout.writeLong(fileSize); // 发送文件长度 dout.flush(); FileInputStream fis = new FileInputStream(file); byte[] buffer = null; while ((buffer = FileBufferUtils.getFileBuffer(fileSize)) != null) { int len = fis.read(buffer); if (len == -1) { break; } socket.getOutputStream().write(buffer, 0, len); socket.getOutputStream().flush(); FileBufferUtils.returnFileBuffer(buffer); // 归还缓冲区 } fis.close(); } socket.close(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.3 CRFileInfo.java & SSFileInfo.java 获取文件信息的实现,其中 `CRFileInfo.java` 是客户端, `SSFileInfo.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并向服务端发送用户名,服务端接收该请求后根据用户名查找该用户路径下的所有文件以及管理员路径下的所有文件并将其名称和大小发送给客户端 ##### 1. CRFileInfo.java - 构造方法:初始化成员变量 `userFileInfoList`、`adminFileInfoList` 和 `username`。 - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,建立 `Socket` 连接并向服务端发送用户名,同时接收服务端传输过来的文件信息。在整个文件传输过程中,该方法不断更新下载进度条的显示。 - `getUserFileInfoList` 和 `getAdminFileInfoList` 方法:等待 `run` 方法执行完成后返回 `userFileInfoList` 和 `adminFileInfoList`。 ```java package app.fds.handles.client; import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; public class CRFileInfo implements Runnable { private static final int SERVER_PORT = 7777; private static final String SERVER_IP = "127.0.0.1"; private List userFileInfoList = new ArrayList(); private List adminFileInfoList = new ArrayList(); private String line; private String username; private CountDownLatch latch = new CountDownLatch(1); public CRFileInfo(String username) { this.username = username; } @Override public void run() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("Connected to server " + SERVER_IP + ":" + SERVER_PORT); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); writer.println(username); StringBuilder responseBuilder = new StringBuilder(); while ((line = reader.readLine()) != null) { if (line.equals("*")) { responseBuilder.append(line); } else { responseBuilder.append(line + ","); } } if (!responseBuilder.toString().equals("*")) { String[] fileInfo = responseBuilder.toString().split("\\*"); userFileInfoList = Arrays.asList(fileInfo[0].split(",")); // 相应用户路径下的文件 adminFileInfoList = Arrays.asList(fileInfo[1].split(",")); // 管理员路径下的文件 } socket.close(); System.out.println("Disconnected from server " + SERVER_IP + ":" + SERVER_PORT); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } public List getUserFileInfoList() throws InterruptedException { latch.await(); return userFileInfoList; } public List getAdminFileInfoList() throws InterruptedException { latch.await(); return adminFileInfoList; } } ``` ##### 2. SSFileInfo.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端获取文件信息请求。首先从 `socket` 中获取输入流并读取客户端发送的用户名,然后查找该用户路径下的所有文件以及管理员路径下的所有文件的名称和大小,最后将其发送给客户端。如果用户路径不存在或不是目录,则向客户端发送一个错误信息。 - `convertFileSize` 方法:将文件大小转换为合适的单位。 ```java package app.fds.handles.server; import java.io.*; import java.net.*; import java.util.*; import app.fds.utils.DataHandleUtils; public class SSFileInfo { private static final int SERVER_PORT = 7777; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); String username = reader.readLine(); File userFolder = new File("src/resource/warehouse/users/" + username); File adminFolder = new File("src/resource/warehouse/admin"); if (userFolder.exists() && userFolder.isDirectory()) { File[] userFiles = userFolder.listFiles(); File[] adminFiles = adminFolder.listFiles(); List fileInfoList = new ArrayList<>(); List fileUserInfoList = new ArrayList<>(); List fileAdminInfoList = new ArrayList<>(); for (File file : userFiles) { String fileInfo = file.getName() + "," + DataHandleUtils.convertFileSize(file.length()); fileUserInfoList.add(fileInfo); } fileUserInfoList.add("*"); for (File file : adminFiles) { String fileInfo = file.getName() + "," + DataHandleUtils.convertFileSize(file.length()); fileAdminInfoList.add(fileInfo); } fileInfoList.addAll(fileUserInfoList); fileInfoList.addAll(fileAdminInfoList); String fileInfoString = String.join("\n", fileInfoList); writer.println(fileInfoString); } else { writer.println("The user directory does not exist!"); } socket.close(); System.out.println("Connection closed: " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.4 CRLoginVerify.java & SSLoginVerify.java 用户登录验证的实现,其中 `CRLoginVerify.java` 是客户端, `SSLoginVerify.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并将用户名、密码和用户类型发送给服务端,服务端接收该请求后根据用户类型查找对应的数据库表并验证用户名和密码是否一致。最后将验证结果发送给客户端 ##### 1. CRLoginVerify.java - 构造方法:初始化成员变量 `username`、`password`、`type` 和 `verify`。 - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,建立 `Socket` 连接并向服务端发送用户名、密码和用户类型,同时接收服务端传输过来的验证结果。 - `getVerifyAns` 方法:等待 `run` 方法执行完成后返回 `verify`。 ```java package app.fds.handles.client; import java.io.*; import java.net.*; import java.util.concurrent.CountDownLatch; public class CRLoginVerify implements Runnable { private static final int SERVER_PORT = 5555; private static final String SERVER_IP = "127.0.0.1"; private String username; private String password; private String type; private boolean verify; private CountDownLatch latch = new CountDownLatch(1); public CRLoginVerify(String username, String password, String type) { this.username = username; this.password = password; this.type = type; } @Override public void run() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("Connected to server " + SERVER_IP + ":" + SERVER_PORT); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); writer.println(username); writer.println(password); writer.println(type); verify = Boolean.valueOf(reader.readLine()); socket.close(); System.out.println("Disconnected from server " + SERVER_IP + ":" + SERVER_PORT); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } public boolean getVerifyAns() throws InterruptedException { latch.await(); return verify; } } ``` ##### 2. SSLoginVerify.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端登录验证请求。首先从 `socket` 中获取输入流并读取客户端发送的用户名、密码和用户类型,然后根据用户类型查找对应的数据库表并验证用户名和密码是否一致,最后将验证结果发送给客户端。 - `verifyUser` 和 `verifyAdmin` 方法:分别验证用户和管理员用户名和密码是否一致,并返回验证结果。 ```java package app.fds.handles.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import app.fds.dao.AdminDAO; import app.fds.dao.UserDAO; public class SSLoginVerify { private static final int SERVER_PORT = 5555; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); String username = reader.readLine(); String password = reader.readLine(); String type = reader.readLine(); boolean verify = false; if (type.equals("users")) { verify = new UserDAO().verifyUser(username, password); if (verify) { new UserDAO().updateUserOnline(username, 1); } } else if (type.equals("admin")) { verify = new AdminDAO().verifyAdmin(username, password); } writer.println(String.valueOf(verify)); socket.close(); System.out.println("Connection closed: " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.5 CRUserInfo.java & SSUserInfo.java 获取用户信息的实现,其中 `CRUserInfo.java` 是客户端, `SSUserInfo.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并请求服务端传输所有用户的信息。服务端接收该请求后从数据库中读取所有用户信息并将其发送给客户端。 ##### 1. CRUserInfo.java - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,建立 `Socket` 连接并向服务端发送请求,同时接收服务端传输过来的所有用户信息。 - `getUserFileInfoList` 方法:等待 `run` 方法执行完成后返回 `userInfoList`。 ```java package app.fds.handles.client; import java.io.*; import java.net.*; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; public class CRUserInfo implements Runnable { private static final int SERVER_PORT = 9999; private static final String SERVER_IP = "127.0.0.1"; private List userInfoList; private String line; private CountDownLatch latch = new CountDownLatch(1); @Override public void run() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("Connected to server " + SERVER_IP + ":" + SERVER_PORT); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); StringBuilder responseBuilder = new StringBuilder(); while ((line = reader.readLine()) != null) { responseBuilder.append(line + ","); } userInfoList = Arrays.asList(responseBuilder.toString().split(",")); socket.close(); System.out.println("Disconnected from server " + SERVER_IP + ":" + SERVER_PORT); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } public List getUserFileInfoList() throws InterruptedException { latch.await(); return userInfoList; } } ``` ##### 2. SSUserInfo.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端获取用户信息请求。从数据库中读取所有用户信息并将其发送给客户端。 ```java package app.fds.handles.server; import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import app.fds.dao.UserDAO; import app.fds.domain.User; public class SSUserInfo { private static final int SERVER_PORT = 9999; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); List fileUserInfoList = new ArrayList<>(); for (User user : new UserDAO().getUserList()) { fileUserInfoList.add(user.toString()); } String fileInfoString = String.join("\n", fileUserInfoList); writer.println(fileInfoString); socket.close(); System.out.println("Connection closed: " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.6 CSCmdProcess.java & SRCmdProcess.java 处理客户端命令的实现,其中 `CSCmdProcess.java` 是客户端, `SRCmdProcess.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并将用户名、密码和命令发送给服务端,同时接收服务端传输过来的命令执行结果。服务端接收该请求后根据命令不同,执行相应的操作并将操作结果发送给客户端。 ##### 1. CSCmdProcess.java - 构造方法:初始化成员变量 `username`、`password`、`command` 和 `state`。 - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,建立 `Socket` 连接并向服务端发送用户名、密码和命令,同时接收服务端传输过来的命令执行结果。 - `getCmdState` 方法:等待 `run` 方法执行完成后返回 `state`。 ```java package app.fds.handles.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.concurrent.CountDownLatch; public class CSCmdProcess implements Runnable { private static final int SERVER_PORT = 3333; private static final String SERVER_IP = "127.0.0.1"; private boolean state; private String username; private String password; private String command; private CountDownLatch latch = new CountDownLatch(1); public CSCmdProcess(String username, String password, String command) { this.username = username; this.password = password; this.command = command; } @Override public void run() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("Connected to server " + SERVER_IP + ":" + SERVER_PORT); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); writer.println(username); writer.println(password); writer.println(command); state = Boolean.valueOf(reader.readLine()); socket.close(); System.out.println("Disconnected from server " + SERVER_IP + ":" + SERVER_PORT); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } public boolean getCmdState() throws InterruptedException { latch.await(); return state; } } ``` ##### 2. SRCmdProcess.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端命令请求。根据命令不同,执行相应的数据库操作并将操作结果发送给客户端。 ```java package app.fds.handles.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import app.fds.dao.UserDAO; public class SRCmdProcess { private static final int SERVER_PORT = 3333; // 服务器端口号 private ServerSocket serverSocket; public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { e.printStackTrace(); } } private static class ServerTask implements Runnable { private Socket socket; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); String username = reader.readLine(); String password = reader.readLine(); String command = reader.readLine(); if (command.equals("delete user")) { boolean state = new UserDAO().deleteUser(username); if (state) { writer.println(state); } } else if (command.equals("verify username exist")) { UserDAO userDAO = new UserDAO(); if (userDAO.verifyUsernameExist(username)) { boolean state = userDAO.insertUser(username, password); if (state) { writer.println(state); } } } else if (command.equals("user offline")) { boolean state = new UserDAO().updateUserOnline(username, 0); if (state) { writer.println(state); } } socket.close(); System.out.println("Connection closed: " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } } } } ``` ------ #### 3.4.7 CSFileUpload.java & SRFileUpload.java 实现文件上传功能的代码,其中 `CSFileUpload.java` 是客户端, `SRFileUpload.java` 是服务端。客户端使用 `Socket` 建立与服务端的连接,并将需要上传的文件发送给服务端;同时客户端在上传过程中更新进度条,以提供用户友好的交互体验。服务端接收该请求后根据不同的上传类型(管理员/用户)进行相应的处理:保存上传文件到本地并将文件信息存储到数据库中。 ##### 1. CSFileUpload.java - 构造方法:初始化成员变量 `filePath`、`username`、`type` 和 `uploadFileBar`。 - `call` 方法:实现 `Task` 中的 `call` 方法,建立 `Socket` 连接并向服务端发送文件名和内容,同时将文件读入缓冲区并向服务端传送,期间更新进度条。 - `run` 方法:实现 `Runnable` 接口中的 `run` 方法,调用 `call` 方法。 ```java package app.fds.handles.client; import java.io.*; import java.net.*; import app.fds.utils.FileBufferUtils; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.scene.control.ProgressBar; import app.fds.utils.DataHandleUtils; public class CSFileUpload extends Task implements Runnable{ private static final int SERVER_PORT = 8888; private static final String SERVER_IP = "127.0.0.1"; private String filePath; private String username; private String type; private ProgressBar uploadFileBar; public CSFileUpload(String filePath, String username, String type, ProgressBar uploadFileBar) { this.filePath = filePath; this.username = username; this.type = type; this.uploadFileBar = uploadFileBar; updateProgress(0, 1); // 初始化进度条 } @Override public Void call() throws Exception { try (Socket socket = new Socket(SERVER_IP, SERVER_PORT); FileInputStream fileInputStream = new FileInputStream(filePath); OutputStream outputStream = socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) { // 发送文件名和内容 File file = new File(filePath); String fileName = file.getName(); if (type.equals("admin")) { dataOutputStream.writeUTF(String.format("src/resource/warehouse/admin/%s*%s", fileName, username)); } else { dataOutputStream.writeUTF(DataHandleUtils.getFileRoute(type, username, fileName)); // 发送信息 } long fileSize = file.length(); // 获取文件大小,用于计算传输进度 dataOutputStream.writeLong(fileSize); // 发送文件长度 byte[] buffer = null; long totalSize = 0; // 已传输数据块的总大小,用于计算传输进度 while ((buffer = FileBufferUtils.getFileBuffer(fileSize)) != null) { int len = fileInputStream.read(buffer); if (len == -1) { break; } outputStream.write(buffer, 0, len); FileBufferUtils.returnFileBuffer(buffer); // 归还缓冲区 totalSize += len; double progress = (double) totalSize / fileSize; // 计算传输进度 Platform.runLater(() -> uploadFileBar.setProgress(progress)); } fileInputStream.close(); System.out.println("File " + fileName + " uploaded."); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } return null; } @Override public void run() { try { call(); } catch (Exception e) { e.printStackTrace(); } } } ``` ##### 2. SRFileUpload.java - `startServer` 方法:启动服务端,创建一个 `ServerSocket` 对象并开始监听客户端连接请求。每当有新的客户端连接请求时,就开启一个新线程处理该请求。 - `ServerTask` 类:实现 `Runnable` 接口,处理客户端上传文件请求。该类通过 `DataInputStream` 读取客户端发送的文件名和内容,并保存到本地目录。同时,该类将文件信息保存到相应的数据库表中。 ```java package app.fds.handles.server; import java.io.*; import java.net.*; import java.sql.SQLException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import app.fds.dao.AdminUploadDAO; import app.fds.dao.UserUploadDAO; import app.fds.domain.UploadFile; import app.fds.utils.FileBufferUtils; public class SRFileUpload { private static final int SERVER_PORT = 8888; // 服务器端口号 private static final int USER_MAX_COUNT = 20; private static final int ADMIN_MAX_COUNT = 20; private static ServerSocket serverSocket; private static int userVariable = 0; private static int adminVariable = 0; private static List userUploadFileList = new ArrayList<>(); private static List adminUploadFileList = new ArrayList<>(); public void startServer() { try { serverSocket = new ServerSocket(SERVER_PORT); // 创建ServerSocket对象,绑定服务器端口号 System.out.println("Server started, listening on port " + SERVER_PORT + "..."); while (true) { Socket socket = serverSocket.accept(); // 监听客户端连接请求,如果有连接请求则建立连接,并返回一个Socket对象 System.out.println("New connection accepted: " + socket.getInetAddress() + ":" + socket.getPort()); // 处理客户端上传文件的请求 new Thread(new ServerTask(socket)).start(); // 使用新线程处理客户端请求 } } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } } private static class ServerTask implements Runnable { private Socket socket; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public ServerTask(Socket socket) { this.socket = socket; } @Override public void run() { try (DataInputStream dataInputStream = new DataInputStream(socket.getInputStream())) { // 读取客户端发送的文件名和内容,并保存到本地目录 String fileInfo = dataInputStream.readUTF(); String username = null; if (fileInfo.contains("/admin/")) { username = fileInfo.split("\\*")[1]; fileInfo = fileInfo.split("\\*")[0]; } FileOutputStream fileOutputStream = new FileOutputStream(fileInfo); String fileName = fileInfo.substring(fileInfo.lastIndexOf("/") + 1, fileInfo.length()); long fileSize = dataInputStream.readLong(); // 读取文件大小 byte[] buffer = null; while ((buffer = FileBufferUtils.getFileBuffer(fileSize)) != null) { int len = dataInputStream.read(buffer); if (len == -1) { break; } fileOutputStream.write(buffer, 0, len); FileBufferUtils.returnFileBuffer(buffer); // 归还缓冲区 } System.out.println("File " + fileName + " received."); fileOutputStream.close(); try { if (fileInfo.contains("/users/")) { userUploadFileList.add(new UploadFile( fileInfo.split("/users/")[1].split("/")[0], fileName, dateFormat.parse(dateFormat.format(new Date())), fileInfo, fileSize)); } else if(fileInfo.contains("/admin/")) { adminUploadFileList.add(new UploadFile( username, fileName, dateFormat.parse(dateFormat.format(new Date())), fileInfo, fileSize)); } } catch (ParseException e1) { e1.printStackTrace(); } // 当 userUploadFileList 的大小达到 USER_MAX_COUNT 时,提交数据到数据库 if (++userVariable == USER_MAX_COUNT) { try { new UserUploadDAO().insertUserUploadOverall(userUploadFileList); } catch (SQLException e) { e.printStackTrace(); } userUploadFileList.clear(); userVariable = 0; } // 当 userUploadFileList 的大小达到 USER_MAX_COUNT 时,提交数据到数据库 if (++adminVariable == ADMIN_MAX_COUNT) { try { new AdminUploadDAO().insertAdminUploadOverall(adminUploadFileList); } catch (SQLException e) { e.printStackTrace(); } adminUploadFileList.clear(); adminVariable = 0; } // 关闭socket socket.close(); } catch (IOException | InterruptedException e) { System.err.println("Error: " + e.getMessage()); } finally { } } } } ``` ------ ### 3.5 实现ui包 #### 3.5.1 OverallPage.java 实现两个方法 `createAdminPage` 和 `createUserPage`,分别用于创建管理员界面和用户界面。其中管理员界面包含三个选项卡,分别是用户信息管理、文件管理和管理员上传。而用户界面只包含两个选项卡,分别是用户上传和用户下载。 1. createAdminPage(String username) 该方法用于创建管理员界面,并返回一个 `Stage` 对象。该方法首先创建了一个 `TabPane` 对象 `adminTP`,并向其中添加了三个选项卡:用户信息管理、文件管理和管理员上传。具体的选项卡内容的生成由 `AdminTab` 类的静态方法完成。然后创建了一个刷新按钮,添加到了第一个选项卡中。点击该按钮会重新生成选项卡,以实现一键刷新的功能。最后将 `TabPane` 添加到 `AnchorPane` 中,并返回一个新的 `Stage` 对象。 2. createUserPage(String username, String password)该方法用于创建用户界面,并返回一个 `Stage` 对象。该方法首先创建了一个 `TabPane` 对象 `userTP`,并向其中添加了两个选项卡:用户上传和用户下载。具体的选项卡内容的生成由 `UserTab` 类的静态方法完成。然后设置了当前窗口关闭事件,在窗口关闭时向服务端发送“用户下线”消息。最后将 `TabPane` 添加到 `AnchorPane` 中,并返回一个新的 `Stage` 对象。 ```java package app.fds.ui; import app.fds.handles.client.CSCmdProcess; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class OverallPage{ public Stage createAdminPage(String username) { Stage primaryStage = new Stage(); AnchorPane adminAP = new AnchorPane(); TabPane adminTP = new TabPane(); adminTP.setTabMinWidth(413); adminTP.setTabMinHeight(39); try { adminTP.getTabs().addAll( AdminTab.createUserInfoTab(primaryStage), AdminTab.createFileManageTab(), AdminTab.createAdminUploadTab(primaryStage, username)); } catch (InterruptedException e) { e.printStackTrace(); } Button btnRefresh = new Button("刷新", new ImageView("/resource/img/refresh.png")); btnRefresh.setMinWidth(1239); btnRefresh.setTranslateX(-9); Tab userInfoTab = adminTP.getTabs().get(0); VBox userInfoOverall = (VBox) userInfoTab.getContent(); userInfoOverall.getChildren().add(1, btnRefresh); btnRefresh.setOnAction(e -> { adminTP.getTabs().clear(); try { adminTP.getTabs().addAll( AdminTab.createUserInfoTab(primaryStage), AdminTab.createFileManageTab(), AdminTab.createAdminUploadTab(primaryStage, username)); } catch (InterruptedException e1) { e1.printStackTrace(); } Button newBtnRefresh = new Button("刷新", new ImageView("/resource/img/refresh.png")); newBtnRefresh.setMinWidth(1239); newBtnRefresh.setTranslateX(-9); Tab newUserInfoTab = adminTP.getTabs().get(0); VBox newUserInfoOverall = (VBox) newUserInfoTab.getContent(); newUserInfoOverall.getChildren().add(1, btnRefresh); }); adminAP.getChildren().add(adminTP); Scene userScene = new Scene(adminAP); primaryStage.setTitle("管理员界面"); primaryStage.getIcons().add(new Image("/resource/img/logo.png")); primaryStage.setResizable(false); primaryStage.setWidth(1300); primaryStage.setHeight(770); primaryStage.setScene(userScene); primaryStage.show(); return primaryStage; } public Stage createUserPage(String username, String password) { Stage primaryStage = new Stage(); AnchorPane userAP = new AnchorPane(); TabPane userTP = new TabPane(); userTP.setTabMinWidth(625); userTP.setTabMinHeight(39); userTP.setMinWidth(1250); userTP.setMinHeight(770); try { userTP.getTabs().addAll( UserTab.createUserUploadTab(primaryStage, username), UserTab.createUserDownloadTab(username)); } catch (InterruptedException e) { e.printStackTrace(); } userAP.getChildren().addAll(userTP); Scene userScene = new Scene(userAP); primaryStage.setOnCloseRequest(new EventHandler() { @Override public void handle(WindowEvent event) { new Thread(new CSCmdProcess(username, password, "user offline")).start(); } }); primaryStage.setTitle("用户界面"); primaryStage.getIcons().add(new Image("/resource/img/logo.png")); primaryStage.setResizable(false); primaryStage.setWidth(1300); primaryStage.setHeight(770); primaryStage.setScene(userScene); primaryStage.show(); return primaryStage; } } ``` ------ #### 3.5.2 AdminTab.java 方法名:`createUserInfoTab`,创建了一个用户信息管理的选项卡。 该方法使用JavaFX组件来显示用户数据,并提供添加和删除用户的接口。 - 初始化一些变量,并设置选项卡的布局。然后创建一个添加用户的按钮,并为单击该按钮时执行的事件处理程序设置事件处理程序。 事件处理程序创建一个新窗口以添加用户。 - 创建 `CRUserInfo` 的实例,在单独的线程中运行以从文件中检索用户信息。 检索到的信息用于创建用户卡片的网格,每个卡片包含用户详细信息和删除用户的选项。 卡片还显示用户是否在线。 - 将所有组件都添加到根VBox中,并将VBox设置为选项卡的内容。 方法名:`createFileManageTab`,该方法创建了一个文件查看的选项卡,并使用JavaFX组件来显示文件管理相关的信息。 - 创建了一个 `CRAllFileInfo` 的实例,在单独的线程中运行以从文件中检索用户的文件信息。然后,将每个用户名和其对应的文件信息添加到列表和哈希表中,以备后续使用。 - 为每个用户创建一个包含文件信息的网格,并使用 `setFilesInfoGrid` 方法设置文件信息网格中的布局。 将网格添加到滚动窗格中,并将滚动窗格添加到哈希表中。 - 创建一个选项卡,并设置其名称和可关闭状态。 在选项卡中,创建一个VBox,并将其用于存储文件管理相关的所有组件。 创建一个ObservableList对象,并将其中包含的所有用户名添加到ComboBox中。 设置ComboBox的外观和行为,使其可滚动和选择。 为ComboBox设置事件监听器,以便在选择不同的用户名时切换滚动窗格中的内容。 - 将所有组件添加到VBox中,并将VBox设置为选项卡的内容。 方法名:`createAdminUploadTab`,该方法创建了一个管理员上传文件的选项卡,并使用JavaFX组件来显示相关的信息。 - 创建了一个选项卡和一个VBox。 然后,创建一个分隔符、一个上传文件的按钮和一个包含文件信息的网格。 该网格设置为可滚动,并设置其外观和行为。 同时,创建了一个包含图片和标签的VBox,用于显示选择文件的提示信息。 - 在按钮和网格上添加事件监听器,当用户单击按钮或网格时,将弹出一个文件选择框,允许用户选择需要上传的文件。 显示所选文件的名称和进度条,以便用户跟踪上传进度。 在上传文件时,创建 `CSFileUpload` 的实例,它将文件上传到服务器并更新进度条。 - 将所有组件添加到VBox中,并将VBox设置为选项卡的内容。 包含了两个私有静态方法: `setFilesInfoGrid` 和 `createAddUser`。 - 第一个方法 `setFilesInfoGrid` 创建了一个包含文件信息的网格,并将其添加到传递给方法的GridPane中。 该方法使用循环遍历传递给该方法的List对象,以获取文件信息并为每个文件创建一个水平框架(HBox)。 在HBox中,还创建了垂直框架(VBox) , 并在其中添加了文件名、上传时间、文件大小和 ProgressBar 组件。 此外,在HBox中还添加了下载文件按钮和文件大小标签的另一个水平框架(HBox)。 - 第二个方法 `createAddUser` 创建了一个新的JavaFX舞台(Stage),用于向应用程序中添加用户。 该方法创建了一个包含用户名和密码输入框以及添加用户按钮的垂直框架(VBox)。 该方法还设置了各种组件的样式和行为,例如将鼠标悬停在输入框上时更改背景颜色等。 如果用户单击"添加用户"按钮,则从输入框中读取用户名和密码,并将它们传递给 `CSCmdProcess` 对象进行处理。 如果该用户名不存在,则将窗口关闭,否则显示错误消息。 ```java package app.fds.ui; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import app.fds.domain.FileInfo; import app.fds.domain.UserFile; import app.fds.handles.client.CRAllFileInfo; import app.fds.handles.client.CRFileDownload; import app.fds.handles.client.CRUserInfo; import app.fds.handles.client.CSCmdProcess; import app.fds.handles.client.CSFileUpload; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.PasswordField; import javafx.scene.control.ProgressBar; import javafx.scene.control.ScrollPane; import javafx.scene.control.Separator; import javafx.scene.control.Tab; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Stage; public class AdminTab { private static List selectedFiles; public static Tab createUserInfoTab(Stage primaryStage) { Tab userInfoTab = new Tab(); userInfoTab.setText("用户管理"); userInfoTab.setClosable(false); VBox userInfoOverall = new VBox(); userInfoOverall.setSpacing(7); userInfoOverall.setTranslateY(-20); Separator userInfoSeparator = new Separator(); userInfoSeparator.setTranslateY(3); GridPane userInfoGrid = new GridPane(); userInfoGrid.setAlignment(Pos.CENTER); userInfoGrid.setPadding(new Insets(10)); userInfoGrid.setHgap(20); userInfoGrid.setVgap(20); userInfoGrid.setMaxWidth(1230); userInfoGrid.setMinHeight(470); userInfoGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); ScrollPane userInfoScrollPane = new ScrollPane(userInfoGrid); userInfoScrollPane.setPadding(new Insets(10)); userInfoScrollPane.setFitToWidth(true); userInfoScrollPane.setMaxWidth(1240); userInfoScrollPane.setMinHeight(500); userInfoScrollPane.setMaxHeight(500); userInfoScrollPane.setTranslateX(-8); userInfoScrollPane.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); Button addUserBtn = new Button("添加用户", new ImageView("/resource/img/addUser.png")); addUserBtn.setMinWidth(1239); addUserBtn.setTranslateX(-9); addUserBtn.setOnAction(e -> { Stage addUserStage = createAddUser(primaryStage); addUserStage.show(); }); CRUserInfo crUserInfo = new CRUserInfo(); new Thread(crUserInfo).start(); try { List userInfoList = crUserInfo.getUserFileInfoList(); int colIndex = 0; int rowIndex = 0; for (int i = 0; i < userInfoList.size() && userInfoList.size() != 1; i = i + 3) { String username = userInfoList.get(i); String password = userInfoList.get(i + 1); HBox userOverall = new HBox(); userOverall.setPrefWidth(230); userOverall.setPrefHeight(100); userOverall.setPadding(new Insets(7.7)); userOverall.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); userOverall.setSpacing(3); VBox userInfo = new VBox(); userInfo.setSpacing(3); VBox imageOnlineVBox = new VBox(); ImageView userImage = new ImageView("/resource/img/userInfo.png"); userImage.setFitWidth(64); userImage.setFitHeight(64); String tip = "用户名:" + username + "\n" + "密码:" + password; Tooltip.install(userImage, new Tooltip(tip)); Label usernameLable = new Label(username); usernameLable.setFont(new Font("Yu Gothic Medium", 13)); usernameLable.setMaxWidth(100); usernameLable.setMinWidth(100); Button btnDelete = new Button("删除"); btnDelete.setMinWidth(60); ImageView online; if (userInfoList.get(i + 2).equals("1")) { online = new ImageView("/resource/img/dropG.png"); } else { online = new ImageView("/resource/img/dropR.png"); } userInfo.setAlignment(Pos.CENTER_LEFT); userOverall.setAlignment(Pos.CENTER); imageOnlineVBox.setAlignment(Pos.TOP_RIGHT); imageOnlineVBox.getChildren().add(online); userInfo.getChildren().addAll(usernameLable, btnDelete); userOverall.getChildren().addAll(userImage, userInfo, imageOnlineVBox); userInfoGrid.add(userOverall, colIndex, rowIndex); btnDelete.setOnAction(e -> { CSCmdProcess cmdProcess = new CSCmdProcess(username, password, "delete user"); new Thread(cmdProcess).start(); try { if (cmdProcess.getCmdState()) { userInfoGrid.getChildren().remove(userOverall); } } catch (InterruptedException e1) { e1.printStackTrace(); } }); colIndex++; if (colIndex > 3) { colIndex = 0; rowIndex++; } } } catch (InterruptedException e) { e.printStackTrace(); } userInfoOverall.setAlignment(Pos.CENTER); userInfoOverall.getChildren().addAll(userInfoSeparator, addUserBtn, userInfoScrollPane); userInfoTab.setContent(userInfoOverall); return userInfoTab; } public static Tab createFileManageTab() throws InterruptedException { CRAllFileInfo crAllFileInfo = new CRAllFileInfo(); Thread infoThread = new Thread(crAllFileInfo); infoThread.start(); List selectFile = new ArrayList(); HashMap> mapFileInfo = new HashMap>(); HashMap mapFileUI = new HashMap(); List userFileList = crAllFileInfo.getUserFileInfoList(); for (UserFile userFile : userFileList) { String otUsername = userFile.getUsername(); List fileInfos = userFile.getUserFileList(); List fileInfoList = new ArrayList(); selectFile.add(otUsername); mapFileInfo.put(otUsername, userFile.getUserFileList()); for (FileInfo fileInfo : fileInfos) { fileInfoList.add(fileInfo.getFileName()); fileInfoList.add(fileInfo.getFileSize()); fileInfoList.add(fileInfo.getCreationTime()); } GridPane filesGrid = new GridPane(); filesGrid.setAlignment(Pos.CENTER); filesGrid.setPadding(new Insets(7)); filesGrid.setHgap(20); filesGrid.setVgap(32); filesGrid.setMaxWidth(1100); filesGrid.setMinHeight(560); filesGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); if (otUsername.equals("公共文件")) { setFilesInfoGrid(filesGrid, fileInfoList, otUsername, "admin", 2); } else { setFilesInfoGrid(filesGrid, fileInfoList, otUsername, "users", 2); } ScrollPane fileScrollPane = new ScrollPane(filesGrid); fileScrollPane.setPadding(new Insets(10)); fileScrollPane.setFitToWidth(true); fileScrollPane.setMaxWidth(1100); fileScrollPane.setMaxHeight(580); fileScrollPane.setMinWidth(1100); fileScrollPane.setMinHeight(580); mapFileUI.put(otUsername, fileScrollPane); } Tab fileManageTab = new Tab(); fileManageTab.setText("文件查看"); fileManageTab.setClosable(false); VBox fileManageOverall = new VBox(); fileManageOverall.setTranslateY(11); ObservableList obList = FXCollections.observableArrayList(selectFile); ComboBox comboBox = new ComboBox(obList); comboBox.setPrefWidth(1270); comboBox.setPrefHeight(30); comboBox.setTranslateX(-8.7); comboBox.setTranslateY(-17); comboBox.getSelectionModel().select(selectFile.indexOf("公共文件")); comboBox.setVisibleRowCount(3); Separator fileManageSeparator = new Separator(); fileManageSeparator.setTranslateY(-23); comboBox.setButtonCell(new ListCell() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setText(null); } else { setText(item); setAlignment(Pos.CENTER); // 设置文本居中对齐 setTranslateX(15); } } }); comboBox.setCellFactory(param -> new ListCell() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setText(null); } else { setText(item); setAlignment(Pos.CENTER); // 设置文本居中对齐 setTranslateX(6); } } }); comboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { if (!newValue.equals(oldValue)) { fileManageOverall.getChildren().remove(2); fileManageOverall.getChildren().add(2, mapFileUI.get(newValue)); } } }); fileManageOverall.setAlignment(Pos.CENTER); fileManageOverall.getChildren().addAll(fileManageSeparator, comboBox, mapFileUI.get("公共文件")); fileManageTab.setContent(fileManageOverall); return fileManageTab; } public static Tab createAdminUploadTab(Stage primaryStage, String username) { Tab uploadTab = new Tab(); VBox uploadOverall = new VBox(); uploadOverall.setSpacing(60); uploadOverall.setTranslateY(-28); Separator uploadSeparator = new Separator(); uploadSeparator.setTranslateY(37); uploadTab.setText("上传文件"); uploadTab.setClosable(false); Button uploadBtn = new Button("上传文件"); uploadBtn.setPrefWidth(260); uploadBtn.setPrefHeight(66); uploadOverall.setAlignment(Pos.CENTER); VBox infoVBox = new VBox(); infoVBox.setAlignment(Pos.CENTER); infoVBox.setMinWidth(300); infoVBox.setMinHeight(120); infoVBox.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: dashed inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); GridPane uploadFilesGrid = new GridPane(); uploadFilesGrid.setAlignment(Pos.CENTER); uploadFilesGrid.setPadding(new Insets(10)); uploadFilesGrid.setHgap(20); uploadFilesGrid.setVgap(20); uploadFilesGrid.setMaxWidth(930); uploadFilesGrid.setMinHeight(440); uploadFilesGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); ScrollPane uploadScrollPane = new ScrollPane(uploadFilesGrid); uploadScrollPane.setPadding(new Insets(10)); uploadScrollPane.setFitToWidth(true); uploadScrollPane.setMaxWidth(940); uploadScrollPane.setMinHeight(466); uploadScrollPane.setMaxHeight(466); uploadScrollPane.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); ImageView imageView = new ImageView("/resource/img/uploadFile.png"); Label uploadLabel = new Label("点击选择需要上传的文件"); uploadFilesGrid.setOnMouseClicked(event -> { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("选择文件"); selectedFiles = fileChooser.showOpenMultipleDialog(primaryStage); if (selectedFiles != null && !selectedFiles.isEmpty()) { if (selectedFiles.size() > 20) { System.out.println("The number of uploaded files exceeds the limit."); } else { uploadFilesGrid.getChildren().clear(); int rowIndex = 0; int colIndex = 0; for (File file : selectedFiles) { HBox uploadFileOverall = new HBox(); uploadFileOverall.setPadding(new Insets(10)); uploadFileOverall.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); VBox uploadFileInfo = new VBox(); uploadFileInfo.setSpacing(7); uploadFileInfo.setMaxWidth(370); uploadFileInfo.setMaxHeight(53); Label uploadFileLabel = new Label(file.getName()); uploadFileLabel.setFont(new Font("Yu Gothic Medium", 16)); uploadFileLabel.setMaxWidth(300); ImageView fileImageView = new ImageView("/resource/img/uploadFile.png"); ProgressBar uploadFileBar = new ProgressBar(0); uploadFileBar.setMinWidth(300); uploadFileInfo.getChildren().addAll(uploadFileLabel, uploadFileBar); uploadFileOverall.getChildren().addAll(fileImageView, uploadFileInfo); uploadFileOverall.setAlignment(Pos.BOTTOM_CENTER); uploadFileInfo.setAlignment(Pos.CENTER_LEFT); uploadFileOverall.setSpacing(7); uploadFilesGrid.add(uploadFileOverall, colIndex, rowIndex); colIndex++; if (colIndex > 1) { colIndex = 0; rowIndex++; } } } } }); uploadBtn.setOnAction(event -> { if (selectedFiles != null) { LinkedList barList = new LinkedList(); for (Node node : uploadFilesGrid.getChildren()) { if (node instanceof HBox) { HBox hbox = (HBox) node; for (Node childNode : hbox.getChildren()) { if (childNode instanceof VBox) { VBox vbox = (VBox) childNode; for (Node innerChildNode : vbox.getChildren()) { if (innerChildNode instanceof ProgressBar) { ProgressBar progressBar = (ProgressBar) innerChildNode; barList.add(progressBar); } } } } } } int index = 0; for (File file : selectedFiles) { CSFileUpload clientSend = new CSFileUpload(file.getPath(), username, "admin", barList.get(index)); new Thread(clientSend).start(); index++; } } }); infoVBox.getChildren().addAll(imageView, uploadLabel); uploadFilesGrid.getChildren().add(infoVBox); uploadOverall.getChildren().addAll(uploadSeparator, uploadScrollPane, uploadBtn); uploadTab.setContent(uploadOverall); return uploadTab; } private static void setFilesInfoGrid(GridPane infoTypeFilesGrid, List fileInfoList, String username, String type, int clo) { int colIndex = 0; int rowIndex = 0; for (int i = 0; i < fileInfoList.size() && fileInfoList.size() != 1; i = i + 3) { String fileName = fileInfoList.get(i); HBox fileOverall = new HBox(); HBox hboxC = new HBox(); hboxC.setSpacing(3); fileOverall.setPadding(new Insets(7.7)); fileOverall.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); VBox fileInfo = new VBox(); fileInfo.setSpacing(7); fileInfo.setMaxWidth(370); fileInfo.setMaxHeight(89); Label fileNameLabel = new Label(fileName); fileNameLabel.setFont(new Font("Yu Gothic Medium", 12)); fileNameLabel.setMaxWidth(200); fileNameLabel.setTranslateX(1); Tooltip.install(fileNameLabel, new Tooltip(fileName)); Label upladTimeLabel = new Label("上传时间:" + fileInfoList.get(i + 2)); upladTimeLabel.setFont(new Font("Yu Gothic Medium", 12)); upladTimeLabel.setMaxWidth(200); Label fileSizeLabel = new Label("文件大小:" + fileInfoList.get(i + 1)); fileSizeLabel.setFont(new Font("Yu Gothic Medium", 12)); fileSizeLabel.setMaxWidth(200); ProgressBar downloadFileBar = new ProgressBar(0); downloadFileBar.setMinWidth(200); ImageView fileImageView = new ImageView("/resource/img/downloadFile.png"); fileImageView.setTranslateY(-10); Button btnDownload = new Button("下载文件"); btnDownload.setOnAction(event -> { CRFileDownload crFileDownload = new CRFileDownload(fileName, username, type, downloadFileBar); new Thread(crFileDownload).start(); }); hboxC.getChildren().addAll(btnDownload, fileSizeLabel); fileInfo.getChildren().addAll(fileNameLabel, upladTimeLabel, hboxC, downloadFileBar); fileOverall.getChildren().addAll(fileImageView, fileInfo); fileOverall.setAlignment(Pos.BOTTOM_CENTER); fileInfo.setAlignment(Pos.CENTER_LEFT); hboxC.setAlignment(Pos.CENTER_LEFT); fileOverall.setSpacing(7); infoTypeFilesGrid.add(fileOverall, colIndex, rowIndex); colIndex++; if (colIndex > clo) { colIndex = 0; rowIndex++; } } } private static Stage createAddUser(Stage userInforStage) { Stage primaryStage = new Stage(); AnchorPane addUserAP = new AnchorPane(); VBox userVBox = new VBox(20); VBox userVboxF = new VBox(7); userVboxF.setTranslateX(-3); VBox userVboxS = new VBox(7); userVboxS.setTranslateX(-2); userVBox.setPrefWidth(530); userVBox.setPrefHeight(310); userVBox.setTranslateY(-20); HBox hboxError = new HBox(); hboxError.setAlignment(Pos.CENTER); ImageView imageViewError = new ImageView("/resource/img/dropR.png"); hboxError.getChildren().addAll(imageViewError, new Text()); Text userTextF = new Text("用户名"); userTextF.setFont(new Font(17)); TextField username = new TextField(); username.setMaxWidth(300); username.setPrefHeight(36); username.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); username.setOnMouseEntered(e -> { username.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); username.setOnMouseExited(e -> { username.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); Text userTextS = new Text("密码"); userTextS.setFont(new Font(17)); PasswordField password = new PasswordField(); password.setMaxWidth(300); password.setPrefHeight(36); password.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); password.setOnMouseEntered(e -> { password.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); password.setOnMouseExited(e -> { password.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); Button addUserBtn = new Button("添加用户"); addUserBtn.setPrefWidth(160); addUserBtn.setPrefHeight(36); addUserBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;" ); addUserBtn.setOnMouseEntered(e -> { addUserBtn.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE99, #6699CC99);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); addUserBtn.setOnMouseExited(e -> { addUserBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); addUserBtn.setOnAction(e -> { String usernameStr = username.getText(); String passwordStr = password.getText(); if (!usernameStr.equals("") && !passwordStr.equals("")) { CSCmdProcess cmdProcess = new CSCmdProcess(usernameStr, passwordStr, "verify username exist"); new Thread(cmdProcess).start(); try { if (cmdProcess.getCmdState()) { primaryStage.close(); } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("输入的用户名已经存在,请重新输入!"); username.setText(""); password.setText(""); if(userVBox.getChildren().indexOf(hboxError) != 3) { userVBox.getChildren().add(hboxError); } } } catch (InterruptedException e1) { e1.printStackTrace(); } } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("请输入用户名或者密码,用户名或密码不能为空!"); if(userVBox.getChildren().indexOf(hboxError) != 3) { userVBox.getChildren().add(hboxError); } } }); userVboxF.setAlignment(Pos.CENTER); userVboxS.setAlignment(Pos.CENTER); userVBox.setAlignment(Pos.CENTER); userVboxF.getChildren().addAll(userTextF, username); userVboxS.getChildren().addAll(userTextS, password); userVBox.getChildren().addAll(userVboxF, userVboxS, addUserBtn); addUserAP.getChildren().add(userVBox); Scene addUserScene = new Scene(addUserAP); primaryStage.setTitle("添加用户"); primaryStage.getIcons().add(new Image("/resource/img/logo.png")); primaryStage.setResizable(false); primaryStage.initOwner(userInforStage); primaryStage.initModality(Modality.WINDOW_MODAL); primaryStage.setWidth(530); primaryStage.setHeight(310); primaryStage.setScene(addUserScene); primaryStage.show(); return primaryStage; } } ``` ------ #### 3.5.3 UserTab.java 方法名:`createUserUploadTab`,用于创建一个包含上传文件功能的JavaFX选项卡。该选项卡由一个VBox容器组成,其中包含一个分隔符、一个“上传文件”按钮、一个显示上传文件进度的网格和一个滚动窗格。 - 创建一个Tab选项卡和一个VBox容器来初始化各个变量,并设置按钮和网格等组件的外观和行为。使用Separator分隔符来分割不同部分的组件,使其更加清晰明了。 - 为“上传文件”按钮和上传文件网格添加事件监听器。当用户单击上传文件按钮时,将弹出一个FileChooser对话框,以允许用户选择要上传的文件。并且,当用户单击上传文件表格时,会显示一个HBox用于展示每个文件的信息。HBox中包含了一个VBox和一个ProgressBar用于显示文件上传进度。文件名称被设置为Label文本,使用了Yu Gothic Medium字体,最大宽度为300像素。使用了ProgressBar进度条控件来显示当前文件的上传进度。 - 将所有上传文件的信息添加到uploadFilesGrid中,并根据需要调整行和列的位置。当用户点击“上传文件”按钮时,程序将启动一个线程来将文件传输到服务器。此外,该方法还使用了多个具有不同类型的节点和控件等来完成JavaFX程序界面的构建和操作。 方法名:`createUserDownloadTab`,用于创建一个包含下载文件功能的JavaFX选项卡。该选项卡由一个VBox容器组成,其中包含一个ComboBox下拉列表、两个GridPane网格和两个滚动窗格。 - 创建一个Tab选项卡和一个VBox容器来初始化各个变量,并设置ComboBox、Separator和网格等组件的外观和行为。使用Separator分隔符来分割不同部分的组件,使其更加清晰明了。 - 使用CRFileInfo类获取用户和管理员的文件信息列表,然后将这些信息添加到相应的网格中。使用ScrollPane滚动窗格来允许用户在网格中浏览文件信息。 - 为ComboBox下拉列表添加事件监听器。当用户选择“我的文件”时,将显示包含用户文件信息的网格,当用户选择“其他文件”时,将显示包含管理员文件信息的网格。使用TranslateTransition动画将滚动窗格从顶部平滑地移动到指定位置。 - 将所有组件添加到VBox容器中,并将其作为选项卡的内容。此外,该方法还使用了多个具有不同类型的节点和控件等来完成JavaFX程序界面的构建和操作。 方法名:`setDownloadFilesGrid`,用于设置显示下载文件信息的GridPane网格。该网格包含文件名称、大小、进度条和下载按钮等信息。 - 使用循环遍历传入的文件信息列表,获取每个文件的名称和大小,并创建用于显示该文件信息的HBox和VBox节点。使用ProgressBar进度条控件来显示文件的下载进度,并使用Tooltip工具提示来显示完整的文件名。此外,在HBox中还包含了“下载文件”按钮和文件大小标签。 - 将所有节点添加到HBox中,并将其设置为边框、背景颜色和间距。设置HBox和VBox的对齐方式以及最大宽度、高度和间距。最后,将HBox添加到GridPane中,并根据需要调整行和列的位置。 - 当用户单击“下载文件”按钮时,程序将启动一个线程来将文件从服务器下载到本地。同时,ProgressBar进度条控件将会更新当前文件的下载进度。 ```java package app.fds.ui; import java.io.File; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import app.fds.handles.client.CRFileDownload; import app.fds.handles.client.CRFileInfo; import app.fds.handles.client.CSFileUpload; import javafx.animation.Interpolator; import javafx.animation.TranslateTransition; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ProgressBar; import javafx.scene.control.ScrollPane; import javafx.scene.control.Separator; import javafx.scene.control.Tab; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; public class UserTab { private static List selectedFiles; public static Tab createUserUploadTab(Stage primaryStage, String username) { Tab uploadTab = new Tab(); VBox uploadOverall = new VBox(); uploadOverall.setSpacing(60); uploadOverall.setTranslateY(-60); Separator uploadSeparator = new Separator(); uploadSeparator.setTranslateY(30); uploadTab.setText("上传文件"); uploadTab.setClosable(false); Button uploadBtn = new Button("上传文件"); uploadBtn.setPrefWidth(260); uploadBtn.setPrefHeight(66); uploadOverall.setAlignment(Pos.CENTER); VBox infoVBox = new VBox(); infoVBox.setAlignment(Pos.CENTER); infoVBox.setMinWidth(300); infoVBox.setMinHeight(120); infoVBox.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: dashed inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); GridPane uploadFilesGrid = new GridPane(); uploadFilesGrid.setAlignment(Pos.CENTER); uploadFilesGrid.setPadding(new Insets(10)); uploadFilesGrid.setHgap(20); uploadFilesGrid.setVgap(20); uploadFilesGrid.setMaxWidth(930); uploadFilesGrid.setMinHeight(440); uploadFilesGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); ScrollPane uploadScrollPane = new ScrollPane(uploadFilesGrid); uploadScrollPane.setPadding(new Insets(10)); uploadScrollPane.setFitToWidth(true); uploadScrollPane.setMaxWidth(940); uploadScrollPane.setMinHeight(466); uploadScrollPane.setMaxHeight(466); uploadScrollPane.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); ImageView imageView = new ImageView("/resource/img/uploadFile.png"); Label uploadLabel = new Label("点击选择需要上传的文件"); uploadFilesGrid.setOnMouseClicked(event -> { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("选择文件"); selectedFiles = fileChooser.showOpenMultipleDialog(primaryStage); if (selectedFiles != null && !selectedFiles.isEmpty()) { if (selectedFiles.size() > 20) { System.out.println("The number of uploaded files exceeds the limit."); } else { uploadFilesGrid.getChildren().clear(); int rowIndex = 0; int colIndex = 0; for (File file : selectedFiles) { HBox uploadFileOverall = new HBox(); uploadFileOverall.setPadding(new Insets(10)); uploadFileOverall.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); VBox uploadFileInfo = new VBox(); uploadFileInfo.setSpacing(7); uploadFileInfo.setMaxWidth(370); uploadFileInfo.setMaxHeight(53); Label uploadFileLabel = new Label(file.getName()); uploadFileLabel.setFont(new Font("Yu Gothic Medium", 16)); uploadFileLabel.setMaxWidth(300); ImageView fileImageView = new ImageView("/resource/img/uploadFile.png"); ProgressBar uploadFileBar = new ProgressBar(0); uploadFileBar.setMinWidth(300); uploadFileInfo.getChildren().addAll(uploadFileLabel, uploadFileBar); uploadFileOverall.getChildren().addAll(fileImageView, uploadFileInfo); uploadFileOverall.setAlignment(Pos.BOTTOM_CENTER); uploadFileInfo.setAlignment(Pos.CENTER_LEFT); uploadFileOverall.setSpacing(7); uploadFilesGrid.add(uploadFileOverall, colIndex, rowIndex); colIndex++; if (colIndex > 1) { colIndex = 0; rowIndex++; } } } } }); uploadBtn.setOnAction(event -> { if (selectedFiles != null) { LinkedList barList = new LinkedList(); for (Node node : uploadFilesGrid.getChildren()) { if (node instanceof HBox) { HBox hbox = (HBox) node; for (Node childNode : hbox.getChildren()) { if (childNode instanceof VBox) { VBox vbox = (VBox) childNode; for (Node innerChildNode : vbox.getChildren()) { if (innerChildNode instanceof ProgressBar) { ProgressBar progressBar = (ProgressBar) innerChildNode; barList.add(progressBar); } } } } } } int index = 0; for (File file : selectedFiles) { CSFileUpload clientSend = new CSFileUpload(file.getPath(), username, "users", barList.get(index)); new Thread(clientSend).start(); index++; } } }); infoVBox.getChildren().addAll(imageView, uploadLabel); uploadFilesGrid.getChildren().add(infoVBox); uploadOverall.getChildren().addAll(uploadSeparator, uploadScrollPane, uploadBtn); uploadTab.setContent(uploadOverall); return uploadTab; } public static Tab createUserDownloadTab(String username) throws InterruptedException { CRFileInfo clientDisplay = new CRFileInfo(username); Thread infoThread = new Thread(clientDisplay); infoThread.start(); List fileUserInfoList = clientDisplay.getUserFileInfoList(); List fileAdminInfoList = clientDisplay.getAdminFileInfoList(); Tab downloadTab = new Tab(); downloadTab.setText("下载文件"); downloadTab.setClosable(false); VBox downloadOverall = new VBox(); downloadOverall.setTranslateY(-20); List selectFile = Arrays.asList("我的文件", "其他文件"); ObservableList obList = FXCollections.observableArrayList(selectFile); ComboBox comboBox = new ComboBox(obList); comboBox.setPrefWidth(1270); comboBox.setPrefHeight(30); comboBox.setTranslateX(-8.7); comboBox.setTranslateY(-27); comboBox.getSelectionModel().select(0); Separator downloadSeparator = new Separator(); downloadSeparator.setTranslateY(-30); GridPane downloadUserFilesGrid = new GridPane(); downloadUserFilesGrid.setAlignment(Pos.CENTER); downloadUserFilesGrid.setPadding(new Insets(7)); downloadUserFilesGrid.setHgap(20); downloadUserFilesGrid.setVgap(20); downloadUserFilesGrid.setMaxWidth(1100); downloadUserFilesGrid.setMinHeight(560); downloadUserFilesGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); setDownloadFilesGrid(downloadUserFilesGrid, fileUserInfoList, username, "users", 2); GridPane downloadOrderFilesGrid = new GridPane(); downloadOrderFilesGrid.setAlignment(Pos.CENTER); downloadUserFilesGrid.setPadding(new Insets(7)); downloadOrderFilesGrid.setHgap(20); downloadOrderFilesGrid.setVgap(20); downloadOrderFilesGrid.setMaxWidth(1100); downloadOrderFilesGrid.setMinHeight(560); downloadOrderFilesGrid.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); setDownloadFilesGrid(downloadOrderFilesGrid, fileAdminInfoList, username, "admin", 2); ScrollPane downloadUserScrollPane = new ScrollPane(downloadUserFilesGrid); downloadUserScrollPane.setPadding(new Insets(10)); downloadUserScrollPane.setFitToWidth(true); downloadUserScrollPane.setMaxWidth(1100); downloadUserScrollPane.setMaxHeight(580); downloadUserScrollPane.setMinWidth(1100); downloadUserScrollPane.setMinHeight(580); ScrollPane downloadOrderScrollPane = new ScrollPane(downloadOrderFilesGrid); downloadOrderScrollPane.setPadding(new Insets(10)); downloadOrderScrollPane.setFitToWidth(true); downloadOrderScrollPane.setMaxWidth(1100); downloadOrderScrollPane.setMaxHeight(580); downloadOrderScrollPane.setMinWidth(1100); downloadOrderScrollPane.setMinHeight(580); TranslateTransition ttUserFileScrollPane = new TranslateTransition(Duration.seconds(0.5), downloadUserScrollPane); ttUserFileScrollPane.setInterpolator(Interpolator.EASE_OUT); TranslateTransition ttOrderFileScrollPane = new TranslateTransition(Duration.seconds(0.5), downloadOrderScrollPane); ttOrderFileScrollPane.setInterpolator(Interpolator.EASE_OUT); comboBox.setOnShown(event -> { ttUserFileScrollPane.setFromY(0); ttUserFileScrollPane.setToY(30); ttUserFileScrollPane.play(); ttOrderFileScrollPane.setFromY(0); ttOrderFileScrollPane.setToY(30); ttOrderFileScrollPane.play(); }); comboBox.setOnHidden(event -> { ttUserFileScrollPane.setFromY(30); ttUserFileScrollPane.setToY(0); ttUserFileScrollPane.play(); ttOrderFileScrollPane.setFromY(30); ttOrderFileScrollPane.setToY(0); ttOrderFileScrollPane.play(); }); comboBox.setButtonCell(new ListCell() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setText(null); } else { setText(item); setAlignment(Pos.CENTER); // 设置文本居中对齐 setTranslateX(16); } } }); comboBox.setCellFactory(param -> new ListCell() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setText(null); } else { setText(item); setAlignment(Pos.CENTER); // 设置文本居中对齐 } } }); comboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { if (newValue.equals("我的文件")) { downloadOverall.getChildren().remove(2); downloadOverall.getChildren().add(2, downloadUserScrollPane); } else if (newValue.equals("其他文件")) { downloadOverall.getChildren().remove(2); downloadOverall.getChildren().add(2, downloadOrderScrollPane); } } }); downloadOverall.setAlignment(Pos.CENTER); downloadOverall.getChildren().addAll(downloadSeparator, comboBox, downloadUserScrollPane); downloadTab.setContent(downloadOverall); return downloadTab; } private static void setDownloadFilesGrid(GridPane downloadTypeFilesGrid, List fileInfoList, String username, String type, int clo) { int colIndex = 0; int rowIndex = 0; for (int i = 0; i < fileInfoList.size() && fileInfoList.size() != 1; i = i + 2) { String fileName = fileInfoList.get(i); HBox fileOverall = new HBox(); HBox hboxC = new HBox(); hboxC.setSpacing(3); fileOverall.setPadding(new Insets(7.7)); fileOverall.setStyle("-fx-background-color:#CDCDCD30;" + "-fx-border-style: solid inside;" + "-fx-border-width: 3px;" + "-fx-border-radius: 5px;" + "-fx-border-color: #CDCDCD;"); VBox fileInfo = new VBox(); fileInfo.setSpacing(7); fileInfo.setMaxWidth(370); fileInfo.setMaxHeight(73); Label fileNameLabel = new Label(fileName); fileNameLabel.setFont(new Font("Yu Gothic Medium", 12)); fileNameLabel.setMaxWidth(200); Tooltip.install(fileNameLabel, new Tooltip(fileName)); Label fileSizeLabel = new Label("文件大小:" + fileInfoList.get(i + 1)); fileSizeLabel.setFont(new Font("Yu Gothic Medium", 12)); fileSizeLabel.setMaxWidth(200); ProgressBar downloadFileBar = new ProgressBar(0); downloadFileBar.setMinWidth(200); ImageView fileImageView = new ImageView("/resource/img/downloadFile.png"); Button btnDownload = new Button("下载文件"); btnDownload.setOnAction(event -> { CRFileDownload crFileDownload = new CRFileDownload(fileName, username, type, downloadFileBar); new Thread(crFileDownload).start(); }); hboxC.getChildren().addAll(btnDownload, fileSizeLabel); fileInfo.getChildren().addAll(fileNameLabel, hboxC, downloadFileBar); fileOverall.getChildren().addAll(fileImageView, fileInfo); fileOverall.setAlignment(Pos.BOTTOM_CENTER); fileInfo.setAlignment(Pos.CENTER_LEFT); hboxC.setAlignment(Pos.CENTER_LEFT); fileOverall.setSpacing(7); downloadTypeFilesGrid.add(fileOverall, colIndex, rowIndex); colIndex++; if (colIndex > clo) { colIndex = 0; rowIndex++; } } } } ``` ------ #### 3.5.4 LoginPage.java `LoginPage` 是一个 JavaFX 应用程序,提供用户登录和管理员登录两个选项卡。登录界面的布局是由 `TabPane` 构成,包含三个选项卡:`userTab`,`adminTab` 和 `aboutTab`。 - `userTab` 包含一个 `VBox`,包含用户登录的 UI 组件,包括用户名的 `TextField`、密码的`PasswordField`和提交登录信息的 `Button`。 - `adminTab` 包含类似的 UI 组件,但标签名称和身份验证过程略有不同。 - 自定义类 `CRLoginVerify` 用于验证用户或管理员提供的登录凭据。如果凭证正确,应用程序将使用 `OverallPage` 类导航到用户或管理员仪表板页面。如果凭证不正确,则会通过包含图像和文本的 `HBox` 显示错误消息。 - `primaryStage` 是应用程序的主要舞台,设置为不可调整大小,大小固定。最后,在应用程序启动时调用 `start()` 方法以显示登录页面。 ```java package app.fds.ui; import app.fds.handles.client.CRLoginVerify; import javafx.application.Application; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.PasswordField; import javafx.scene.control.Separator; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; public class LoginPage extends Application{ @Override public void start(Stage primaryStage) throws Exception { AnchorPane loginAP = new AnchorPane(); TabPane loginTP = new TabPane(); loginTP.setTabMinWidth(182); loginTP.setTabMinHeight(30); loginTP.setMaxWidth(600); Tab userTab = new Tab(); Tab adminTab = new Tab(); Tab aboutTab = new Tab(); userTab.setText("用户登录"); userTab.setClosable(false); adminTab.setText("管理员登录"); adminTab.setClosable(false); aboutTab.setText("系统操作指南"); aboutTab.setClosable(false); HBox hboxError = new HBox(); hboxError.setAlignment(Pos.CENTER); ImageView imageViewError = new ImageView("/resource/img/dropR.png"); hboxError.getChildren().addAll(imageViewError, new Text()); //用户登录窗口样式 VBox userVBox = new VBox(); VBox userVboxF = new VBox(); VBox userVboxS = new VBox(); userVBox.setPrefWidth(600); userVBox.setPrefHeight(360); Separator userSeparator = new Separator(); userSeparator.setTranslateY(-27); Text userTextF = new Text("用户名"); userTextF.setFont(new Font(17)); userTextF.setTranslateX(121); TextField username = new TextField(); username.setMaxWidth(300); username.setPrefHeight(36); username.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); username.setOnMouseEntered(e -> { username.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); username.setOnMouseExited(e -> { username.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); Text userTextS = new Text("密码"); userTextS.setFont(new Font(17)); userTextS.setTranslateX(129); PasswordField userPassword = new PasswordField(); userPassword.setMaxWidth(300); userPassword.setPrefHeight(36); userPassword.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); userPassword.setOnMouseEntered(e -> { userPassword.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); userPassword.setOnMouseExited(e -> { userPassword.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); Button userBtn = new Button("登录"); userBtn.setPrefWidth(160); userBtn.setPrefHeight(36); userBtn.setTranslateX(216); userBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;" ); userBtn.setOnMouseEntered(e -> { userBtn.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE99, #6699CC99);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); userBtn.setOnMouseExited(e -> { userBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); userBtn.setOnAction(e -> { String usernameStr = username.getText(); String passwordStr = userPassword.getText(); if (!usernameStr.equals("") && !passwordStr.equals("")) { CRLoginVerify crLoginVerify = new CRLoginVerify(usernameStr, passwordStr, "users"); new Thread(crLoginVerify).start(); try { if (crLoginVerify.getVerifyAns()) { primaryStage.close(); new OverallPage().createUserPage(usernameStr, passwordStr).show(); } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("输入的用户名或者密码错误,请重新输入!"); if(userVBox.getChildren().indexOf(hboxError) != 4) { userVBox.getChildren().add(hboxError); } } } catch (InterruptedException ie) { ie.printStackTrace(); } } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("请输入用户名或者密码,用户名或密码不能为空!"); if(userVBox.getChildren().indexOf(hboxError) != 4) { userVBox.getChildren().add(hboxError); } } }); userVBox.setSpacing(20); userVBox.setTranslateY(30); userVboxF.setTranslateX(150); userVboxF.setSpacing(10); userVboxS.setTranslateX(150); userVboxS.setSpacing(10); //管理员登录窗口样式 VBox adminVBox = new VBox(); VBox adminVboxF = new VBox(); VBox adminVboxS = new VBox(); adminVBox.setPrefWidth(600); adminVBox.setPrefHeight(300); Separator adminSeparator = new Separator(); adminSeparator.setTranslateY(-27); Text adminTextF = new Text("账号"); adminTextF.setFont(new Font(17)); adminTextF.setTranslateX(129); TextField admin_name = new TextField(); admin_name.setMaxWidth(300); admin_name.setPrefHeight(36); admin_name.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); admin_name.setOnMouseEntered(e -> { admin_name.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); admin_name.setOnMouseExited(e -> { admin_name.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); Text adminTextS = new Text("密码"); adminTextS.setFont(new Font(17)); adminTextS.setTranslateX(129); PasswordField adminPassword = new PasswordField(); adminPassword.setMaxWidth(300); adminPassword.setPrefHeight(36); adminPassword.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;" ); adminPassword.setOnMouseEntered(e -> { adminPassword.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE30, #6699CC30);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); adminPassword.setOnMouseExited(e -> { adminPassword.setStyle( "-fx-background-color: linear-gradient(to right, #FF996630, #FFCC9930);" + "-fx-background-radius: 10px;" + "-fx-border-radius: 10px;" + "-fx-border-color: #848484;"); }); adminPassword.getText(); Button adminBtn = new Button("登录"); adminBtn.setPrefWidth(160); adminBtn.setPrefHeight(36); adminBtn.setTranslateX(216); adminBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;" ); adminBtn.setOnMouseEntered(e -> { adminBtn.setStyle( "-fx-background-color: linear-gradient(to right, #7B68EE99, #6699CC99);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); adminBtn.setOnMouseExited(e -> { adminBtn.setStyle( "-fx-background-color: linear-gradient(to right, #FF996699, #FFCC9999);" + "-fx-background-radius: 25px;" + "-fx-border-radius: 25px;"); }); adminBtn.setOnAction(e -> { String adminNameStr = admin_name.getText(); String adminPasswordStr = adminPassword.getText(); if (!adminNameStr.equals("") && !adminPasswordStr.equals("")) { CRLoginVerify crLoginVerify = new CRLoginVerify(adminNameStr, adminPasswordStr, "admin"); new Thread(crLoginVerify).start(); try { if (crLoginVerify.getVerifyAns()) { primaryStage.close(); new OverallPage().createAdminPage(adminNameStr).show(); } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("输入的账户或者密码错误,请重新输入!"); if(adminVBox.getChildren().indexOf(hboxError) != 4) { adminVBox.getChildren().add(hboxError); } } } catch (InterruptedException ie) { ie.printStackTrace(); } } else { Text text = (Text) hboxError.getChildren().get(1); text.setText("请输入账户或者密码,账户或密码不能为空!"); if(adminVBox.getChildren().indexOf(hboxError) != 4) { adminVBox.getChildren().add(hboxError); } } }); adminVBox.setSpacing(20); adminVBox.setTranslateY(30); adminVboxF.setTranslateX(150); adminVboxF.setSpacing(10); adminVboxS.setTranslateX(150); adminVboxS.setSpacing(10); //用户登录窗口组件添加 userVboxF.getChildren().addAll(userTextF, username); userVboxS.getChildren().addAll(userTextS, userPassword); userVBox.getChildren().addAll(userSeparator, userVboxF, userVboxS, userBtn); //管理员登录窗口组件添加 adminVboxF.getChildren().addAll(adminTextF, admin_name); adminVboxS.getChildren().addAll(adminTextS, adminPassword); adminVBox.getChildren().addAll(adminSeparator, adminVboxF, adminVboxS, adminBtn); userTab.setContent(userVBox); adminTab.setContent(adminVBox); loginTP.getTabs().addAll(userTab, adminTab, aboutTab); loginAP.getChildren().add(loginTP); Scene loginScene = new Scene(loginAP); primaryStage.setTitle("登录界面"); primaryStage.getIcons().add(new Image("/resource/img/logo.png")); primaryStage.setResizable(false); primaryStage.setWidth(607); primaryStage.setHeight(400); primaryStage.setScene(loginScene); primaryStage.show(); } } ``` ------ ### 3.6 实现utils包 #### 3.6.1 DataHandleUtils.java ```java package app.fds.utils; import java.io.File; import java.util.List; import app.fds.dao.UserDAO; import app.fds.domain.User; public class DataHandleUtils { /** * 根据类型、用户名和文件名生成文件路径。 * @param type 类型(admin或users) * @param username 用户名(仅适用于用户类型) * @param fileName 文件名 * @return 文件路径字符串,如果类型不是admin或users,则返回null,反之返回会对应的存储路径 */ public static String getFileRoute(String type, String username, String fileName) { if ("admin".equals(type)) { return String.format("src/resource/warehouse/admin/%s", fileName); } else if ("users".equals(type)) { return String.format("src/resource/warehouse/users/%s/%s", username, fileName); } return null; } /** * 将文件大小从字节转换为更容易阅读的格式。 * * @param fileSize 文件大小,单位为字节。 * @return 可读的文件大小字符串。 */ public static String convertFileSize(long fileSize) { String sizeStr; double convertedSize; if (fileSize < 1024) { sizeStr = fileSize + " B"; } else if (fileSize < 1024 * 1024) { convertedSize = (double) fileSize / 1024; sizeStr = String.format("%.2f KB", convertedSize); } else if (fileSize < 1024 * 1024 * 1024) { convertedSize = (double) fileSize / (1024 * 1024); sizeStr = String.format("%.2f MB", convertedSize); } else { convertedSize = (double) fileSize / (1024 * 1024 * 1024); sizeStr = String.format("%.2f GB", convertedSize); } return sizeStr; } /** * 初始化用户文件夹。如果文件夹不存在,则创建文件夹。 */ public static void initializeFolder() { List users = new UserDAO().getUserList(); String filePath; for (User user : users) { filePath = "src/resource/warehouse/users/" + user.getUsername(); File folder = new File(filePath); if (!folder.exists() && !folder.isDirectory()) { folder.mkdirs(); } } } } ``` ------ #### 3.6.2 FileBufferUtils.java ```java package app.fds.utils; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class FileBufferUtils { private static final Object LOCK = new Object(); private static BufferPool bufferPool = null; /** * 根据文件大小选择适当的缓冲池,并从所选池中返回一个字节数组。 * * @param fileSize 文件大小 * @return 缓冲区字节数组 */ public static byte[] getFileBuffer(long fileSize) throws InterruptedException { return getBufferPool().getBuffer(fileSize); } /** * 将字节数组返回到适当的缓存池中,以实现有效的归还缓冲区。 * * @param buffer 缓冲区字节数组 */ public static void returnFileBuffer(byte[] buffer) throws InterruptedException { getBufferPool().returnBuffer(buffer); } /** * 返回单例模式的缓存池对象,线程安全 */ public static BufferPool getBufferPool() { if (bufferPool == null) { synchronized (LOCK) { if (bufferPool == null) { bufferPool = new BufferPool(); } } } return bufferPool; } private static class BufferPool { // 定义阻塞队列 private BlockingQueue minPool; private BlockingQueue medPool; private BlockingQueue maxPool; private BufferPool() { // 初始化最小缓存池,包含10个大小为12MB的缓冲区 this.minPool = new ArrayBlockingQueue<>(30); for(int i = 0; i < 30; i++) { minPool.add(new byte[12 * 1024 * 1024]); } // 初始化中等缓存池,包含5个大小为17MB的缓冲区 this.medPool = new ArrayBlockingQueue<>(15); for(int i = 0; i < 15; i++) { medPool.add(new byte[17 * 1024 * 1024]); } // 初始化最大缓存池,包含2个大小为36MB的缓冲区 this.maxPool = new ArrayBlockingQueue<>(6); for(int i = 0; i < 6; i++) { maxPool.add(new byte[36 * 1024 * 1024]); } } /** * 根据文件大小选择对应的缓存池,并获取缓存空间 * * @param fileSize 文件大小 * @return 缓冲区字节数组 */ private byte[] getBuffer(long fileSize) throws InterruptedException { if (fileSize <= 0) { return null; } else if (fileSize > 1L * 1024 * 1024 * 1024) { return maxPool.take(); } else if (fileSize > 100L * 1024 * 1024) { return medPool.take(); } else { return minPool.take(); } } /** * 将缓存池归还到对应的缓存池中 * * @param buffer 缓冲区字节数组 */ private void returnBuffer(byte[] buffer) { if (buffer == null) { return; } else if (buffer.length == 12 * 1024 * 1024) { minPool.offer(buffer); } else if (buffer.length == 17 * 1024 * 1024) { medPool.offer(buffer); } else if (buffer.length == 36 * 1024 * 1024) { maxPool.offer(buffer); } } } } ``` ------ #### 3.6.3 JDBCUtils.java ```java package app.fds.utils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JDBCUtils { private static String url; private static String username; private static String password; private static String driverClass; private static final String FILENAME = "src/resource/config/dbInfo.properties"; static { // 加载配置文件信息 Properties props = new Properties(); InputStream is = null; try { is = new FileInputStream(new File(FILENAME)); props.load(is); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); driverClass = props.getProperty("jdbc.driverClass"); Class.forName(driverClass); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 获取数据库连接对象 * * @return Connection */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } /** * 关闭JDBC连接资源 * * @param conn 连接 * @param st Statement对象 * @param rs 结果集 */ public static void closeResource(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 关闭JDBC连接资源 * * @param conn 连接 * @param st Statement对象 */ public static void closeResource(Connection conn, Statement st) { if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } ``` ------ #### 3.6.4 OpnDBUtils.java ```java package app.fds.utils; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class OpnDBUtils { /** * 针对于不同的表,通用的插入、更新、删除操作,针对单个对象。 * * @param sql 执行的sql语句 * @param args 占位符的可变参数 * @return boolean 返回是否成功插入、更新、删除了一条记录 */ public static boolean createGDBO(String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } int count = ps.executeUpdate(); return count > 0; } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(conn, ps); } return false; } /** * 针对于不同的表,通用的批量插入数据操作。 * * @param sql SQL语句 * @param dataList 存储多个映射类数据的列表 * @param clazz 映射类的类型 * @return 成功插入的行数 * @throws SQLException */ public static int insertBatch(Class clazz, String sql, List dataList) throws SQLException { if (dataList == null || dataList.size() == 0) { return 0; } Connection conn = null; PreparedStatement ps = null; int result = 0; try { // 获取数据库连接 conn = JDBCUtils.getConnection(); // 关闭自动提交事务 conn.setAutoCommit(false); // 预编译sql ps = conn.prepareStatement(sql); for (T data : dataList) { // 给预编译SQL语句设置参数 setParameters(ps, data, clazz); // 加入到批处理中 ps.addBatch(); } // 执行批处理 int[] batchResult = ps.executeBatch(); // 求和batchResult得到成功插入的行数 for (int i : batchResult) { result += i; } // 提交事务 conn.commit(); } catch (SQLException e) { // 回滚事务 conn.rollback(); throw e; } finally { // 释放资源 JDBCUtils.closeResource(conn, ps); } return result; } /** * 针对于不同的表的通用的查询操作,返回表中的一条记录 * * @param clazz 运行时类是哪个类 * @param sql 执行的sql语句 * @param args 占位符的可变参数 * @return T 返回查询结果并封装成对象T */ public static T queryForObject(Class clazz, String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } rs = ps.executeQuery(); // 获取结果集的元数据 :ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); if (rs.next()) { T t = clazz.getDeclaredConstructor().newInstance(); // 处理结果集一行数据中的每一个列 for (int i = 0; i < columnCount; i++) { // 获取列值 Object columValue = rs.getObject(i + 1); // 获取每个列的列标签 String columnLabel = rsmd.getColumnLabel(i + 1); // 给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(conn, ps, rs); } return null; } /** * 针对不同表的查询操作,返回多条记录并处理 * * @param clazz 运行时类是哪个类 * @param sql 执行的sql语句 * @param args 占位符的可变参数 * @return List 返回查询结果并封装成对象T的集合 */ public static List queryForList(Class clazz, String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } rs = ps.executeQuery(); // 获取结果集的元数据 :ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); // 创建集合对象 ArrayList list = new ArrayList(); while (rs.next()) { T t = clazz.getDeclaredConstructor().newInstance(); // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0; i < columnCount; i++) { // 获取列值 Object columValue = rs.getObject(i + 1); // 获取每个列的列标签 String columnLabel = rsmd.getColumnLabel(i + 1); // 给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columValue); } list.add(t);// 对于查询到的多条数据来说,把每一个对象放进集合里 } return list; } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(conn, ps, rs); } return null; } /** * 给预编译SQL语句设置参数 */ private static void setParameters(PreparedStatement ps, T data, Class clazz) throws SQLException { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; Object fieldValue = null; try { // 反射获取属性值 field.setAccessible(true); fieldValue = field.get(data); } catch (IllegalAccessException e) { e.printStackTrace(); } // 设置参数 ps.setObject(i + 1, fieldValue); } } } ``` ------ ### 3.7 实现main包 先启动`ServerLaunch`,在启动界面`FDSLaunch`。 #### 3.7.1 FDSLaunch.java ```java package app.fds.main; import app.fds.ui.LoginPage; import javafx.application.Application; public class FDSLaunch { public static void main(String[] args) { Application.launch(LoginPage.class, args); } } ``` #### 3.7.2 ServerLaunch.java ```java package app.fds.main; import app.fds.handles.server.SRCmdProcess; import app.fds.handles.server.SRFileUpload; import app.fds.handles.server.SSAllFileInfo; import app.fds.handles.server.SSFileDownload; import app.fds.handles.server.SSFileInfo; import app.fds.handles.server.SSLoginVerify; import app.fds.handles.server.SSUserInfo; import app.fds.utils.DataHandleUtils; public class ServerLaunch { public static void main(String[] args) { new Thread(() -> DataHandleUtils.initializeFolder()).start(); // 初始化服务器存储文件夹 new Thread(() -> new SSFileDownload().startServer()).start(); // 启动下载服务器[端口号:6666] new Thread(() -> new SRFileUpload().startServer()).start(); // 启动上传服务器[端口号:8888] new Thread(() -> new SSFileInfo().startServer()).start(); // 启动获取单个相应用户文件信息服务器[端口号:7777] new Thread(() -> new SSUserInfo().startServer()).start(); // 启动获取用户信息服务器[端口号:9999] new Thread(() -> new SSLoginVerify().startServer()).start(); // 启动登录验证服务器[端口号:5555] new Thread(() -> new SSAllFileInfo().startServer()).start(); // 启动获取所有用户文件信息服务器[端口号:4444] new Thread(() -> new SRCmdProcess().startServer()).start(); // 启动启动接收用户命令执行服务器[端口号:3333] } } ``` ------ ## 4. 配置数据库文件 路径:src/resource/config/dbInfo.properties ```java jdbc.url=jdbc:mysql://localhost:3306/xxx?useSSL=true&serverTimezone=Asia/Shanghai jdbc.username=xxx jdbc.password=xxx jdbc.driverClass=com.mysql.cj.jdbc.Driver ``` ------ ### 4.1 sql文件 ```sql CREATE SCHEMA `file_distribution_sys` ; CREATE TABLE `file_distribution_sys`.`users` ( `username` VARCHAR(15) NOT NULL, `password` VARCHAR(15) NOT NULL, `online` INT NOT NULL DEFAULT 0, PRIMARY KEY (`username`)); CREATE TABLE `file_distribution_sys`.`admin` ( `username` VARCHAR(15) NOT NULL, `password` VARCHAR(15) NOT NULL, PRIMARY KEY (`username`)); CREATE TABLE `file_distribution_sys`.`users_upload` ( `username` VARCHAR(15) NOT NULL, `file_name` VARCHAR(45) NOT NULL, `upload_time` DATETIME NOT NULL, `address` VARCHAR(200) NOT NULL, `file_size` BIGINT NOT NULL, PRIMARY KEY (`username`, `file_name`, `upload_time`)); CREATE TABLE `file_distribution_sys`.`admin_upload` ( `username` VARCHAR(15) NOT NULL, `file_name` VARCHAR(45) NOT NULL, `upload_time` DATETIME NOT NULL, `address` VARCHAR(200) NOT NULL, `file_size` BIGINT NOT NULL, PRIMARY KEY (`username`, `file_name`, `upload_time`)); ALTER TABLE `file_distribution_sys`.`users` ADD CONSTRAINT ck_user_online CHECK (`online` = 0 or `online` = 1); ALTER TABLE `file_distribution_sys`.`users_upload` ADD CONSTRAINT `fk_users_upload_username` FOREIGN KEY (`username`) REFERENCES `file_distribution_sys`.`users` (`username`) ON DELETE NO ACTION ON UPDATE NO ACTION; ALTER TABLE `file_distribution_sys`.`admin_upload` ADD CONSTRAINT `fk_admin_upload_username` FOREIGN KEY (`username`) REFERENCES `file_distribution_sys`.`admin` (`username`) ON DELETE NO ACTION ON UPDATE NO ACTION; INSERT INTO `file_distribution_sys`.`users` VALUES ('雨露沾青衫', 'Users06222020', 1), ('花落满人间', 'Users07012020', 0), ('Bruce Wayne', 'Users11272023', 1), ('Lucas Alexander', 'Users12032019', 1), ('南絮', 'Users05202019', 0), ('Jane Smith', 'Users07252022', 1), ('Pablo Gomez', 'Users10272020', 1), ('青石巷', 'Users03162019', 0), ('Juan Hernandez', 'Users08282021', 1), ('Miguel Garcia', 'Users11242018', 1), ('Emily Johnson', 'Users04032020', 0), ('林下月疏', 'Users02122023', 1), ('Olivia Kim', 'Users11112022', 1), ('人烟寒橘柚', 'Users08152019', 0), ('Daniel Wong', 'Users05012021', 1), ('David Liu', 'Users02222020', 1), ('奈南墙冷落', 'Users10032018', 0), ('Tom Smith', 'Users04122023', 1), ('Jerry Chen', 'Users07202021', 1), ('Amy Wang', 'Users02202022', 0), ('素衣叹风尘', 'Users04242019', 1), ('Hao Zhang', 'Users10302020', 1), ('似黄粱梦', 'Users06012018', 0), ('Peter Li', 'Users08082022', 1), ('缓步香茵', 'Users01292023', 1), ('Tony Guo', 'Users12122017', 0), ('Karen Chen', 'Users03062021', 1); INSERT INTO `file_distribution_sys`.`admin` VALUES ('Eddie', 'Admin07311998'), ('Root', 'Admin03172001'); ``` ------ ## 5.项目部署运行 ### 5.1 运行项目 1. 先运行服务端【ServerLaunch.java】 ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20132958.png) 2. 然后运行客户端【FDSLaunch.java】 ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133023.png) #### 5.1.1 用户运行界面 ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20181426.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20181440.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133131.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133358.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133438.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133450.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133528.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133528.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133539.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20133551.png) ------ #### 5.1.2 管理员运行界面 ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20181457.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20181517.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180547.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180559.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180612.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180631.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180656.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180725.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180744.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180820.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180828.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180836.png) ![输入图片说明](markdown-image/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-06-06%20180854.png)