记一次从Oracle数据库取BLOB数据遇到的坑
发布于 10 天前 作者 blackmatch 442 次浏览 来自 分享

需求

requirement.jpg

源库中的数据是以BLOB的形式存储的,且数据中含有中文,MySQL数据库的字符集为utf8,最终想要的效果就是在浏览器中以文本的形式展示源库中的数据。为了实现这一需求,尝试了2种方案:

  • 从Oracle层面解决,通过视图将相关字段转换成VARCHAR2类型后在返回,这样从Oracle中查询数据的时候,直接拿到的就是字符串类型的数据。这样做的弊端是:Oracle数据库VARCHAR2类型最大只能支持4kb,如果超过了这个大小就会出错。
  • 从Oracle取到数据后,使用Node.js转换成字符串后再存入到MySQL数据库中。

我使用了第2种解决方案,但是过程并不是很顺利。

遇到的问题

从Oracle数据库中取到的数据,在Node.js中是Buffer对象,要将Buffer对象转换成字符串对Node.js来说实在是太常规了,直接buffer.toString就完事了,可事实并非如此,得到的字符串都是乱码。一般遇到这个问题,大家的第一反应肯定是编码问题,我也是这么想的,考虑到数据中有中文,而Node.js原生并没有支持中文的相关编码,默认是utf8,已经尝试过了。所以就引入了iconv-lite这个模块,用来对Buffer对象进行解码,但是Oracle中使用的字符集是SIMPLIFIED CHINESE_CHINA.ZHS32GB18030,所以我想当然的就使用GB18030编码来解码,代码示例:

const iconv = require('iconv-lite');

// Convert from an encoded buffer to js string.
const str = iconv.decode(buffer, 'gb18030');

结果得到的字符串还是乱码,然后我又把iconv-lite支持的所有中文编码又试了一遍,得到的字符串全都是乱码。

解决

经过一番Google和尝试后仍然没有解决,然后就在上述提到的两种方案之间来回折腾。后来在朋友的引导下,得到了一个思路:先探测Buffer对象的编码,得到确定的编码后,再进行解码。于是乎就找到了这个模块:detect-character-encoding。这个模块主要是用来探测字符编码的,使用方法也很简单,示例代码:

const fs = require('fs');
const detectCharacterEncoding = require('detect-character-encoding');

const fileBuffer = fs.readFileSync('file.txt');
const charsetMatch = detectCharacterEncoding(fileBuffer);

console.log(charsetMatch);
// {
//   encoding: 'UTF-8',
//   confidence: 60
// }

于是乎就用这个模块对上述提到的Buffer对象进行探测,得到的编码竟然是UTF-16LE,然后使用这个编码进行解码,果然得到了正确的字符串。问题到此彻底解决了。

注意事项

  • 探测编码时请多用一些数据样例来探测,最后使用可信度最高的编码。
  • 千万不要动态探测编码,然后动态解码,因为这个模块的探测结果是随着数据的变化而变化的。
  • 使用iconv-lite模块解码时,如果编码名称中有字母,请一律使用小写字母。
  • 一定要确保从Oracle取到的数据在Node.js环境中为Buffer对象。

其他说明

  • 连接Oracle使用的模块是oracledb
  • 连接MySQL使用的模块是knex

总结

这次遇到的问题,其实解决方案是比较清晰的,但是在对Buffer进行解码遇到问题后没有冷静下来分析,在2个解决方案之间来回折腾浪费了很多时间;当已经很明确问题出现在哪个环节时,应该借助相关工具进一步确认问题的根源所在,比如:这次在解码环节出现了问题,而问题的根源也比较清晰,就是解码时使用的编码不对,所以就应该先明确Buffer对象所使用的编码,然后再用正确的编码进行解码即可。

12 回复

UTF-16LE 就是 ucs2Buffer.toString() 默认参数是 Buffer.toString('utf8'), 你这个需求无非是显式指定输出编码 buf.toString('ucs2') 官方文档没仔细看吧~~
其实用 buf.slice(10), 截取前面几个块看看(基本)就知道是啥编码了

  • 如果 00 有规律间隔出现(假定数据里面包含多个数字或者拉丁字母),那么极大可能性是 ucs2
  • 可能是 utf8 或者双字节编码 gbk 之类
  • 可能是普通 ascii 编码
  • 可能是 base64 编码

@waitingsong 感谢大佬指出,我对编码不太熟,所以看到打印的buffer也不太敏感。

http://nodejs.cn/api/buffer.html

Node.js 支持的字符编码有:

  • ‘ascii’ - 仅支持 7 位 ASCII 数据。
  • ‘utf8’ - 多字节编码的 Unicode 字符。
  • ‘utf16le’ - 2 或 4 个字节,小端序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。
  • ‘ucs2’ - ‘utf16le’ 的别名。
  • ‘base64’ - Base64 编码。
  • ‘latin1’ - 将 Buffer 编码成单字节编码的字符串。
  • ‘binary’ - ‘latin1’ 的别名。
  • ‘hex’ - 将每个字节编码成两个十六进制字符。

不支持 UTF16BE,所以看到间隔出现的 00 基本上就是 UTF16LE 了

@waitingsong 感谢科普。

@waitingsong 哇,GET

来自酷炫的 CNodeMD

<Buffer 23 21 63 d0 8d 17 42 e2 78 f0 3a 95 68 a4 6b bb bf dd ea 86 83 f1 67 b2 dd 8e b6 63 cc ea ba de b6 2b 73 dd 1f ba 95 b1 35 34 15 ff 04 6c 14 4d 8c 28 ... >
<Buffer d0 91 3f cd 23 c9 a4 6d 9a f8 92 17 da 2d 14 15 6c f3 2f 06 4a 52 28 29 61 09 36 f8 3d 4a 5d c0 c6 ff 30 84 67 62 fe 3c a8 71 f1 ec 64 53 a1 6e 44 22 ... >
<Buffer ac 34 01 87 08 0d 45 9b f7 47 4d ba 36 61 4d e2 73 62 e7 b3 01 41 d3 86 0c 72 4e 7c 97 81 71 1a e3 73 2e 4e 1a c7 18 27 e7 90 04 9c a0 86 ae 2c 1d 21 ... >
<Buffer c0 58 09 38 09 7f 54 d5 c6 fe 98 61 a2 a1 da 8b 63 e7 fb 83 40 03 a1 34 83 3c c7 7e 2f 80 17 16 c7 5f 71 b2 38 c1 73 e2 04 c7 e0 c5 7c 54 a4 51 0b 4c ... >
<Buffer cd 8b 60 ca 26 65 91 46 36 c5 79 90 a4 19 e0 6b 9e 06 0a 4d 08 49 48 02 17 f0 35 a3 04 82 c1 0f 6e 18 e6 31 e0 42 1c 27 d4 4e 93 d4 cc a3 09 d5 12 e8 ... >
<Buffer 25 d3 12 88 9d 2c 52 35 69 fc b3 0b 15 4d d2 19 6c f3 34 10 42 09 e4 41 03 d7 8f 7b 69 ea 90 62 fc c2 66 18 c2 00 93 1a 37 16 ee ba 28 8c 36 69 aa 8d ... >
<Buffer 09 16 8d 46 a5 20 2d cd 8b d3 94 0a 05 93 9d 4d b2 f9 6d b4 d1 f8 13 4d 76 93 9d 09 76 9b 9a b0 b3 3b 59 d3 6c 7e ba c9 1a d7 d5 c5 4a 5b d4 28 c1 17 ... >
<Buffer 38 4a 82 13 43 14 a9 8a 34 ef c3 5e 58 5b d2 68 e0 67 03 81 40 49 13 4a 48 58 c2 33 f8 3d 4a 1d 33 0c cf 36 0f 8a 09 14 78 a4 c6 8d 07 51 92 92 38 88 ... >
<Buffer 60 f8 1e 10 13 62 e2 a2 c9 73 86 2f 65 6b bb 0f f6 e5 80 8d c1 98 6c af 5b db 31 4b c9 e6 5e 5b ca 5c d9 c6 b6 6e 94 ca 1c 46 71 58 23 10 0d 21 46 21 ... >
<Buffer 61 5d d3 39 eb 26 95 4a 84 1f 98 4c ac 6c 34 f1 39 9f 4e b2 b4 a4 cd da 84 26 e7 c4 77 a1 04 97 b8 3e fb e2 84 38 89 e7 d8 49 1d 53 2b dd 5a d4 ce 2b ... >
<Buffer b8 4b 0b 89 21 8a 56 45 9a b5 29 2f 44 49 93 ae e0 67 fe 84 7f 85 14 4a 1b 68 cc b3 f1 7b 94 b9 a6 38 3c f3 30 14 f3 67 80 49 1c 16 e2 b4 4d 6a e2 d0 ... >
<Buffer 2c 22 5f 1d fc 50 21 59 4c b8 ce 0f 8c d9 d6 76 5f 74 1b 63 0c 87 30 61 ed d6 5b d0 ba d9 b2 b6 77 65 ee 76 8c 6e 97 6d cc cd 4e ff 30 ab 61 24 a0 f0 ... >
<Buffer cb 3e 14 ba 61 95 d5 60 04 0b 84 5f 71 9c 27 6e 49 ea b6 09 89 ed d4 76 04 25 a8 c1 63 3b ae 89 f3 c0 89 49 13 ab de 21 01 59 a0 6e ba 8c 54 54 c4 b6 ... >
<Buffer 66 6b 0a 89 13 3a ad 9a 34 f2 61 2f 6c 6d d6 a8 e0 67 fe 1a 28 04 12 d2 84 25 d8 60 3f 2f 61 64 18 3f 63 0c c3 80 6b 30 89 43 ea 60 69 9d 60 56 9b 4c ... >
<Buffer 4b 93 34 69 25 54 81 c8 69 09 a2 12 52 13 db f9 7f 48 4a da f4 13 92 d8 89 bd a1 84 a0 46 5e ff 1a e2 7c 70 e2 a4 89 55 ab 54 80 0a 06 15 2e a8 e4 50 ... >
<Buffer d1 16 5a 27 fd 80 16 29 7c 74 09 82 42 d5 c4 76 ec a4 39 48 4b 4a a0 84 36 76 f0 ba 80 69 b0 e3 b5 d7 6e 88 9d 04 3b 6e ea 9a 98 44 fd 49 b0 50 0a 42 ... >
<Buffer 90 52 83 7d 06 45 8d aa 94 c4 ca 86 34 a9 d4 1a 6e 8f 3f 06 43 20 c1 21 b1 89 cd 1e ec ae 71 e8 d9 9c 6f ef 96 83 70 60 0c 5c 30 3e e5 0c 56 5a 41 2f ... >
<Buffer c6 b0 61 07 21 21 46 f7 62 9d 01 44 dc da 6e eb 56 36 c7 d8 98 e2 80 dd ad bd 77 6a ed d2 ae 7f 94 b9 8e ce 76 dd e8 9a 15 e6 0f 82 8d 01 1e 9c c6 07 ... >
<Buffer 16 4b ff b6 09 82 04 09 16 a6 91 8a 0f dd bf 24 9b 6c 12 d3 92 34 6d 13 9b cc 26 3b 13 eb 36 35 c9 ce fe 25 66 93 c6 24 9b 9f 86 2e 2d f4 a1 ba 42 ed ... >
<Buffer f6 d4 25 50 03 8b 54 4d 1b fd a3 2e d3 9a 4c 0d 70 cd fb 31 92 90 90 07 0d 5c 63 df 4b 89 4b 8a f1 b5 8d 61 18 42 81 0b 18 af 0e ee 4b a4 4e 02 ad 56 ... >
<Buffer 46 6b 0b e9 83 28 52 35 69 fc 13 8f 36 4d da 0d fc cc a7 81 92 40 4a db 90 c0 33 f8 3d c2 5c 67 18 3f db 3c 18 e6 63 80 43 8c 17 8f 64 49 07 71 33 b2 ... >
<Buffer 9b 44 18 eb 20 26 80 71 31 f1 32 15 23 d9 d6 76 0f 18 9b 63 0e c6 63 c2 da ad b7 03 eb e6 ea da de 95 49 3b c6 b6 cb 18 0d 65 e0 23 83 6a 36 8d 08 be ... >
<Buffer 90 34 82 82 a6 49 63 52 5e 9c d2 68 4b e2 b2 3f fc ca 16 2d 88 3f 54 98 c5 dd a1 76 8b 11 76 76 87 85 b2 08 02 0b 2e 1b d7 62 6a ab 6e ac f8 50 da f8 ... >
<Buffer 23 31 6c d0 49 4c 90 e8 5e b8 8e 88 8b 61 6b bb bf dd c6 20 83 f1 67 b2 b5 b3 ed c0 39 58 e9 6d bb cb 5c bb 8d 6d 97 d2 55 9a 91 a8 71 2c 64 68 e2 06 ... >
<Buffer 36 10 77 69 dd 8b 88 14 c1 63 45 f1 61 b9 b5 4d 9b b6 76 5b 2f 5b 57 d7 24 5d 72 5a ad d3 c6 dc 9a d5 a6 17 93 c6 2c 2d 2b 13 7c e8 d6 c1 36 84 e9 d3 ... >

@waitingsong 大佬,像这样的能看出编码么?试了好多种编码解出来都是乱码。

不是UTF8,不是GBK。是否多层包装编码?

@waitingsong 不知道,昨天试了很多种编码来解码,都不行。目前无解。。。多层包装编码是什么意思?

@blackmatch 比如原文是 utf8 编码,第一次包装成 gbk,然后第二次包装成 ucs…

你的代码没有经过开光,所以有BUG

@waitingsong 可能是,客户不告诉我们是怎么包装的,说这是商业机密。。。。所以现在很难解码。

@blackmatch 这是加密数据? 如果让你们处理数据然后又不告诉解码方式,这啥操作?

回到顶部