From 81730da6fe27689cfa3a63692a26d1b87d6db045 Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期一, 31 八月 2026 15:14:17 +0800
Subject: [PATCH] feat(system): 新增 mysqldump 数据库备份接口

---
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/database/DatabaseBackupController.java |   29 +++++++++
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupService.java             |   14 ++++
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupServiceImpl.java         |  100 +++++++++++++++++++++++++++++++++
 3 files changed, 143 insertions(+), 0 deletions(-)

diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/database/DatabaseBackupController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/database/DatabaseBackupController.java
new file mode 100644
index 0000000..51ed463
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/database/DatabaseBackupController.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.controller.admin.database;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.system.service.database.DatabaseBackupService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+
+@Tag(name = "绠$悊鍚庡彴 - 鏁版嵁搴撳浠�")
+@RestController
+@RequestMapping("/system/database")
+public class DatabaseBackupController {
+
+    @Resource
+    private DatabaseBackupService databaseBackupService;
+
+    @GetMapping("/backup")
+    @Operation(summary = "涓嬭浇鏁版嵁搴撳浠�")
+    @PreAuthorize("@ss.hasPermission('system:database:backup')")
+    public void backupDatabase(HttpServletResponse response) throws Exception {
+        databaseBackupService.backupDatabase(response);
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupService.java
new file mode 100644
index 0000000..b48ac79
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupService.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.service.database;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+public interface DatabaseBackupService {
+
+    /**
+     * 澶囦唤鏁版嵁搴擄紝骞跺皢 SQL 鏂囦欢鍐欏叆鍒� HTTP 鍝嶅簲涓�
+     *
+     * @param response HTTP 鍝嶅簲
+     */
+    void backupDatabase(HttpServletResponse response) throws Exception;
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupServiceImpl.java
new file mode 100644
index 0000000..28bf9bd
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupServiceImpl.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.system.service.database;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Service
+@Slf4j
+public class DatabaseBackupServiceImpl implements DatabaseBackupService {
+
+    @Value("${spring.datasource.dynamic.datasource.master.url:}")
+    private String dbUrl;
+
+    @Value("${spring.datasource.dynamic.datasource.master.username:root}")
+    private String dbUsername;
+
+    @Value("${spring.datasource.dynamic.datasource.master.password:}")
+    private String dbPassword;
+
+    @Override
+    public void backupDatabase(HttpServletResponse response) throws Exception {
+        if (dbUrl == null || dbUrl.isEmpty()) {
+            throw new ServiceException(500, "鏈壘鍒版暟鎹簱杩炴帴閰嶇疆");
+        }
+
+        // 瑙f瀽 JDBC URL锛屾彁鍙� host, port, dbname
+        // 鏍煎紡: jdbc:mysql://localhost:3306/ruoyi-vue-pro?useSSL=false
+        Pattern pattern = Pattern.compile("jdbc:mysql://([^:]+):(\\d+)/([^?]+)");
+        Matcher matcher = pattern.matcher(dbUrl);
+        String host = "127.0.0.1";
+        String port = "3306";
+        String dbName = "";
+        
+        if (matcher.find()) {
+            host = matcher.group(1);
+            port = matcher.group(2);
+            dbName = matcher.group(3);
+        } else {
+            // fallback
+            log.warn("鏃犳硶瑙f瀽 JDBC URL: {}, 浣跨敤榛樿閰嶇疆", dbUrl);
+            dbName = "ruoyi-vue-pro"; // 榛樿鍚嶏紝濡傛灉鏈夌殑璇�
+        }
+
+        String timestamp = DateUtil.format(new Date(), "yyyyMMdd_HHmmss");
+        String fileName = "backup_" + timestamp + ".sql";
+        File tempFile = File.createTempFile("backup_", ".sql");
+        
+        try {
+            // 鏋勫缓 mysqldump 鍛戒护
+            String[] command = {
+                    "mysqldump",
+                    "-h" + host,
+                    "-P" + port,
+                    "-u" + dbUsername,
+                    "-p" + dbPassword,
+                    dbName,
+                    "-r", tempFile.getAbsolutePath()
+            };
+
+            ProcessBuilder pb = new ProcessBuilder(command);
+            Process process = pb.start();
+            int exitCode = process.waitFor();
+
+            if (exitCode != 0) {
+                String error = IoUtil.read(process.getErrorStream(), StandardCharsets.UTF_8);
+                log.error("鏁版嵁搴撳浠藉け璐�, exitCode: {}, error: {}", exitCode, error);
+                throw new ServiceException(500, "鏁版嵁搴撳浠藉け璐�: " + error);
+            }
+
+            // 璁剧疆鍝嶅簲澶�
+            response.setContentType("application/octet-stream; charset=UTF-8");
+            response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
+            response.setContentLengthLong(tempFile.length());
+
+            // 鍐欏叆鍝嶅簲娴�
+            try (FileInputStream fis = new FileInputStream(tempFile);
+                 OutputStream os = response.getOutputStream()) {
+                IoUtil.copy(fis, os);
+            }
+
+        } finally {
+            // 鍒犻櫎涓存椂鏂囦欢
+            FileUtil.del(tempFile);
+        }
+    }
+}

--
Gitblit v1.9.3