最近用到了正则表达式,总结下常用的正则表达式。

所有单词

'pear orange apple abc123 oracle'.match(/\b[a-z]+\b/gi);
//pear orange apple oracle

以指定字符串开头的单词

一个条件

以 app 开头的单词

'pear orange apple abc123 oracle'.match(/\bapp[a-z]*\b/gi);
//apple

也可以写成这样:

'pear orange apple abc123 oracle'.match(/(\bapp)(?<=\1)[a-z]*\b/gi);
//apple

总体来讲,这个模式不如上一个模式直接,好在可以通过这个模式来了解断言和引用两种语法:
这里使用了后行断言语法(?<=y)x,仅当x前是y时匹配x:(?<=\1)中的\1引用的是模式(\bapp)所匹配的文本(即开头是 app 三个字母,\b是词边界),(?<=\1)表达的意思是只在开头是 app 时匹配后续的字母。(\bapp)(?<=\1)[a-z]*合到一起就是开头的 app 加上后续的字母 le,得到 apple。

多个条件

以 app 或 ora 开头的单词

'pear orange apple abc123 oracle'.match(/\b(app|ora)[a-z]*\b/gi);
//orange apple oracle

这里通过(x|y)语法实现多个条件任选

以指定字符串结尾的单词

le结尾的单词

'pear orange apple abc123 oracle'.match(/\b[a-z]*\Ble\b/gi);
//apple oracle

不以指定字符串开头的单词

比较拗口,也可以说以非指定字符串开头的单词。这里以非app开头的单词为例

'pear orange apple oracle'.match(/\b(?!app)[a-z]*/gi);
//pear orange oracle

不以指定字符串结尾的单词

le结尾的单词

'pear orange apple abc123 oracle'.match(/[a-z]*(?<!le)\b/gi);
//pear orange

以下为本次任务用到的模式:

匹配 sql 语句中的注释

匹配 sql 语句中的注释部分,sql 注释有两种形式:--/**/

/(\/\*(.|\r\n)\*\/|\-{2,}[^(\r\n)].*(\r\n)*)/gi

其中\/\*(.|\r\n)\*\/匹配/**/形式的注释,之所以将它方前面,是因为这里使用了任选模式(x|y),在找到匹配项之前,它会从左到右依次匹配任选模式,一旦匹配到左边的就不会继续匹配右边的任选模式了。现实中/**/注释中可能会存在--形式的字符,这就要求匹配/**/形式注释的模式优先(靠左),否则将会出现匹配不到的情况。

匹配 sql 语句中用到的表和函数

/\b((call|join)[\s\r\n\t]+(?!(\"?\w+\"?\.?\"?)?(t[e]?mp_|ds_|unnest|query_str|json_|splittable|\())\1+\"?\w+\"?\.?\"?\w*)|\b(((from)[\s\r\n\t\b\(]+)(?!(\"?\w+\"?\.?\"?)?(t[e]?mp_|ds_|unnest|query_str|json_|splittable|(\bselect)))\1+\"?\w+\"?\.?\"?\w*)|[\s\r\n\t\b]*crm_\w+\b/gi

这是迄今我写的最复杂的正则表达式了,这个模式可以满足以下要求:

    • 匹配关键字 callfromjoin和表名;
    • 支持开头关键字排除,如排除tmp_开头的;
    • 支持开头指定关键字,如匹配所有crm_开头的;
    • 支持以下 sql 写法:

select a.c1,d.c4
from (((t1 a left outer join t2 b on (a.c1 = b.c1))
left outer join t3 c on ( b.c2 = c.c2))
left outer join t4 d on ( c.c3 = d.c3))

参考:MDN 正则表达式

近日,公司要求对相关查询页面中的用户名、手机号码、生日、地址等敏感信息进行脱敏处理,所谓脱敏也就是隐去其中的一部分,使之不完整,以保护用户个人信息。通过 sql 实现最为简单,在此总结整理一下。业务系统使用的是 Postgresql 数据库,以下语句只保证在 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个部分,替换时首尾原样引用,中间部分则替换为****

邮箱脱敏

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

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

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

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

生日的脱敏

生日要求月份位和日期位分别以**代替,由于公司产品尊重个人选择,生日中的年份是可选的,因此脱敏后的结果可能出形如: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 的第三个参数完成替换)。

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

很不幸,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