近日,公司要对相关查询页面中的用户名、手机号码、生日、地址等敏感信息进行脱敏处理,所谓脱敏也就是隐去其中的一部分,使之不完整,以保护用户个人信息。通过 sql 实现最为简单,在此总结整理一下。以下语句只保证在 Postgresql、MySQL、SQL Server 库中有效。

手机号码脱敏

手机号码脱敏要求中间四位以****代替。

PostgreSQL 方案

使用拼接

使用 left 和 right 拼接这个方法简单好用,易于维护。

select left('17817812598',3)||'****'||right('17817812598',4); --结果为 178****2598
使用 regexp_replace

regexp_replace 函数支持正则表达式,regexp_replace 的第一个参数是需要做脱敏处理的手机号码,第二个参数是一个正则表达式,第三个字段指定替换文本,即:替换对匹配到的子串进行替换。这是将手机号码分成了三个子串,其中第二个子串(中间 4 位数字)用****替换。

select regexp_replace('17817812598','(\d{3})(\d{4})(\d{4})','\1****\3'); --结果为 178****2598

regexp_replace 不会改变原始字段值,只是简单地返回替换后的副本。这里的正则表达式按数字长度将手机号码的前三位、中间四位和后四位分为 3 个部分,替换时首尾原样引用,中间部分则替换为****

MySQL 方案

使用拼接
select concat(left('17817812598',3), '****', right('17817812598',4)); --结果为 178****2598
使用 regexp_replace

regexp_replace 函数需要 MySQL 8.0 或更高版本。第一个参数是原始字符串,也就是需要脱敏的手机号码;第二个参数是匹配模式,这里是指定的4位数字;第三个参数指定替换字符串;第四个参数4指定模式匹配的开始位置(4即从参数1的第4个字符串开始应用模式匹配);第五个参数1用于指定需要用第三个参数替换的匹配项,1即仅替换第一个匹配项,指定0或省略此参数则替换调所有匹配项。

SELECT REGEXP_REPLACE('17817812598', '[0-9]{4}', '****', 4, 1); --结果为 178****2598

SQL Server 方案

select concat(left('17817812598',3), '****', right('17817812598',4)); --结果为 178****2598

邮箱脱敏

PostgreSQL 方案

邮箱脱敏要求 @前 1 位左边的部分以*代替,由于邮箱格式的特殊性:有且只能有一个 @符号,以及 regexp_replace 只替换最先匹配到的字符的特性。可以直接使用 regexp_replace 的基本形式,第一个参数指定邮箱,第二个参数指定需要替换的字符,第三个参数指定替换结果字符。

select regexp_replace('abc@123.com', left('abc@123.com',position('@' in 'abc@123.com')-2), '*'); --结果为 *c@123.com

也可以使用正则表达式实现

--处理方式与对手机号码使用的方式相同
select regexp_replace('abc@123.com','(\w*)(\w{1}@)(\w*)','*\2\3'); --结果为 *c@123.com

MySQL 方案

MySQL 使用以下两种 regexp_replace 都可以达到同样目的,第二种方式通过 concat 动态拼接正则表达式。

select regexp_replace('abc@123.com', left('abc@123.com',position('@' in 'abc@123.com')-2), '*'); --结果为 *c@123.com

SQL Server 方案

select '*' + SUBSTRING('abc@123.com', CHARINDEX('@', 'abc@123.com') - 1, len('abc@123.com')); --结果为 *c@123.com

生日的脱敏

PostgreSQL 方案

生日要求月份位和日期位分别以**代替,由于公司产品尊重个人选择,生日中的年份是可选的,因此脱敏后的结果可能出形如:1990/**/** 或 /**/** 两种形式。这里直接用 regexp_replace 配合正则表达式实现(也可以通过条件判断加字符串拼接来实现)。

select regexp_replace('12/30','(/{0,1}\d{1,2}/\d{1,2})\Z', '/**/**'); --显示/**/**
select regexp_replace('1990/12/30','(/{0,1}\d{1,2}/\d{1,2})\Z', '/**/**'); --显示 1990/**/**

这里的正则表达式是将月份和日期拆出来,月份的开头有个可选的/,结尾的\Z 标志用于告诉程序只在末尾部分匹配得到结果字符串(即这个正则表达式只取出来符合条件的月份和日期,接着用 regexp_replace 的第三个参数完成替换)。

MySQL 方案

select regexp_replace('12/30','(/{0,1}[0-9]{1,2}/[0-9]{1,2})$', '/**/**', 1, 1); -- 显示/**/**
select regexp_replace('1990/12/30','(/{0,1}[0-9]{1,2}/[0-9]{1,2})$', '/**/**', 1, 1); -- 显示 1990/**/**

SQL Server 方案

select replace('12/30' , right('12/30', 5), '**/**'); -- 显示**/**
select replace('1999/12/30' , right('1999/12/30', 5), '**/**'); -- 显示 1990/**/**

用户名和地址的脱敏比较好处理,直接使用 left 拼接实现反而更方便。

有一次领导在晨会上说书不能外借,一直没弄明白原因,今天我似乎有点眉目了,顺便在网络上搜索了一些人的见解,书之所以不能外借,总结如下:

  1. 怕借阅的人不爱惜,导致书籍破损、涂抹,甚至丢失收不回来;
  2. 有收藏价值的书和情感寄托的书,如:绝版书、善本、祖传书籍,不能借,接丢了就彻底没了。

于我自身的感悟,则需要通过一个实例来讲述:曾经为了备考某个证书买了两本工具书,备考期间就有同事向我预借了其中的一本,备考过程中我在这两本书中随手记录了大量的感想和注记。等到考完试,自然就借出了预借的那本,写本文当天我翻出了剩下的另一本,再次翻看这些注记,阅读时的多数情境都还历历在目,甚至彼时的坐姿、疑虑、感想,外界的冷暖、动静还能自然地在脑海里重放和回响。这些随手作的注记就像是记忆的催化剂,当你再次看到它们时这些记忆自然会浮现在眼前。我个人觉得这些注记是除书籍本身之外最宝贵的东西,甚至有几分私密性可言,借出去若是收不回来,等于这段记忆片区就丢失了。如果让我说一个书不外借的理由,书籍中这些注记就是其一。在这里并无对借书同事的抱怨,仅仅记录一些感想,况且,让更多人受益本就是工具书该有的作用。

很不幸,22021年的最后一天过的不顺利,缘自30日我的一个疏忽,进而引发了31日的故障,最终,31日整个上午我都在紧张地处理问题。

最近更新了一个功能的抽数任务,移除了任务中某个输出表的一个字段,这个字段在后续的关联任务中有用到,那么关联任务也要同步调整以取到这个字段。这就要求两个任务要同步更新了,否则后续的关联任务会找不到字段报错失败。

此前意识到此处是个风险点,特意提醒自己注意。

按计划要求31日更新上线,30日上午提前更新几个用户跑了任务发现一切正常,下午想到前置任务需要重新执行一次全量耗时较多,不如把剩余用户的前置任务更新并执行掉,这样31日就很轻松了,我确实这样做了,但是没有对其中哪个用户执行任务做验证,至此祸根已埋下,果然31日早晨这些客户的后续关联任务全失败了。

为此,总结本次经验教训:
端正态度,任务清单/执行步骤一定要写,带上风险点,不可偷懒,好记性不如烂笔头;

严格按照执行步骤操作,不能突发奇想、投机取巧,这往往容易忽视风险;

再小的改动,也要及时手动执行任务,尽早验证,留足回旋余地,最大限度降低风险。

最近一阵子老丈人染病求医入院,就诊当天遇到一个多次入院的病友家庭,作为过来人,他们对我们做了认真翔实的指导。病房里患者间会相互打听病情,交流中无不透露着同情、怜悯、支持和关心的情感,甚至连自己带饭都要问一下隔壁患者需不需要帮忙带饭。
老丈人入院前情绪有些低落,进入病房这个小团体后意识到原来这么多人跟自己一样受病痛困扰、跟病痛斗争,在相互间的倾诉、关心、支持和鼓励中很快乐观开朗起来了,可能这就是同病相怜吧。

—-2021-12-22—-
早上开始手术,排第一位,希望顺利。

今天上午将网站的 PHP 版本升级到了 PHP 8.1.0,遇到了几个问题,前前后后倒腾了两三个小时,这里记录下以备不时之需。

2 not upgraded

好久没登 vps 更新系统了,更新时出现 2 not upgraded 的提示,执行以下命令解决:

apt-get dist-upgrade

'/etc/init.d/php-fpm': No such file or directory

复制这里的代码保存名为 php-fpm 的文件到 /etc/init.d 目录,cd 进入 /etc/init.d 执行 chmod 777 php-fpm 加权限。

configure: error: Please reinstall the iconv library.

起初猜测是系统依赖跟新版本存在兼容性问题,切回 8.0.12 尝试更新到 8.0.13 发现还是过不去,仍然报 Please reinstall the iconv library. 百度上能搜的结果倒是很多,多是相互复制,其实方法就一种即重新编译,照着试了两个版本都不管用,后来发现问题是这些方法给的 configure 的配置不适用。最后,执行下列命令解决。

LIBICONV_VERSION=1.15
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-${LIBICONV_VERSION}.tar.gz
tar xvzf libiconv-${LIBICONV_VERSION}.tar.gz
cd libiconv-${LIBICONV_VERSION}
./configure --enable-static
make install