org.dromara.jpom.service.dblog.BackupInfoService Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.service.dblog;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.*;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.sql.Direction;
import cn.hutool.db.sql.Order;
import cn.keepbx.jpom.event.ISystemTask;
import cn.keepbx.jpom.plugins.IPlugin;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseServerController;
import org.dromara.jpom.common.JpomManifest;
import org.dromara.jpom.common.ServerConst;
import org.dromara.jpom.db.DbExtConfig;
import org.dromara.jpom.db.StorageServiceFactory;
import org.dromara.jpom.model.data.BackupInfoModel;
import org.dromara.jpom.model.enums.BackupStatusEnum;
import org.dromara.jpom.model.enums.BackupTypeEnum;
import org.dromara.jpom.model.user.UserModel;
import org.dromara.jpom.plugin.PluginFactory;
import org.dromara.jpom.service.h2db.BaseDbService;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 备份数据库 service
*
* @author Hotstrip
* @since 2021-11-18
**/
@Service
@Slf4j
public class BackupInfoService extends BaseDbService implements ISystemTask {
private final DbExtConfig dbExtConfig;
public BackupInfoService(DbExtConfig dbExtConfig) {
this.dbExtConfig = dbExtConfig;
}
/**
* 检查数据库备份
*/
@Override
public void executeTask() {
if (dbExtConfig.getMode() != DbExtConfig.Mode.H2) {
return;
}
try {
BaseServerController.resetInfo(UserModel.EMPTY);
// 创建备份
this.createAutoBackup();
// 删除历史备份
this.deleteAutoBackup();
} finally {
BaseServerController.removeEmpty();
}
}
/**
* 删除历史 自动备份信息
*/
private void deleteAutoBackup() {
Integer autoBackupReserveDay = dbExtConfig.getAutoBackupReserveDay();
if (autoBackupReserveDay != null && autoBackupReserveDay > 0) {
//
Entity entity = Entity.create();
entity.set("backupType", 3);
entity.set("createTimeMillis", " < " + (SystemClock.now() - TimeUnit.DAYS.toMillis(autoBackupReserveDay)));
List entities = super.queryList(entity);
if (entities != null) {
for (Entity entity1 : entities) {
String id = entity1.getStr("id");
this.delByKey(id);
}
}
}
}
/**
* 创建自动备份数据
*/
private void createAutoBackup() {
// 自动备份
Integer autoBackupIntervalDay = dbExtConfig.getAutoBackupIntervalDay();
if (autoBackupIntervalDay != null && autoBackupIntervalDay > 0) {
BackupInfoModel backupInfoModel = new BackupInfoModel();
backupInfoModel.setBackupType(3);
List infoModels = super.queryList(backupInfoModel, 1, new Order("createTimeMillis", Direction.DESC));
BackupInfoModel first = CollUtil.getFirst(infoModels);
if (first != null) {
Long createTimeMillis = first.getCreateTimeMillis();
long interval = SystemClock.now() - createTimeMillis;
if (interval < TimeUnit.DAYS.toMillis(autoBackupIntervalDay)) {
return;
}
}
this.autoBackup();
}
}
/**
* 自动备份
*/
public Future autoBackup() {
if (dbExtConfig.getMode() != DbExtConfig.Mode.H2) {
return null;
}
// 执行数据库备份
return this.backupToSql(null, BackupTypeEnum.AUTO);
}
/**
* 备份数据库 SQL 文件
*
* @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要
*/
public Future backupToSql(final List tableNameList) {
// 判断备份类型
BackupTypeEnum backupType = BackupTypeEnum.ALL;
if (!CollectionUtils.isEmpty(tableNameList)) {
backupType = BackupTypeEnum.PART;
}
return this.backupToSql(tableNameList, backupType);
}
/**
* 备份数据库 SQL 文件
*
* @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要
*/
private Future backupToSql(final List tableNameList, BackupTypeEnum backupType) {
final String fileName = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DatePattern.PURE_DATETIME_PATTERN);
// 设置默认备份 SQL 的文件地址
File file = FileUtil.file(StorageServiceFactory.dbLocalPath(), DbExtConfig.BACKUP_DIRECTORY_NAME, fileName + DbExtConfig.SQL_FILE_SUFFIX);
final String backupSqlPath = FileUtil.getAbsolutePath(file);
// 数据源参数
final String url = StorageServiceFactory.get().dbUrl();
final String user = dbExtConfig.userName();
final String pass = dbExtConfig.userPwd();
JpomManifest instance = JpomManifest.getInstance();
// 先构造备份信息插入数据库
BackupInfoModel backupInfoModel = new BackupInfoModel();
String timeStamp = instance.getTimeStamp();
try {
DateTime parse = DateUtil.parse(timeStamp);
backupInfoModel.setBaleTimeStamp(parse.getTime());
} catch (Exception ignored) {
}
backupInfoModel.setName(fileName);
backupInfoModel.setVersion(instance.getVersion());
backupInfoModel.setBackupType(backupType.getCode());
backupInfoModel.setFilePath(backupSqlPath);
this.insert(backupInfoModel);
// 开启一个子线程去执行任务,任务完成之后修改对应的数据库备份信息
return ThreadUtil.execAsync(() -> {
// 修改用的实体类
BackupInfoModel backupInfo = new BackupInfoModel();
backupInfo.setId(backupInfoModel.getId());
try {
log.debug("start a new Thread to execute H2 Database backup...start");
StorageServiceFactory.get().backupSql(url, user, pass, backupSqlPath, tableNameList);
// 修改备份任务执行完成
backupInfo.setFileSize(FileUtil.size(file));
backupInfo.setSha1Sum(SecureUtil.sha1(file));
backupInfo.setStatus(BackupStatusEnum.SUCCESS.getCode());
this.updateById(backupInfo);
log.debug("start a new Thread to execute H2 Database backup...success");
} catch (Exception e) {
// 记录错误日志信息,修改备份任务执行失败
log.error("start a new Thread to execute H2 Database backup...catch exception...", e);
backupInfo.setStatus(BackupStatusEnum.FAILED.getCode());
this.updateById(backupInfo);
}
return backupInfo;
});
}
/**
* 根据 SQL 文件还原数据库
* 还原数据库时只能同步,防止该过程中修改数据造成数据不一致
*
* @param backupSqlPath 备份 sql 文件地址
*/
public boolean restoreWithSql(String backupSqlPath) {
try {
long startTs = System.currentTimeMillis();
IPlugin plugin = PluginFactory.getPlugin("db-h2");
Map map = new HashMap<>(10);
map.put("backupSqlPath", backupSqlPath);
plugin.execute("restoreBackupSql", map);
// h2BackupService.restoreBackupSql(backupSqlPath);
long endTs = System.currentTimeMillis();
log.debug("restore H2 Database backup...success...cast {} ms", endTs - startTs);
return true;
} catch (Exception e) {
// 记录错误日志信息,返回数据库备份还原执行失败
log.error("restore H2 Database backup...catch exception...message: {}", e.getMessage(), e);
return false;
}
}
/**
* load table name list from h2 database
*
* @return list
*/
public List h2TableNameList() {
String sql = "show tables;";
List list = super.query(sql);
// 筛选字段
return list.stream()
.filter(entity -> StringUtils.hasLength(String.valueOf(entity.get(ServerConst.TABLE_NAME))))
.flatMap(entity -> Stream.of(String.valueOf(entity.get(ServerConst.TABLE_NAME))))
.distinct()
.collect(Collectors.toList());
}
@Override
public int delByKey(String keyValue) {
// 根据 id 查询备份信息
BackupInfoModel backupInfoModel = super.getByKey(keyValue);
Objects.requireNonNull(backupInfoModel, "备份数据不存在");
// 删除对应的文件
boolean del = FileUtil.del(backupInfoModel.getFilePath());
Assert.state(del, "删除备份数据文件失败");
// 删除备份信息
return super.delByKey(keyValue);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy