1. 程式人生 > >手把手教你實現Java許可權管理系統 後端篇(十三):系統備份還原

手把手教你實現Java許可權管理系統 後端篇(十三):系統備份還原

系統備份還原

在很多時候,我們需要系統資料進行備份還原。我們這裡就使用MySql的備份還原命令實現系統備份還原的功能。

新建工程

新建一個maven專案,並新增相關依賴,可以用Spring boot腳手架生成。

新建 kitty-bakcup 工程,這是一個獨立運行於後臺系統的應用程式,可以分開部署。

pom.xml 檔案新增相關依賴。

        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>

新增Spring boot啟動類。

package com.louis.kitty.backup;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages={"com.louis.kitty"})
public class KittyBackupApplication {

    public static void main(String[] args) {
        SpringApplication.run(KittyBackupApplication.
class, args); } }

新增配置

建立專案配置檔案,新增備份還原資料來源配置。

resources/application.yml

# backup datasource
spring:
  backup:
    datasource:
      host: localhost
      userName: root
      password: 123456
      database: kitty

新增配置屬性讀取配置類。

BackupDataSourceProperties.java

package com.louis.kitty.backup.datasource;

import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "spring.backup.datasource") public class BackupDataSourceProperties { private String host; private String userName; private String password; private String database; public String getHost() { return host; } public void setHost(String host) { this.host = host; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDatabase() { return database; } public void setDatabase(String database) { this.database = database; } }

新增swagger配置類,用於測試備份還原介面。

package com.louis.kitty.backup.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2).select()
                .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build();
    }
    
}

新增跨域配置類,因為前後端分離,跨域肯定是要支援的。

package com.louis.kitty.backup.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")    // 允許跨域訪問的路徑
        .allowedOrigins("*")    // 允許跨域訪問的源
        .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")    // 允許請求方法
        .maxAge(168000)    // 預檢間隔時間
        .allowedHeaders("*")  // 允許頭部設定
        .allowCredentials(true);    // 是否傳送cookie
    }
}

備份還原邏輯

備份還原邏輯封裝在一個工具類中,可以單獨從專案中提取出來,方便複用。

內部main方法提供簡單實用示例,可以方便快速的知曉實用方法。

備份還原功能主要是藉助命令列執行MySql的資料備份還原命令實現的。

package com.louis.kitty.backup.util;

import java.io.File;
import java.io.IOException;

/**
 * MySQL備份還原工具類
 * @author Louis
 * @date Sep 21, 2018
 */
public class MySqlBackupRestoreUtils {

    /**
     * 備份資料庫
     * @param host host地址,可以是本機也可以是遠端
     * @param userName 資料庫的使用者名稱
     * @param password 資料庫的密碼
     * @param savePath 備份的路徑
     * @param fileName 備份的檔名
     * @param databaseName 需要備份的資料庫的名稱
     * @return
     * @throws IOException 
     */
    public static boolean backup(String host, String userName, String password, String backupFolderPath, String fileName,
            String database) throws Exception {
        File backupFolderFile = new File(backupFolderPath);
        if (!backupFolderFile.exists()) {
            // 如果目錄不存在則建立
            backupFolderFile.mkdirs();
        }
        if (!backupFolderPath.endsWith(File.separator) || !backupFolderPath.endsWith("/")) {
            backupFolderPath = backupFolderPath + File.separator;
        }
        // 拼接命令列的命令
        String backupFilePath = backupFolderPath + fileName;
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysqldump --opt ").append(" --add-drop-database ").append(" --add-drop-table ");
        stringBuilder.append(" -h").append(host).append(" -u").append(userName).append(" -p").append(password);
        stringBuilder.append(" --result-file=").append(backupFilePath).append(" --default-character-set=utf8 ").append(database);
        // 呼叫外部執行 exe 檔案的 Java API
        Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString()));
        if (process.waitFor() == 0) {
            // 0 表示執行緒正常終止
            System.out.println("資料已經備份到 " +backupFilePath + " 檔案中");
            return true;
        }
        return false;
    }

    /**
     * 還原資料庫
     * @param restoreFilePath 資料庫備份的指令碼路徑
     * @param host IP地址
     * @param database 資料庫名稱
     * @param userName 使用者名稱
     * @param password 密碼
     * @return
     */
    public static boolean restore(String restoreFilePath, String host, String userName, String password, String database)
            throws Exception {
        File restoreFile = new File(restoreFilePath);
        if (restoreFile.isDirectory()) {
            for (File file : restoreFile.listFiles()) {
                if (file.exists() && file.getPath().endsWith(".sql")) {
                    restoreFilePath = file.getAbsolutePath();
                    break;
                }
            }
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysql -h").append(host).append(" -u").append(userName).append(" -p").append(password);
        stringBuilder.append(" ").append(database).append(" < ").append(restoreFilePath);
        try {
            Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString()));
            if (process.waitFor() == 0) {
                System.out.println("資料已從 " + restoreFilePath + " 匯入到資料庫中");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private static String[] getCommand(String command) {
        String os = System.getProperty("os.name");  
        String shell = "/bin/sh";
        if(os.toLowerCase().startsWith("win")){  
            shell = "cmd";
        }  
        String[] cmd = { shell, "/c", command };
        return cmd;
    }

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        String userName = "root";
        String password = "123456";
        String database = "kitty";
        
        System.out.println("開始備份");
        String backupFolderPath = "c:/dev/";
        String fileName = "kitty.sql";
        backup(host, userName, password, backupFolderPath, fileName, database);
        System.out.println("備份成功");
        
        System.out.println("開始還原");
        String restoreFilePath = "c:/dev/kitty.sql";
        restore(restoreFilePath, host, userName, password, database);
        System.out.println("還原成功");

    }
}

備份還原服務

備份還原服務通過呼叫工具類實現備份還原功能。

MysqlBackupService.java

package com.louis.kitty.backup.service;

import java.io.IOException;

/**
 * MySql命令列備份恢復服務
 * @author Louis
 * @date Sep 20, 2018
 */
public interface MysqlBackupService {

    /**
     * 備份資料庫
     * @param host host地址,可以是本機也可以是遠端
     * @param userName 資料庫的使用者名稱
     * @param password 資料庫的密碼
     * @param savePath 備份的路徑
     * @param fileName 備份的檔名
     * @param databaseName 需要備份的資料庫的名稱
     * @return
     * @throws IOException 
     */
    boolean backup(String host, String userName, String password, String backupFolderPath, String fileName, String database) throws Exception;

    /**
     * 恢復資料庫
     * @param restoreFilePath 資料庫備份的指令碼路徑
     * @param host IP地址
     * @param database 資料庫名稱
     * @param userName 使用者名稱
     * @param password 密碼
     * @return
     */
    boolean restore(String restoreFilePath, String host, String userName, String password, String database) throws Exception;

}

MysqlBackupServiceImpl.java

package com.louis.kitty.backup.service.impl;

import org.springframework.stereotype.Service;

import com.louis.kitty.backup.service.MysqlBackupService;
import com.louis.kitty.backup.util.MySqlBackupRestoreUtils;

@Service
public class MysqlBackupServiceImpl implements MysqlBackupService {

    @Override
    public boolean backup(String host, String userName, String password, String backupFolderPath, String fileName,
            String database) throws Exception {
        return MySqlBackupRestoreUtils.backup(host, userName, password, backupFolderPath, fileName, database);
    }

    @Override
    public boolean restore(String restoreFilePath, String host, String userName, String password, String database)
            throws Exception {
        return MySqlBackupRestoreUtils.restore(restoreFilePath, host, userName, password, database);
    }

}

備份還原介面

備份還原服務通過呼叫服務類實現備份還原的REST介面。

提供備份查詢、建立備份、版本還原、刪除備份的功能。

MySqlBackupController.java

package com.louis.kitty.backup.controller;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.louis.kitty.backup.constants.BackupConstants;
import com.louis.kitty.backup.datasource.BackupDataSourceProperties;
import com.louis.kitty.backup.service.MysqlBackupService;
import com.louis.kitty.backup.util.FileUtils;
import com.louis.kitty.backup.util.HttpResult;

/**
 * 系統資料備份還原
 * 採用MYSQL備份還原命令
 * @author Louis
 * @date Sep 20, 2018
 */
@RestController
@RequestMapping("/backup")
public class MySqlBackupController {

    @Autowired
    MysqlBackupService mysqlBackupService;
    @Autowired
    BackupDataSourceProperties properties;

    @GetMapping("/backup")
    public HttpResult backup() {
        String host = properties.getHost();
        String userName = properties.getUserName();
        String password = properties.getPassword();
        String database = properties.getDatabase();
        String backupFodlerName = BackupConstants.DEFAULT_BACKUP_NAME+ "_" + (new SimpleDateFormat(BackupConstants.DATE_FORMAT)).format(new Date());
        String backupFolderPath = BackupConstants.BACKUP_FOLDER + backupFodlerName + File.separator;
        String fileName = BackupConstants.BACKUP_FILE_NAME;
        try {
            mysqlBackupService.backup(host, userName, password, backupFolderPath, fileName, database);
        } catch (Exception e) {
            return HttpResult.error(500, e.getMessage());
        }
        return HttpResult.ok();
    }

    @GetMapping("/restore")
    public HttpResult restore(@RequestParam String name) {
        String host = properties.getHost();
        String userName = properties.getUserName();
        String password = properties.getPassword();
        String database = properties.getDatabase();
        String restoreFilePath = BackupConstants.RESTORE_FOLDER + name;
        try {
            mysqlBackupService.restore(restoreFilePath, host, userName, password, database);
        } catch (Exception e) {
            return HttpResult.error(500, e.getMessage());
        }
        return HttpResult.ok();
    }
    
    @GetMapping("/findRecords")
    public HttpResult findBackupRecords() {
        List<Map<String, Object>> backupRecords = new ArrayList<>();
        File restoreFolderFile = new File(BackupConstants.RESTORE_FOLDER);
        if(restoreFolderFile.exists()) {
            for(File file:restoreFolderFile.listFiles()) {
                Map<String, Object> bean = new HashMap<>();
                bean.put("name", file.getName());
                bean.put("title", file.getName());
                if(BackupConstants.DEFAULT_BACKUP_NAME.equals(file.getName())) {
                    bean.put("title", "系統預設備份");
                }
                backupRecords.add(bean);
            }
        }
        return HttpResult.ok(backupRecords);
    }
    
    @GetMapping("/delete")
    public HttpResult deleteBackupRecord(@RequestParam String name) {
        if(BackupConstants.DEFAULT_BACKUP_NAME.equals(name)) {
            return HttpResult.error("系統預設備份無法刪除!");
        }
        String restoreFilePath = BackupConstants.RESTORE_FOLDER + name;
        try {
            FileUtils.deleteFile(new File(restoreFilePath));
        } catch (Exception e) {
            return HttpResult.error(500, e.getMessage());
        }
        return HttpResult.ok();
    }

}

介面測試

建立備份

呼叫備份介面,生成備份。

備份建立成功之後,會在_backup目錄下生成以時間戳相關的備份目錄,目錄下包含資料庫備份SQL檔案。

為防止所有備份被刪除,backup目錄下提供系統預設備份,放置系統初始化資料,通過刪除介面,不可刪除。

查詢備份

通過備份查詢介面,可以查詢所有備份記錄,顯示在前臺,用於提供備份的還原和刪除。

備份查詢介面返回如下資料結構,name為操作標識,title用於前臺顯示備份資訊。

{
  "code": 200,
  "msg": null,
  "data": [
    {
      "name": "backup",
      "title": "系統預設備份"
    },
    {
      "name": "backup_2018-09-22_103504",
      "title": "backup_2018-09-22_103504"
    },
    {
      "name": "backup_2018-09-22_103506",
      "title": "backup_2018-09-22_103506"
    },
    {
      "name": "backup_2018-09-22_103508",
      "title": "backup_2018-09-22_103508"
    }
  ]
}

刪除備份

根據查詢結果,傳入備份的name作為引數,即可呼叫刪除介面刪除備份。

還原備份

根據查詢結果,傳入備份的name作為引數,即可呼叫還原介面還原備份。

 

前臺測試

結合前臺頁面操作,我們可以以介面的方式查詢、建立、刪除和還原備份。

我們在前臺頁面新增備份還原操作入口,如下圖所示。

在系統備份還原操作介面,提供查詢、建立、刪除、還原備份的功能。

前臺頁面的實現參考前臺篇章教程。

原始碼下載