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, "未找到数据库连接配置"); } // 解析 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("无法解析 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); } } }