服务器环境下后台执行含JavaScript的JSP以自动生成PDF的可行方案咨询
服务器环境下后台执行含JavaScript的JSP以自动生成PDF的可行方案咨询
看起来你现在的核心痛点是:依赖用户登录会话的Edge窗口,在服务器注销后会直接中断PDF生成流程,而且之前尝试的无头工具因为配置或适配问题没起作用对吧?结合你的场景,我整理了几个针对性的可行方案,从低改动到高稳定性的都有:
方案一:迁移逻辑到Java后端(最优稳定方案)
你的前端JS逻辑其实非常清晰:每隔90秒调用GetInvoicesNo.jsp拉取待处理的发票号,再逐个调用GeneratePDF.jsp生成PDF。这些完全可以在Java后端实现,彻底摆脱浏览器依赖:
- 替换前端定时任务:用Java生态的定时任务框架(比如Quartz Scheduler,或者如果是Spring项目直接用Spring Task)替代前端的
setInterval,配置每90秒执行一次任务。 - 后端模拟HTTP请求:
- 用
HttpURLConnection、RestTemplate或者OkHttp直接调用GetInvoicesNo.jsp的接口,获取发票号列表。 - 遍历每个发票号,要么直接调用
GeneratePDF.jsp对应的Java业务逻辑(效率最高,无需HTTP跳转),要么模拟HTTP请求访问GeneratePDF.jsp生成PDF。
- 用
- 注册为后台服务:把这个定时任务打包成独立的Java应用(比如Spring Boot Jar),或者用
procrun/NSSM注册成Windows服务,完全不受用户登录/注销状态的影响。
- 优点:彻底脱离浏览器依赖,服务器环境下稳定性拉满,资源占用也远低于浏览器方案;
- 缺点:需要对现有JSP的Java逻辑有一定了解,做少量代码迁移,不过你的逻辑很简单,改动量其实很小。
方案二:无头浏览器+Windows服务(低改动快速适配方案)
你之前用的是Edge的普通窗口模式,换成无头模式再注册成Windows服务,就能让进程在后台独立运行,不受用户会话影响:
- 调整Edge启动命令为无头模式:
(msedge --headless=new --disable-gpu --no-sandbox --disable-extensions http://localhost:8088/APP/AutoDownloadPDF.jsp--headless=new是Edge 90+版本的新无头模式,兼容性更好;如果是旧版本Edge,替换成--headless即可) - 用NSSM包装成Windows服务:
- 下载NSSM(一款轻量的Windows服务管理工具),解压后进入对应系统位数的目录。
- 执行
nssm install AutoPDFGenerator,在弹出的配置窗口中:- 「Path」选择Edge的exe完整路径(比如
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe) - 「Arguments」填入上面的无头模式参数和JSP地址
- 「Service name」设置为
AutoPDFGenerator(自定义名称)
- 「Path」选择Edge的exe完整路径(比如
- 配置完成后,在Windows服务管理器中启动这个服务,它会在系统后台会话运行,即使服务器注销也不会停止。
- 优点:几乎不需要改动现有JSP/JS代码,5分钟就能完成配置;
- 缺点:依赖浏览器进程,长期运行可能存在内存泄漏风险,建议定期重启服务,或者在服务配置中设置自动重启。
方案三:重新配置Playwright(针对你之前的尝试)
你提到Playwright没成功,大概率是配置没覆盖到你的JS异步场景(比如没等待AJAX请求完成)。给你一个适配你逻辑的Playwright Java示例,直接模拟你的前端JS行为:
import com.microsoft.playwright.*; import java.util.concurrent.TimeUnit; public class AutoPDFGenerator { public static void main(String[] args) { try (Playwright playwright = Playwright.create()) { // 启动Edge无头浏览器 Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions() .setHeadless(true) .setChannel("msedge") .setArgs(java.util.Arrays.asList("--no-sandbox", "--disable-gpu"))); BrowserContext context = browser.newContext(); while (true) { // 模拟前端AJAX请求获取发票号 Page apiPage = context.newPage(); String invoiceResponse = apiPage.evaluate("async () => {" + "const res = await fetch('http://localhost:8088/APP/GetInvoicesNo.jsp?EntityID=40&MappingID=<%=getMappingID%>', {method: 'POST'});" + "return await res.text();" + "}"); apiPage.close(); String[] invoiceNos = invoiceResponse.trim().split(","); for (String invoiceNo : invoiceNos) { if (!invoiceNo.trim().isEmpty()) { String pdfUrl = String.format( "http://localhost:8088/APP/GeneratePDF.jsp?EntityCode=%s&InvoiceNo=%s", "<%=getEntityCode%>", invoiceNo.trim() ); // 打开PDF页面生成文件(如果GeneratePDF直接输出流,也可以用page.download()直接保存) Page pdfPage = context.newPage(); pdfPage.navigate(pdfUrl); // 等待PDF生成完成,根据实际情况调整等待时间或等待特定元素 pdfPage.waitForTimeout(5000); pdfPage.close(); } } // 等待90秒后执行下一轮 TimeUnit.SECONDS.sleep(90); } } catch (Exception e) { e.printStackTrace(); // 这里可以加异常重试或日志告警逻辑 } } }
把这个代码打包成Jar,再用NSSM注册成Windows服务,就能在后台稳定运行了。
- 优点:可以复用现有JS的逻辑思路,无需大改后端;
- 缺点:需要编写少量Java代码,调试Playwright的配置可能需要花点时间。
给你之前尝试失败的小提示
如果之前用HTMLUnit没成功,大概率是它对现代JS的支持不足(HTMLUnit对ES6+的兼容性较差);Playwright的话,可能是没指定正确的浏览器渠道、没启用JS支持,或者没等待AJAX请求完成——上面的示例已经帮你覆盖了这些点,可以试试。
备注:内容来源于stack exchange,提问作者rizuwan




