You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Rails、Prawn、CombinePDF的可盖章PDF验证及代码排查

问题解答

一、现有盖章代码的问题分析

以下是代码中存在的几个关键问题:

  1. Active Storage操作无事务回滚保障
    代码在ActiveRecord事务内执行account_statement_file.purge和attach操作,但Active Record事务仅覆盖数据库变更,文件存储(如本地磁盘、云存储)的操作不会被回滚。若attach失败,原文件已被删除,会直接导致数据丢失。

  2. PDF处理鲁棒性不足

    • 直接使用Date.parse(paid_on_date),若paid_on_date格式不符合预期会抛出异常,建议指定解析格式(如Date.strptime(paid_on_date, '%Y-%m-%d'))并提前做格式校验。
    • rescue Exception => e会捕获所有异常(包括系统信号、内存错误等非业务异常),应改为rescue StandardError => e,仅处理业务相关错误。
    • URI.open未设置超时,若Blob URL访问缓慢会导致请求阻塞,需添加超时参数:URI.open(self.account_statement_file.blob.url, timeout: 10)。
  3. 盖章文本定位失效
    使用pdf.text的align: :center和valign: :center无法保证文本居中在页面,因为Prawn默认从当前光标位置开始绘制。需通过bounding_box定位到整个页面范围,确保盖章在页面中心:

    pdf.bounding_box([0, pdf.bounds.top], width: pdf.bounds.width, height: pdf.bounds.height) do
      pdf.text text, size: 30, style: :bold, align: :center, valign: :center
    end
    
  4. 资源泄漏风险
    paid_stamp_pdf_file未显式关闭和清理,建议改用Tempfile的block形式自动管理资源:

    Tempfile.new('paid') do |paid_stamp_pdf_file|
      Prawn::Document.generate(paid_stamp_pdf_file.path) do |pdf|
        # ... 盖章内容生成逻辑
      end
      # ... 后续CombinePDF处理逻辑
    end
    
  5. 未处理payment_method_text为nil的情况
    若payment_method_text未定义或为nil,会导致拼接出"nil"字符串,需添加默认值:payment_method_text.to_s。

二、上传前验证与兼容转换方案

方案1:上传时验证PDF兼容性

在模型层添加验证逻辑,尝试用CombinePDF加载并执行简单操作,验证文件是否可正常处理:

class YourModel < ApplicationRecord
  validate :check_pdf_compatibility

  private

  def check_pdf_compatibility
    return unless account_statement_file.attached? && account_statement_file.content_type == 'application/pdf'

    Tempfile.new(['pdf_check', '.pdf']) do |temp_file|
      temp_file.write(account_statement_file.download)
      temp_file.close

      begin
        # 尝试加载并保存PDF,验证解析与写入能力
        pdf = CombinePDF.load(temp_file.path)
        pdf.save(temp_file.path)
      rescue StandardError => e
        errors.add(:account_statement_file, "PDF格式不兼容,无法完成盖章:#{e.message}")
      end
    end
  end
end

方案2:上传时自动转换为兼容格式

使用Ghostscript将上传的PDF转换为标准PDF 1.4格式,修复非标准结构问题(需服务器安装Ghostscript):

class YourModel < ApplicationRecord
  after_commit :convert_to_standard_pdf, on: [:create, :update]

  private

  def convert_to_standard_pdf
    return unless account_statement_file.attached? && account_statement_file.content_type == 'application/pdf'

    Tempfile.create(['input', '.pdf']) do |input_file|
      input_file.write(account_statement_file.download)
      input_file.close

      Tempfile.create(['output', '.pdf']) do |output_file|
        # 调用Ghostscript转换为PDF 1.4,兼容CombinePDF与Prawn
        gs_cmd = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dNOPAUSE -dBATCH -sOutputFile=#{output_file.path} #{input_file.path}"
        system(gs_cmd)

        # 替换原文件
        account_statement_file.purge_later
        account_statement_file.attach(
          io: File.open(output_file.path),
          filename: account_statement_file.filename,
          content_type: 'application/pdf'
        )
      end
    end
  end
end

方案3:客户端初步验证(辅助)

前端使用PDF.js库加载上传的PDF,检查是否能正常渲染,提前拦截明显损坏的文件,但最终兼容性仍需后端验证。


内容的提问来源于stack exchange,提问作者SomeSchmo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 06:27:28