mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-17 21:34:45 +08:00
99 lines
2.3 KiB
Go
99 lines
2.3 KiB
Go
|
|
package repository
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"fmt"
|
|||
|
|
"io"
|
|||
|
|
"os/exec"
|
|||
|
|
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// PgDumper implements service.DBDumper using pg_dump/psql
|
|||
|
|
type PgDumper struct {
|
|||
|
|
cfg *config.DatabaseConfig
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewPgDumper creates a new PgDumper
|
|||
|
|
func NewPgDumper(cfg *config.Config) service.DBDumper {
|
|||
|
|
return &PgDumper{cfg: &cfg.Database}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Dump executes pg_dump and returns a streaming reader of the output
|
|||
|
|
func (d *PgDumper) Dump(ctx context.Context) (io.ReadCloser, error) {
|
|||
|
|
args := []string{
|
|||
|
|
"-h", d.cfg.Host,
|
|||
|
|
"-p", fmt.Sprintf("%d", d.cfg.Port),
|
|||
|
|
"-U", d.cfg.User,
|
|||
|
|
"-d", d.cfg.DBName,
|
|||
|
|
"--no-owner",
|
|||
|
|
"--no-acl",
|
|||
|
|
"--clean",
|
|||
|
|
"--if-exists",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cmd := exec.CommandContext(ctx, "pg_dump", args...)
|
|||
|
|
if d.cfg.Password != "" {
|
|||
|
|
cmd.Env = append(cmd.Environ(), "PGPASSWORD="+d.cfg.Password)
|
|||
|
|
}
|
|||
|
|
if d.cfg.SSLMode != "" {
|
|||
|
|
cmd.Env = append(cmd.Environ(), "PGSSLMODE="+d.cfg.SSLMode)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stdout, err := cmd.StdoutPipe()
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, fmt.Errorf("create stdout pipe: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := cmd.Start(); err != nil {
|
|||
|
|
return nil, fmt.Errorf("start pg_dump: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回一个 ReadCloser:读 stdout,关闭时等待进程退出
|
|||
|
|
return &cmdReadCloser{ReadCloser: stdout, cmd: cmd}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Restore executes psql to restore from a streaming reader
|
|||
|
|
func (d *PgDumper) Restore(ctx context.Context, data io.Reader) error {
|
|||
|
|
args := []string{
|
|||
|
|
"-h", d.cfg.Host,
|
|||
|
|
"-p", fmt.Sprintf("%d", d.cfg.Port),
|
|||
|
|
"-U", d.cfg.User,
|
|||
|
|
"-d", d.cfg.DBName,
|
|||
|
|
"--single-transaction",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cmd := exec.CommandContext(ctx, "psql", args...)
|
|||
|
|
if d.cfg.Password != "" {
|
|||
|
|
cmd.Env = append(cmd.Environ(), "PGPASSWORD="+d.cfg.Password)
|
|||
|
|
}
|
|||
|
|
if d.cfg.SSLMode != "" {
|
|||
|
|
cmd.Env = append(cmd.Environ(), "PGSSLMODE="+d.cfg.SSLMode)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cmd.Stdin = data
|
|||
|
|
|
|||
|
|
output, err := cmd.CombinedOutput()
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("%v: %s", err, string(output))
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cmdReadCloser wraps a command stdout pipe and waits for the process on Close
|
|||
|
|
type cmdReadCloser struct {
|
|||
|
|
io.ReadCloser
|
|||
|
|
cmd *exec.Cmd
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *cmdReadCloser) Close() error {
|
|||
|
|
// Close the pipe first
|
|||
|
|
_ = c.ReadCloser.Close()
|
|||
|
|
// Wait for the process to exit
|
|||
|
|
if err := c.cmd.Wait(); err != nil {
|
|||
|
|
return fmt.Errorf("pg_dump exited with error: %w", err)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|