保存PDF至文件并上传S3:开发正常生产环境报错求助
你的代码在开发环境跑起来毫无问题,一到生产环境就触发500错误,这种环境差异导致的问题,通常集中在权限、文件处理时机或者配置细节上,我给你梳理几个高概率的原因和对应的解决办法:
1. 临时目录不存在或权限不足
生产环境部署时,tmp/pdfs这个目录很可能没被自动创建,或者Rails运行进程没有读写这个目录的权限——开发环境你可能手动建过或者系统权限比较宽松,但生产环境的部署流程往往不会自动帮你处理这些细节。
修复方式:在代码里先确保目录存在,用FileUtils.mkdir_p递归创建目录(连父目录一起生成):
def upload_to_s3(pdf) save_dir = "#{Rails.root}/tmp/pdfs" # 确保目录存在,不存在则自动创建 FileUtils.mkdir_p(save_dir) unless Dir.exist?(save_dir) save_path = "#{save_dir}/invoice_#{@invoice.id}.pdf" # 后续代码保持不变... end
你也可以在部署脚本(比如Capistrano的deploy.rb)里添加创建目录并设置权限的步骤,避免每次部署都要手动处理。
2. 文件未完全写入就触发上传
你用File.new打开文件写入后,没有显式关闭文件。虽然Ruby会在对象被回收时自动关闭文件,但生产环境的磁盘IO速度可能比开发环境慢,导致你刚写完就去读取文件时,内容还没完全刷到磁盘上,上传的是不完整的PDF,进而引发错误。
修复方式:改用File.open的块写法,块执行结束时会自动关闭文件,确保所有内容都写入磁盘:
# 替换原来的File.new和f.write部分 File.open(save_path, 'w:ASCII-8BIT') do |f| f.write(pdf) end
3. S3上传器的生产环境配置错误
开发环境和生产环境的S3配置(比如密钥、桶名、区域)大概率是分开的,可能生产环境的环境变量没配置对,或者CarrierWave的初始化配置在生产环境有问题(比如用了错误的存储桶,或者IAM角色权限不足)。
排查步骤:
- 检查
config/initializers/carrierwave.rb里的生产环境分支配置,确认桶名、区域、存储方式都正确; - 验证生产环境的S3密钥(或者IAM角色权限)是否拥有上传文件的权限;
- 查看完整的堆栈跟踪,有没有AWS相关的错误提示(比如
AccessDenied),这能直接帮你定位问题。
4. 文件删除时机的潜在问题
你上传完成后立刻删除文件,但如果uploader.store!在生产环境被配置为异步执行(有些上传器会启用后台上传),那可能上传还没完成就把源文件删了,导致上传失败。另外,如果上传过程中抛出异常,文件也会被留在临时目录里占用空间。
修复方式:用begin...ensure块确保文件无论成功失败都会被清理,同时确认上传是同步执行的:
def upload_to_s3(pdf) save_dir = "#{Rails.root}/tmp/pdfs" FileUtils.mkdir_p(save_dir) unless Dir.exist?(save_dir) save_path = "#{save_dir}/invoice_#{@invoice.id}.pdf" File.open(save_path, 'w:ASCII-8BIT') do |f| f.write(pdf) end uploader = InvoiceUploader.new begin File.open(save_path) { |file| uploader.store!(file) } @invoice.update(pdf: uploader.url) uploader.url ensure # 确保文件被删除,即使上传失败 File.delete(save_path) if File.exist?(save_path) end end
最后提醒一句:一定要看完整的500错误堆栈跟踪!你只贴了开头部分,里面的具体错误信息(比如是Errno::EACCES权限错误,还是AWS::S3::Errors::AccessDenied,或者ActiveRecord的验证错误)才是精准定位问题的关键。
内容的提问来源于stack exchange,提问作者Steve Q

