DataPulse-智能文件同步系统-1

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


项目信息

项目名称:DataPulse-智能文件同步系统
项目目标: 实现一个智能文件同步系统,能够在本地文件夹和远程服务器之间同步文件,具备冲突检测和自动合并功能。
实现成果:
一个完整可视化的文件同步系统。
实现文件的实时监控和自动同步
冲突检测和自动合并功能
图形化界面,方便用户操作和管理
至少支持两种同步模式:单向同步和双向同步


一、页面设计及功能

登录注册页面
首页:左侧显示用户信息,右侧显示该用户本地维护的文件
功能:
用户登录,系统初始化时读取配置文件中的用户信息,用户输入账号和密码进行登录。
上传文件功能,点击选择文件,文件存储至用户本地文件夹中并将文件发送给服务器,服务器存储上传文件。
删除文件功能,在文件表格中选中需要删除的文件,删除本地文件夹中的相应文件和服务器中的文件。

在这里插入图片描述

二、功能实现

1.上传功能

技术点:
IO流操作,文件读写,socket网络通信

上传按钮监听类:
本地存储上传文件:
获取选中文件,将文件存复制到本地
JFileChooser.APPROVE_OPTION:用户点击了“打开”按钮,选择了一个或多个文件。
FileMap为存储File对象的HashSet,是User中的一个成员变量,存储当前用户本地所有的文件
本地文件名为用户名+上传文件名
若文件不存在,创建文件,并将上传文件的内容写入新创建的文件中
若重复上传同一文件,则本地需要维护最新的文件,将原有文件删除再重新复制最新上传文件

服务器同步上传文件:
客户端:将用户名长度、操作指令、文件名和文件内容发送给服务器,UPLOAD、DELETE分别代表上传和删除操作
服务端:接收文件名,若文件不存在,创建文件,存在则删除重新创建,接收文件内容,写入新建文件

网络通信编程:
Socket:用于TCP网络通信的客户端。
ServerSocket:用于TCP网络通信的服务器端。
服务器端的 ServerSocket:服务器端创建一个 ServerSocket 对象,并指定一个端口号。调用 serverSocket.accept() 方法会使得服务器进入阻塞状态,直到一个客户端尝试连接到这个端口。

客户端的 Socket:客户端创建一个 Socket 对象,并尝试连接到服务器的IP地址和端口号。一旦连接建立,客户端 Socket 对象就与服务器端的某个 Socket 对象形成了一个通信链路。

关联但不等同:当服务器端的 serverSocket.accept() 方法返回时,它返回一个新的 Socket 对象,这个对象代表了与客户端的连接。虽然客户端和服务器端的 Socket 对象不是同一个对象,但它们是一对,通过TCP/IP协议栈进行通信。

输入输出流:每个 Socket 对象都有相关的输入流和输出流。服务器端的 Socket 输入流可以读取客户端发送的数据,服务器端的 Socket 输出流可以向客户端发送数据。同理,客户端的 Socket 对象也有输入流和输出流用于与服务器通信。

关闭连接:当通信完成后,应该关闭相关的 Socket 对象以及它们的输入输出流。这会释放系统资源,并通知对方通信已经结束。

更新文件显示面板显示上传的文件
filePanel为文件展示面板
通过filePanel获取表格组件,并通过model向其中添加列显示文件名和文件大小(B)

问题记录:
使用缓冲流读写doc、pdf等文件时内容会乱码
查阅资料后发现word等文档需要使用特定的库,因为这些文件格式是专有的,并没有在Java标准库中直接支持。
后续发现使用FileInputStream,FileOutPutStream读写doc,pdf等文件内容不会乱码,暂时使用文件字节流代替缓冲流处理乱码问题。

UpListener

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;
import java.util.HashSet;


public class UpListener implements ActionListener {
    JPanel fileJPanel;
    User user ;
    public UpListener(User user){
        this.user = user ;
    }
    public UpListener(){}
    @Override
    public void actionPerformed(ActionEvent e) {
        JFileChooser fileChooser = new JFileChooser();
        int result = fileChooser.showOpenDialog(null);

        if (result == JFileChooser.APPROVE_OPTION) {
            System.out.println("选中文件");
            // 用户选择了文件
            File selectedFile = fileChooser.getSelectedFile();
            System.out.println("Selected file: " + selectedFile.getAbsolutePath());
            HashSet<File> files = user.getFileMap();
            //将上传的文件复制后保存至本地
            String oldName = selectedFile.getName();
            String newName = user.getUname()+oldName;
            System.out.println("上传后的文件名为:"+newName);
            File file = new File("userdata\\"+newName);
            files.add(file);
            if(!file.exists()){
                try {
                    file.createNewFile();
                } catch (IOException ioException) {
                    files.remove(file);
                    //创建文件失败 从set中移除file对象
                    ioException.printStackTrace();
                }
            }else{
                file.delete();
                try {
                    file.createNewFile();
                } catch (IOException ioException) {
                    files.remove(file);
                    ioException.printStackTrace();
                }
            }
            //将上传的文件内容复制到新创建的文件中去
            copyFile(selectedFile,file);
            //将文件同步上传至服务器
            sendFileToServer("127.0.0.1",12345,file,user.getUname());

            //上传操作需要更新文件面板
            Component[] coms = fileJPanel.getComponents();
            for(Component com  : coms){
                if(com != null){
                    if("cyPane".equals(com.getName())){
                        Component component = ((JScrollPane)com).getViewport().getView();

                        JTable table = (JTable) component;
                        DefaultTableModel model = (DefaultTableModel) table.getModel();

                        model.addRow(new Object[]{file.getName(),file.length()/8});
                    }
                }

            }
            System.out.println("coms"+coms.length);
        }
    }

    public void copyFile(File oldFile , File newFile){
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null ;
        try {
            inputStream = new FileInputStream(oldFile);
            outputStream = new FileOutputStream(newFile,true);
            byte[] bytes = new byte[1024];
            int count ;
            while((count = inputStream.read(bytes))!= -1){

                outputStream.write(bytes,0,count);

            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void sendFileToServer(String ip , int port , File file,String uName){
        String filePath = file.getPath();
        String fileName = file.getName();
        //文件大小 单位为字节
        long size = file.length();
        //文件名大小

        System.out.println("本地文件的路径为:"+filePath);
        System.out.println("本地文件名为:"+fileName);
        Socket socket = null ;
        FileInputStream fis = null;
        OutputStream outputStream = null ;
        try {
            socket = new Socket(ip,port);
            fis =  new FileInputStream(filePath);
            outputStream = socket.getOutputStream();
            //发送指令
            outputStream.write("UPLOAD".getBytes());

            //发送用户名长度
            int len = uName.length();
            outputStream.write(len);

            //文件名长度不超过256
            byte[] fName = fileName.getBytes();
            outputStream.write(fName.length);
            System.out.println("文件名字节长度:"+fName.length);
            outputStream.write(fName);

            byte[] bytes = new byte[2048];
            int count ;

            while((count = fis.read(bytes)) != -1){
                outputStream.write(bytes,0,count);
            }
            System.out.println("文件发送成功!");

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.删除功能

删除本地文件
从table组件中获取选中文件名,从组件中移除选中行,并根据获取到的文件名找到本地对应的文件删除文件
删除服务器文件
将操作指令、用户名、删除的文件名发送给服务器,服务器找到相应文件删除
发送文件名前先将文件的个数发送至服务器,下面代码使用字节流将一个int发送至服务器

     //发送一个int表示要删除文件的个数
     int delFiles =  fileNames.length;
     int b1 = (delFiles >> 24) & 0xFF;
     int b2 = (delFiles >> 16) & 0xFF;
     int b3 = (delFiles >> 8) & 0xFF;
     int b4 = (delFiles >> 0) & 0xFF;
     out.write(b1);
     out.write(b2);
     out.write(b3);
     out.write(b4);

问题记录
调用file.delete()方法文件无法删除,手动进行删除提示IOException
跟当前删除的文件相关的读写流没有关闭完全,因此需要检查代码,将相关的流全部关闭

DelListener

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;

public class DelListener implements ActionListener {
    JTable fileTable ;
    User user ;
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("删除按钮点击事件!");
        //页面上将文件名从列表中移除
        int[] selectedRows = fileTable.getSelectedRows();

        String[] fileNames = new String[selectedRows.length];
        System.out.println("待删除文件名:"+fileNames);
        int index = 0;

        DefaultTableModel model = (DefaultTableModel) fileTable.getModel();

        //获取文件名
        for (int row : selectedRows) {
            // 假设表格有三列
            String fileName = (String) model.getValueAt(row, 0); // 获取第一列的值
            fileNames[index++] = fileName;
        }
        //本地删除文件
        delFiles(fileNames);

        for (int i = selectedRows.length - 1; i >= 0; i--) {
            model.removeRow(selectedRows[i]);
        }

        //服务器删除文件
        delServerFiles("127.0.0.1",12345, fileNames,user.getUname());
    }

    public void delFiles(String[] fileNames){
        for (String s : fileNames){
            String path = "userdata\\"+s;
            System.out.println("本地删除的文件相对路径:"+path);
            File file = new File(path);
            if(file.exists()){
                boolean isDel = file.delete();
                System.out.println(path+"删除成功"+isDel);
            }
        }
    }

    public void delServerFiles( String ip , int port , String[] fileNames , String uName ){
        //将文件名发给服务器
        try {
            Socket socket = new Socket(ip, port);
            OutputStream out = socket.getOutputStream();
            out.write("DELETE".getBytes());
            out.write(uName.getBytes().length);
            out.write(uName.getBytes());
            //发送一个int表示要删除文件的个数
            int delFiles =  fileNames.length;
            int b1 = (delFiles >> 24) & 0xFF;
            int b2 = (delFiles >> 16) & 0xFF;
            int b3 = (delFiles >> 8) & 0xFF;
            int b4 = (delFiles >> 0) & 0xFF;
            out.write(b1);
            out.write(b2);
            out.write(b3);
            out.write(b4);

            for (String fileName : fileNames){
                out.write(fileName.getBytes().length);
                out.write(fileName.getBytes());
            }
            out.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

3.服务器

Server

userFile存储用户名和当前用户所有文件名-文件对象map的映射,后续多用户处理可能会用到
每次获取一个客户端连接,新建一个线程进行处理
服务器将接收到的四个字节还原成int

     int b1 = in.read();
     int b2 = in.read();
     int b3 = in.read();
     int b4 = in.read();
     int delfiles = (b1<<24)|(b2<< 16)|(b3 << 8)|b4;
import java.net.Socket;
import java.util.*;


public class Server {
    //key 用户名 value 存储文件名-文件对象的map
    static HashMap<String,HashMap<String,File>> userFile = new HashMap<>();

    public static void main(String[] args) {

        ServerSocket server = null ;
        try {
            server = new ServerSocket(12345);
            // 监听发送到这个端口的连接
            System.out.println("阻塞监听连接:");
            while(true){
                Socket socket = server.accept();// 阻塞方法
                System.out.println("监听连接成功" + socket.getInetAddress());
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //处理服务器发送的数据
                        handleClient(socket);
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(server != null ){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    public static void handleClient(Socket socket){
        System.out.println("进入handleClient方法");
        InputStream in = null;
        OutputStream out =null;
        try {
            in = socket.getInputStream();
            //读取指令
            byte[] command = new byte[6];
            in.read(command);
            String cmd = new String(command);
            System.out.println("服务器收到的指令为:"+cmd);

            if("UPLOAD".equals(cmd)){
                int uLen = in.read();
                // 输入流获取文件名长度的ascii码
                int len = in.read();
                byte[] fileName = new byte[len];
                in.read(fileName);
                String fName = new String(fileName);
                //文件名
                String uName = fName.substring(0,uLen);
                System.out.println("服务器接收到的用户名为:"+uName);
                System.out.println("接受到上传的文件名为:"+fName);
                File directory = new File("serverFile\\"+uName);
                if(!directory.exists()){
                    boolean isCreated = directory.mkdir();
                    System.out.println(directory.getPath()+"文件夹是否创建成功"+isCreated);
                }

                File file = new File("serverFile\\"+uName+"\\"+fName);

                if(!file.exists()){
                    file.createNewFile();
                    userFile.getOrDefault(uName,new HashMap<>()).put(fName,file);
                }else{
                    file.delete();
                    file.createNewFile();
                    userFile.getOrDefault(uName,new HashMap<>()).put(fName,file);
                }
                out = new FileOutputStream(file);
                byte[] bytes = new byte[2048];
                int count ;
                while((count = in.read(bytes)) != -1){
                    out.write(bytes,0,count);
                }
                System.out.println("文件上传成功!");
            }else if("DELETE".equals(cmd)){
                int uLen = in.read();
                byte[] bytes = new byte[uLen];
                in.read(bytes);
                String uName = new String(bytes);
                System.out.println("删除指令服务器接收到的用户名"+uName);
                int b1 = in.read();
                int b2 = in.read();
                int b3 = in.read();
                int b4 = in.read();
                int delfiles = (b1<<24)|(b2<< 16)|(b3 << 8)|b4;
                System.out.println("服务器接收到待删除的文件数量为"+delfiles);
                for (int i = 0; i < delfiles; i++) {
                    int len = in.read();
                    byte[] b = new byte[len];
                    in.read(b);
                    String fileName = new String(b);
                    System.out.println("待删除文件名为:"+fileName);
                    File file = new File("serverFile\\"+uName+"\\"+fileName);
                    System.out.println("");
                    if(file.exists()){
                        System.out.println("文件存在");
                        boolean ret = file.delete();
                        System.out.println(ret);
                        if(ret){
                            userFile.get(uName).remove(fileName);
                            System.out.println("服务器删除"+fileName+"文件成功!");
                        }
                    }
                    System.out.println("循环结束");
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null ){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


}

总结

实现了登录,上传文件和删除文件功能,后续会继续完善,包括多客户端同步等功能

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值