Ruby on Rails ActiveStorage:image_tag无法显图但img用URL可行
问题现象
使用Ruby on Rails ActiveStorage结合AWS S3存储文件时,仅直接通过person.as_photo.url生成的预签名URL能正常显示图片,image_tag(person.as_photo)和rails_storage_proxy_path生成的代理/重定向URL均返回404错误。
配置与测试代码
1. storage.yml配置
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) amazon: service: S3 # Instead of specifying these or using Rails.application.credentials we can rely on env vars or any other valid way of authenticating the aws sdk gem access_key_id: # 测试用硬编码密钥 secret_access_key: # 测试用硬编码密钥 region: us-east-1 bucket: test-my-s3-bucket-name
2. Personnel模型代码
class Personnel < ApplicationRecord ... # NEW ACTIVESTORAGE CODE has_one_attached :as_photo # OPTIONAL: validates :as_photo, size: { less_than: 3.megabytes , message: 'is too large' }, content_type: {in: ['image/jpeg', 'image/png', 'image/gif', 'image/pjpeg', 'image/x-png'], message: I18n.t("errors.file_invalid_format", :formats => "JPG,GIF,PNG") } before_save :define_attachment_key # 基于模型名生成文件key,用于在S3中创建"子文件夹"分类存储 def photo_path # 确保key全局唯一,使用ActiveStorage的安全token生成方法 "public/#{Rails.env}/models/#{self.class.name}/photo/#{ActiveStorage::Blob.generate_unique_secure_token}" end def define_attachment_key as_photo.key = photo_path if as_photo.new_record? # 多附件场景可在此添加其他..._path方法,无自定义路径的附件会存在桶根目录 end end
3. 测试模板代码
<h1>Personnel</h1> <h2>There are a total of <%= @count %> Personnel records</h2> <ul> <% @persons.each do |person| %> <li> <%= person.id %> <% if person.as_photo.present? %> <ul> <li> <%= image_tag(person.as_photo) %> </li> <li> Proxy: <%= image_tag rails_storage_proxy_path(person.as_photo) %> </li> <li> img tag with as.url: <img src="<%= person.as_photo.url %>" alt="an image wow"> </li> </ul> <% end %> </li> <% end %> </ul>
问题核心原因
直接调用url生成的是AWS S3预签名URL,绕过了Rails代理;而image_tag(attachment)和rails_storage_proxy_path依赖Rails的ActiveStorage路由代理请求,404的根源是自定义Blob key格式干扰了ActiveStorage的内部路径解析逻辑。
ActiveStorage默认维护Blob的key与存储路径的映射关系,你手动设置的public/...格式key,会导致代理路由尝试查找文件时,无法匹配到S3上的实际文件路径,最终返回404。
解决方案
方案1:移除自定义key设置(推荐)
ActiveStorage默认会自动生成唯一key和合理的存储结构,若仅需按模型分类存储,可通过S3配置的prefix参数实现,无需手动修改Blob的key:
修改storage.yml的amazon配置,添加统一前缀:
amazon: service: S3 access_key_id: # 你的密钥 secret_access_key: # 你的密钥 region: us-east-1 bucket: test-my-s3-bucket-name prefix: "public/#{Rails.env}/models" # 所有模型的文件都会存储在此前缀路径下
随后移除模型中的before_save :define_attachment_key和photo_path方法,让ActiveStorage自动管理key。
方案2:使用直接服务URL替代代理路由
若无需Rails代理(比如文件允许公开访问),可将S3存储桶配置为公开读写,然后使用person.as_photo.service_url生成永久URL,此时image_tag(person.as_photo)会自动使用该URL,无需经过Rails代理。
注意:公开存储桶需做好权限控制,避免未授权访问。
内容的提问来源于stack exchange,提问作者Rosey

