飞书-字符串识别收货信息商品地址

1,600 阅读2分钟

前言

近期需要整一个活,这个活呢?听起来十分高大上,可能需要用到自然语言识别,从一串无规则,长度不一的字符串内,取出相对应的收货地址以及商品信息等,完成自动创建订单

流程

由于部分逻辑涉及业务,就简要书写大概流程

项目流程大概如下,主要是通过C端用户发送一段字符串,推送给相关机器人配置,然后完成一些列的信息截取和相关操作

graph TB
字符串-->发送机器人
发送机器人-->机器人(拆分字符串)
机器人(拆分字符串)-->创建并写入excel
机器人(拆分字符串)-->拆分商品
拆分商品-->创建并写入excel
创建并写入excel-->转移文件权限
创建并写入excel-->发货
发货-->用户导出订单信息

相关工具及准备

由于需要检验用户输入的地址错别字问题,业务上将解析包换成了百度的地址识别

准备操作

飞书机器人的配置: 飞书机器人配置及开发

唯一一点要提到的就是,在配置完机器人后,得书写轻服务应用(类似于云开发函数),不太理解的老哥们,可以点我!!

配置完云函数后,将机器人添加进相对应的群里,及可以试试收到相关用户信息

向群里发送信息

微信图片_20210717113236.png

云开发

这里注意的是,需要配置一个云函数为默认接收机器人的消息,这里需要讲该云函数的HTTP链接放入配置机器人 配置事件回调地址

微信图片_20210717113323.png

日志后台

微信图片_20210717113341.png

接下来我们可以就可以愉快的写js了,完成我们的需求

解析用户字符串

基础关键字库

商品的种类千奇百怪,配置了基础的关键字库,用来解析相关商品及其单位和数量

如下:

const unitKeyWords = ["箱", "单", '盒', '斤', '件', '个']
const goodsKeyWords = ['猪肉', '牛肉', '羊肉', '鸡肉', '罗汉果']
const goodsTitle = ['商品名称', '商品', '名称', '数量']
const goodsArr = [] // 商品

过滤特殊字符串

由于一串字符串内,可能存在大量的奇奇怪怪的分隔符,例如 ',,,!,@,‘’,?。,等等 所以拿到该字符串后,直接将特殊字符直接过滤

function stripscript(s) {
    let pattern = new RegExp(
    '[`~!@#$^&*()=|{}\':;\',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“’。,、?-]',
    );
    let rs = '';
    for (let i = 0; i < s.length; i++) {
        rs = rs + s.substr(i, 1).replace(pattern, ' ');
    }
    rs = rs.replace(/[\r\n]/g, '');
    return rs
}

识别用户手机号码

xxx xxxx xxxx 或者 xxx-xxxx-xxxx 转换成 xxxxxxxxxx

function transPhone(s) {
    s = s.replace(/(\d{3})-(\d{4})-(\d{4})/g, '$1$2$3');
    s = s.replace(/(\d{3}) (\d{4}) (\d{4})/g, '$1$2$3');
    return rs;
}

过滤商品开头的标识

比如有些字符串内存在

  • 张三 12345678901 深圳市南山区xxxx 商品:猪肉1斤
  • 张三 12345678901 深圳市南山区xxxx 商品名称:猪肉1斤
  • 张三 12345678901 深圳市南山区xxxx 名称:猪肉 数量1斤,羊肉 1
  • 张三 12345678901 深圳市南山区xxxx 猪肉 1斤,羊肉 1
  • 张三 12345678901 深圳市南山区xxxx 猪肉 一斤 羊肉 二

所以需要在识别字符串前,我们应当将干扰我们工作的字符全部替换掉

function transTitleStr(str) {
  // 过滤商品如果前面包含-> 商品名称: 猪肉
  //                     -> 商品: 猪肉
  // 商品二字
  for (let i = 0; i < goodsTitle.length; i++) {
    rs = rs.replace(goodsTitle[i], '');
  }
  rs = rs.replace(/[\r\n]/g, '');
  return str
}

地址与商品分离

当我们将干扰我们的字符给剔除后,剩下的就是有规律性的字符串了

上面的字符串也就变成了如下字符串

  • 张三 12345678901 深圳市南山区xxxx 猪肉1斤
  • 张三 12345678901 深圳市南山区xxxx 猪肉1斤
  • 张三 12345678901 深圳市南山区xxxx 猪肉 1斤 羊肉 1
  • 张三 12345678901 深圳市南山区xxxx 猪肉 1斤 羊肉 1
  • 张三 12345678901 深圳市南山区xxxx 猪肉 1斤 羊肉 2

接下来一次解析相关的字符串即可

本文采用的方法是暴力for的写法来解析,还原xdm留言更好的方法

graph TB
正则匹配字符串-->字符串起始位置
字符串起始位置-->抓取单位数量
抓取单位数量 --> 删除字符串
删除字符串 --> for循序继续匹配
for循序继续匹配-->正则匹配字符串
for循序继续匹配-->结束跳出

循环删除

// goodsKeyWords为商品关键字库
function splitStr(str) {
    for (let i = 0; i < goodsKeyWords.length; i++) {
        let ds = str.match(goodsKeyWords[i]);
        if (!!ds) {
            let startKey = ds.index + ds[0].length - 1
            // 抓取商品数量
            while (startKey < str.length) {
                startKey++
                let nextStr = str[startKey]
                if (!nextStr) {
                  break
                }
                let delLen = statiGoods(str, startKey, ds[0], 0)
                str = delStrFn(str, ds.index, delLen.length)
                break;
          }
        }
    }
    return str
    }
}

抓取当前商品数量以及单位

// 计算当前商品后面的数量及单位
// 存在 1箱 10箱 111箱
function strNextIsNumber(str, ind) {
  let numstr = ''
  for (let i = ind; i < str.length; i++) {
    // 数量 将大写数字转换成阿拉伯
    let nowStr = qualityMap[str[i]] || str[i]

    // 如果当前字符串是单位
    if (unitKeyWords.includes(nowStr)) {
      if (!numstr.length) {
        numstr += 1 + nowStr
        break;
      }

      numstr += nowStr
      break;
    }

    if (nowStr == ' ') {
      numstr += ' '
      continue
    }

    // 如果下一个是数字就拼接,不是就跳出
    if (!/\d/.test(nowStr)) {
      break;
    }

    numstr += nowStr
  }

  return !!numstr && numstr || ''
}

删除字符串

function delStrFn(str, x, num) {
  return str.substring(0, x) + str.substring(x + num, str.length);
}

统计商品

function statiGoods(str, strIndex, nextStr) {
    let numstr = ''
    let delStr = nextStr

    for (let i = strIndex; i < str.length; i++) {
        let nowStr = qualityMap[str[i]] || str[i]
    
        numstr = strNextIsNumber(str, i)
        break;
    }

    goodsArr.push({
      name: delStr,
      value: numstr.trim()
    })

    return delStr + numstr

}

搞完以上商品,新的问题又来了,如果用户输入的字符串如下咋办呢?

  • 张三 12345678901 深圳市南山区xxxx 名称:(猪肉+羊肉)
  • 张三 12345678901 深圳市南山区xxxx (猪肉+羊肉) 1斤

接下来针对 statiGoods改造,让他支持商品A+B的模式

function statiGoods(str, strIndex, nextStr, isHasParent) {
    for (let i = strIndex; i < str.length; i++) {
        //somecode
        // 如果当前字符串是 + 
        if (nowStr == '+') {
          let newStr = str.substring(i + 1, str.length);
          let vals = forStrGetGoods(newStr)
          if (vals.length) {
            let startKey = vals.index + vals[0].length
            let _valstr = statiGoods(newStr, startKey, vals[0], 1)
            delStr += '+' + _valstr
          }
        }
    }
    // 这里加多了一个if
    if (!isHasParent) {
        goodsArr.push({
          name: delStr,
          value: numstr.trim()
        })
    }
    
    return delStr + numstr
}

A+B商品遍历循环,判断后面是否是商品

// 拿到字符串内的商品
function forStrGetGoods(str) {
  let json = null
  for (let i = 0; i < goodsKeyWords.length; i++) {
    let ds = str.match(goodsKeyWords[i]);
    if (!!ds) {
      json = ds
      break;
    }
  }
  return json
}

通过改造完上面的函数后,我们就可以得到如下的信息

// goodsArr 
// [{name: 商品A}, {value: 1}]
// [{name: 商品A}, {value: 1件}]
// [{name: 商品A}, {value: 2箱}]
// [{name: 商品A+商品B  1件}, {}]

// addresInfo: 
/*
{
    "phone":"13911111111",
    "province":"江苏省",
    "city":"苏州市",
    "area":"吴江区",
    "detail":"干将东路678号江苏大厦11楼",
    "name":"徐天宇",
    "postalCode":"215000"
}
*/
postman.js:547

显然,目前的商品信息不太符合我们要的数据结构,再写一个函数将数据处理下就行了~~

function resetGoodsInfo() {
  let arr = []
  for (let i = 0; i < goodsArr.length; i++) {
    let item = goodsArr[i]
    if (item.name.indexOf('+') > -1) {
      let md = item.name.split(' ')

      let val = 1

      for (let i = 0; i < md.length; i++) {
        if (/\d/.test(md[i])) {
          val = md[i].match(/\d/)
          unit = md[i].substring(val.index + val.length, md[i].length);
          val += unit
        }
      }

      for (let i = 0; i < md.length; i++) {

        if (md[i].indexOf('+') > -1) {
          arr = md[i].split('+').map(item => {
            return {
              name: item,
              value: val
            }
          })
        }
      }
    }
  }
  goodsArr = goodsArr.filter((item, key) => item.name.indexOf('+') == -1)
  goodsArr.push(...arr)
}

对接飞书api

本项目用的是excel文档,所以会用sheet工作表接口,不过其他文档应该大同小异

由于飞书文档api写的也比较详细,这里就不过多的贴上代码了,大概给XDM列举出运用到的api以及相关的作用

app_idapp_secret登陆后台飞书应用,拿到该应用相对应的信息即可

 axios({
    url: 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json; charset=utf-8'
    },
    data: {
      app_id: 'app_id',
      app_secret: 'app_secret',
    }
  }).

步骤依次如下:

温馨提示

1.如果文件夹内已经存在文件,直接写入并转移权限就行,没必要重新生成文件了 2.可以在创建后的文件,直接修改文件名字,这样可以达到更友好的提示

api如下:

open.feishu.cn/open-apis/s…

结语

如果XDM有不一样的解析思路,可以提出来一起相互讨论下~~