Rails中实作图片上传API

其实想说的是如何通过JSON文件的方式来上传图片。

问题描述

如标题所述,需要通过接口将图片上传到ROR项目中,如何实现?

解答

先说一个简单的。

以一个model picture为例,看一下具体步骤:

model picture的信息:

Table name pictures

id          :integer
file_name    :string
image       :string

这里,设置routes, 建model,建controller的过程省略,可参见:Rails:Web API接口实作

分三步走:

  • carrierwave

    编辑 Gemfile

    gem 'carrierwave'
    gem 'mini_magick'
    

    终端执行bundle,重启服务器。

  • 新增uploader

    执行:rails g uploader PicImage

    编辑app/uploaders/pic_image_uploader.rb

    class PicImageUploader < CarrierWave::Uploader::Base
     + include CarrierWave::MiniMagick
     ......  
    end
    

    编辑 app/models/picture.rb,把 carrierwave 的 Uploader 挂上去。

    class Picture < ApplicationRecord
      + mount_uploader :image, PicImageUploader
      ......
    end
    
  • 设定create

编辑app/controllers/api/v1/pictures_controller.rb 中,create部分:

# upload image through api
class Api::V1::PicturesController < ApiController
  before_action :authenticate_user!, only: %i[create]

  def create
    @picture = Picture.new(picture_params)  
    if @picture.save
      render json: { file_name: @picture.file_name, image: @picture.image }
    else
      render json: { message: 'failed', error: @picture.errors }, status: 400
    end
  end
  .......
  private

  def picture_params
    params.permit(:file_name, :image)
  end

end

再来个复杂的。👇

如何通过JSON文件来上传?

在搜索解答的过程中,发现了另一种方式,假定你需要通过JSON文件的方式去上传图片,如何实现?
即接口调用是这样的:/api/v1/pictures.json,由于JSON不支持嵌入式文件,所以会有些波折。

具体的实现方式是先将需要上传的图片file经base64编译后,调用接口创建一个实例, 然后在picture controller的create中,对传递过来的参数file_base64进行解析, 将解析后的文件写入temfile,并通过ActionDispatch::Http::UploadedFile来新建一个uploader file,如此,一个image从encoding到decoding的过程结束,再使用Picture.new创建即可。

具体来说,前面的步骤都一样,只是create部分会有些不同。

先给picture新增一个字段,file_base64, 它是图片经base64编译后得到的字符串。

执行:

rails g migration add_file_base64_to_pictures file_base64:string
rake db:migrate

picture.json文件长这样:

picture {:file_base64 => "file_base64", :file_name => "file_name"}

编辑app/controllers/api/v1/pictures_controller.rb 中,create部分:

class Api::V1::PicturesController < ApiController
  before_action :authenticate_user!, only: %i[create]

  def create
    if params[:picture][:file_base64]
      # create a new tempfile named file
      tempfile = Tempfile.new('file')
      tempfile.binmode
      # get the file ,decode it with base64, write it to the tempfile
      tempfile.write(Base64.decode64(params[:picture][:file_base64]))
      # create a new uploaded file
      @uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tempfile, filename: params[:picture][:file_name])
      @picture = Picture.new(params[:picture])
      @picture.image = @uploaded_file
      # delete the tempfile
      tempfile.delete
    end
    if @picture.save
      render json: @picture
    else
      render json: { message: 'failed', error: @picture.errors }, status: 400
    end
  end

end

这里,create部分显得很臃肿,我们skinny一下,改成这样:

class Api::V1::PicturesController < ApiController
before_action :authenticate_user!, only: %i[create]
before_action :process_file, only: %i[create]

def create
  if @picture.save
    render json: @picture
  else
    render json: { message: 'failed', error: @picture.errors }, status: 400
  end
end

private

def process_file
  if params[:picture][:file_base64]
    tempfile = Tempfile.new('file')
    tempfile.binmode
    tempfile.write(Base64.decode64(params[:picture][:file_base64]))
    @uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tempfile, filename: params[:picture][:file_name])
    @picture = Picture.new(params[:picture])
    @picture.image = @uploaded_file
    tempfile.delete
  end
end

end

呃,貌似process_file还是有点臃肿, 可以继续拆,这里就不实作了。

OK. 用postman测试一下,确认正常。

这里,你可能会问,编译后的文件在哪呢?在public/uploaders/picture/image下。

此外,上传的文件不需要 git commit, 在 .gitignore 中添加 public/uploads 这个目录即可。

参考

Sending files to a Rails JSON API

Base64 upload from Android/Java to RoR Carrierwave

Tempfile