Python实现宝塔自动备份目录到七牛云功能(带钉钉通知备份结果)

宝塔的自动备份目录功能只有在备份失败才会推送钉钉消息,而且消息不可自定义,于是做了此脚本。代码主要来源于七牛云的SDK,部分参考网上,在此感谢。

#!/usr/bin/env python
# coding:utf-8
# author:feng
# date:  2023/02/04
# runtime:python 3.6
# func:打包压缩文件并上传到七牛云,上传结果通过钉钉推送
# remarks:
# 需要下载七牛云sdk,使用"pip install qiniu"下载即可
# 钉钉机器人需要设置关键词,且推送消息中需要有关键词才能成功推送,本例中设置的关键词是“备份”,可自行修改,
# 但要和机器人保一致
# 代码主要参考七牛SDK,部分参考网上代码,在此感谢。

import os
import re
import json
import requests
from datetime import datetime
from qiniu import Auth, BucketManager, put_file, etag


class BackupSingleFile2Qiniuyun:
    def __init__(self):
        # 以下参数需要设置
        # 操作日志保存完整路径()
        self.log_path = r'/BackupLog.txt'
        # 待备份目录(或文件)完整路径
        self.src_path = '/data/'
        # 待上传文件存储路径(待备份文件打包压缩后的文件存储目录)
        self.dest_path = '/Backup/'
        # access key(七牛云)
        self.key = ''
        # secret key(七牛云)
        self.secret = ''
        # 桶名称(七牛云)
        self.bucket_name = ''
        # 七牛云目录名
        self.qny_dir = 'backup/'
        # 保留文件份数(本地和七牛云都保存相同的份数)
        self.remain_num = 3
        # 钉钉机器人Webhook地址
        self.web_hook = ''

        # 以下参数无需修改
        self.q = None
        self.bucket = None
        # 不用修改
        self.host = 'rs.qbox.me'
        # 文件名(待备份目录名)
        self.file_name = ''
        # 获取当前日期
        self.now_date = None
        # 压缩文件名
        self.compressed_file_name = ''
        # 正则匹配规则
        self.regex = ''

    def start_backup(self):
        self.replace_file_path()
        self.file_name = self.get_need_backup_dir_name()
        self.regex = '^' + self.file_name + '-\\d{4}-\\d{2}-\\d{2}\\.tar\\.gz'
        self.get_now_date()
        self.q = Auth(self.key, self.secret)
        self.bucket = BucketManager(self.q)
        str_log = self.get_timestamp() + ' 初始化完成!'
        print(str_log)
        self.write_log(str_log)

        # 压缩文件
        ret = self.compress_file()
        if 0 != ret:
            str_log = self.get_timestamp() + ' 本次备份失败 !'
            print(str_log)
            self.write_log(str_log)
            # 推送钉钉消息
            self.send_msg(2, '压缩文件失败!')
            return
        str_log = self.get_timestamp() + ' 压缩文件完成!'
        print(str_log)
        self.write_log(str_log)

        # 上传文件
        ret = self.upload_file()
        if 0 != ret:
            str_log = self.get_timestamp() + ' 本次备份失败!'
            print(str_log)
            self.write_log(str_log)
            # 推送钉钉消息
            self.send_msg(2, '上传文件失败!')
            return
        str_log = self.get_timestamp() + ' 上传文件完成!'
        print(str_log)
        self.write_log(str_log)

        # 删除七牛云过期文件
        self.delete_qny_invalid_file()
        str_log = self.get_timestamp() + ' 删除七牛云过期文件完成!'
        print(str_log)
        self.write_log(str_log)

        # 删除本地过期文件
        self.delete_local_invalid_file()
        str_log = self.get_timestamp() + ' 删除本地过期文件完成!'
        print(str_log)
        self.write_log(str_log)

        # 推送钉钉消息
        self.send_msg(1, '')

        str_log = self.get_timestamp() + ' 本次备份成功!'
        print(str_log)
        self.write_log(str_log)

    def replace_file_path(self):
        if not self.src_path.endswith('/'):
            self.src_path = self.src_path + '/'
        if not self.dest_path.endswith('/'):
            self.dest_path = self.dest_path + '/'
        if not self.qny_dir.endswith('/'):
            self.qny_dir = self.qny_dir + '/'

    def get_now_date(self):
        self.now_date = datetime.strptime(datetime.now().strftime("%Y.%m.%d %H:%M:%S"), "%Y.%m.%d %H:%M:%S")

    @staticmethod
    def get_timestamp() -> str:
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    def write_log(self, str_log: str):
        with open(self.log_path, 'a', encoding='utf-8') as l:
            str_log = str_log + '\n'
            l.write(str_log)

    def get_need_backup_dir_name(self) -> str:
        if self.src_path is '':
            return ''
        str_src_file_name = ''
        if not self.src_path.endswith('/'):
            str_src_file_name = self.src_path[self.src_path.rfind('/') + 1:]
            self.src_path = self.src_path + '/'
        else:
            str_src_file_name = self.src_path[:-1]
            str_src_file_name = str_src_file_name[str_src_file_name.rfind('/') + 1:]
        return str_src_file_name

    def delete_local_invalid_file(self):
        list_file_name = list()
        for file in os.listdir(self.dest_path):
            ret = re.compile(self.regex).findall(file)
            if ret:
                list_file_name.append(file)
        if len(list_file_name) <= self.remain_num:
            return
        # 排序
        list_file_name.sort()
        need_delete_num = len(list_file_name) - self.remain_num
        for index in range(0, need_delete_num):
            str_file_path = self.dest_path + list_file_name[0]
            list_file_name.pop(0)
            os.remove(str_file_path)
            str_log = self.get_timestamp() + ' 删除本地过期文件' + str_file_path
            print(str_log)
            self.write_log(str_log)

    def delete_qny_invalid_file(self):
        list_need_file = list()
        list_file = self.get_file_list()
        for file in list_file:
            temp_file = file[len(self.qny_dir):]
            ret = re.compile(self.regex).findall(temp_file)
            if ret:
                list_need_file.append(file)
        if len(list_need_file) <= self.remain_num:
            return
        list_need_file.sort()
        need_delete_num = len(list_need_file) - self.remain_num
        for index in range(0, need_delete_num):
            ret, info = self.bucket.delete(self.bucket_name, list_need_file[0])
            str_log = self.get_timestamp() + ' 删除七牛云过期文件:' + list_need_file[0]
            list_need_file.pop(0)
            print(str_log)
            self.write_log(str_log)

    def get_file_list(self) -> list:
        # 前缀
        prefix = self.qny_dir  # 填写存储桶的目标文件夹
        # 列举条目
        limit = 1000
        # 列举出除'/'的所有文件以及以'/'为分隔的所有前缀
        delimiter = None
        # 标记
        marker = None
        ret, eof, info = self.bucket.list(self.bucket_name, prefix, marker, limit, delimiter)
        items: list = ret.get('items')
        # 所有百分号(%25)都被转义为了百分号(%), 将 25 补充回去
        keys = []
        for item in items:
            key = item.get('key')
            idx = key.find('%')
            if idx >= 0:
                key = f'{key[:idx + 1]}25{key[idx + 1:]}'
            keys.append(key)
        return keys

    def compress_file(self) -> int:
        # 拷贝文件夹
        cmd = 'cp -R ' + self.src_path + ' ' + self.dest_path
        ret = os.system(cmd)
        if 0 != ret:
            str_msg = self.get_timestamp()
            str_msg += ' 复制文件夹失败!'
            print(str_msg)
            self.write_log(str_msg)
            return 1

        # 切换目录并压缩文件夹
        self.compressed_file_name = self.file_name + '-' + datetime.now().date().strftime('%Y-%m-%d') + '.tar.gz'
        cmd = 'cd ' + self.dest_path + '&& tar -zcvf ' + self.compressed_file_name + ' ' + self.dest_path + \
              self.file_name + '/ --remove-file'
        ret = os.system(cmd)
        if 0 != ret:
            str_msg = self.get_timestamp()
            str_msg += ' 压缩文件夹失败!'
            print(str_msg)
            self.write_log(str_msg)
            return 1

        return 0

    def upload_file(self) -> int:
        # 上传后保存的文件全路径
        file_path = self.qny_dir + self.compressed_file_name
        # 生成上传token
        token = self.q.upload_token(self.bucket_name, file_path)
        # 要上传文件的路径
        localfile = self.dest_path + self.compressed_file_name

        ret, info = put_file(token, file_path, localfile)
        if ret is not None:
            if ret['hash'] != etag(localfile):
                str_log = self.get_timestamp() + ' 文件上传失败!'
                print(str_log)
                self.write_log(str_log)
                return 1
        return 0

    @staticmethod
    def get_file_size(file_path: str) -> str:
        size = os.path.getsize(file_path) / 1024 / 1024
        size = '%.2f' % size
        return size

    def get_backup_file_list(self) -> str:
        content = ''
        for file in os.listdir(self.dest_path):
            if file.find(self.file_name) == -1:
                continue
            content += file + ' ' + self.get_file_size(os.path.join(self.dest_path, file)) + ' MB'
            content += '\n'
        return content

    def send_msg(self, backup_status: int, failed_reason: str):
        header = {"Content-Type": "application/json;charset=utf-8"}
        url = self.web_hook

        # content里面要设置关键字
        msg_content = ''
        if backup_status == 1:
            content = self.get_backup_file_list()
            msg_content = self.get_timestamp() + ' 文件(或目录)备份成功,当前存在备份文件:\n' + content
            data_info = {
                "msgtype": "text",
                "text": {
                    "content": msg_content
                },
                "isAtAll": False
            }
            value = json.dumps(data_info)
            response = requests.post(url, data=value, headers=header)
            err_msg = response.json()['errmsg']
            if err_msg == 'ok':
                self.write_log(msg_content[:-1] + ',钉钉消息推送成功。')
            else:
                msg_content = msg_content[:-1] + ',钉钉消息推送失败,失败原因:'
                self.write_log(msg_content + err_msg)
        elif backup_status == 2:
            msg_content = self.get_timestamp() + ' 文件(或目录)备份失败,失败原因:' + failed_reason
            data_info = {
                "msgtype": "text",
                "text": {
                    "content": msg_content
                },
                "isAtAll": False
            }
            value = json.dumps(data_info)
            response = requests.post(url, data=value, headers=header)
            err_msg = response.json()['errmsg']
            if err_msg == 'ok':
                self.write_log(msg_content + ',钉钉消息推送成功。')
            else:
                msg_content = msg_content + ',钉钉消息推送失败,失败原因:'
                self.write_log(msg_content + err_msg)


if __name__ == '__main__':
    backup = BackupSingleFile2Qiniuyun()
    backup.start_backup()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值