记录小程序后端支持中语音文字识别等有意思功能的实现思路

[id_35494429[id_995673373]]

记录小程序后端支持中语音文字识别等有意思功能的实现思路

先放几张设计图看下大概功能:

[id_224050024]

从图片中可以观察到,其中包含了一些相对较为复杂的功能模块,例如语音文字识别技术、红包分配的计算算法以及周边红包的计算算法等;而其他部分则主要是基本的增删改查操作。我花费了近一周的时间进行编码和测试,接下来我将简要介绍各个功能点的实现思路和方法。

语音识别

在使用该功能时,A用户设定了一个中文口令红包,而B用户在领取该红包时必须通过语音准确复述该口令,只有当复述内容与设定的口令完全一致,B用户才能获得红包中相应比例的金额。

录音操作是通过调用小程序内置的原生接口来完成的,但在此过程中,遇到了一个问题,那就是微信平台所采用的录音文件格式为.silk。为了解决这个问题,网上提供的方法是先将这种.silk格式的文件转换成wav或MP3格式,之后才能利用各大云服务提供商的接口来执行语音识别任务。

此处采用了该库进行wav格式的转换,随之后又借助百度提供的语音识别公开接口对语音数据进行识别。

业务实现步骤如下:

实现前端录音功能,通过upload接口将silk格式的语音文件上传至数据库,系统自动启动语音识别任务,并向前端异步发送成功反馈,随后前端持续查询识别结果。

由于上传至识别并获取结果这一过程耗时较长,因此建议将识别环节设置为异步处理方式。(第三步)

upload语音接口部分代码:

  1. // ... 业务代码略

  2. 获取由本对象生成的语音内容,赋值给变量$voice。// 上传并入库

  3. $this->identifyVoice($voice); // 触发语音识别task

  4. // ...

  5. public function identifyVoice($voice)

  6. {

  7.    WorkerUtil::sendTaskByRouteAndParams('task/detectvoice', ['voiceid' => $voice->id, 'type' =>[id_160723[id_1337818091]]]);

  8. }

显而易见,一条信息已被发送至后端task服务,其中包含着指向语音文件的记录标识符及其类别。

后端task服务处理如下:

  1. class DetectVoice extends Action

  2. {

  3.    public function run($voiceid, $type = 'redpack')

  4.    {

  5.        if ($type == 'redpack') {

  6.            $voice = Voices::findOne($voiceid);

  7. $url赋值为$voice对象中的voice属性值。

  8.            $saveName = '/runtime/redpack-'.$voiceid.'.silk';

  9.            $convertName = '/runtime/redpack-'.$voiceid.'.wav';

  10.        }

  11. 执行保存操作,将远程语音链接中的内容按照指定的本地文件名保存至本地。

  12.        $cfg = [

  13.            'appKey' => 'xxx',

  14.            'appSecret' => 'xxx',

  15.            'appId' => 'xxx',

  16.        ];

  17.        $util = new BaiduVoiceUtil($cfg);

  18.        $code = exec(执行命令bash,进入路径/www/silk-v3-decoder/,运行脚本converter.sh,并传入参数{$saveName},输出格式为wav。);

  19.        if ($code == 0) {

  20. $result等于$util对象调用asr方法,传入转换后的名称作为参数所返回的结果。

  21.            if ($result['err_no'] == 0) {

  22. $voicesResult$ 等于将 $result$ 数组转换成 JSON 格式的编码结果。'result'], JSON_UNESCAPED_UNICODE);

  23. 将结果赋值给voice对象的result属性,使其等于voicesResult。

  24. 执行保存操作,针对变量voice所指向的对象。

  25.                @unlink($saveName);

  26.                @unlink($convertName);

  27.            }

  28.        }

  29.    }

  30.    ...

  31. }

task服务的处理流程明确:首先接收待识别的voiceid,接着检索相关记录,将语音文件下载至本地的tmp目录,然后使用shell命令进行格式转换,转换完成后,利用百度的语音识别接口进行识别操作,并将识别结果存入数据库。

voice表结构如下:

如此,便完成了语音识别功能。

红包分配

应用场景:创建红包时

拆分红包通常有两种方式,其一是在红包创建之初就预先设定好每一份的具体额度,其二是在红包开启时进行实时分配。本次我们采用的是第一种方式。

具体讨论可在知乎: 找到。

坦白讲,阅读完这份答案后,我确实收获颇丰,比如对微信红包的构建方式、分配算法等方面有了更深入的理解。

由于我们的应用规模不及微信,故无需过多关注(如负载、并发等问题),产品需求仅是金额分配需采用类似微信红包的分配方式。鉴于此,在考虑扩展性、性能和时间成本后,我直接采纳了陈鹏提供的解决方案,并将其改编为PHP版本。同时,我还选用了redis来存储红包份额,并作为处理潜在并发问题的方案。

先上代码(redpack/create):

  1. 红色红包对象是通过调用getCreatedRedPackByBody方法获取的。

  2. // ... 业务逻辑代码略

  3. // 设置随机红包份额

  4. 本对象将红包开启概率设定为指定红包参数。

  5. protected function setRedPackOpenOdds($rp)

  6. {

  7.    $remainNum = $rp->num;

  8.    $remainMoney = $rp->fee;

  9.    $key = 'redpack:'.$rp->id;

  10.    $redis = yii::$app->redis;

  11.    while (!empty($remainNum)) {

  12. 此处的金额是通过调用`getRandomMoney`方法,基于剩余数量和剩余金额计算得出的,即:$money = $this->getRandomMoney($remainNum, $remainMoney);。

  13. Redis执行命令,$redis->executeCommand('RPUSH', [$key, $money]);

  14.    }

  15.    $redis->executeCommand('expire', [$key, 259200]);

  16. }

  17. protected function执行获取随机金额操作,并将剩余数量和剩余金额的引用传递给相应变量。

  18. {

  19.    if ($remainNum == 1) {

  20.        $remainNum--;

  21.        return $remainMoney;

  22.    }

  23.    $randomNum = StringUtil::getRandom(6, 1);

  24.    $seed = $randomNum / 1000000;

  25.    $min = 1;

  26. $remainMoney除以$remainNum得到的结果,再乘以系数,即得最大值$max。2;

  27.    $money = $seed * $max;

  28.    $money = $money <= $min ? $min : ceil($money);

  29.    $remainNum--;

  30.    $remainMoney -= $money;

  31.    return $money;

  32. }

这部分代码逻辑也相对简单,主要就是:

将当前金额与数量数据输入至函数“getRandomMoney”,计算得出本次随机金额后,将该金额存入redis列表中,键名为“redpack:id”。同时,对总金额与总数量进行相应扣除,直至完全扣除完毕。

有几点值得注意的地方:

在原答案中,采用了基于java.math.BigDecimal的随机数生成方法,然而在PHP中,并没有找到与之对应的函数,而且PHP自带的随机数生成功能也并不理想。这里采用了本人独创的随机数生成技术,通过生成六位数的随机数字,再将其除以数字的位数,从而得到如0.608948般的随机数值。此外,每个红包的份额都设定了24小时的过期期限,这一设置旨在实现红包自动过期的功能。

redis里的结果(单位为分):

10元分配15个

100元分配7个:

50元分配25个:

可以看到基本实现了随机分配,也兼顾了手气最佳的要求。

操作简便,只需在领取红包并分配份额时,按照该清单左侧的顺序逐一进行出栈操作即可。

红包地图

应用场景:查看周围发布的红包

实现这一功能的核心在于周边定位的计算方法。首先,必须确保在创建红包时能够获取到精确的经纬度信息,这部分工作由前端负责完成,而我们只需负责记录相关信息。

在调用该接口时,需将用户当前的地理位置坐标传递。依据这些坐标,计算周边区域,进而筛选出表中位于该区域内的相关记录。

代码如下:

  1. /**

  2. *

  3. * @param double $lng 经度

  4. * @param double $lat 纬度

  5. * @param integer $radius 范围

  6. * @return array

  7. */

  8. public function run($lng, $lat, $radius = 500)

  9. {

  10. 此操作通过调用方法getAroundByCoordinates,根据经度$lng和纬度$lat,以及指定的半径$radius,获取周边相关坐标信息。

  11.    $field = 'id,lat,lng';

  12.    $data = (new Query())

  13.            ->select($field)

  14.            ->from('{{app_redpack}}')

  15.            ->where(sprintf(限制条件为:经度值位于%f至%f之间,纬度值介于%f至%f之间,同时`ishandle`字段等于1,且`isexpire`字段为0。, $coordinates[0], $coordinates[2], $coordinates[1], $coordinates[3]))

  16.            ->all();

  17.    return ResponseUtil通过代码和数据获取输出数组。Api::SUCCESS, $data);

  18. }

  19. /**

  20. * 地球的圆周是24901英里。

  21. * 24,901/360度 = 69.17 英里 / 度

  22. * @param double $longitude 经度

  23. * @param double $latitude 纬度

  24. 参数为整数型,代表范围,计量单位为米。

  25. * @return array

  26. */

  27. public function通过坐标值来获取位置信息($longitude,$latitude),并设定搜索范围($radius)。

  28. {

  29.    (double) $degree = (24901 * 1609) / 360.0;

  30.    (double) $dpmLat = 1 / $degree;

  31.    (double半径值 $radiusLat$ 等于 $dpmLat$ 乘以 $raidus$。

  32.    (double计算得到的最小纬度值为:纬度坐标减去纬度半径。

  33.    (double计算得到的最大纬度值为:纬度坐标加上纬度半径。

  34.    (double$mpdLng$ 的值等于 $degree$ 乘以余弦函数,该函数的参数是 $latitude$ 乘以 $\pi$ 除以 $180$ 的结果。180));

  35.    (double) $dpmLng = 1 / $mpdLng;

  36.    (double半径长度等于单位长度的经度值乘以半径。

  37.    (double计算出的最小经度值为:经度坐标减去搜索半径。

  38.    (double计算出的最长经度值为:经度坐标加上搜索半径。

  39.    return指定坐标范围分别为:最小纬度点为 $minLat,最小经度点为 $minLng,最大纬度点为 $maxLat,最大经度点为 $maxLng。

  40. }

这个算法的核心是getAroundByCoordinates,它能够依据输入的经纬度数据以及指定的范围尺寸,精确计算出包括左上、左下、右上、右下在内的四个角落的坐标值。若将这些坐标点在地图上标注出来,便形成了一个矩形区域的轮廓。

有兴趣的可以尝试使用这个工具,随意选取一个坐标点,按照既定方法计算出四个角的位置,观察这四个角是否正好位于radius所规定的区域内。

必须指出的是,这项技术并非出自我的笔下,我实在想不起它的来源是何方。不过,我确实记得将Java的执行方案转换为了PHP。对于原作者,我表示诚挚的歉意。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读