fltp备份文件后统计验证
上一篇(https://www.cnblogs.com/jying/p/16805821.html)记录了自己在centos使用lftp备份文件的过程,本篇记录自己对备份后的文件与源文件目录的对比统计。
三种思路:
1、代码(如java等)循环遍历所有备份的源和ftp目标文件夹,统计个数对比。
2、执行linux命令行统计ftp备份文件夹和本地源文件夹文件总大小写入统计文件,再利用java代码读取统计值后对比。
3、执行linux命令行统计ftp备份文件夹和本地源文件夹文件总个数写入统计文件,再利用java代码读取统计值后对比。
其中网上的介绍多是用的第一种方式,而且几乎全都是复制粘贴的重复垃圾文章,个人尝试可以执行,但文件夹较多和层级较多时会非常耗时(因为ftp的连接原理导致),最终放弃方式一。
用到的方式为commons.net(org.apache.commons.net)包的ftp功能,网上的垃圾文章直接略过,直接用官方的实例测试:
org.apache.commons.net测试实例
其中main函数里的测试方式可以是:
public static void test() throws UnknownHostException {
String cmd = "-n ftp服务器ip 账号 密码 /";
String[] cmds = cmd.split(" ");
main(cmds);
}
不记得有没有参考这俩文章了:https://www.cnblogs.com/chen1281024/p/15625278.html 、https://www.cnblogs.com/leonlipfsj/p/15972372.html
然后考虑方式2,关于linux统计目录下文件大小的命令是du,
du常用的选项:
-h:--human-readable 以人类可读的方式显示,即自动转为K,M,G等单位。
-a:-all 显示目录占用的磁盘空间大小,含子目录和文件占用磁盘空间的大小详细列表
-s:--summarize 显示目录占用的磁盘空间大小,不含子目录和文件占用的磁盘空间大小详细列表
-b:-bytes 显示目录或文件大小时,以byte为单位。
-k:--kilobytes 以KB(1024bytes)为单位输出。
-m:--megabytes 以MB为单位输出。
-c:--total 显示目录或文件占用的磁盘空间大小,统计它们的总和。
--apparent-size:显示目录或文件自身的大小
-l :统计硬链接占用磁盘空间的大小
-L:统计符号链接所指向的文件占用的磁盘空间大小 du -sh : 查看当前目录总共占的容量。而不单独列出各子项占用的容量。
du -sh * | sort -n 统计当前文件夹(目录)大小,并按文件大小排序
du -lh --max-depth=1 : 查看当前目录下一级子文件和子目录占用的磁盘容量。
关于以上常用选项的实例可以参考:https://blog.csdn.net/pichcar1982/article/details/121531546
因为lftp也支持du,所以貌似可以直接通过du统计服务器本地源文件目录大小和ftp服务器文件目录大小对比就可以了,但实际执行过程发现本地文件要比同步到ftp服务器上的文件大,于是使用ls -l查看发现单个文件大小也不一致,个人猜测是服务器本地文件上传保存的字节流和ftp备份的字节流长度不一致导致的。还有一种解释(https://blog.csdn.net/mtawaken/article/details/8491413 或https://blog.csdn.net/weixin_42803243/article/details/123724755)说是服务器本身的存储块大小不一致导致的,所以du加参数--apparent-size即可,而我加上此参数发现还是不一致,即使单个文件显示一致了,整个目录的大小仍然有差异。
方式3,对比文件个数, linux命令ls -l 可以按行列出目录下所有文件,可以直接根据行数统计出文件个数。
# 查看当前目录下的文件数量(不包含子目录中的文件)
ls -l|grep "^-"| wc -l # 查看当前目录下的文件数量(包含子目录中的文件) 注意:R,代表遍历子目录
ls -lR|grep "^-"| wc -l # 查看当前目录下的文件夹目录个数(不包含子目录中的目录),同上,如果需要查看子目录的,加上R
ls -l|grep "^d"| wc -l
wc -l 表示统计输出信息的行数,因为经过前面的过滤已经只剩下普通文件,一个目录或文件对应一行,所以统计的信息的行数也就是目录或文件的个数。参考:https://www.cnblogs.com/wangyuxing/p/15818042.html
在lftp中也可以使用该方式来统计文件个数,但有一些限制,比如lftp中的ls命令默认就是显示按行的文件详情,等同于普通命令ls -l,而且lftp中使用R参数无效,这意味着无法循环遍历子文件夹目录(了解过ftp的连接过程则知道在ftp里切换目录需要重新连接),所以在lftp中的统计文件个数命令写为:
ls 文件夹目录 | grep "^-" | wc -l
这也就意味着只能统计单层的文件夹里的文件个数。但这也是能准确统计是否备份成功的最准确方式了。
所以我们需要对文件存储路径进行优化,优化后的存储路径应该满足最终的统计路径只有一层:
1、存储根目录/模块/直接存储文件
2、存储根目录/模块/年月/日/存储文件
3、存储根目录/模块/年月日/存储文件
4、存储根目录/年月/日/存储文件
5、存储根目录/年月/日/模块/存储文件
推荐使用方式2或方式5,而且所有存储格式应该统一,这样便于备份脚本只写一种遍历即可。我这边目前因为采用了多种存储方式,导致编写备份脚本时要分开写多个(参考上一篇文章)。
一个备份和统计的实例如下:
if [ -d 存储根目录/模块/年月/日 ]; then
echo "目录存在"
ls 存储根目录/模块/年月/日/存储文件 -l|grep "^-"| wc -l >> /本地脚本目录/bak_logs/年月/日/模块.bak.count
lftp -u 账号,密码 ftp服务器ip << EOF
mirror --reverse --only-missing --only-newer 存储根目录/模块/年月/日 --parallel=3 --log=/本地脚本目录/logs/年月/日/模块_小时.log
ls 存储根目录/模块/年月/日/存储文件 |grep "^-"| wc -l >> /本地脚本目录/bak_logs/年月/日/模块.bak.count
bye
EOF
else
echo "不存在"
fi
有了统计个数,就可以通过代码来读取并推送邮件给管理员了。

package test; import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Properties; public class BakFile { private Properties props;
private WarningEmail warningEmail; public BakFile(Properties _props) {
props = _props;
} public void run() {
try {
// 初始化
this.InitSetup(); // 获取统计信息
this.readResultCount(); } catch (Exception exIO) {
warningEmail.send_report_mail("核验备份文件出错啦!!!", exIO.toString());
exIO.printStackTrace();
}
} private void InitSetup() throws IOException, SQLException {
// 提醒邮件
warningEmail = new WarningEmail(props);
} public void readResultCount() {
// 获取昨天的日期,
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,-1);//昨天
String yesterday_ym = new SimpleDateFormat("yyyy-MM").format(cal.getTime());
String yesterday_d = new SimpleDateFormat("dd").format(cal.getTime());
String yesterday = yesterday_ym+"-"+yesterday_d; String path = "/var/jenkins_home/bak_logs/" + yesterday_ym + "/" + yesterday_d;
File dirFile = new File(path);
//如果dir对应的文件不存在,或者不是一个目录,则退出
if (!dirFile.isDirectory()) {
warningEmail.send_report_mail(yesterday+"核验备份文件异常", "未获取到昨天备份目录");
return;
}
//获取文件夹下所有文件
File[] files = dirFile.listFiles();
File file;
List<String> list;
String content = "";
String bodyTrContent = "";
if(files!=null && files.length>0) {
for (int i = 0; i < files.length; i++) {
file = files[i];
if(file.getName().endsWith("count")) {
list = readFileContent(file);
content = setContent(file, list);
bodyTrContent = String.format("%s%s", bodyTrContent, content);
}
}
content = setMailHtml(bodyTrContent, yesterday);
warningEmail.send_report_mail(yesterday+"文件备份情况", content);
} else {
warningEmail.send_report_mail(yesterday+"文件备份为空", "未获取到昨天备份目录");
}
} public List<String> readFileContent(File file) {
BufferedReader reader = null;
FileReader fileReader = null;
List<String> list = new ArrayList<String>();
try {
fileReader = new FileReader(file);
reader = new BufferedReader(fileReader);
String tempString = null;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null) {
list.add(tempString);
// System.out.println(tempString);
}
} catch (IOException e) { } finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
if(fileReader != null) {
try {
fileReader.close();
} catch (IOException e1) {
}
}
}
return list;
} public String setContent(File file, List<String> list) {
if(list.size()>=2) {
String line1 = list.get(list.size()-2);
String line2 = list.get(list.size()-1);
return setTableBodyTr(file.getName().replace(".bak.count", ""), line1, line2);
} else if(list.size()==1){ //
String line1 = list.get(list.size()-1);
return setTableBodyTr(file.getName().replace(".bak.count", ""), line1, "未同步");
} else {
return setTableBodyTr(file.getName().replace(".bak.count", ""), "未获取到文件夹", "未同步");
}
} public String setMailHtml(String content, String yesterday) {
String html = String.format("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>" +
" <html lang='en'> " +
" <head> " +
" <meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>" +
" </head> " +
" <body>"
+ "<div style='font-size:12px;'>%s文件备份情况:<br/><br/>"
+ "<table width='584' style='border-collapse: collapse;font-size:9pt;width:438pt'><tr style='background-color:#f7f7f7;'>"
+ "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:38pt;'>目录</td>"
+ "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:60pt;'>本地</td>"
+ "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:60pt;'>ftp</td>"
+ "</tr>", yesterday);
html = String.format("%s%s", html, content);
html = String.format("%s</table></body></html>", html); return html;
} public String setTableBodyTr(String mName, String local, String ftp) {
String content = "";
if(!local.equals(ftp)) {
ftp = String.format("<span style='color:red;' color='red'>%s</span>", ftp);
}
content = String.format("%s<tr><td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:38pt;'>%s</td>", content, mName);
content = String.format("%s<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:60pt;'>%s</td>", content, local);
content = String.format("%s<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:60pt;'>%s</td>", content, ftp);
content = String.format("%s</tr>", content);
return content;
} }
java获取统计信息并邮件推送给管理员

package test; import java.util.Date;
import java.util.Properties; import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart; public class WarningEmail { private Properties props; public WarningEmail(Properties _props) {
this.props = _props;
} public void send_report_mail(String subject, String content) {
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
// System.out.println(subject);
// System.out.println(content);
try {
String smtp = props.getProperty("mail.smtp");
String user = props.getProperty("mail.user");
String password = props.getProperty("mail.pwd");
String from = props.getProperty("mail.from");
String to = props.getProperty("mail.admin"); Properties p = new Properties();
p.put("mail.smtp.host", smtp);
p.put("mail.smtp.port", "587");
p.put("mail.smtp.auth", "true"); Session ssn = Session.getDefaultInstance(p);
Transport transport = ssn.getTransport("smtp");
transport.connect(smtp, user, password); send_email(ssn, transport, subject, content, from, to);
} catch (Exception e) {
e.printStackTrace();
}
} private boolean send_email(Session ssn, Transport transport, String subject, String content, String from, String to) {
try {
BodyPart html = new MimeBodyPart();
html.setContent(content, "text/html; charset=utf-8"); Multipart mainPart = new MimeMultipart();
mainPart.addBodyPart(html); MimeMessage message = new MimeMessage(ssn);
message.setContent(mainPart); message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
// message.setRecipients(Message.RecipientType.BCC, InternetAddress.parse("抄送人邮箱")); // 密送人
message.setSubject(subject);
message.setContent(mainPart);
message.setSentDate(new Date()); transport.sendMessage(message, message.getAllRecipients());
System.out.println(String.format("%s : %s", to, subject));
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
发送邮件
fltp备份文件后统计验证的更多相关文章
- AngularJS 表单提交后显示验证信息与失焦后显示验证信息
虽然说AngularJS的实时表单验证非常有用,非常高效方便,但是当用户还没有完成输入时便弹出一个错误提示,这种体验是非常糟糕的. 正常的表单验证逻辑应该是在用户提交表单后或完成当前字段中的输入后,再 ...
- (进阶篇)PHP实现用户注册后邮箱验证,激活帐号
我们在很多网站注册会员时,注册完成后,系统会自动向用户的邮箱发送一封邮件,这封邮件的内容就是一个URL链接,用户需要点击打开这个链接才能激活之前在该网站注册的帐号.激活成功后才能正常使用会员功能. 本 ...
- AngularJs在单击提交后显示验证信息.
<!DOCTYPE html> <html ng-app="app"> <head> <meta charset="utf-8& ...
- (进阶篇)PHP(thinkphp5框架)实现用户注册后邮箱验证,激活帐号
本文将结合实例,讲解如何使用thinkphp5+Mysql完成注册帐号.发送激活邮件.验证激活帐号.处理URL链接过期的功能. 业务流程 1.用户提交注册信息. 2.写入数据库,此时帐号状态未激活. ...
- jquery mobile 登陆后页面验证
调用 pagebeforechange方法 $(document).bind("pagebeforechange", function (e, data) { if (typeof ...
- 获取分组后统计数量最多的纪录;limit用法;sql执行顺序
CREATE TABLE emp(id INT PRIMARY KEY,NAME VARCHAR(11),dep_id INT ,salary INT); CREATE TABLE dept(id I ...
- 解决Eclipse导入项目后Validating验证缓慢的问题
减少不必要的验证即可 步骤:Window-Preferences-左侧的Validation 如图所示,将Build一列的勾全部去掉就好了. 如需手动校验,右键项目名-选择Validate即可.
- element ui里dialog关闭后清除验证条件
//vue <!--添加用户dialog begin--> <el-dialog title="编辑用户" :visible.sync="dialogF ...
- linux 下awk后统计某一列数据之和简单的命令
例如有文件test.txt格式如下: aaa:1 bbb:2 cat a|awk -F\: '{print"+"$2}'|xargs echo 0|bc -l
随机推荐
- RestTemplate用法
RestTemplate 用法 RestTemplate简介 RestTemplate 是一个同步的web http客户端请求模板工具,spring框架做的抽象模板, 常见的http客户端请求工具有: ...
- KingbaseFlySync 专用机版本升级
关键字: KingbaseFlySync.Linux.x86_64.mips64el.aarch64.Java 专线机版本升级 1.备份kfs配置文件和rename问题,kufl目录 fsrepctl ...
- 输入法词库解析(四)百度分类词库.bdict(.bcd)
前言 .bdict 是百度的分类词库格式,可以在 https://shurufa.baidu.com/dict 下载. 手机百度的分类词库格式 .bcd 是一样的,可以在 https://mime.b ...
- 不给字段创建索引,字段不存放在source中,字段无法聚合查询等
某个字段不被搜索,也就是说不想为这个字段建立inverted index(反向索引),可以这么做: PUT twitter { "mappings": { "uid&qu ...
- 10_SpringBoot更加详细
一. 原理初探 1.1 自动装配 1.1.1 pom.xml spring-boot-dependencies: 核心依赖在父工程中 我们在写入或者引入一些SpringBoot依赖的时候, 不需要指定 ...
- 天天向上力量B
N=eval(input()) up=pow(1+0.001*N,365) down=pow(1-0.001*N,365) print("{:.2f}, {:.2f}, {:.0f}&quo ...
- 方法的重写(override / overwrite)
1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作 2.应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法. 重写的规 ...
- HFS局域网分享文件的神器(附下载链接)
温馨提示,下载链接在页末 前言 假如说你需要传递个学习资料给好基友,我们有许多种方式可选:硬盘媒介.网络分享等. 要是论速度,还是得拿3.0或以上的U盘来拷贝,确实神速哈哈.但是其也有局限性,比如需要 ...
- 商品期货通用模型JF1
更多精彩内容,欢迎关注公众号:数量技术宅,也可添加技术宅个人微信号:sljsz01,与我交流. 行情不确定性加剧 回顾2022年上半年的期货市场行情,在一个个宏观事件的不断冲击下,期货市场的不确定性加 ...
- lnmp配置laravel访问环境报错锦集
1.laravel配置域名访问变成下载,实际就是Nginx没有识别到.php文件.把.php文件的配置加到Nginx即可 .... # 这一段放到项目的Nginx.conf配置文件里面 locatio ...