JAVA 上传客户端

(1)客户端自动上传流程图

(2)客户端自动上传流程:

  1. 监听指定文件夹的创建事件,并存储到队列中

  2. 从队列中取出创建事件,检查事件中文件是否已写入完成

  3. 写入完成后执行上传操作,如果上传失败,则重试三次

  4. 上传成功,或失败则生成与影像名称相同的占位符来作为上传操作的标志(成功 filename.success  失败 filename.fail)

(3)代码解析
        1)当JVM启动时初始化一个文件监听器
package com.xxx.teis;

import com.xxx.teis.monitor.FileWatcher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootApplication
public class ListenerFileChangeApplication {

    private static void initWatcher() {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        //初始化文件监听器
        pool.execute(new FileWatcher());

    }

    public static void main(String[] args) {
        initWatcher();
        SpringApplication.run(ListenerFileChangeApplication.class, args);
    }
}

2)开启目录监听

package com.cfcc.teis.monitor;

import com.cfcc.teis.listener.FileWatchedListener;
import com.sun.nio.file.SensitivityWatchEventModifier;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.LinkedHashMap;

/**
* @author gzy
* 目录监听,对目录开启事件监听
*/
public class WatchDir {
    private final WatchService watcher;
    private final LinkedHashMap<WatchKey, Path> keys;
    private final boolean subDir;
    private FileWatchedListener listener;
    
    public static void match(String glob, String path) throws IOException {

        Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult visitFile(Path path,
                                             BasicFileAttributes attrs) throws IOException {

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc)
                    throws IOException {
                return FileVisitResult.CONTINUE;
            }
        });
    }

    public WatchDir(File file, boolean subDir, FileWatchedListener fileWatchedListener/*, FileActionCallback callback*/) throws Exception {
        if (!file.isDirectory())
            throw new Exception(file.getAbsolutePath() + "is not a directory!");

        //获取文件系统监听器
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new LinkedHashMap<>();
        this.subDir = subDir;
        this.listener = fileWatchedListener;

        Path dir = Paths.get(file.getAbsolutePath());

        if (subDir) {
            registerAll(dir);
        } else {
            register(dir);
        }

        processEvents();
    }

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>) event;
    }

    /**
     * @param dir
     * @throws IOException
     * 注册要监听的事件类型,文件增、删、改
     * SensitivityWatchEventModifier.HIGH 2秒
     * SensitivityWatchEventModifier 控制调度器的时间间隔,时间间隔分别为 2秒 10秒 30秒,默认为10秒
     * 如果设置为 2秒,那么WatchService 捕获到监听时间的更短,实时性会好一点
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher,new WatchEvent.Kind[] {
                        StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_MODIFY
                }, SensitivityWatchEventModifier.HIGH);
        keys.put(key, dir);
    }

    /**
     * @param start
     * @throws IOException
     */
    private void registerAll(final Path start) throws IOException {
        //start 表示从start 节点开始遍历文件系统
        //visitor 是遍历过程中的行为控制器
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            //这里在包装类SimpleFileVisitor 中实现了preVisitDirectory 这个表示在访问目录之前要做的事
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                //注册目录的事件监听
                register(dir);
                //继续遍历
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 对创建的目录及它的下级目录注册事件
     * @param newDir
     * @throws IOException
     */
    void registerMultistageDir(Path newDir) throws IOException {
        register(newDir);

        File[] files = newDir.toFile().listFiles();
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                registerMultistageDir(files[i].toPath());
            }
        }
    }

    @SuppressWarnings("rawtypes")
    void processEvents() {

        for (;;) {
            WatchKey key;
            try {
                //阻塞获取转备好的事件
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }
            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("Operation unrecognized");
                continue;
            }
            
            //处理准备好的事件
            for (WatchEvent<?> event : key.pollEvents()) {
                //获取事件的类型
                Kind kind = event.kind();

                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    continue;
                }

                // Changes in directories may be files or directories
                WatchEvent<Path> ev = cast(event);
                //返回触发该事件的那个文件或目录的路径
                Path name = ev.context();
                Path child = dir.resolve(name);
                File file = child.toFile();

                //根据文件变化调用不同的回调函数
                if (kind.name().equals(FileAction.DELETE.getValue())) {
                    listener.onDeleted(file);
                    //callback.delete(file);
                } else if (kind.name().equals(FileAction.CREATE.getValue())) {
                    listener.onCreated(file);
                    //callback.create(file);
                } else if (kind.name().equals(FileAction.MODIFY.getValue())) {
                    listener.onModified(file);
                    //callback.modify(file);
                } else {
                    continue;
                }

                // if directory is created, and watching recursively, then
                // register it and its sub-directories
                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    if (Files.isDirectory(child)) {
                        try {
                            //对创建的目录注册事件
                            registerMultistageDir(child);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            boolean valid = key.reset();
            if (!valid) {
                // Remove inaccessible directories
                // Because it is possible to remove the directory, it will not be accessible.
                keys.remove(key);
                // If none of the directories to be monitored exists, execution is interrupted
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }
}

3)根据事件类型,调用不同的回调函数来处理

package com.cfcc.teis.listener;

import com.cfcc.teis.common.constants.Constants;
import com.cfcc.teis.common.spring.SpringContextHolder;
import com.cfcc.teis.config.SystemConfig;
import com.cfcc.teis.monitor.FileUploadCallback;
import com.cfcc.teis.monitor.FileWatcher;
import com.cfcc.teis.monitor.PictureType;
import com.cfcc.teis.upload.thread.UploadThread;
import com.cfcc.teis.util.FileSearchUtil;
import com.cfcc.teis.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @Author: gzy
* @Date: 2020/9/5
* @Descript: 文件监听适配器
*/
public class FileWatchedAdapter implements FileWatchedListener{

    private Logger logger = LoggerFactory.getLogger(FileWatcher.class);
    private ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(4);

    @Override
    public void onCreated(File file) {

        try {

            logger.info("文件已创建\t" + file.getAbsolutePath());
            // add some actions
            //这里使用匹配器对需要添加监听事件的文件进行匹配
            final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:d:/pictures/**/**/*.{jpg}");
            //图片类型
            PictureType pictureType = null;
            //如果是一个图片
            if (pathMatcher.matches(Paths.get(file.getAbsolutePath()))) {
                //根据文件路径来确定上传的接口
                String absolutePath = file.getAbsolutePath();
                if (absolutePath.contains(Constants.VOUCHER_FILE_PATH)) {
                    pictureType = PictureType.VOUCHER;
                } else if (absolutePath.contains(Constants.ANNEX_FILE_PATH)) {
                    pictureType = PictureType.ANNEX;
                } else if (absolutePath.contains(Constants.BILL_FILE_PATH)) {
                    pictureType = PictureType.BILL;
                } else if (absolutePath.contains(Constants.OTHERS_FILE_PATH)){
                    pictureType = PictureType.OTHERS;
                } else {
                    logger.error("unknown picture type");
                }
                //PictureType pictureType = Enum.valueOf(PictureType.class, photoType);
                SystemConfig context = SpringContextHolder.getBean(SystemConfig.class);
                String user = context.getUser();
                String bookorgcode = StringUtil.getBookOrgFromPath(absolutePath);
                String acct = StringUtil.getAcctFromPath(absolutePath);
                String fileName = file.getName();
                String fileNameOfNoSuffix = fileName.substring(0, fileName.indexOf("."));
                switch (pictureType) {
                    case BILL:
                        if (!StringUtil.isBill(fileNameOfNoSuffix)) {
                            logger.info("this file {} don't upload", file.getName());
                            break;
                        }

                        String billUploadUrl = context.getBillUploadUrl();
                        uploadFile(file, billUploadUrl, acct, user, bookorgcode);
                        break;
                    case VOUCHER:
                        if (!StringUtil.isVoucher(fileNameOfNoSuffix)) {
                            logger.info("this file {} don't upload", file.getName());
                            break;
                        }
                        String voucherUploadUrl = context.getVoucherUploadUrl();
                        uploadFile(file, voucherUploadUrl, acct, user, bookorgcode);
                        break;
                    case ANNEX:
                        if (!StringUtil.isAnnex(fileNameOfNoSuffix)) {
                            logger.info("this file {} don't upload", file.getName());
                            break;
                        }

                        String annexUploadUrl = context.getAnnexUploadUrl();
                        uploadFile(file, annexUploadUrl, acct, user, bookorgcode);
                        break;

                    case OTHERS:
                        if (!StringUtil.isOthers(fileNameOfNoSuffix)) {
                            logger.info("this file {} don't upload", file.getName());
                            break;
                        }
                        String othersUploadUrl = context.getOthersUploadUrl();
                        uploadFile(file, othersUploadUrl, acct, user, bookorgcode);
                        break;
                    default:
                        logger.error("当前影像是未知类型!");
                        break;
                }
            }
        } catch (Exception e) {
            logger.error("create file excute fail, message is {}", e.getMessage());
        }

    }

    @Override
    public void onDeleted(File file) {

        try {
            System.out.println("文件已删除\t" + file.getAbsolutePath());
            // add some actions
            final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:d:/pictures/**/*.{jpg}");
            if (pathMatcher.matches(Paths.get(file.getAbsolutePath()))) {
                String fold = file.getParent();
                String fileName = file.getName().substring(0, file.getName().indexOf("."));
                //主文件删除后,标志位文件也删除
                List<File> files = FileSearchUtil.searchFiles(new File(fold), fileName);
                for (File file1 : files) {
                    file1.delete();
                }
            }
        } catch (Exception e) {
            logger.error("create file excute fail, message is {}", e.getMessage());
        }

    }

    @Override
    public void onModified(File file) {
        System.out.println("文件已修改\t" + file.getAbsolutePath());
        // add some actions
    }

    @Override
    public void onOverflowed(File file) {

    }

    private void uploadFile(File wrapper, String url, String dAcct, String userName, String sBookorgcode) {

        pool.schedule(new UploadThread(wrapper, url, dAcct, userName, sBookorgcode, new FileUploadCallback() {
            @Override
            public void creteFile(File file, String flag) {
                String absolutePath = file.getAbsolutePath();
                int index = absolutePath.indexOf(".");
                String fileName = absolutePath.substring(0, index);
                File newFile = null;
                if ("success".equals(flag)) {
                    newFile = new File(fileName + ".success");
                } else {
                    newFile = new File(fileName + ".fail");
                }
                try {
                    newFile.createNewFile();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }

        }), 1, TimeUnit.SECONDS);
    }
}

4.客户端封装

1)下载winsw https://github.com/winsw/winsw/releases

2)xml 文件配置

<configuration>
  
  <!-- ID of the service. It should be unique accross the Windows system-->
  <id>uploader</id>
  <!-- Display name of the service -->
  <name>uploader</name>
  <!-- Service description -->
  <description>uploader is a service cratead from a minimal configuration</description>
  
  <!-- Path to the executable, which should be started -->
  <executable>jdk1.8.0_181/bin/java.exe</executable>
  <arguments>-jar uploader.jar</arguments>
   <!-- 开机启动 -->
  <startmode>Automatic</startmode>
  <!-- 日志配置 -->
  <logpath>logs/service</logpath>
  <logmode>rotate</logmode>
</configuration>

3)安装客户端

在file-upload 文件夹下双击执行upload_install.bat 脚本

4)启动服务

5)查看是否安装并启动成功 

选中 计算机(我的电脑) à右键管理

查看服务中是否有uploader  这个服务,并且服务状态是正在运行,启动类型是自动,这样整个上传客户端就安装完成了。

代码工程目录:https://gitee.com/duxingke123/teis-uploader

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独行客-编码爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值