数据库中的不可见字符
场景:产品经理通过后台系统插入一个字母组成的优惠码(例如:coupon)之后,再从后台通过关键字coupon去查找这个优惠码,查不到,同时影响线上使用。
假设表名:exchange_code.
假设列名:code , varchar类型
症状1:通过Sequel Pro按字段名等等条件搜索coupon,搜索不出来这条记录。
等价于SQL: SELECT * from exchange_cdoe where code = 'coupon';
症状2:通过contains条件搜索,能找到这条记录。
等价于SQL: SELECT * from exchange_cdoe where code contains 'coupon';
症状3:通过 length 函数,计算一下code 长度, 发现长度并不等于coupon的长度6.
看上去是6个字母,却表现出上面的情况。
后面团队想到办法,把里面的内容复制出来,转化为 unicode。
发现了里面包含 unicode \200b 这个不可见字符。
coupon
http://tool.chinaz.com/tools/unicode.aspx (从左至右复制上面这一行字符,去这个网址转换unicode试试)
问题复现
利用java 复现问题
@Test public void testBlog(){ // 字母m 对应unicode u6d String unicode = "\\u6d\\u200b\\u6d"; System.out.println("unicode:"+unicode); String string = unicode2String(unicode) ; System.out.println("string:"+string); } /** * unicode 转字符串 */ public static String unicode2String(String unicode) { StringBuffer string = new StringBuffer(); String[] hex = unicode.split("\\\\u"); for (int i = 1; i < hex.length; i++) { // 转换出每一个代码点 int data = Integer.parseInt(hex[i], 16); // 追加成string string.append((char) data); } return string.toString(); }
输出结果:
unicode:\u6d\u200b\u6d
string:mm
输出结果中mm之间有一个不可见不占位的 \u200b 特殊字符,类似的 \u200c 也是一样的效果。
这些字符的产生 也许是因为”实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。” — https://zh.wikipedia.org/wiki/Unicode 。
问题预防:
通过正则表达式校验数据合法性(例如,只允许数字和大小写字母)。
@Test public void testBlog(){ // 字母m 对应unicode \\u6d String unicode = "\\u6d\\u200b\\u6d"; System.out.println("unicode:"+unicode); String string = unicode2String(unicode) ; System.out.println("string:"+string); Pattern pattern = Pattern.compile("[a-zA-Z0-9.]+"); System.out.println("pass? "+pattern.matcher(string).matches()); }
输出结果:
unicode:\u6d\u200b\u6d
string:mm
pass? false
=== 程序员日常小故事 之 隐形的字符===
产品W:“怎么回事啊,这条记录我刚插入的,后台怎么查不出来啊”
程序员T:“我看看怎么回事,之前都是好的啊”
产品H:“这个功能模块怎么又出问题了?”
程序员Z:“我看看啊,数据库里去查一下,怎么数据库里按字段查,也查不出来啊。 ”
某某程序员:“按ID查。”
某某程序员:“查一下这个字段的长度”
某某程序员:“奶奶的,怎么和看上去的长度不一样”
某某程序员:“看看字符串的unicode编码”
某某程序员:“复制到Java里面转成unicode看看”
某某程序员:“奶奶的怎么有不可见字符”
程序员T:“产品W,你是怎么输入不可见字符的…”
产品W:“我是正常操作的呀”