hsy
17 小时以前 81730da6fe27689cfa3a63692a26d1b87d6db045
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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, "未找到数据库连接配置");
        }
 
        // 解析 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);
        }
    }
}