原生移动端向Rails API高效传文件:中间件转Rails对象最优方案
我正在构建Postman请求模板,给移动端开发人员用来实现和Rails API的交互功能。现在的问题是,原生端上传文件到Rails API时,怎么通过中间件把文件对象转换成Rails能直接处理的对象,实现计算量最小的高效上传?
目前的实现是在请求体传{"file": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//gA7Q1JFQVRPUj...},然后在控制器里要做这些解码和写入操作:
tempfile = Tempfile.new("fileupload") tempfile.binmode tempfile.write(Base64.decode64("base64 code")) tempfile.rewind() mime_type = Mime::Type.lookup_by_extension(File.extname('filename.jpg')[1..-1]).to_s uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tempfile, filename: 'filename.jpg', type: mime_type)
感觉这种方式计算效率不高,想找更轻量化的实现,最好让中间件处理解码、保存,直接转成Active Storage对象。
1. 改用multipart/form-data上传(最推荐,计算量最小)
别用Base64编码,让移动端直接用multipart/form-data格式上传文件——这是Rails原生支持的上传方式,不需要额外解码,中间件会自动把文件解析成ActionDispatch::Http::UploadedFile对象,直接就能传给Active Storage。
控制器里只需要一行代码:
@model.attachment.attach(params[:file])
Postman里直接选form-data,键填file,值选文件上传,这样模板给移动端开发人员也更直观,完全避免Base64的编码解码开销(Base64会让文件体积增大30%左右,还多了编码解码的计算量)。
2. 若必须用Base64,写自定义中间件处理
如果因为某些限制只能传Base64,那可以写个中间件提前处理解码,不用在控制器里写重复代码:
步骤1:生成中间件文件
在app/middleware/base64_file_upload_middleware.rb中编写:
class Base64FileUploadMiddleware def initialize(app) @app = app end def call(env) request = ActionDispatch::Request.new(env) # 只处理POST/PUT/PATCH请求,且参数里有base64格式的file字段 if request.post? || request.put? || request.patch? file_param = request.params[:file] if file_param&.start_with?('data:') # 解析base64数据,拆分类型与编码内容 mime_type, encoded_data = file_param.split(',', 2) mime_type = mime_type.match(/data:(.*?);/)[1] # 从MIME类型推导扩展名 ext = Mime::Type.lookup(mime_type).try(:symbol) || 'bin' filename = "upload.#{ext}" # 直接解码到Tempfile,避免大文件占用过多内存 tempfile = Tempfile.new(["base64_upload", ".#{ext}"], binmode: true) tempfile.write(Base64.decode64(encoded_data)) tempfile.rewind # 替换成Rails原生的UploadedFile对象 request.params[:file] = ActionDispatch::Http::UploadedFile.new( tempfile: tempfile, filename: filename, type: mime_type ) end end @app.call(env) end end
步骤2:注册中间件
在config/application.rb中添加中间件注册:
module YourAppName class Application < Rails::Application # ...其他配置 config.middleware.use Base64FileUploadMiddleware end end
步骤3:控制器直接使用
现在控制器里不用再处理解码逻辑,直接将参数传给Active Storage即可:
def create @model = Model.new(model_params) if @model.save render json: @model, status: :created else render json: @model.errors, status: :unprocessable_entity end end private def model_params params.require(:model).permit(:name, :file) # 直接使用处理后的:file参数 end
3. 优化现有Base64处理代码(临时过渡方案)
如果暂时不想改动中间件,至少优化现有代码,减少不必要的计算步骤:
# 一次性解析base64头部,获取MIME类型与编码数据 mime_type, encoded_data = params[:file].split(',', 2) mime_type = mime_type.match(/data:(.*?);/)[1] # 从MIME类型推导文件名与扩展名 ext = Mime::Type.lookup(mime_type).symbol filename = "upload.#{ext}" # 合并Tempfile初始化与写入操作 tempfile = Tempfile.new(["fileupload", ".#{ext}"], binmode: true) tempfile.write(Base64.decode64(encoded_data)) tempfile.rewind uploaded_file = ActionDispatch::Http::UploadedFile.new( tempfile: tempfile, filename: filename, type: mime_type )
这样减少了冗余的变量定义与方法调用,比如不用单独调用File.extname,直接从MIME类型推导扩展名。
内容的提问来源于stack exchange,提问作者ohhh

