package logic import ( "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "strings" "libreofficeMicro/pb/libreoffice" ) type LibreofficeLogic struct{} func NewLibreofficeLogic() *LibreofficeLogic { return &LibreofficeLogic{} } // ConvertToPdf 通过 URL 下载文件并用 LibreOffice 转换为 PDF,返回 PDF 二进制数据 func (l *LibreofficeLogic) ConvertToPdf(req *libreoffice.ConvertToPdfRequest) (*libreoffice.ConvertToPdfResponse, error) { // 创建临时工作目录 workDir, err := os.MkdirTemp("", "libreoffice-*") if err != nil { return nil, fmt.Errorf("创建临时目录失败: %w", err) } defer os.RemoveAll(workDir) // 下载源文件 inputPath := filepath.Join(workDir, req.FileName) if err := downloadFile(req.FileUrl, inputPath); err != nil { return nil, fmt.Errorf("下载文件失败: %w", err) } // 调用 LibreOffice 转换 pdfPath, err := convertToPdfWithLibreoffice(inputPath, workDir) if err != nil { return nil, fmt.Errorf("LibreOffice 转换失败: %w", err) } // 读取 PDF 文件内容 pdfData, err := os.ReadFile(pdfPath) if err != nil { return nil, fmt.Errorf("读取 PDF 文件失败: %w", err) } pdfFileName := strings.TrimSuffix(req.FileName, filepath.Ext(req.FileName)) + ".pdf" return &libreoffice.ConvertToPdfResponse{ PdfData: pdfData, FileName: pdfFileName, }, nil } // ConvertToPdfFromBytes 接收文件二进制数据,用 LibreOffice 转换为 PDF,返回 PDF 二进制数据 func (l *LibreofficeLogic) ConvertToPdfFromBytes(req *libreoffice.ConvertToPdfFromBytesRequest) (*libreoffice.ConvertToPdfFromBytesResponse, error) { // 创建临时工作目录 workDir, err := os.MkdirTemp("", "libreoffice-*") if err != nil { return nil, fmt.Errorf("创建临时目录失败: %w", err) } defer os.RemoveAll(workDir) fmt.Println("ConvertToPdfFromBytes --1") // 将二进制数据写入临时文件 inputPath := filepath.Join(workDir, req.FileName) if err := os.WriteFile(inputPath, req.FileData, 0644); err != nil { return nil, fmt.Errorf("写入临时文件失败: %w", err) } fmt.Println("ConvertToPdfFromBytes --2") // 调用 LibreOffice 转换 pdfPath, err := convertToPdfWithLibreoffice(inputPath, workDir) if err != nil { return nil, fmt.Errorf("LibreOffice 转换失败: %w", err) } fmt.Println("ConvertToPdfFromBytes --3") // 读取 PDF 文件内容 pdfData, err := os.ReadFile(pdfPath) if err != nil { return nil, fmt.Errorf("读取 PDF 文件失败: %w", err) } fmt.Println("ConvertToPdfFromBytes --4") pdfFileName := strings.TrimSuffix(req.FileName, filepath.Ext(req.FileName)) + ".pdf" return &libreoffice.ConvertToPdfFromBytesResponse{ PdfData: pdfData, FileName: pdfFileName, }, nil } // GetPreviewUrl 已废弃,请使用 ConvertToPdf 或 ConvertToPdfFromBytes // libreofficeExec 查找 libreoffice 可执行文件路径,优先使用 PATH, // 找不到则按 Ubuntu/Debian 常见安装路径回退。 func libreofficeExec() (string, error) { if path, err := exec.LookPath("libreoffice"); err == nil { return path, nil } candidates := []string{ "/usr/bin/libreoffice", "/usr/lib/libreoffice/program/soffice", "/opt/libreoffice/program/soffice", } for _, p := range candidates { if _, err := os.Stat(p); err == nil { return p, nil } } return "", fmt.Errorf("未找到 libreoffice 可执行文件,请确认容器内已安装 LibreOffice") } // convertToPdfWithLibreoffice 调用容器内 LibreOffice CLI 将文件转换为 PDF func convertToPdfWithLibreoffice(inputPath, outDir string) (string, error) { fmt.Println("convertToPdfWithLibreoffice --1") bin, err := libreofficeExec() fmt.Println("convertToPdfWithLibreoffice --2 bin:", bin) if err != nil { return "", err } fmt.Println("convertToPdfWithLibreoffice --3") cmd := exec.Command( bin, "--headless", "--convert-to", "pdf", "--outdir", outDir, inputPath, ) fmt.Println("convertToPdfWithLibreoffice --4") output, err := cmd.CombinedOutput() fmt.Println("convertToPdfWithLibreoffice --5 output:", string(output)) if err != nil { return "", fmt.Errorf("libreoffice 执行失败: %w, 输出: %s", err, string(output)) } // LibreOffice 会将输出文件命名为 .pdf base := strings.TrimSuffix(filepath.Base(inputPath), filepath.Ext(inputPath)) pdfPath := filepath.Join(outDir, base+".pdf") if _, err := os.Stat(pdfPath); os.IsNotExist(err) { return "", fmt.Errorf("转换完成但未找到 PDF 文件: %s", pdfPath) } return pdfPath, nil } // downloadFile 从 URL 下载文件到指定路径 func downloadFile(url, destPath string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("下载文件 HTTP 状态码异常: %d", resp.StatusCode) } f, err := os.Create(destPath) if err != nil { return err } defer f.Close() _, err = io.Copy(f, resp.Body) return err } // copyFile 复制文件 func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) return err }