Gem 2021 Rails-carrierwave 通过 api 上传音频实例

dayudodo · 2021年08月21日 · 最后由 tablecell 回复于 2021年08月31日 · 298 次阅读

carrierwave 是 rails 中可以上传文件的工具库,虽然 rails 在 5.2 版本之后已经提供了 active storage,更安全、功能强大,但 carrierwave 用起来比较简单,适合小的项目。

本文要讲的是使用 api 来上传音频文件到 rails 服务器的办法。

carrierwave 官网地址

https://github.com/carrierwaveuploader/carrierwave

star: 8.6k

根据 ruby toolbox 统计,其在文件上传中排名第二,第一的 paperclip 已经停更了。

image-20210821170730387

首先,在 Gemfile 中添加

gem 'carrierwave'

安装好

bundle install

生成一个音频上传类

rails generate uploader Soundfile

会保存在 app/uploaders/soundfile_uploader.rb 中,其内容如下:

class SoundfileUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
     "storage/#{model.class.to_s.underscore}/#{mounted_as}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_allowlist
  #   %w(jpg jpeg gif png)
  # end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   if original_filename
  #     original_filename.force_encoding 'UTF-8'
  #   end
  # end
end

storage

指定了你要保存文档的位置,我是想把文件上传后保存到服务器中,所以 storage :file 并不需要改动。如果想保存到云存储中,请参考官方文档。

store_dir

里面你可以设置成自己想要保存的路径,其默认是保存在 public 目录下,类似于这样:public/storage/practice/文件名,这样上传之后是可以通过 url 来访问的,可以根据你自己的需求调整。也可以使用绝对路径,比如/home/user/storage,请确认你有对应的权限。

建立关联

我想给练习添加一个音频

rails g migration add_soundfile_to_practices soundfile:string
rails db:migrate

db/migrate/20210818034921_add_soundfile_to_practices.rb 内容如下:

class AddSoundfileToPractices < ActiveRecord::Migration[6.0]
  def change
    add_column :practices, :soundfile, :string
  end
end

在 practice.rb 中建立关联

记得要使用 mount_uploader, 如果是多个文件,需要使用 mount_uploaders, 后面多个 S!

class Practice < ApplicationRecord
    belongs_to :user
    belongs_to :pth_sentence
    mount_uploader :soundfile, SoundfileUploader
end

Controller

app/controllers/practices_controller.rb 中主要代码

def new
  @practice = Practice.new
end

def create
  @practice =  Practice.new(practice_params)
  if @practice.save
    render json: { status: 'success', msg: '创建成功' }
  else
    render :new
  end
end

def practice_params
  prms =
    params.require(:practice).permit(
      :user_id,
      :pth_sentence_id,
      :soundfile
    )
  # prms[:soundfiles].first.headers.force_encoding 'utf-8'
  prms
end

routes

routes.rb 中要有路由

resources :practices

测试 web 上传

如果你使用 web 上传,那么可以在 views 中这样来写(app/views/practices/_form.html.erb):

<%= form_for(@practice) do |f| %>
  <%= f.text_field 'user_id' %>
  <%= f.text_field 'pth_sentence_id' %>
  <%= f.file_field :soundfile %>
  <%= f.submit %>
<% end %>

测试 API 上传

参数要组装成 rails 需要的格式,类名 [属性],客户端我用的是 uniapp, 所以用 uniapp 来举例。

uni.uploadFile({
    url: `http://192.168.1.8:3000/practices`, 
    name: 'practice[soundfile]',
    filePath:this.voicePath,
    formData: {
        'practice[user_id]': 1,
        'practice[pth_sentence_id]': 30,
    },
    success: (uploadFileRes) => {
        console.log(uploadFileRes);
    },
    fail: (msg) => {
        console.log(msg.error)
    }
});

uniapp 提供了 uploadFile 的 API:

  • filePath 是要上传的文件名称

  • name 是参数名称,注意其写法为 practice[soundfile],要与 model 中对应

  • 其它参数写在 formData 中。

测试成功,文件已经上传到指定的目录中

image-20210821185153643

观察后台,可以看到 soundfile 已经保存好了。

[1] pry(main)> u= User.find 6
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 LIMIT 1
=> #<User id: 6, email: "c6@c6.com", created_at: "2021-08-18 03:25:59", updated_at: "2021-08-18 03:25:59", phone: "13317555983", name: "king1", title: nil>
[2] pry(main)> u.practices.last
  Practice Load (0.2ms)  SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> #<Practice:0x00007f9c9c83dca0
 id: 8,
 user_id: 6,
 pth_sentence_id: 44,
 local_sound: nil,
 comment: nil,
 created_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
 updated_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
 soundfile: "07-最后他选择了后者_.mp3">

常见用法

可用 url

[4] pry(main)> u.practices.last.soundfile.url
  Practice Load (0.4ms)  SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> "/storage/practice/soundfile/07-%E6%9C%80%E5%90%8E%E4%BB%96%E9%80%89%E6%8B%A9%E4%BA%86%E5%90%8E%E8%80%85_.mp3"

服务器路径

[5] pry(main)> u.practices.last.soundfile.current_path
  Practice Load (0.5ms)  SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> "/home/study/funny_words/public/storage/practice/soundfile/07-最后他选择了后者_.mp3"

可能出现的错误

1、no implicit conversion of nil into String

使用 mount_uploaders, sssss, 复数!我恨复数,我恨复数,我恨复数。

2、Encoding::UndefinedConversionError "\xE6" from ASCII-8BIT to UTF-8

image-20210821175802042

编码问题导致的,经分析,是 headers 中的 filename 编码有问题,见上图 (吐槽:外国人开发的工具总是容易产生国际化问题),经搜索官网 issues,可能的解决办法如下。

1、application.rb 中添加 config.encoding = "utf-8"

2、在 practices_controller.rb 的 practice_params 中添加 prms[:soundfile].first.headers.force_encoding 'utf-8'

def practice_params
  prms =
    params.require(:practice).permit(
      :user_id,
      :pth_sentence_id,
      :soundfile
    )
  prms[:soundfile].headers.force_encoding 'utf-8'
  prms
end

这样就会把 headers 强制转成 utf-8 编码。

3、Can't verify CSRF token authenticity

image-20210821184643969

使用 API 上传的时候,可能会遇到 CSRF 问题,这是为了防止一些网络攻击,可以通过在 application.rb 中添加

config.action_controller.default_protect_from_forgery = false

来关闭此项检查,这样开发时就方便喽。注意!在生产环境中要使用其它安全令牌比如 jwt 来进行合法用户校验。

运行环境

ruby 2.6.5, rails 6.0.4, mysql 5.7(utf8mb4)

class AddSoundfileToPractices < ActiveRecord::Migration[6.0]

[6.0] 这个语法术语叫什么?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号