基于Web的MySQL数据库备份系统实现

项目背景与需求

数据库的备份通常需要定时和手动两种方式。传统的备份方式往往需要在命令行中输入复杂的命令,或者依赖于第三方工具。为了让备份操作更加简便和直观,我们可以设计一个基于Web界面的备份系统,用户通过填写Web表单,系统自动完成备份并上传至远程服务器。

项目功能
  1. Web表单界面:通过简单的Web界面,用户可以填写MySQL数据库连接信息和远程服务器的存储路径。
  2. 数据库备份:系统会执行mysqldump命令进行数据库备份,并将备份文件存储在本地。
  3. 远程传输:系统能够通过SCP协议将备份文件传输到指定的远程服务器。
  4. 状态反馈:备份完成后,系统会提供详细的操作状态反馈,告知用户备份是否成功。
系统结构

该系统主要由前端、后端和Shell脚本三部分组成。

  1. 前端(HTML + CSS + JavaScript)

    • 用户通过表单提交数据库连接信息和远程服务器信息。
    • 提交时,前端使用AJAX技术向后端发起请求,避免页面刷新。
    • 在备份过程中,前端提供了加载提示,操作结果会实时反馈给用户。
  2. 后端(PHP)

    • PHP接收前端提交的表单数据,并通过调用Shell脚本来执行实际的备份操作。
    • PHP代码会通过popenshell函数启动备份脚本,并实时获取备份结果。
  3. Shell脚本(Bash)

    • Shell脚本负责数据库备份操作,调用mysqldump命令进行数据备份,并将备份文件保存至本地。
    • 备份完成后,脚本使用SCP协议将文件上传至远程服务器。
系统代码解析

1. 前端HTML结构

前端HTML页面通过表单输入数据库和远程服务器的配置信息。如下所示:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MySQL Web Backup</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" href="imgs/biaoq.jpg" type="image/x-icon">
</head>
<body>
    <div class="form-container">
        <h1>MySQL backup system</h1>

        <form id="myForm" method="post">
            <div class="input-section">
                <h2>备份端信息</h2>
                <div class="input-group">
                    <label for="db_ip">数据库 IP:</label>
                    <input type="text" id="db_ip" name="db_ip" required>
                </div>
                <div class="input-group">
                    <label for="db_port">数据库端口:</label>
                    <input type="text" id="db_port" name="db_port" required>
                </div>
                <div class="input-group">
                    <label for="db_name">数据库名称:</label>
                    <input type="text" id="db_name" name="db_name" required>
                </div>
                <div class="input-group">
                    <label for="db_user">数据库用户名:</label>
                    <input type="text" id="db_user" name="db_user" required>
                </div>
                <div class="input-group">
                    <label for="db_password">数据库密码:</label>
                    <input type="password" id="db_password" name="db_password" required>
                </div>
            </div>

            <div class="input-section">
                <h2>存储端信息</h2>
                <div class="input-group">
                    <label for="remote_server">远程服务器 IP:</label>
                    <input type="text" id="remote_server" name="remote_server" required>
                </div>
                <div class="input-group">
                    <label for="remote_path">远程路径:</label>
                    <input type="text" id="remote_path" name="remote_path" required>
                </div>
                <div class="input-group">
                    <label for="remote_user">远程用户名:</label>
                    <input type="text" id="remote_user" name="remote_user" required>
                </div>
                <div class="input-group">
                    <label for="remote_password">远程服务器密码:</label>
                    <input type="password" id="remote_password" name="remote_password" required>
                </div>
            </div>

        </form>
        <h3>注:服务器之间需网络互通</h3>

        <button id="submitButton" type="button">执行备份</button>
        <div id="resultMessage"></div>
    </div>

    <!-- 加载提示 -->
    <div id="loadingMessage">正在执行备份,请稍等...</div>

<script>
    const form = document.getElementById('myForm');
    const submitButton = document.getElementById('submitButton');
    const resultMessage = document.getElementById('resultMessage');
    const loadingMessage = document.getElementById('loadingMessage');

    submitButton.addEventListener('click', function() {
        loadingMessage.style.display = "block";  // 显示加载提示
        resultMessage.innerHTML = '';  // 清空上次结果

        const formData = new FormData(form);

        // 使用 Fetch API 进行 AJAX 提交
        fetch('backup_process.php', {
            method: 'POST',
            body: formData,
        })
        .then(response => response.text())  // 获取返回的文本数据
        .then(data => {
            console.log('返回的数据:', data);  // 输出返回的数据

            // 判断返回结果,显示不同的提示
            if (data.includes("备份操作成功")) {
                resultMessage.innerHTML = '<span style="color: green;">' + data + '</span>';
            } else {
                resultMessage.innerHTML = '<span style="color: red;">' + data + '</span>';
            }

            loadingMessage.style.display = "none";  // 隐藏加载提示
        })
        .catch(error => {
            console.error('请求失败:', error);  // 输出请求错误
            resultMessage.innerHTML = '提交失败,请稍后再试。';
            loadingMessage.style.display = "none";
        });
    });
</script>

</body>
</html>

2. 后端PHP逻辑

在后端,PHP负责接收前端提交的表单数据,并调用Shell脚本执行数据库备份:

<?php
// 禁用输出缓冲,确保信息实时发送到浏览器
ob_implicit_flush(true);
ob_end_flush();

// 设置 header 来禁用缓存并确保实时输出
header('Content-Type: text/html; charset=utf-8');

// 获取表单数据并进行合法性验证
$db_ip = $_POST['db_ip'] ?? '';  // 数据库 IP 地址
$db_port = $_POST['db_port'] ?? '';  // 数据库端口
$db_name = $_POST['db_name'] ?? '';  // 数据库名称
$db_user = $_POST['db_user'] ?? '';  // 数据库用户名
$db_password = $_POST['db_password'] ?? '';  // 数据库密码
$remote_server = $_POST['remote_server'] ?? '';  // 远程服务器 IP
$remote_path = $_POST['remote_path'] ?? '';  // 远程服务器备份路径
$remote_user = $_POST['remote_user'] ?? '';  // 远程服务器用户名
$remote_password = $_POST['remote_password'] ?? '';  // 远程服务器密码

// 检查必填字段是否为空
if (empty($db_ip) || empty($db_port) || empty($db_name) || empty($db_user) || empty($db_password)) {
    echo "备份端字段都必须填写,请返回并填写完整的表单。";
    exit(1);  // 输入不合法,退出脚本
}

// 使用 escapeshellarg() 函数转义表单数据,避免命令注入
$db_ip = escapeshellarg($db_ip);
$db_port = escapeshellarg($db_port);
$db_name = escapeshellarg($db_name);
$db_user = escapeshellarg($db_user);
$db_password = escapeshellarg($db_password);
$remote_server = escapeshellarg($remote_server);
$remote_path = escapeshellarg($remote_path);
$remote_user = escapeshellarg($remote_user);
$remote_password = escapeshellarg($remote_password);


// 获取当前目录路径
$currentDir = dirname(__FILE__);

// 构造命令行参数
$script_path = 'bak.sh';  // 备份脚本路径
$command = "$currentDir/$script_path $db_ip $db_port $db_name $db_user $db_password $remote_server $remote_path $remote_user $remote_password";

// 执行备份脚本并获取状态码
$process = popen($command, 'r');
if (!$process) {
    echo "无法启动备份脚本,请检查服务器配置。";
    flush();
    exit(1);  // 返回非零状态码,表示启动失败
}

// 获取命令输出并过滤,只输出错误信息
$output = '';
$output2 = '';
while ($line = fgets($process)) {
    // 如果包含 "ERROR" 字符串,则视为错误输出
    if (strpos($line, 'ERROR') !== false) {
        $output .= $line;
    } else if (strpos($line, 'incomplete') !== false) {
        $output2 .= $line;// 如果有备份完成的提示信息,输出成功提示
    }
}

pclose($process);

// 根据输出结果返回信息
if (!empty($output)) {
    echo $output;  // 如果有错误信息,输出错误内容
} else if (!empty($output2)) {
    echo "备份操作成功! " . $output2;  // 如果有备份完成的提示信息,输出成功提示
} else {
    echo "备份操作成功!";  // 如果没有错误,返回成功消息
}
?>

3. Shell脚本(Bash)

Shell脚本负责执行数据库备份、传输备份文件到远程服务器,并记录日志。以下是Shell脚本代码:

#!/bin/bash

# 获取传入的参数
DB_HOST=$1  # 数据库主机
DB_PORT=$2  # 数据库端口
DB_NAME=$3  # 数据库名称
DB_USER=$4  # 数据库用户名
DB_PASSWORD=$5  # 数据库密码
REMOTE_SERVER=$6  # 远程服务器地址
REMOTE_PATH=$7  # 远程备份路径
REMOTE_USER=$8  # 远程服务器用户名
REMOTE_PASS=$9  # 远程服务器密码

# 当前时间,用于命名备份文件
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# 当前路径
SHDIR=$(cd "$(dirname "$0")" && pwd)

# 备份文件保存路径
BACKUP_DIR="$SHDIR/bak"

#日志文件保存路径
LOG_FILE="$SHDIR/backup.log"

# 备份文件名
BACKUP_FILE="$BACKUP_DIR/$DB_NAME-$TIMESTAMP.sql"
REMOTE_FILE="$REMOTE_PATH/$DB_NAME-$TIMESTAMP.sql"

# 写入日志函数
log_message() {
    echo "$(date +'%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE  # 写入日志文件
    echo "$1"  # 同时输出到标准输出
}

# 检查数据库是否存在,设置连接超时时间为2秒
DB_EXISTS=$(timeout 2 mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD -e "SHOW DATABASES LIKE '$DB_NAME';" 2>> $LOG_FILE)

# 如果数据库不存在或连接失败,返回错误信息
if [[ -z "$DB_EXISTS" ]]; then
    log_message "ERROR: Database '$DB_NAME' does not exist on host '$DB_HOST' or could not connect."
    log_message "ERROR: Unable to find database '$DB_NAME'. Please check the database connection."
    exit 1  # 设置非零退出状态
fi

# 开始备份,给出反馈
log_message "Starting database backup for $DB_NAME at $DB_HOST"

# 执行备份
mysqldump -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD $DB_NAME > $BACKUP_FILE 2>> $LOG_FILE

# 检查备份是否成功
if [ $? -eq 0 ]; then
    log_message "Database backup successful! Backup file saved to: $BACKUP_FILE"
else
    log_message "ERROR: Database backup failed!"
    exit 1
fi

# 如果需要传输文件到远程服务器
if [[ -n "$REMOTE_SERVER" && -n "$REMOTE_PATH" && -n "$REMOTE_USER" && -n "$REMOTE_PASS" ]]; then
    log_message "Starting file transfer to remote server $REMOTE_SERVER:$REMOTE_PATH"

    # 使用 SSH 检查远程路径是否存在,如果不存在,则创建该目录
    sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no $REMOTE_USER@$REMOTE_SERVER "mkdir -p $REMOTE_PATH"

    # 使用 timeout 限制文件传输最大时间为10秒
    timeout 10 sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no $BACKUP_FILE $REMOTE_USER@$REMOTE_SERVER:$REMOTE_PATH

    if [[ $? -eq 0 ]]; then
        log_message "File successfully transferred to $REMOTE_SERVER:$REMOTE_PATH"
        rm -f $BACKUP_FILE  # 传输成功后删除本地备份文件
        log_message "Backup file $BACKUP_FILE deleted after successful transfer."
    else
        log_message "ERROR: Unable to connect to remote server $REMOTE_SERVER, file transfer failed or timed out! The backup was completed successfully in the path of $BACKUP_FILE."
        exit 2
    fi
else
    log_message "The remote server information is incomplete, the backup has been completed successfully, and the path is the backup server: $BACKUP_FILE."
    exit 0  # 正常退出
fi

4. CSS样式

给web端添加样式,更加美观。以下面是CSS代码:

/* 基本样式重置 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;  /* 设置盒模型为 border-box,包含 padding 和 border */
}

/* 页面基本样式 */
body {
    font-family: 'Arial', sans-serif;  /* 设置字体 */
    background: linear-gradient(135deg, #2174ff, #f1f1f6); /* 背景渐变效果 */
    color: #e5e5e5;  /* 字体颜色 */
    display: flex;
    justify-content: center;  /* 页面内容水平居中 */
    align-items: center;  /* 页面内容垂直居中 */
    height: 100vh;  /* 设置页面高度为视口高度 */
    text-align: center;  /* 文字居中 */
}

h1 {
    color: #c5c5c7;  /* 标题颜色 */
    margin-bottom: 25px;  /* 标题底部间距 */
    font-size: 2rem;  /* 字体大小 */
    text-align: center;  /* 标题居中 */
}

h3 {
    color: #ffffff;  /* 小标题颜色 */
    margin-bottom: 10px;  /* 小标题底部间距 */
    font-size: 1rem;  /* 小标题字体大小 */
    text-align: center;  /* 小标题居中 */
}

/* 表单容器,保持 16:9 宽高比 */
.form-container {
    width: 80vw;  /* 宽度占视口宽度的 80% */
    height: 45vw; /* 高度根据 16:9 比例计算,约为 45% 宽度 */
    max-width: 1900px;  /* 最大宽度限制 */
    max-height: 750px;  /* 最大高度限制 */
    position: relative;
    background: rgba(0, 0, 0, 0.7);  /* 半透明背景 */
    padding: 20px;
    border-radius: 10px;  /* 圆角边框 */
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);  /* 阴影效果 */
    display: flex;
    flex-direction: column;  /* 垂直排列 */
    justify-content: space-between;  /* 内容分布均匀 */
}

/* 表单样式 */
form {
    flex-grow: 1;  /* 使表单容器填满剩余空间 */
    display: flex;
    flex-direction: row;  /* 水平排列输入部分 */
    justify-content: space-between;  /* 左右对齐 */
    overflow-y: auto;  /* 允许竖向滚动 */
}

/* 输入框部分样式 */
.input-section {
    display: flex;
    max-width: 800px;  /* 最大宽度 */
    max-height: 450px;  /* 最大高度 */
    flex-direction: column;  /* 输入框竖排 */
    gap: 15px;  /* 每个输入框之间的间距 */
    padding: 20px;
    border: 2px solid #202020;  /* 边框颜色 */
    border-radius: 10px;  /* 圆角边框 */
    background-color: rgba(0, 0, 0, 0.8);  /* 背景色 */
    width: 48%;  /* 占据父容器宽度的 48% */
}

/* 输入框和标签同一行排列 */
.input-group {
    display: flex;
    align-items: center;  /* 标签和输入框在同一行 */
}

/* 标签样式 */
.input-group label {
    width: 30%;  /* 标签宽度 */
    text-align: right;  /* 标签右对齐 */
    margin-right: 10px;  /* 标签与输入框之间的间距 */
    font-size: 1rem;
    color: #d4def6;  /* 标签字体颜色 */
}

/* 输入框样式 */
.input-group input[type="text"],
.input-group input[type="password"] {
    width: 70%;  /* 输入框宽度 */
    padding: 10px;  /* 内边距 */
    margin-bottom: 15px;  /* 输入框底部间距 */
    border: 2px solid #323748;  /* 边框颜色 */
    background-color: #c9d2f5;  /* 输入框背景色 */
    color: #ecf0f1;  /* 输入框文字颜色 */
    border-radius: 8px;  /* 圆角边框 */
    font-size: 1rem;  /* 字体大小 */
    transition: border-color 0.3s ease;  /* 边框颜色过渡 */
}

/* 输入框获得焦点时的样式 */
.input-group input[type="text"]:focus,
.input-group input[type="password"]:focus {
    border-color: #c0392b;  /* 聚焦时边框颜色 */
    outline: none;  /* 去掉聚焦时的外边框 */
    background-color: #333;  /* 聚焦后背景变黑 */
}

/* 按钮样式 */
button {
    background: linear-gradient(45deg, #2174ff, #ebecf5);  /* 按钮背景渐变 */
    color: white;
    font-size: 1.1rem;  /* 字体大小 */
    padding: 20px 60px;  /* 内边距 */
    border: none;  /* 去掉边框 */
    border-radius: 25px;  /* 圆角边框 */
    cursor: pointer;  /* 鼠标悬停时变为手指图标 */
    transition: all 0.3s ease;  /* 按钮效果过渡 */
    margin: 10px auto;  /* 水平居中 */
    display: block;  /* 使按钮为块级元素 */
}

/* 按钮的悬停效果 */
button:hover {
    transform: translateY(-3px);  /* 向上移动 */
    box-shadow: 0 4px 20px rgba(52, 152, 219, 0.6);  /* 添加阴影 */
}

/* 按钮的点击效果 */
button:active {
    transform: translateY(0);  /* 恢复原位 */
    box-shadow: none;  /* 去掉阴影 */
}

/* 加载提示样式 */
#loadingMessage {
    position: fixed;
    bottom: 20px;  /* 固定在页面底部 */
    left: 50%;  /* 居中显示 */
    transform: translateX(-50%);  /* 水平居中 */
    background-color: rgba(0, 0, 0, 0.7);  /* 半透明背景 */
    color: #fff;  /* 白色文字 */
    padding: 10px 20px;  /* 内边距 */
    border-radius: 5px;  /* 圆角边框 */
    font-size: 16px;  /* 字体大小 */
    display: none;  /* 默认隐藏 */
    z-index: 9999;  /* 显示在最上层 */
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);  /* 阴影效果 */
}

/* 结果信息样式 */
#resultMessage {
    margin-top: 20px;  /* 结果与上方间距 */
    font-family: Arial, sans-serif;  /* 设置字体 */
}

/* 响应式设计 */
@media (max-width: 600px) {
    h1 {
        font-size: 1.8rem;  /* 小屏幕下标题字体大小调整 */
    }

    .form-container {
        width: 95vw;  /* 小屏幕下宽度为 95% */
        height: 53.4375vw;  /* 高度根据 16:9 比例调整 */
    }

    form {
        flex-direction: column;  /* 小屏幕上表单内容竖向排列 */
    }

    .input-section {
        width: 100%;  /* 小屏幕上输入部分占满宽度 */
    }

    .input-group {
        flex-direction: column;  /* 标签和输入框竖排 */
        align-items: flex-start;  /* 左对齐 */
    }

    .input-group label {
        width: 100%;  /* 标签宽度 100% */
        margin-right: 0;  /* 去掉标签与输入框间距 */
    }

    .input-group input[type="text"],
    .input-group input[type="password"] {
        width: 100%;  /* 输入框宽度 100% */
    }
}

5. 各文件位置及权限

为保证脚本能运行,日志文件、bak文件夹、脚本需要是apache拥有:

6. 项目资源代码

链接: https://pan.baidu.com/s/1TGzT_22F6VsJ4IfNJ1Nxtw?pwd=1q8q 提取码: 1q8q 

7. 效果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值