(1)客户端自动上传流程图
(2)客户端自动上传流程:
-
监听指定文件夹的创建事件,并存储到队列中
-
从队列中取出创建事件,检查事件中文件是否已写入完成
-
写入完成后执行上传操作,如果上传失败,则重试三次
-
上传成功,或失败则生成与影像名称相同的占位符来作为上传操作的标志(成功 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 这个服务,并且服务状态是正在运行,启动类型是自动,这样整个上传客户端就安装完成了。