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