using System.Diagnostics; namespace Rmsys.Infrastructure; /// /// Word/OFD → PDF 转换(数据包内 PAD 只能显示 PDF,不能显示 docx)。 /// /// 依赖外部转换器:服务器上装 LibreOffice(soffice)即可;可用环境变量 /// RMSYS_SOFFICE 指定 soffice 可执行文件全路径。 /// 转换结果按「材料Id_版本号」缓存在 App_Data/_pdfcache,同一份材料只转一次。 /// public static class PdfConverter { private static readonly object _lock = new(); private static bool _checked; private static string? _soffice; /// 转换器是否可用(不可用时不阻断出包,只是包里没有 PDF 正文) public static bool Available { get { EnsureProbe(); return _soffice != null; } } public static string? ExePath { get { EnsureProbe(); return _soffice; } } private static void EnsureProbe() { if (_checked) return; lock (_lock) { if (_checked) return; _checked = true; var custom = Environment.GetEnvironmentVariable("RMSYS_SOFFICE"); if (!string.IsNullOrWhiteSpace(custom) && File.Exists(custom)) { _soffice = custom; return; } var candidates = new[] { "soffice", "soffice.exe", "soffice.com", @"C:\Program Files\LibreOffice\program\soffice.exe", @"C:\Program Files (x86)\LibreOffice\program\soffice.exe", "/usr/bin/soffice", "/usr/lib/libreoffice/program/soffice" }; foreach (var c in candidates) { try { if (c.Contains('/') || c.Contains('\\')) { if (File.Exists(c)) { _soffice = c; return; } } else { var psi = new ProcessStartInfo(c) { Arguments = "--version", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var p = Process.Start(psi); if (p != null && p.WaitForExit(8000) && p.ExitCode == 0) { _soffice = c; return; } try { p?.Kill(true); } catch { } } } catch { /* 试下一个 */ } } } } /// /// 把源文件转成 PDF,返回 PDF 全路径;转不了返回 null。 /// PDF 直接返回源文件;docx/doc 走 LibreOffice;其余类型(.lrmx/.ofd 等)返回 null。 /// public static string? ToPdf(string srcFull, string cacheDir, string cacheKey) { try { if (!File.Exists(srcFull)) return null; var ext = Path.GetExtension(srcFull).ToLowerInvariant(); // 已经是 PDF:直接用源文件 if (ext == ".pdf") return srcFull; // 只对 Word 家族做转换 if (ext != ".docx" && ext != ".doc") return null; if (!Available || ExePath == null) return null; Directory.CreateDirectory(cacheDir); var cache = Path.Combine(cacheDir, cacheKey + ".pdf"); if (File.Exists(cache) && File.GetLastWriteTimeUtc(cache) >= File.GetLastWriteTimeUtc(srcFull)) return cache; var outDir = Path.Combine(Path.GetTempPath(), "rmsyspdf_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(outDir); var profile = Path.Combine(Path.GetTempPath(), "rmsyslo_" + Guid.NewGuid().ToString("N")); try { // 每次用独立 UserInstallation,避免并发调用时 LibreOffice 单实例锁冲突 var args = $"--headless --norestore --nolockcheck --nodefault --nofirststartwizard " + $"-env:UserInstallation=file:///{profile.Replace('\\', '/')} " + $"--convert-to pdf:writer_pdf_Export --outdir \"{outDir}\" \"{srcFull}\""; var psi = new ProcessStartInfo(ExePath!) { Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var proc = Process.Start(psi); if (proc == null) return null; if (!proc.WaitForExit(180_000)) { try { proc.Kill(true); } catch { } return null; } var produced = Directory.GetFiles(outDir, "*.pdf").FirstOrDefault(); if (produced == null || new FileInfo(produced).Length < 1024) return null; File.Copy(produced, cache, overwrite: true); return cache; } finally { try { if (Directory.Exists(outDir)) Directory.Delete(outDir, true); } catch { } try { if (Directory.Exists(profile)) Directory.Delete(profile, true); } catch { } } } catch { return null; // 转换失败不阻断出包 } } }