• Java自制微信聊天图片自动保存软件


    一、前言

    这个demo主要是整合了网上的一些方法。这只是个简易的demo,可以在此基础上增加其它的业务逻辑,比如ocr识别等需要搜集微信图片的场景,把图片先存下来再进行其它操作。

    实现思路

    1. 可以借助三方的的对话机器人 SDK,如 Wechaty https://wechaty.gitbook.io/wechaty/v/zh/

    优点:不用跟微信应用绑定,更灵活,还有收发消息的功能
    缺点:不是官方工具,账号可能有风险,最好使用小号;相对可靠的工具会收费

    1. 本文采用该方法:将桌面版微信开启 “保留聊天记录”,“开启文件自动下载”,之后 会话图片会被保存在本地,图片会被保存为 .dat 扩展名的加密文件,不能直接查看,需要解密。 所以只需要监控微信聊天记录保存的目录,有 dat 文件新增时,把它解密成图片格式再存到其它的文件夹里就行了。

    优点:无风险,可靠性更高;整合 Spring 等框架更方便,业务更稳定。
    缺点:不灵活,程序需要与微信在同一台机器上运行;微信目前没有Linux版本,只能部署在Mac或Windows系统

    二、代码

    项目为maven项目,jdk1.8

    2.1 pom 文件

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        
        <groupId>org.examplegroupId>
        <artifactId>wechat-image-pullerartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
        
        <dependencies>
            
            <dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-apiartifactId>
                <version>1.7.36version>
            dependency>
            
            <dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-reload4jartifactId>
                <version>1.7.36version>
                <scope>testscope>
            dependency>
            
            <dependency>
                <groupId>commons-iogroupId>
                <artifactId>commons-ioartifactId>
                <version>2.11.0version>
            dependency>
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-coreartifactId>
                <version>5.8.5version>
            dependency>
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-logartifactId>
                <version>5.8.5version>
            dependency>
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-settingartifactId>
                <version>5.8.5version>
            dependency>
    
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2.2 项目目录结构

    1659688919157.png

    2.3 dat文件解析

    原理:微信存储图片的时候做了异或加密,然后将后缀修改为了dat。由于文件大小没有变化,可以很容易得到异或值,通过异或值,将文件进行字节码解码,就可以将文件还原成为图片了。
    参考:https://blog.csdn.net/a627428179/article/details/95485146

    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.nio.file.Files;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicReference;
    
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    
    /**
     * 微信 DAT加密文件 转图片
     * 
     * @author: jiangxiangbo
     * @date: 2022/8/3
     */
    public class DatFileParseUtil {
    
        private static final Log log = LogFactory.get();
        
        /**
         * 文件流的前 N个byte的16进制表示 --> 文件扩展名
         */
        private final static Map<String, String> FILE_TYPE_MAP = new HashMap<>();
    
        static {
            getAllFileType();
        }
    
        /**
         * dat文件解析
         * 
         * @param file 文件
         * @param targetPath 转换后目录
         * @return void 
         * @author jiangxiangbo
         * @date 2022/8/3
         */
        public static void parse(File file, String targetPath) {
            AtomicReference<Integer> integer = new AtomicReference<>(0);
            AtomicInteger x = new AtomicInteger();
            if (file.isFile()) {
                Object[] xori = getXor(file);
                if (xori != null && xori[1] != null){
                    x.set((int)xori[1]);
                }
            }
            Object[] xor = getXor(file);
            if (x.get() == 0 && xor[1] != null && (int) xor[1] != 0) {
                x.set((int) xor[1]);
            }
            xor[1] = xor[1] == null ? x.get() : xor[1];
    
            // 创建目录及文件
            File desc = getAbsoluteFile(targetPath, file.getName().split("\\.")[0] + (xor[0] != null ? "." + xor[0] : ""));
            try (InputStream reader = Files.newInputStream(file.toPath());
                 OutputStream writer = Files.newOutputStream(desc.toPath()))
            {
                byte[] bytes = new byte[1024 * 10];
                int b;
                while ((b = reader.read(bytes)) != -1) {
                    for (int i = 0; i < bytes.length; i++) {
                        bytes[i] = (byte) (int) (bytes[i] ^ (int) xor[1]);
                        if (i == (b - 1)) {
                            break;
                        }
                    }
                    writer.write(bytes, 0, b);
                    writer.flush();
                }
                integer.set(integer.get() + 1);
                log.info("【 parse file success, {} size: {} kb 】", file.getName(), ((double) file.length() / 1000));
            } catch (Exception e) {
                log.error("parse file error!", e);
            }
        }
    
        /**
         * 创建目录及文件
         *
         * @param path
         * @param fileName
         * @return java.io.File
         * @author jiangxiangbo
         * @date 2022/7/11
         */
        private static File getAbsoluteFile(String path, String fileName) {
            try {
                File desc = new File(path + File.separator + fileName);
                if (!desc.getParentFile().exists()) {
                    desc.getParentFile().mkdirs();
                }
                if (!desc.exists()) {
                    desc.createNewFile();
                }
                return desc;
            } catch (IOException e) {
                log.error(e, "create new file error !");
            }
            return null;
        }
        
        /**
         * @param path       图片目录地址
         * @param targetPath 转换后目录
         */
        public static void parse(String path, String targetPath) {
            File[] files = new File(path).listFiles();
            if (files == null) {
                return;
            }
            int size = files.length;
            log.info("file total is {}", size);
            AtomicInteger x = new AtomicInteger();
            for (File file1 : files) {
                if (file1.isFile()) {
                    Object[] xori = getXor(file1);
                    if (xori != null && xori[1] != null){
                        x.set((int)xori[1]);
                    }
                    break;
                }
            }
            Arrays.stream(files).parallel().forEach(itemFile -> parse(itemFile, targetPath));
            log.info("file parse success");
        }
    
        /**
         * 文件异或值
         *
         * @param file
         * @return
         */
        private static Object[] getXor(File file) {
            Object[] xor = null;
            if (file != null) {
                byte[] bytes = new byte[4];
                try (InputStream reader = Files.newInputStream(file.toPath())) {
                    reader.read(bytes, 0, bytes.length);
                } catch (Exception e) {
                    log.error("getXor error!", e);
                }
                xor = getXor(bytes);
            }
            return xor;
        }
    
        /**
         * 异或运算
         * 
         * @param bytes
         * @return
         */
        private static Object[] getXor(byte[] bytes) {
            Object[] xorType = new Object[2];
            int[] xors = new int[3];
            for (Map.Entry<String, String> type : FILE_TYPE_MAP.entrySet()) {
                String[] hex = {
                        String.valueOf(type.getKey().charAt(0)) + type.getKey().charAt(1),
                        String.valueOf(type.getKey().charAt(2)) + type.getKey().charAt(3),
                        String.valueOf(type.getKey().charAt(4)) + type.getKey().charAt(5)
                };
                xors[0] = bytes[0] & 0xFF ^ Integer.parseInt(hex[0], 16);
                xors[1] = bytes[1] & 0xFF ^ Integer.parseInt(hex[1], 16);
                xors[2] = bytes[2] & 0xFF ^ Integer.parseInt(hex[2], 16);
                if (xors[0] == xors[1] && xors[1] == xors[2]) {
                    xorType[0] = type.getValue();
                    xorType[1] = xors[0];
                    break;
                }
            }
            return xorType;
        }
    
        /**
         * 初始化文件头信息 与文件类型映射 map
         * 

    * 文件头数据来源于网络;可在此处扩展类型 * * @author jiangxiangbo * @date 2022/8/3 */ private static void getAllFileType() { FILE_TYPE_MAP.put("ffd8ffe000104a464946", "jpg"); FILE_TYPE_MAP.put("89504e470d0a1a0a0000", "png"); FILE_TYPE_MAP.put("47494638396126026f01", "gif"); FILE_TYPE_MAP.put("49492a00227105008037", "tif"); // 16色位图(bmp) FILE_TYPE_MAP.put("424d228c010000000000", "bmp"); // 24位位图(bmp) FILE_TYPE_MAP.put("424d8240090000000000", "bmp"); // 256色位图(bmp) FILE_TYPE_MAP.put("424d8e1b030000000000", "bmp"); FILE_TYPE_MAP.put("255044462d312e360d25", "pdf"); FILE_TYPE_MAP.put("504b0304140006000800", "docx"); } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202

    2.4 监控新增的dat文件

    借助 commons-io 的 FileAlterationMonitor 监控器。

    继承 FileAlterationListenerAdaptor,自定义文件监听器

    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    import com.xs.puller.config.AppConfig;
    import com.xs.puller.util.DatFileParseUtil;
    import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
    
    import java.io.File;
    
    /**
     * 文件监听器
     *
     * @author: jiangxiangbo
     * @date: 2022/8/3
     */
    public class DatFileListener extends FileAlterationListenerAdaptor {
    
        private static final Log log = LogFactory.get();
        
        /**
         * 文件创建时被触发
         * 
         * @param file The file created (ignored)
         * @return void 
         * @author jiangxiangbo
         * @date 2022/8/3
         */
        @Override
        public void onFileCreate(File file) {
            String fileName = file.getName();
            log.info("new file created, name is : {}", fileName);
            String thumbFlag = fileName.substring(fileName.length() - 6);
            if (fileName.contains(AppConfig.scanFileType) && !AppConfig.FILTER_FILE_TAG.equals(thumbFlag)) {
                DatFileParseUtil.parse(file, AppConfig.targetPath);
            }
        }
    
        /**
         * 文件夹创建时被触发
         *
         * @param directory The directory created (ignored)
         * @return void
         * @author jiangxiangbo
         * @date 2022/8/4
         */
        @Override
        public void onDirectoryCreate(File directory) {
            log.info("new directory created, name is : {}", directory.getName());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    把监控器做个简单的封装:

    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    import com.xs.puller.config.AppConfig;
    import com.xs.puller.listener.DatFileListener;
    import org.apache.commons.io.monitor.FileAlterationMonitor;
    import org.apache.commons.io.monitor.FileAlterationObserver;
    
    import java.util.Arrays;
    
    /**
     * MonitorHolder
     * 
     * @author: jiangxiangbo
     * @date: 2022/8/4
     */
    public class FileAlterationMonitorHolder {
    
        private static final Log log = LogFactory.get();
    
        /** 根目录文件夹观察者 */
        private final static FileAlterationObserver DIR_OBSERVER = new FileAlterationObserver(AppConfig.scanPath);
    
        /** 监控器 */
        private static FileAlterationMonitor monitor;
        
        /**
         * 初始化文件监控器
         * 
         * @return void 
         * @author jiangxiangbo
         * @date 2022/8/4
         */
        public static void init() {
            // 文件观察者
            FileAlterationObserver fileObserver = new FileAlterationObserver(AppConfig.scanPath);
            fileObserver.addListener(new DatFileListener());
            monitor = new FileAlterationMonitor(AppConfig.scanInterval, Arrays.asList(fileObserver, DIR_OBSERVER));
            log.info(">>>>>>>>>>>>>>> monitor init success");
        }
        
        /**
         * 启动监控
         * 
         * @return void 
         * @author jiangxiangbo
         * @date 2022/8/4
         */
        public static void start() {
            try {
                monitor.start();
                log.info(">>>>>>>>>>>>>>> monitor start success");
            } catch (Exception e) {
                log.error(e,">>>>>>>>>>>>>>> monitor start error !");
            }
        }
        
        /**
         * 停止监控
         * 
         * @return void 
         * @author jiangxiangbo
         * @date 2022/8/4
         */
        public static void stop() {
            try {
                monitor.stop();
                log.info(">>>>>>>>>>>>>>> monitor stop success");
            } catch (Exception e) {
                log.error(e,">>>>>>>>>>>>>>> monitor stop error !");
            }
        }
        
        private FileAlterationMonitorHolder() {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    说明

    这里已经可以实现功能了,只要在main方法中执行即可:

    import com.xs.puller.frame.AppFrame;
    
    /**
     * 启动类
     * 
     * @author: jiangxiangbo
     * @date: 2022/8/3
     */
    public class ImagePullerApplication {
    
        public static void main(String[] args) {
            /// new AppFrame();
            FileAlterationMonitorHolder.init();
            FileAlterationMonitorHolder.start();
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.5 简单绘制应用界面

    简单绘制一个界面,并添加相应的触发函数

    package com.xs.puller.frame;
    
    import com.xs.puller.config.AppConfig;
    import com.xs.puller.context.FileAlterationMonitorHolder;
    
    import javax.swing.*;
    import javax.swing.border.EmptyBorder;
    import javax.swing.border.TitledBorder;
    import java.awt.*;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    
    /**
     * @author: jiangxiangbo
     * @date: 2022/8/4
     */
    public class AppFrame extends JFrame {
    
        public static final int WIDTH = 450;
        public static final int HEIGHT = 200;
        private JPanel contentPane;
        private JTextField scanPath, targetPath;
        
        public AppFrame() {
            this.setTitle("Wechat Image Puller");
            this.setSize(WIDTH, HEIGHT);
            this.setResizable(false);
    
            // 设置关闭时退出JVM
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 设置窗体居中
            setLocationRelativeTo(null);
            // 内容面板
            contentPane = new JPanel();
            contentPane.setBorder(new EmptyBorder(10, 5, 5, 5));
            // 设置布局
            contentPane.setLayout(new BorderLayout(1, 1));
            setContentPane(contentPane);
            // 3行1列的表格布局
            JPanel panel = new JPanel(new GridLayout(3, 1, 2, 8));
            panel.setBorder(new TitledBorder(null, "", TitledBorder.LEADING, TitledBorder.TOP, null, null));
            // 给panel添加边框
            contentPane.add(panel, BorderLayout.CENTER);
    
            // 第一行
            JPanel panel_1 = new JPanel();
            panel.add(panel_1);
            JLabel label = new JLabel("扫描路径:");
            panel_1.add(label);
            scanPath = new JTextField();
            panel_1.add(scanPath);
            scanPath.setColumns(25);
            scanPath.setText(AppConfig.scanPath);
    
            // 第二行
            JPanel panel_2 = new JPanel();
            panel.add(panel_2);
            JLabel label2 = new JLabel("存储文件夹:");
            panel_2.add(label2);
            targetPath = new JTextField();
            panel_2.add(targetPath);
            targetPath.setColumns(25);
            targetPath.setText(AppConfig.targetPath);
    
            // 第三行
            JPanel panel_3 = new JPanel();
            panel.add(panel_3);
            JButton jbOk = new JButton("开始");
            panel_3.add(jbOk);
            JButton jbStop = new JButton("停止");
            panel_3.add(jbStop);
            jbStop.setEnabled(false);
            
            this.setVisible(true);
            this.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
    
            // 给开始按钮添加事件
            jbOk.addActionListener(e -> {
                AppConfig.setScanPath(scanPath.getText());
                AppConfig.setTargetPath(targetPath.getText());
    
                FileAlterationMonitorHolder.init();
                FileAlterationMonitorHolder.start();
                
                jbOk.setEnabled(false);
                jbStop.setEnabled(true);
                scanPath.setEditable(false);
                targetPath.setEditable(false);
            });
    
            // 给结束按钮添加事件
            jbStop.addActionListener(e -> {
                FileAlterationMonitorHolder.stop();
                jbOk.setEnabled(true);
                jbStop.setEnabled(false);
                scanPath.setEditable(true);
                targetPath.setEditable(true);
            });
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    通过界面启动:

    import com.xs.puller.frame.AppFrame;
    
    /**
     * 启动类
     * 
     * @author: jiangxiangbo
     * @date: 2022/8/3
     */
    public class ImagePullerApplication {
        public static void main(String[] args) {
            new AppFrame();
    ///        FileAlterationMonitorHolder.init();
    ///        FileAlterationMonitorHolder.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    效果:
    1659691180367.png

    2.6 其它类代码

    applcation.properties

    # 扫描文件扩展名
    scanFileType=.dat
    # 扫描路径
    scanPath=C:/Users/xs/Documents/WeChat Files/wxid_zxgfcregd22/FileStorage/MsgAttach
    # 扫描间隔 ms
    scanInterval=500
    # 目标文件夹
    targetPath=D:/data/wechat/temp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    AppConfig

    package com.xs.puller.config;
    
    import cn.hutool.setting.dialect.Props;
    
    /**
     * 应用配置
     * 
     * @author: jiangxiangbo
     * @date: 2022/8/4
     */
    public class AppConfig {
        
        /** 过滤缩略图 */
        public final static String FILTER_FILE_TAG = "_t.dat";
        
        private final static Props PROPS =  new Props("application.properties");
        
        /** 扫描文件扩展名 */
        public static String scanFileType = PROPS.getStr("scanFileType");
        
        /** 扫描路径 */
        public static String scanPath = PROPS.getStr("scanPath");
        
        /** 扫描间隔 ms */
        public static Long scanInterval = PROPS.getLong("scanInterval");
    
        /** 目标路径 */
        public static String targetPath = PROPS.getStr("targetPath");
    
        public static String getScanFileType() {
            return scanFileType;
        }
    
        public static void setScanFileType(String scanFileType) {
            AppConfig.scanFileType = scanFileType;
        }
    
        public static String getScanPath() {
            return scanPath;
        }
    
        public static void setScanPath(String scanPath) {
            AppConfig.scanPath = scanPath;
        }
    
        public static Long getScanInterval() {
            return scanInterval;
        }
    
        public static void setScanInterval(Long scanInterval) {
            AppConfig.scanInterval = scanInterval;
        }
    
        public static String getTargetPath() {
            return targetPath;
        }
    
        public static void setTargetPath(String targetPath) {
            AppConfig.targetPath = targetPath;
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    2.7 打包并转换为exe可执行文件

    参考:https://blog.csdn.net/weixin_47160526/article/details/123496190

    2.8 小程序下载链接

    链接:https://pan.baidu.com/s/1EgVjqgflYo6X7H3ve9qKcw?pwd=a8su
    提取码:a8su

  • 相关阅读:
    Microsoft.IO.RecyclableMemoryStream源码解读
    ESP32-C3入门教程 基础篇⑥——SPI通信点亮LCD显示屏
    Shiro和Spring Security对比
    拥抱Jetpack之印象篇
    thinkphp8路由
    分享一个学英语的网站
    Java 格式化时间与时间戳与时间间隔
    元宇宙基础知识全汇总
    创新案例分享 | 建设排污许可证后执法监管系统,完善当地生态环境数据资源
    rust 引用详解
  • 原文地址:https://blog.csdn.net/qq_44503377/article/details/126182327