说在前面
现在大多数人都有自己的服务器,也会利用服务器搭建一些个人网站,那么有多少人会定时备份自己服务器的数据库数据呢?为了防止突发情况的发生,我们还是需要定时对自己数据库数据进行备份,今天我们一起来看看怎么使用node编写一个备份脚本,一键将数据备份到gitee和邮箱中。
脚本编写
1、配置信息填写
邮箱配置
这里我使用的qq邮箱作为发件账号,需要开启邮箱授权,获取授权码。
{
host: "smtp.qq.com", // 主机
secureConnection: true, // 使用 SSL
port: 465, // SMTP 端口
auth: {
user: "jyeontu@qq.com", // 自己用于发送邮件的账号
pass: "jyeontu", // 授权码(这个是假的,改成自己账号对应即可,获取方法: QQ邮箱-->设置-->账户-->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务-->IMAP/SMTP开启 复制授权码)
}
}
- (1)打开pc端qq邮箱,点击设置,再点击帐户
- (2)往下拉 可开启POP3/SMTP服务 根据提示即可获取qq邮箱授权码
- (3)将获取到的授权码复制到配置信息里即可
数据库配置
填写数据库对应的配置信息。
{
host: "localhost",
user: "root", //数据库账号
password: "jyeontu", //数据库密码
database: "test", //数据库名称
}
gitee仓库配置
这里我们可以使用gitee来做一个免费的备份仓库,把备份数据推送到gitee上,这里需要填写一下gitee仓库的配置信息。
- (1)新建私有数据库
因为我之前做了一个书签同步和tab标签保存的功能,数据也都放在这个仓库里,所以直接新建了一个目录sqlData用于保存数据库备份数据。
-
(2)配置ssh公钥 因为我们最后还需要将数据同步推送到远程仓库,不配置ssh认证的话,每次推送都需要输入密码,所以需要先配置下,配置好之后就可以不输密码,一键推送数据,不懂配置的同学可以Google一下,网上很多教程的,这里就不多说了。
-
(3)拉取仓库到本地并填写配置信息 创建好仓库后我们现在本地拉取一下,然后将仓库的本地地址和数据库存放目录填写到配置信息即可。
{
giteePath: "E:/A_Gitee/jyeontu_info_storage", //用于保存数据的gitee仓库
dir: "sqlData", //存放数据库数据的目录名
}
2、数据库数据导出
(1)引入模块
const mysql = require("mysql");
const fs = require("fs");
const archiver = require("archiver");
const { dbConfig } = require("./config.js");
mysql:用于连接和操作 MySQL 数据库。fs:文件系统模块,用于文件操作,如写入文件。archiver:用于创建压缩文件。- 从
./config.js引入dbConfig,获取数据库连接配置信息。
(2)连接数据库
const connection = mysql.createConnection(dbConfig);
function connectDatabase() {
return new Promise((resolve) => {
connection.connect((error) => {
if (error) throw error;
console.log("成功连接数据库!");
resolve("成功连接数据库!");
});
});
}
- 返回一个 Promise,在数据库连接成功时,打印“成功连接数据库!”并 resolve 一个成功连接的消息字符串。如果连接失败,抛出错误。
(3)导出表数据
function exportTableData(tableName) {
return new Promise((resolve) => {
connection.query(
`SHOW CREATE TABLE ${tableName}`,
(error, results, fields) => {
if (error) throw error;
const createTableStatement = results[0]["Create Table"];
connection.query(
`SELECT * FROM ${tableName}`,
(error, results, fields) => {
if (error) throw error;
const insertStatements = results
.map((row) => {
const keys = [],
values = [];
Object.entries(row).forEach((item) => {
keys.push(item[0]);
values.push(`'${item[1]}'`);
});
return `INSERT INTO ${tableName} (${keys.join(
","
)}) VALUES (${values.join(",")});`;
})
.join("\n");
const sql = `${createTableStatement};\n\n${insertStatements}`;
fs.writeFile(
`${dbConfig.database}/${tableName}.sql`,
sql,
(err) => {
if (err) throw err;
console.log(`表${tableName} 数据已成功导出.`);
resolve(`表${tableName} 数据已成功导出.`);
}
);
}
);
}
);
});
}
- 接受一个表名作为参数,返回一个 Promise。
- 首先查询指定表的创建语句。
- 然后查询表中的所有数据。
- 将数据转换为插入语句格式,并与创建语句拼接成完整的 SQL 内容。
- 将 SQL 内容写入以表名命名的
.sql文件中,如果写入成功,打印表名和“数据已成功导出.”的消息并 resolve 相应消息字符串。如果写入失败,抛出错误。
(4)数据库查询
function mysqlQuery() {
return new Promise((resolve) => {
connection.query("SHOW TABLES", (error, results) => {
if (error) throw error;
resolve(results);
});
});
}
- 返回一个 Promise,执行查询所有表名的 SQL 语句,将查询结果 resolve 出去。
(5)获取所有表数据
async function getAllTableData() {
const reqList = [];
const results = await mysqlQuery();
results.forEach((result, index) => {
const tableName = result[`Tables_in_${dbConfig.database}`];
reqList.push(exportTableData(tableName));
});
await Promise.all(reqList);
console.log("已全部导出!");
}
- 首先调用
mysqlQuery获取所有表名。 - 遍历所有表名,对每个表名调用
exportTableData函数,并将返回的 Promise 添加到reqList数组中。 - 使用
Promise.all等待所有表数据导出完成。
(6)打包成压缩文件
async function getZip() {
return new Promise((resolve) => {
const output = fs.createWriteStream(`${dbConfig.database}.zip`);
const archive = archiver("zip", {
zlib: { level: 9 }, // 设置压缩级别
});
output.on("close", function () {
console.log("压缩完成," + archive.pointer() + " total bytes");
resolve("finished");
});
archive.on("error", function (err) {
throw err;
});
archive.pipe(output);
// 要压缩的目录路径
const sourceDir = dbConfig.database;
archive.directory(sourceDir, false);
archive.finalize();
});
}
- 使用
fs.createWriteStream创建一个可写流output,用于将压缩后的数据写入一个以数据库名称命名的.zip文件中。 - 使用
archiver模块创建一个归档器archive,指定压缩格式为zip,并通过设置zlib选项的level属性为9来设置较高的压缩级别,以获得更好的压缩效果。 - 为输出流
output添加一个close事件监听器。当压缩完成,输出流关闭时,这个监听器会被触发。它会在控制台打印压缩完成的消息,并包含总共写入的字节数(通过archive.pointer()获取)。然后,调用resolve函数来通知 Promise 已成功完成,传递一个字符串"finished"作为成功的结果。 - 为归档器
archive添加一个error事件监听器。如果在压缩过程中发生错误,这个监听器会被触发,并抛出错误,以便在调用getZip函数的地方进行错误处理。 - 使用
archive.pipe(output)将归档器和输出流连接起来。这意味着归档器生成的压缩数据将被自动写入输出流。 - 指定要压缩的目录路径为
dbConfig.database所代表的目录。 - 使用
archive.directory(sourceDir, false)将指定目录添加到归档器中。第二个参数false表示不保留目录的完整路径在压缩包中(只包含目录内的内容)。 - 最后,调用
archive.finalize()开始压缩过程。这个方法会触发归档器开始将数据写入输出流,并在完成后触发输出流的close事件。
3、将数据库数据发送到邮箱
(1)引入模块和配置
const nodemailer = require("nodemailer");
const { mail, dbConfig } = require("./config.js");
- 引入
nodemailer模块,用于发送邮件。 - 从
./config.js文件中引入mail和dbConfig对象,包含邮件发送的配置信息以及数据库相关的配置。
(2)创建邮件传输对象
const smtpTransport = nodemailer.createTransport(mail);
- 使用
nodemailer.createTransport方法创建一个邮件传输对象smtpTransport。
(3)定义发送邮件函数
const sendMail = () => {
return new Promise((resolve) => {
//...
});
};
- 定义一个名为
sendMail的函数,该函数返回一个 Promise,使得调用者可以异步等待邮件发送的结果。
(4)设置邮件选项
const mailOptions = {
from: "1311395125@qq.com", // 发件人地址
to: "jyeontu@163.com", // 收件人地址
subject: "数据库备份数据", // 邮件主题
text: dbConfig.database, // 邮件正文内容,这里可能是数据库的名称
attachments: [
{
filename: dbConfig.database + ".zip", // 附件文件名
path: dbConfig.database + ".zip", // 附件文件路径
},
],
};
- 创建一个
mailOptions对象,设置邮件的发件人、收件人、主题、正文和附件。 - 正文内容为从
dbConfig中获取的数据库名称。 - 附件设置为以数据库名称命名的
.zip文件。
(5)发送邮件并处理结果
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
console.error("发送邮件失败:", error);
} else {
console.log("邮件发送成功");
}
smtpTransport.close(); // 发送完成关闭连接池
resolve(true);
});
- 使用
smtpTransport.sendMail方法发送邮件,传入mailOptions和一个回调函数。 - 在回调函数中,如果有错误,打印错误信息;如果发送成功,打印“邮件发送成功”。
- 发送完成后,调用
smtpTransport.close关闭邮件传输对象的连接池。 - 最后,调用
resolve(true)通知 Promise 已成功完成。
4、将数据库数据同步到gitee
以下是对这段代码的解释:
(1)引入模块和配置
const axios = require("axios");
const child_process = require("child_process");
const fs = require("fs");
const { giteeConfig, dbConfig } = require("./config");
- 引入
axios模块,用于发送 HTTP 请求。 - 引入
child_process模块,用于在 Node.js 中执行系统命令。 - 引入
fs模块,用于文件系统操作。 - 从
./config文件中引入giteeConfig和dbConfig对象,包含与 Gitee 相关的配置信息以及数据库配置信息。
(2)提取配置信息
const { giteePath, dir } = giteeConfig;
const sourceFilePath = dbConfig.database + ".zip";
const destinationFilePath = `${giteePath}/${dir}/${sourceFilePath}`;
- 从
giteeConfig中提取giteePath(Gitee 仓库的本地路径)和dir(存放数据库文件的目录)。 - 确定源文件路径为数据库名称加上
.zip后缀。 - 计算目标文件路径,即 Gitee 仓库路径下特定目录中的源文件路径。
(3)同步最新数据
// 同步最新数据
console.log("正在同步最新数据……");
child_process.execSync(`cd ${giteePath} && git pull`);
console.log("已成功同步最新数据");
- 使用
child_process.execSync执行命令,切换到 Gitee 仓库路径并执行git pull命令,以同步远程仓库的最新数据。
(4)更新数据库文件数据
// 更新数据库文件数据
console.log("正在更新数据库文件数据……");
fs.copyFileSync(sourceFilePath, destinationFilePath);
console.log("已成功更新数据库文件数据");
- 使用
fs.copyFileSync同步方式将源文件(数据库的压缩文件)复制到目标文件路径(Gitee 仓库中的特定位置)。
(5)推送到远程数据库
// 推送到远程数据库
console.log("正在推送到远程数据库……");
child_process.execSync(`cd ${giteePath} && git add.`);
child_process.execSync(
`cd ${giteePath} && git commit -m 更新数据库${dbConfig.database}数据`
);
child_process.execSync(`cd ${giteePath} && git push`);
console.log("已成功推送到远程数据库");
- 使用
child_process.execSync依次执行命令,切换到 Gitee 仓库路径,执行git add.将所有更改添加到暂存区,执行git commit提交更改并提供提交信息,执行git push将更改推送到远程仓库。
5、定时备份
如果需要定时备份数据库数据的话,可以使用corn编写一个定时任务来定时执行脚本即可。
* * * * * *分别对应:秒、分钟、小时、日、月、星期。- 每个字段可以是具体的值、范围、通配符(*表示每一个)或一些特殊的表达式。
例如:
0 0 * * *:每天午夜 0 点执行。
0 30 9 * * 1-5:周一到周五上午 9:30 执行。
你可以根据自己的需求设置合适的 cron 表达式来定时执行特定的任务。
测试
创建数据库
CREATE DATABASE test;
创建表
CREATE TABLE `t_user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`age` int NOT NULL,
PRIMARY KEY (`id`)
)
插入数据
INSERT INTO t_user (name,age) VALUES ('张三', 25);
INSERT INTO t_user (name,age) VALUES ('李四', 24);
导出数据
-
本地导出数据
-
邮件数据
-
gitee仓库数据
使用
1、源码下载
git clone https://gitee.com/zheng_yongtao/node-scripting-tool.git
-
源码已经上传到gitee仓库 gitee.com/zheng_yongt…
-
具体目录如下:
2、依赖下载
npm install
3、配置数据填写
这里的配置信息需要修改为你自己的信息,数据库、gitee仓库、邮件配置。
4、脚本运行
node index.js
更多脚本
该脚本仓库里还有很多有趣的脚本工具,有兴趣的也可以看看其他的:gitee.com/zheng_yongt…
🌟觉得有帮助的可以点个star~
🖊有什么问题或错误可以指出,欢迎pr~
📬有什么想要实现的工具或想法可以联系我~
公众号
关注公众号『前端也能这么有趣』,获取更多有趣内容。
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。