MENU

利用短信模板提取短信变量

March 10, 2024 • Read: 910 • PHP,编码

前段时间工作中遇到了一个字符串变量提取的场景,通过正则实现了,感觉实现思路蛮有意思的,就分享一下。

前言

对接过国内短信发送平台的开发者,应该知道大部分短信平台都需要使用 "短信模板"来发送短信。既:在短信服务提供商那边申请一个模板,在发送短信时只需要传递模板中的变量即可。

例如短信内容是:"您的验证码是:123456,5分钟内有效,请勿泄露"。

在短信平台中模板格式是:"您的验证码是:${code},${time}分钟内有效,请勿泄露"

我们只需要向短信平台传递 {"code":123456,"time":5} 这样一段请求参数,再由短信平台将参数替换为短信正文,最后发送到用户手机上。

需求

我们的业务系统本身需要对接多个短信平台,有的短信平台要求直接使用短信模板,有的短信平台要求直接传递短信原文。

为了统一短信组件的调用,我们在业务中统一向短信发送组件传递短信原文,再由短信发送组件根据不同短信平台要求,拆出短信变量。

利用模板变量

尝试通过正则实现这个功能,步骤如下:

  1. 通过匹配变量标签,提取出模板中所以的 key,拿到一个由 key 组成的一维数组
  2. 接着将模板中变量标签部分的内容,用正则替换成另一个正则表达式,用这个正则提取短信原文中的值
  3. 最后将 第一步的数组 key 和第二步的数组 value 组合成一个新的数组,实现短信变量的提取。

使用一个相对复杂的例子,实现代码如下:

<?php
$template = '尊敬的${name}你好,您的订单${on}将于${y}年${m}月${d}过期,请及时通过${pay}续费,支付金额:${money} ${unit}。';
$content = '尊敬的张三你好,您的订单144514将于2023年8月21过期,请及时通过支付宝续费,支付金额:2.34 RMB。';

// {"name":"张三","on":"144514","y":"2023","m":"8","d":"21","pay":"支付宝","money":"2.34","unit":"RMB"}
echo json_encode(GetTempParam($template,$content),JSON_UNESCAPED_UNICODE);

/**
 * @param $template 第三方短信平台模板
 * @param $content 需要发送的短信原文
 * @param $identifier 第三方平台变量标签
 * @return array 返回变量数组
 */
function GetTempParam($template, $content, $identifier = ['${', '}'])
{
    /**
     * 模板编写注意:变量不能连续、不可嵌套、若包含正则语法 需要转义(自动) 例如:"尊敬的\[$name\]您好"
     * 模板: 尊敬的${name}您好,您的订单${on}将于${y}年${m}月${d}过期,请及时通过${pay}续费,支付金额:${money} ${unit}。
     * 内容:尊敬的张三您好,您的订单114514将于2023年8月21过期,请及时通过支付宝续费,支付金额:2.33 RMB。
     * 标签:['${', '}']
     */
    // 标签转义为 ['\${', '}']
    $identifier = array_map('quotemeta', $identifier);
    
    // 第一步:使用 /(?<=\${)(.*?)(?=})/ 提取变量 keys ['name','on','y' ...]
    $pattern = sprintf('/(?<=%s)(.*?)(?=%s)/', $identifier[0], $identifier[1]);
    preg_match_all($pattern, $template, $temp_keys);
    // 如果没找到 key 就没有变量
    if (empty($temp_keys[1])) return [];
    /**
     * 第二步:将模板标签替换为 (.*),结果当作正则匹配内容
     * $content_pattern = 尊敬的(.*)你好,您的订单(.*)将于(.*)年(.*)月(.*)过期,请及时通过(.*)续费,支付金额:(.*) (.*)。
     * $content_vals[0] = ['张三','114514','2023' ...] ,注意 若 模板不匹配 则为 null
     */
    $content_pattern = preg_replace(sprintf('/%s(.*?)%s/', $identifier[0], $identifier[1]), '(.*)', $template);
    preg_match_all('/' . $content_pattern . '/', $content, $content_vals, PREG_SET_ORDER);
    // 如果为 0 就是没匹配上 大概率是内容错了
    if (empty($content_vals[0])) {
        return false;
    }
    // 去掉多余的匹配内容
    array_shift($content_vals[0]);
    // ["name"=>"张三","on"=>"114514","y"=>"2023"...]
    return array_combine($temp_keys[1], $content_vals[0]);
}
Last Modified: March 12, 2024