Selaa lähdekoodia

优化文件上传后的备份处理

redclan 6 vuotta sitten
vanhempi
commit
d2e230be93

+ 5 - 0
dicom_monitor/pom.xml

@@ -88,6 +88,11 @@
 			<artifactId>commons-io</artifactId>
 			<version>1.3.2</version>
 		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.6</version>
+		</dependency>
 		<dependency>
 			<groupId>commons-lang</groupId>
 			<artifactId>commons-lang</artifactId>

+ 101 - 0
dicom_monitor/src/main/java/com/zskk/dicom/monitor/uploader/FileMover.java

@@ -0,0 +1,101 @@
+package com.zskk.dicom.monitor.uploader;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+
+import com.zskk.dicom.monitor.config.Configs;
+import com.zskk.dicom.monitor.report.ErrReporter;
+import com.zskk.dicom.monitor.utils.ExceptionUtil;
+import com.zskk.dicom.monitor.utils.FileHashUtil;
+import com.zskk.dicom.monitor.utils.MonitorFileUtils;
+
+public class FileMover implements Runnable {
+
+	File sourceFile;
+	File targetFile;
+
+	public FileMover(File sourceFile, File targetFile) {
+		this.sourceFile = sourceFile;
+		this.targetFile = targetFile;
+	}
+
+	@SuppressWarnings("static-access")
+	@Override
+	public void run() {
+		try {
+			// 暂停一秒,等待系统处理完文件的占用
+			Thread.currentThread().sleep(1000);
+		} catch (InterruptedException e1) {
+			e1.printStackTrace();
+		}
+		String targetFileStr;
+		try {
+			targetFileStr = targetFile.getCanonicalPath();
+			if (targetFile.exists()) {
+				// 如果移动的文件,已经在目标地址存在
+				// 那么,比对Hash值,看是否为同一个文件
+				// 如果是同一个文件,只需要删除当前文件,不必移除
+				// 如果不是同一个文件,另备份目录
+				Configs.sysLog.info("备份目标文件已经存在,需要比对文件Hash值");
+				String tHash = FileHashUtil.getFileMD5(targetFile);
+				String sHash = FileHashUtil.getFileMD5(sourceFile);
+				Configs.sysLog.info(sourceFile.getAbsolutePath() + "[" + sHash + "] ----->>----- " + targetFile.getAbsolutePath() + "[" + tHash + "]");
+				if (tHash.equals(sHash)) {
+					// 2个文件的Hash值相同
+					// 不再移动文件,删除原文件
+					boolean isSucDel = sourceFile.delete();
+					Configs.sysLog.info("删除源文件:" + sourceFile.getAbsolutePath() + "  结果:" + isSucDel);
+					if (!isSucDel) {
+						// 文件没有成功删除
+						ErrReporter.report("文件删除失败:" + sourceFile.getCanonicalPath());
+					}
+				} else {
+					// 2个文件的Hash值不同
+					targetFile = new File(targetFileStr + "2");
+					if (targetFile.exists()) {
+						// 如果备份文件也存在,
+						// 先删除,再将源文件移至此文件
+						targetFile.delete();
+					}
+					// 移动文件到另一个名称下
+					try {
+						FileUtils.moveFile(sourceFile, targetFile);
+						Configs.sysLog.info("文件移动:" + sourceFile.getCanonicalPath() + " to:" + targetFileStr + "2    成功");
+					} catch (IOException ioe) {
+						if (ioe instanceof FileNotFoundException) {
+							// 源文件已不存在
+							Configs.sysLog.info("文件移动:" + sourceFile.getCanonicalPath() + " to:" + targetFileStr + "2    " + ioe.getMessage());
+						} else {
+							// 文件没有成功删除
+							ErrReporter.report("文件移动失败:" + sourceFile.getCanonicalPath() + " to:" + targetFileStr + "2    " + ioe.getMessage());
+						}
+					}
+				}
+			} else {
+				// 上传完成后,移除文件
+				try {
+					FileUtils.moveFile(sourceFile, targetFile);
+					Configs.sysLog.info("文件移动:" + sourceFile.getCanonicalPath() + " to:" + targetFile.getAbsolutePath() + "    成功");
+				} catch (IOException ioe) {
+					if (ioe instanceof FileNotFoundException) {
+						// 源文件已不存在
+						Configs.sysLog.info("文件移动:" + sourceFile.getCanonicalPath() + " to:" + targetFile.getAbsolutePath() + "    " + ioe.getMessage());
+					} else {
+						// 文件没有成功删除
+						ErrReporter.report("文件移动失败:" + sourceFile.getCanonicalPath() + " to:" + targetFileStr + "    " + ioe.getMessage());
+					}
+				}
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+			// 文件没有成功删除
+			ErrReporter.report("文件(" + targetFile.getAbsolutePath() + ")获取getCanonicalPath时遇到错误:" + ExceptionUtil.getExceptionTxt(e));
+		}
+		// 移除空的目录
+		MonitorFileUtils.removeBeforeTodayEmptyDir(sourceFile.getParentFile());
+	}
+
+}

+ 25 - 12
dicom_monitor/src/main/java/com/zskk/dicom/monitor/uploader/FileUploader.java

@@ -12,6 +12,8 @@ import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import com.zskk.dicom.monitor.config.Configs;
 import com.zskk.dicom.monitor.report.ErrReporter;
@@ -20,25 +22,22 @@ import com.zskk.dicom.monitor.utils.MonitorFileUtils;
 
 public class FileUploader {
 
+	private static ExecutorService pools = Executors.newFixedThreadPool(2);;
+
 	public static void upload(File file) {
-		File dir = file.getParentFile();
 		try {
 			String boundaryKey = UUID.randomUUID().toString().replaceAll("-", "").substring(8, 24);
 			String content = "\r\n----" + boundaryKey + "\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Disposition: form-data; name=\"" + renameFileName(file.getName()) + "\"; filename=\"" + renameFileName(file.getName()) + "\"\r\n" + "Content-Transfer-Encoding: binary\r\n\r\n";
 			String postUrl = "http://" + Configs.postHost + ":" + Configs.postPort + Configs.postUri;
 			Boolean uploadResult = uploadToUrl(postUrl, boundaryKey, content, "", file);
-			// 创建目录
-			String targetFile = MonitorFileUtils.touchBackDir(file);
 			if (uploadResult == true) {
-				// 上传完成后,移除文件
-				boolean isSucDel = file.renameTo(new File(targetFile));
-				if (!isSucDel) {
-					// 文件没有成功删除
-					ErrReporter.report("文件移动失败:" + file.getCanonicalPath() + " to:" + targetFile);
-				}
+				// 创建目录
+				String targetFileStr = MonitorFileUtils.touchBackDir(file);
+				pools.execute(new FileMover(file, new File(targetFileStr)));
+			} else {
+				// 文件上传失败
+				ErrReporter.report("文件上传失败:" + file.getPath());
 			}
-			// 移除空的目录
-			MonitorFileUtils.removeEmptyDir(dir);
 		} catch (Exception e) {
 			ErrReporter.report(ExceptionUtil.getExceptionTxt(e));
 			e.printStackTrace();
@@ -65,6 +64,16 @@ public class FileUploader {
 				in = new FileInputStream(fromFile);
 			}
 		}
+		if (in == null) {
+			// 多线程竞争时,文件流可能是空的
+			// 第二次尝试
+			// 有些图片格式,会被系统临时占用,抛出文件被占用异常
+			Thread.currentThread().sleep(1000);
+			// 多线程竞争时,文件流可能是空的,读之前做一次判断,以免处理已经上传过的文件(或已被删除)
+			if (fromFile.exists()) {
+				in = new FileInputStream(fromFile);
+			}
+		}
 		if (in == null) {
 			// 多线程竞争时,文件流可能是空的
 			return false;
@@ -101,7 +110,11 @@ public class FileUploader {
 			while ((bytes = dataIn.read(bufferOut)) != -1) {
 				out.write(bufferOut, 0, bytes);
 			}
-			in.close();
+			dataIn.close();
+			try {
+				in.close();
+			} catch (Exception e) {
+			}
 			// 结尾部分
 			byte[] foot = (enddata).getBytes("utf-8");// 定义最后数据分隔线
 			out.write(foot);

+ 140 - 0
dicom_monitor/src/main/java/com/zskk/dicom/monitor/utils/FileHashUtil.java

@@ -0,0 +1,140 @@
+package com.zskk.dicom.monitor.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * 得到文件Md5值<br />
+ * 文件比较时使用
+ * 
+ * @author dandian
+ *
+ */
+public class FileHashUtil {
+
+	private static final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+	/**
+	 * Get MD5 of a file (lower case)
+	 * 
+	 * @return empty string if I/O error when get MD5
+	 */
+	public static String getFileMD5(File file) {
+
+		FileInputStream in = null;
+		try {
+			in = new FileInputStream(file);
+			FileChannel ch = in.getChannel();
+			return MD5(ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
+		} catch (FileNotFoundException e) {
+			return "";
+		} catch (IOException e) {
+			return "";
+		} finally {
+			if (in != null) {
+				try {
+					in.close();
+				} catch (IOException e) {
+					// 关闭流产生的错误一般都可以忽略
+				}
+			}
+		}
+
+	}
+
+	/**
+	 * MD5校验字符串
+	 * 
+	 * @param s
+	 *            String to be MD5
+	 * @return 'null' if cannot get MessageDigest
+	 */
+
+	private static String getStringMD5(String s) {
+		MessageDigest mdInst;
+		try {
+			// 获得MD5摘要算法的 MessageDigest 对象
+			mdInst = MessageDigest.getInstance("MD5");
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+			return "";
+		}
+
+		byte[] btInput = s.getBytes();
+		// 使用指定的字节更新摘要
+		mdInst.update(btInput);
+		// 获得密文
+		byte[] md = mdInst.digest();
+		// 把密文转换成十六进制的字符串形式
+		int length = md.length;
+		char str[] = new char[length * 2];
+		int k = 0;
+		for (byte b : md) {
+			str[k++] = hexDigits[b >>> 4 & 0xf];
+			str[k++] = hexDigits[b & 0xf];
+		}
+		return new String(str);
+	}
+
+	@SuppressWarnings("unused")
+	private static String getSubStr(String str, int subNu, char replace) {
+		int length = str.length();
+		if (length > subNu) {
+			str = str.substring(length - subNu, length);
+		} else if (length < subNu) {
+			// NOTE: padding字符填充在字符串的右侧,和服务器的算法是一致的
+			str += createPaddingString(subNu - length, replace);
+		}
+		return str;
+	}
+
+	private static String createPaddingString(int n, char pad) {
+		if (n <= 0) {
+			return "";
+		}
+
+		char[] paddingArray = new char[n];
+		Arrays.fill(paddingArray, pad);
+		return new String(paddingArray);
+	}
+
+	/**
+	 * 计算MD5校验
+	 * 
+	 * @param buffer
+	 * @return 空串,如果无法获得 MessageDigest实例
+	 */
+
+	private static String MD5(ByteBuffer buffer) {
+		String s = "";
+		try {
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			md.update(buffer);
+			byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数,
+			// 用字节表示就是 16 个字节
+			char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符,
+			// 所以表示成 16 进制需要 32 个字符
+			int k = 0; // 表示转换结果中对应的字符位置
+			for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节
+				// 转换成 16 进制字符的转换
+				byte byte0 = tmp[i]; // 取第 i 个字节
+				str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, >>>,
+				// 逻辑右移,将符号位一起右移
+				str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
+			}
+			s = new String(str); // 换后的结果转换为字符串
+
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+		}
+		return s;
+	}
+
+}

+ 27 - 0
dicom_monitor/src/main/java/com/zskk/dicom/monitor/utils/MonitorFileUtils.java

@@ -1,6 +1,10 @@
 package com.zskk.dicom.monitor.utils;
 
 import java.io.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 
 import com.zskk.dicom.monitor.config.Configs;
 
@@ -25,7 +29,30 @@ public class MonitorFileUtils {
 		}
 	}
 
+	public static void removeBeforeTodayEmptyDir(File dirFile) {
+		// 文件的上级目录
+		String fs[] = dirFile.list();
+		if (fs == null || fs.length < 1) {
+			DateFormat df = new SimpleDateFormat("yyyyMMdd");
+			Calendar ca = Calendar.getInstance();
+			ca.setTimeInMillis(dirFile.lastModified());
+			String fileDay = df.format(ca.getTime());
+			String curDay = df.format(new Date());
+			if (!fileDay.equals(curDay)) {
+				String filePath = dirFile.getAbsolutePath();
+				// 不是同一天
+				Boolean delRs = dirFile.delete();
+				if (delRs) {
+					Configs.sysLog.info("Dir is delete suc:" + filePath);
+				} else {
+					Configs.sysLog.info("Dir is delete fail:" + filePath);
+				}
+			}
+		}
+	}
+
 	/**
+	 * 检测备份目录并返回备份目标文件(路径)
 	 * 
 	 * @param dcomFile
 	 * @return