如何用Sidekiq实现RailsAdmin导出操作的后台执行?
我之前也踩过类似的坑——Rails Admin默认的同步导出在数据量上去后,很容易触发网关超时,尤其是生产环境的反向代理(比如Nginx)一般都有请求超时限制。下面一步步帮你把导出逻辑移到Sidekiq后台:
第一步:先搞懂Rails Admin导出的核心逻辑
你找不到项目里的导出代码很正常,因为这个功能是Rails Admin gem内置的,核心逻辑在gem源码的rails_admin/main_controller.rb里的export方法。我们不用直接改gem源码,而是通过自定义Rails Admin动作来覆盖默认的同步导出行为。
先确认你的项目里有没有app/controllers/rails_admin目录,如果没有的话可以新建,用来存放自定义的rails_admin控制器扩展。
第二步:自定义导出动作,触发Sidekiq任务
在config/initializers/rails_admin.rb里修改动作配置,把默认的export替换成异步触发的逻辑:
RailsAdmin.config do |config| # 保留你的其他配置... config.actions do dashboard # 必选动作 index # 必选动作 new # 替换默认的export动作 export do controller do proc do # 把导出参数和当前用户ID传给Sidekiq Worker AdminExportWorker.perform_async( params.except(:authenticity_token).to_h, current_user.id ) # 给管理员返回提示,告诉他们任务在后台运行 flash[:notice] = "报表正在生成中,完成后会通过邮件通知你!" redirect_to back_or_index end end end # 保留其他默认动作 show edit delete bulk_delete end end
第三步:编写Sidekiq Worker处理导出逻辑
新建app/workers/admin_export_worker.rb,把Rails Admin的导出逻辑搬到这里:
class AdminExportWorker include Sidekiq::Worker sidekiq_options retry: 2 # 失败后重试2次 def perform(params, user_id) user = User.find(user_id) # 1. 获取要导出的模型类 model_class = params[:model_name].constantize # 2. 获取该模型的Rails Admin配置 model_config = RailsAdmin.config(model_class) # 3. 构建和Rails Admin列表页一致的查询(包含筛选、排序) query = model_class.includes(model_config.list.includes) # 处理筛选参数 query = query.where(params[:f].permit!.to_h) if params[:f].present? # 处理排序参数 if params[:sort].present? sort_direction = params[:sort_reverse] == 'true' ? 'DESC' : 'ASC' query = query.order("#{params[:sort]} #{sort_direction}") end # 4. 用Rails Admin的导出器生成文件内容 exporter = RailsAdmin::Export::Factory.instance.create( params[:format], model_config, query ) file_content = exporter.export # 5. 存储导出文件(这里用Active Storage,也可以换成云存储) blob = ActiveStorage::Blob.create_and_upload!( io: StringIO.new(file_content), filename: "#{model_class.name.downcase}_report_#{Time.now.strftime('%Y%m%d%H%M%S')}.#{params[:format]}", content_type: Mime::Type.lookup_by_extension(params[:format]).to_s ) # 6. 通知管理员报表已生成 AdminMailer.report_ready(user, blob).deliver_now # 记录日志,和你原来的log_info对应 Rails.logger.info("Generated admin report: #{params.merge(current_user_id: user_id).to_s}") rescue => e Rails.logger.error("Failed to generate admin report: #{e.message}") # 通知管理员导出失败 AdminMailer.report_failed(user, e.message).deliver_now raise # 让Sidekiq记录失败日志 end end
第四步:添加邮件通知(可选但推荐)
新建app/mailers/admin_mailer.rb,用来给管理员发送导出结果通知:
class AdminMailer < ApplicationMailer default from: 'your-app@example.com' def report_ready(user, blob) @user = user @download_url = rails_blob_url(blob, disposition: 'attachment') mail(to: @user.email, subject: '你的报表已生成') end def report_failed(user, error_message) @user = user @error_message = error_message mail(to: @user.email, subject: '报表生成失败') end end
然后在app/views/admin_mailer下创建对应的邮件模板,比如report_ready.html.erb:
<h1>报表已生成</h1> <p>您好 <%= @user.name %>,您请求的报表已完成,点击下面的链接下载:</p> <p><%= link_to '下载报表', @download_url %></p>
第五步:调整现有代码(可选)
你原来在rails_admin/application_controller.rb里的log_info方法可以保留,现在它会在触发导出任务时记录请求日志,而Worker里的日志会记录实际完成情况,这样日志链更完整。
额外注意事项
- 确保预发布和生产环境的Sidekiq服务正常运行,并且配置了足够的进程处理导出任务
- 如果导出文件很大,建议用云存储(比如AWS S3)替代本地存储,避免服务器磁盘被占满
- 可以给管理员加一个任务状态页面,让他们能查看自己的导出任务进度
内容的提问来源于stack exchange,提问作者Łukasz Szmelc

