基于Java 的增量与完全备份小工具

时间:2023-10-01 19:50:14

前段时间,因为各种原因,自己动手写了一个小的备份工具,用了一个星期,想想把它的设计思路放上来,当是笔记吧。

需求场景:这个工具起初的目的是为了解决朋友公司对其网络的限制(不可以用任何同步软件,git,外网SVN,U盘只读)。本来只是想做一个自动打包和发送邮件的工具,后来就发展成了这个。

软件功能:这个软件最终实现的功能包括1、读取配置文件,对配置文件中指定目录的文件进行日期检测,获取对应修改过的文件。2、将读出的文件进行压缩;3、将压缩文件发送到指定邮箱;4、对压缩文件进行历史版本的保留

根据最后的功能需求,工具最后选择采用的API包如下:

1、  使用commons-config实现配置文件的读写

2、  使用commons-io实现文件的检测和读写

3、  使用zip4j实现对文件的压缩

4、  使用javamail实现邮件的发送

系统最后的流程图如下:

基于Java 的增量与完全备份小工具

主要的工具类如下:

 //用于发送邮件的Bean
package com.sean.bean; import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Properties; import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
/**
* 邮件类
* @author Sean
* @blog http://www.cnblogs.com/Seanit/
* @email hxy9104@126.com
* 2015-6-9
*/
public class EmailBean {
private Session session;
private MimeMessage message;
private Properties properties;
private MimeMultipart body; /**
* 默认构造函数
*/
public EmailBean(){
} public EmailBean(Properties properties){
this.properties=properties;
createMail(properties);
} public EmailBean(String src){
this.properties=new Properties();
try {
properties.load(new InputStreamReader(ClassLoader.getSystemResourceAsStream(src),"utf-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
createMail(properties);
} /**
* 创建连接
* @author Sean
* 2015-6-8
* @param properties 邮箱相关配置
*/
public void createMail(Properties properties) {
this.properties=properties;
try {
session=Session.getDefaultInstance(properties,null);
message=new MimeMessage(session);
body=new MimeMultipart();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("邮件初始化失败");
}
} /**
* 设置发件人
* @author Sean
* 2015-6-8
* @param from 发件人地址
* @return 成功返回true,失败防护false
*/
public boolean setFrom(String from){
try {
message.setFrom(new InternetAddress(from));
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 设置收件人
* @author Sean
* 2015-6-8
* @param toSend 收件人邮箱地址
* @return 成功返回true,失败防护false
*/
public boolean setTOSend(String toSend){
try {
message.setRecipients(RecipientType.TO, toSend);
return true;
} catch (MessagingException e) {
e.printStackTrace();
return false;
}
} /**
* 设置抄送人
* @author Sean
* 2015-6-8
* @param copyTo 抄送人地址
* @return 成功返回true,失败防护false
*/
public boolean setCopyTO(String copyTo){
try {
message.setRecipients(RecipientType.CC, (Address[])InternetAddress.parse(copyTo));
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 设置主题
* @author Sean
* 2015-6-8
* @param subject 邮件主题
* @return 成功返回true,失败防护false
*/
public boolean setSubject(String subject){
try {
message.setSubject(subject);
return true;
} catch (MessagingException e) {
e.printStackTrace();
return false;
}
} /**
* 设置邮件正文
* @author Sean
* 2015-6-8
* @param content 邮件正文
* @return 成功返回true,失败防护false
*/
public boolean setContent(String content){
try {
BodyPart bodyPart=new MimeBodyPart();
bodyPart.setContent(""+content, "text/html;charset=GBK");
body.addBodyPart(bodyPart);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 添加附件
* @author Sean
* 2015-6-8
* @param src 附件地址
* @return
*/
public boolean addFiles(String src){
try {
BodyPart bodyPart=new MimeBodyPart();
FileDataSource file =new FileDataSource(src);
bodyPart.setDataHandler(new DataHandler(file));
bodyPart.setFileName(file.getName());
body.addBodyPart(bodyPart);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 发送邮件
* @author Sean
* 2015-6-8
* @return
*/
public boolean send(){
Transport transport=null;
try {
message.setContent(body);
message.saveChanges();
transport=session.getTransport();
transport.connect(properties.getProperty("mail.smtp.host"), properties.getProperty("username"), properties.getProperty("password"));
transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
if(message.getRecipients(Message.RecipientType.CC)!=null){
transport.sendMessage(message, message.getRecipients(Message.RecipientType.CC));
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}finally{
try {
transport.close();
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
 //用于操作配置文件的bean
package com.sean.bean; import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
/**
* 配置文件操作类
* 实现功能:对properties 文件进行读写操作
* @author Sean
* 2015-6-6
*/
public class PropertiesBean {
private String src="";
private PropertiesConfiguration pc=null;
/**
* 默认构造函数
*/
public PropertiesBean() {}
public PropertiesBean(String src) {
this.src=src;
init();
} /**
* 初始化函数
* @author Sean
* 2015-6-6
*/
public void init(){
if(src.trim().equals("")){
throw new RuntimeException("The path is null");
}
try {
pc=new PropertiesConfiguration(src);
} catch (ConfigurationException e) {
e.printStackTrace();
}
} /**
* 取值函数,根据对应的关键字获取对应的值
* @author Sean
* 2015-6-6
* @param key 关键字
* @return 返回关键字对应的值,若无该关键字,抛出异常
*/
public String getValue(String key){
if(!pc.containsKey(key)){
throw new RuntimeException("not such a key");
}
return pc.getString(key);
} /**
* 设置对应的值,传入键值对,根据关键字修改对应的值
* @author Sean
* 2015-6-6
* @param key 关键字
* @param value 值
*/
public void setValue(String key,String value){
if(!pc.containsKey(key)){
throw new RuntimeException("not such a key");
}
pc.setProperty(key, value);
try {
pc.save();
} catch (ConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} /**
* 设置配置文件的地址
* @author Sean
* 2015-6-6
* @param src 配置文件的路径
*/
public void setSrc(String src) {
this.src = src;
}
}
//用于进行IO操作的Bean

package com.sean.bean;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; import org.apache.commons.io.FileUtils;
/**
* 文件操作工具类
* @author Sean
* @blog http://www.cnblogs.com/Seanit/
* @email hxy9104@126.com
* 2015-6-6
*/
public class FilesBean {
private List<File> list=null;
public FilesBean(){} /**
* 获取文件夹目录下的列表,目录为空返回空列表
* @param dir 传入对应文件夹目录
* @param ifFirst 控制递归时候的循环表示
* @return 返回list<File>文件列表
*/
public List<File> listFiles(String dir,boolean ifFirst){
/**
* 递归判断,仅在第一次调用函数的时候新建列表,为防止重复存入列表
*/
if(ifFirst){
list=new ArrayList<File>();
} if (dir==null||dir=="") {
return null;
}
for (File f : FileUtils.getFile(dir + "\\").listFiles()) { if (f.isDirectory()) {
listFiles(f.getAbsolutePath(),false);
} else {
list.add(f);
}
}
return list;
} /**
* 根据过滤条件获取列表
* @param dir 目录
* @param filter 过滤条件
* @param ifFirst 递归标识
* @return 返回文件列表
*/
public List<File> listFiles(String dir,String filter,boolean ifFirst){
/**
* 递归判断,仅在第一次调用函数的时候新建列表,为防止重复存入列表
*/
if(ifFirst){
list=new ArrayList<File>();
} if (dir==null||dir=="") {
return null;
}
for (File f : FileUtils.getFile(dir + "\\").listFiles()) { if (f.isDirectory()) {
listFiles(f.getAbsolutePath(),filter,false);
} else {
if(!f.getName().endsWith(filter)){
list.add(f);
} }
}
return list;
} /**
* 获取指定目录下,指定日期修改后的文件
* @param dir 传入指定检测目录
* @param date 传入指定修改日期
* @param ifFirst 控制递归时候的循环表示
* @return 返回获取的文件列表,若目录或日期为空,则返回null
*/
public List<File> getModifiedFiles(String dir,Date date,boolean ifFirst){
/**
* 递归判断,仅在第一次调用函数的时候新建列表,为防止重复存入列表
*/
if(ifFirst){
list=new ArrayList<File>();
} if (dir==null||dir==""||date==null) {
return null;
} for (File f : FileUtils.getFile(dir + "\\").listFiles()) {
if (f.isDirectory()) {
getModifiedFiles(f.getAbsolutePath(),date,false);
} else {
if (FileUtils.isFileNewer(f, date.getTime())) {
list.add(f);
}
}
}
return list;
} /**
* 获取某一日期之前创建的文件
* @author Sean
* 2015-6-9
* @param dir 文件路径
* @param ifFirst 控制递归时候的循环表示
* @param date 日期
* @return 返回列表
*/
public List<File> getOlderFiles(String dir,Date date,boolean ifFirst){
/**
* 递归判断,仅在第一次调用函数的时候新建列表,为防止重复存入列表
*/
if(ifFirst){
list=new ArrayList<File>();
} if (dir==null||dir==""||date==null) {
return null;
} for (File f : FileUtils.getFile(dir + "\\").listFiles()) {
if (f.isDirectory()) {
getOlderFiles(f.getAbsolutePath(),date,false);
} else {
if (FileUtils.isFileOlder(f, date)) {
list.add(f);
}
}
}
return list;
} /**
* 获取除过滤条件外的修改文件列表
* @param dir
* @param date
* @param filter
* @param ifFirst
* @return
*/
public List<File> getModifiedFiles(String dir,Date date,String filter,boolean ifFirst){
/**
* 递归判断,仅在第一次调用函数的时候新建列表,为防止重复存入列表
*/
if(ifFirst){
list=new ArrayList<File>();
} if (dir==null||dir==""||date==null) {
return null;
} for (File f : FileUtils.getFile(dir + "\\").listFiles()) {
if (f.isDirectory()) {
getModifiedFiles(f.getAbsolutePath(),date,filter,false);
} else {
if (FileUtils.isFileNewer(f, date.getTime())) {
if(!f.getName().endsWith(filter)){
list.add(f);
} }
}
}
return list;
} }
//用于压缩文件的工具类
package com.sean.utils; import java.io.File;
import java.util.ArrayList; import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants; public class ZipUtil {
/**
* 压缩文件列表,无目录结构
* @author Sean
* 2015-6-6
* @param zipSrc zip文件地址
* @param list 文件列表
* @return 若成功,返回文件地址,不成功返回null
*/
public static String zipFiles(String zipSrc,ArrayList<File> list){
ZipParameters zipParameters=new ZipParameters();
zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_MAXIMUM);
ZipFile zipFile=null;
try {
zipFile=new ZipFile(zipSrc);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
try {
zipFile.addFiles(list, zipParameters);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return zipSrc;
} /**
* 压缩文件夹
* @author Sean
* 2015-6-6
* @param src 文件夹地址
* @param zipSrc 压缩文件存放地址
* @return 若成功,返回文件夹地址,若不成功,返回null
*/
public static String zipFolder(String src,String zipSrc){
ZipParameters zipParameters=new ZipParameters();
zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_MAXIMUM);
ZipFile zipFile=null;
try {
zipFile=new ZipFile(zipSrc);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
try {
zipFile.addFolder(src, zipParameters);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return zipSrc;
} /**
* 往zip文件中添加文件列表
* @author Sean
* 2015-6-7
* @param zipSrc zip文件路径
* @param list 要添加的文件列表
* @param src 要添加到zip文件中的哪个路径
*/
public static void addFileToZip(String zipSrc,ArrayList<File> list,String src){
ZipFile zipFile=null;
try {
zipFile=new ZipFile(zipSrc);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ZipParameters zipParameters=new ZipParameters();
zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
zipParameters.setRootFolderInZip(src);
try {
zipFile.addFiles(list, zipParameters);
} catch (ZipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } /**
* 添加文件列表到zip根目录下
* @author Sean
* 2015-6-7
* @param zipSrc zip文件地址
* @param list 添加的文件列表
*/
public static void addFileToZip(String zipSrc,ArrayList<File> list){
addFileToZip(zipSrc,list,"");
} }
主流程:
package com.sean.main; import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils; import com.sean.bean.EmailBean;
import com.sean.bean.FilesBean;
import com.sean.bean.PropertiesBean;
import com.sean.utils.ZipUtil; /**
* change2Mail 主程序
* 检测文件修改情况并发送给指定邮箱
* @version 1.0
* @author sean
* 2015-06-09
*
*/
public class Change2Mail { /**
* @param args
* @throws ParseException
*/
public static void main(String[] args) throws ParseException{
//获取properties操作类
PropertiesBean propertiesBean = new PropertiesBean("cfg.properties");
FilesBean filesBean=new FilesBean();
//邮件对象
EmailBean email=new EmailBean("cfg.properties");
String first = propertiesBean.getValue("first");
//当前日期
String today=new SimpleDateFormat("yyyyMMdd").format(new Date());
//邮件正文
String content="";
//首次使用判断
if (first.trim().equals("true")) {
System.out.println("首次运行");
content+="欢迎首次使用change2Mail,<br/>以下是你的更改文件备份目录<br/>";
//获取检查文件目录
String cheakDir = propertiesBean.getValue("cheakDir");
if(!StringUtils.isEmpty(cheakDir)){
System.out.println("正在进行更改文件目录备份。。。。。。");
//目录分割
String[] dirs = cheakDir.split(";");
for (int i = 0; i < dirs.length; i++) {
File tmp=new File(dirs[i]);
List<File> list=filesBean.listFiles(dirs[i],true);
//创建对应文件存储目录列表
File file=new File(ClassLoader.getSystemResource("").getPath()+tmp.getName());
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("创建目录列表记录文件失败。。。。。");
return ;
}
}
//将列表写入文件
logFiles(file,list);
//压缩文件并加入邮件
try {
ZipUtil.zipFolder(dirs[i], "tmp\\"+tmp.getName()+today+".zip");
content+=tmp.getName()+"<br/>";
email.addFiles("tmp\\"+tmp.getName()+today+".zip");
} catch (Exception e) {
e.printStackTrace();
System.out.println("压缩出错");
return ;
} }
}
content+="以下是你的固定备份目录<br/>";
String zipDir = propertiesBean.getValue("zipDir");
if(!StringUtils.isEmpty(zipDir)){
System.out.println("正在进行固定备份目录备份。。。。。。");
String[] zipDirs = zipDir.split(";");
for (int i = 0; i < zipDirs.length; i++) {
try {
File tmp=new File(zipDirs[i]);
ZipUtil.zipFolder(zipDirs[i], "tmp\\"+tmp.getName()+today+"bak.zip");
content+=tmp.getName()+"<br/>";
email.addFiles("tmp\\"+tmp.getName()+today+"bak.zip");
} catch (Exception e) {
e.printStackTrace();
System.out.println("压缩出错");
return ;
}
}
}
System.out.println("更新配置文件。。。。。");
propertiesBean.setValue("first", "false");
propertiesBean.setValue("lastData", today);
System.out.println("发送文件。。。。。");
email.setFrom(propertiesBean.getValue("username"));
email.setTOSend(propertiesBean.getValue("toSend"));
email.setContent(content);
email.setSubject("change2Mail备份文件"+today);
if(email.send()){
System.out.println("发送成功");
}else{
System.out.println("发送失败");
} } else {
System.out.println("备份开始。。。。。");
String data=propertiesBean.getValue("lastData");
String filter=propertiesBean.getValue("filter");
String cheakDir = propertiesBean.getValue("cheakDir");
content+="你好,自"+data+"起的文件变动如下:<br/>";
if(!StringUtils.isEmpty(cheakDir)){
System.out.println("正在进行更改文件目录备份。。。。。。");
String[] dirs = cheakDir.split(";");
for (int i = 0; i < dirs.length; i++) {
File tmp=new File(dirs[i]);
File file=new File(ClassLoader.getSystemResource("").getPath()+tmp.getName());
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
System.out.println("创建文档失败");
e.printStackTrace();
}
}
content+="文件夹"+tmp.getName()+":<br/>";
List<File> list=filesBean.getModifiedFiles(dirs[i],new SimpleDateFormat("yyyyMMdd").parse(data),filter,true);
List<File> now =filesBean.listFiles(dirs[i],filter,true);
List<File> news=new ArrayList<File>();
List<File> del=new ArrayList<File>();
List<File> modefy=new ArrayList<File>();
List<String> lines=null;
try {
lines=FileUtils.readLines(file);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("检查新建和修改的文件。。。。。。");
for(File f:list){
if(!lines.contains(f.getAbsolutePath())){
news.add(f);
}else{
modefy.add(f);
}
} System.out.println("检查删除的文件。。。。。。");
for(String f:lines){
File old=new File(f);
if(!now.contains(old)){
del.add(old);
}
}
logFiles(file,now);
content+="删除文件:<br/>";
for(File f:del){
content+=" "+f.getName()+"<br/>";
}
content+="新增文件:<br/>";
for(File f:news){
content+=" "+f.getName()+"<br/>";
}
content+="修改文件:<br/>";
for(File f:modefy){
content+=" "+f.getName()+"<br/>";
}
try {
if(list.size()!=0){
list.add(file);
ZipUtil.zipFiles("tmp\\"+tmp.getName()+today+".zip", (ArrayList<File>)list); email.addFiles("tmp\\"+tmp.getName()+today+".zip");
} } catch (Exception e) {
e.printStackTrace();
System.out.println("压缩失败");
return;
} } String zipDir = propertiesBean.getValue("zipDir");
if(!StringUtils.isEmpty(zipDir)){
System.out.println("正在进行固定备份目录备份。。。。。。");
String[] zipDirs = zipDir.split(";");
for (int i = 0; i < zipDirs.length; i++) {
try {
File tmp=new File(zipDirs[i]);
ZipUtil.zipFolder(zipDirs[i], "tmp\\"+tmp.getName()+today+"bak.zip");
email.addFiles("tmp\\"+tmp.getName()+today+"bak.zip");
} catch (Exception e) {
e.printStackTrace();
System.out.println("压缩出错");
return ;
}
}
} System.out.println("更改配置文件。。。。。");
propertiesBean.setValue("lastData", today);
System.out.println("发送文件。。。。。");
email.setFrom(propertiesBean.getValue("username"));
email.setTOSend(propertiesBean.getValue("toSend"));
email.setContent(content);
email.setSubject("change2Mail备份文件"+today);
if(email.send()){
System.out.println("发送成功");
}else{
System.out.println("发送失败");
} SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
long to = df.parse(today).getTime();
long todel=Integer.parseInt(propertiesBean.getValue("time"))*(1000 * 60 * 60 * 24);
Date lastBak=new Date(to-todel) ;
System.out.println("删除"+df.format(lastBak)+"之前保存的历史文件。。。。。");
List<File> fileToDel=filesBean.getOlderFiles("tmp\\", lastBak,true);
for(File f:fileToDel){
try {
FileUtils.deleteQuietly(f);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("删除"+f.getName()+"文件失败");
} }
System.out.println(new Date());
}
}
}
/**
* 将文件列表的绝对地址输出到一个文件中
* @param file 输出文件
* @param list 文件列表
* @return 成功返回true,失败返回false
*/
public static boolean logFiles(File file, List<File> list) {
List<String> lines=new ArrayList<String>();
try {
for (File f : list) {
lines.add(f.getAbsolutePath());
}
FileUtils.writeLines(file, lines,false);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
} }
配置文件:
#autho:sean
#data:2015/05/26
#email:hxy9104@126.com #mail config javamail的配置
mail.debug = true
mail.smtp.auth = true
mail.smtp.host = smtp.126.com
mail.transport.protocol = smtp #mail info 邮箱设置
username = 12121@126.com
password = 121212
toSend = 121212@qq.com #soft config 软件设置
#if the fisrt time to run 是否第一次运行
first = true
#the path to check 检测的目录
cheakDir = F:\\myfolder\\BaseClass;
#the path allways to zip 完全备份的目录
zipDir = F:\\myfolder\\myProject\\Change2Mail;
#the data lastcheck 最后备份时间
lastData = 20150629
#storage life of the bak zip 保留文件
time = 5
#filter string 过滤条件
filter = .class

工具github地址:https://github.com/Seanid/change2mail

为了便于使用,工具最后导出为jar包,并编写了一个bat进行执行,加入了jre文件夹,可以再无jdk环境下使用。最后压缩包地址:http://pan.baidu.com/s/1i3vQJv3

本工具只是个人闲时开发,纯属娱乐,大神勿喷