【发布时间】:2016-09-17 17:00:45
【问题描述】:
我有一个 Rails 应用程序,它接受来自第三方来源的 JSON 数据,我相信我遇到了一些 ActiveRecord 幕后魔术,它可以识别从 JSON 中提取的哈希中的 ASCII-8BIT 字符并保存它们无论我做什么,我的数据库都是如此。
这里是类的简化描述...
class MyClass < ActiveRecord::Base
serialize :data
end
以及如何创建对象...
a = MyClass.new
a.data = {
"a" =>
{
"b" => "bb",
"c" => "GIF89a\x01\x00\x01\x00\x00\x00\x00\x00!\vNETSCAPE2.0\x03\x01\x00\x00!\x04\t\x00\x00\x01\x00,\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02L\x01\x00;"
}
}
我相信这些是 ASCII-8BIT 字符,如果它们被保存为这样就足够公平了(尽管我尝试在任何地方都使用 UTF8)。但我需要这些字符是 UTF-8,因为当我去查看它们时,我得到:
ActionView::Template::Error ("\xEF" from ASCII-8BIT to UTF-8):
64: <div>
65: <pre><%= mc.prettify %></pre>
66: </div>
app/models/my_class.rb:28:in `prettify'
prettify 中的第 28 行是:
JSON.pretty_generate(self.data)
所以我试图重新编码哈希中的任何字符串。我构建了执行此操作的功能(使用anonymous classes 和refinements 到Hash、Array 和String),但无论如何,ASCII-8BIT 都会返回给我。用最简单的话来说,这就是正在发生的事情:
mc = MyClass.find(123)
mc.data['a']['c'].encode!(Encoding.find('UTF-8'), {invalid: :replace, undef: :replace, replace: ''})
mc.data['a']['c'].encoding #=> #<Encoding:UTF-8>
mc.data['a']['c'].valid_encoding? #=> true
mc.save!
mc.reload
mc.data['a']['c'].encoding #=> #<Encoding:ASCII-8BIT> <-- !!!!!
ActiveRecord 在保存此哈希时对其做了什么?我该怎么做才能在serialized MySQL(v5.6,通过mysql2 gem)mediumtext列(使用Ruby 2.2.4和Rails 4.1.4)中永久存储所有编码为UTF-8的字符串的哈希?
我的.cnf
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
# ...
skip-character-set-client-handshake
collation_server=utf8_unicode_ci
init-connect='SET NAMES utf8mb4'
character-set-server=utf8
【问题讨论】:
-
我怀疑 ActiveRecord 正在这样做。我怀疑你的数据库是。您的表/列的排序规则设置为什么?
-
或者我错了。这里真正的问题是您的 JSON 中有二进制数据。你真的,真的不应该,因为你最终会得到不是有效 UTF-8 的字节。这将在某处引起问题:在您的数据库、Ruby 或 JSON 解析器中。即使
\xNN表示的字节也必须是有效的 UTF-8 字节——UTF-8 是文本编码,而不是二进制数据。真正的解决方案是首先使用例如以安全的方式对二进制数据进行编码。 Base64。这既常见又必要。 -
@Jordan 排序规则是 utf8_unicode_ci
-
见我的第二条评论。您不会成功地将原始二进制数据存储为 UTF-8 文本。
-
@Jordan 因为我从不将这些显示为图像(仅文本),我仍然会将之前的字符串解码为
JSON.pretty_generate(并得到错误)或在屏幕上显示无意义的 base64。 (解析pretty_generate输出寻找base64 会非常昂贵。)我更倾向于清空有问题的字符串。真的没有办法把它锤入 UTF-8 吗?
标签: mysql ruby-on-rails ruby activerecord utf-8