You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rails控制器编码二进制文件并在JavaScript正确解码?

解决Rails传递二进制文件到JavaScript的乱码问题

核心问题分析

你遇到的乱码本质是二进制文件的编码解码不匹配:

  • Ruby端若未以二进制模式读取文件,会破坏原始字节;
  • escape_javascript的转义未被反转,且atob直接将Base64转为UTF-8字符串,无法还原二进制字节(非UTF-8兼容的字节会被篡改)。

方案一:修复编码解码链路

1. Rails控制器端修正编码

# 以二进制模式读取文件,避免编码污染
file_content = File.read(@file_path, mode: 'rb')
# 使用strict_encode64生成无换行的Base64字符串,适配JS处理
@file_payload = Base64.strict_encode64(file_content)
# 无需额外escape_javascript,Base64本身是安全ASCII字符,用raw直接嵌入JS

2. JavaScript端添加反转义+二进制解码工具函数

// 反转义Rails的escape_javascript处理结果(如果控制器仍使用了该方法则需要)
function unescapeJavascript(str) {
  return str.replace(/\\'/g, "'")
            .replace(/\\"/g, '"')
            .replace(/\\\\/g, '\\')
            .replace(/\\n/g, '\n')
            .replace(/\\r/g, '\r')
            .replace(/\\t/g, '\t')
            .replace(/\\f/g, '\f')
            .replace(/\\b/g, '\b');
}

// 将Base64转为Uint8Array,保留原始二进制字节
function base64ToUint8Array(base64) {
  const binaryString = atob(base64);
  const bytes = new Uint8Array(binaryString.length);
  for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
}

3. JS.erb中生成正确的File对象

// 读取Base64字符串(若控制器用了escape_javascript则调用unescapeJavascript)
const base64Str = "<%= raw @file_payload %>";
// const base64Str = unescapeJavascript("<%= @file_payload %>");

// 获取正确的文件类型,避免硬编码text/plain
const fileType = "<%= MIME::Types.type_for(@file_path).first || 'application/octet-stream' %>";
const fileName = "<%= raw File.basename(@file_path) %>";

// 用Uint8Array创建File,保留二进制完整性
const fileBytes = base64ToUint8Array(base64Str);
const file = new File([fileBytes], fileName, {
  type: fileType,
  lastModified: new Date(),
});

// 后续绑定到文件输入框的逻辑不变
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
const input = $(`input[data-id='<%= @datum_id %>'][type='file']`);
input[0].files = dataTransfer.files;

方案二:直接传输二进制文件(更可靠)

绕过Base64编码解码环节,让桌面端JS直接请求Rails接口获取二进制文件:

1. Rails新增文件获取接口

# 注意:必须校验file_path的合法性,防止路径遍历攻击
def fetch_file
  allowed_dir = Rails.root.join('tmp', 'uploaded_files') # 限制文件读取目录
  target_path = allowed_dir.join(params[:file_name]).cleanpath

  if target_path.exist? && target_path.fnmatch?("#{allowed_dir}/**")
    send_file target_path, 
              type: MIME::Types.type_for(target_path).first,
              disposition: 'inline'
  else
    head :forbidden
  end
end

2. 桌面端JS请求二进制文件

// 收到手机上传完成通知后,请求文件
fetch(`/fetch_file?file_name=<%= raw File.basename(@file_path) %>`, {
  headers: { 'Accept': 'application/octet-stream' }
})
.then(response => response.blob())
.then(blob => {
  const fileName = "<%= raw File.basename(@file_path) %>";
  const file = new File([blob], fileName, {
    type: blob.type,
    lastModified: new Date(),
  });

  // 绑定到文件输入框
  const dataTransfer = new DataTransfer();
  dataTransfer.items.add(file);
  const input = $(`input[data-id='<%= @datum_id %>'][type='file']`);
  input[0].files = dataTransfer.files;
});

内容的提问来源于stack exchange,提问作者user1130176

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:15:29