Recently, a WeChat password red envelope function was about to be put into use. The boss suddenly became worried. He said that the wool party is so rampant now. If he is not careful, will the 100,000 promotional red envelope be used up in two days? .... Then can we make a function to check whether the same person is receiving the red envelope? Isn't that enough? He patted his head and continued, "Tom, please do a voiceprint recognition for us!"
The specific process is as follows:
Voiceprint registered users (final rendering)
Voiceprint login (final rendering)
Upload file identification:
pm2 thread
Because the voiceprint recognition service provider cannot directly use the client to directly call and audio is not supported, it is necessary to develop its own server to connect.
Technology stack koa + co-wecaht-api + mysql + ffmpeg + pm2 + knex
Note: Since the service provider does not support WeChat amr files, you need to use ffmpeg to transcode the WeChat audio amr files into wav.
The following is some relevant code, open. .
WeChat jssdk development If you are already familiar with the WeChat API, skip to the next section
Get WeChat token
var api = await new WechatAPI (
config . appid ,
config . appsecret ,
async ( ) => {
// 传入一个获取全局token的方法
var txt = await fs . readFile ( "./token/access_token.txt" , "utf8" ) ;
return JSON . parse ( txt ) ;
} ,
async token => {
// 请将token存储到全局,跨进程、跨机器级别的全局,比如写到数据库、redis等
// 这样才能在cluster模式及多机情况下使用,以下为写入到文件的示例
await fs . writeFile ( "./token/access_token.txt" , JSON . stringify ( token ) ) ;
}
) ;
Note: If the token file cannot be read, manually create a new text file in the corresponding directory, such as access_token.txt
Get WeChat signature
var jsapi_ticket = await api . getLatestTicket ( ) ;
let nonce_str = 'abcdefg' ; // 密钥,字符串任意,可以随机生成
let timestamp = parseInt ( new Date ( ) . getTime ( ) / 1000 ) + '' ; // 时间戳
let url = ctx . request . body . url ; // 使用接口的url链接,不包含#后的内容
let str = 'jsapi_ticket=' + jsapi_ticket . ticket + '&noncestr=' + nonce_str + '×tamp=' + timestamp + '&url=' + url ;
let signature = sha1 ( str ) ;
ctx . body = {
appId : config . appid ,
timestamp : timestamp ,
nonceStr : nonce_str ,
signature : signature
}
cross domain request
const Koa = require ( "koa" ) ;
const app = new Koa ( ) ;
const cors = require ( "koa-cors" ) ;
... . .
app . use (
cors ( {
origin : "http://www.xxxx.com" ,
maxAge : 5 ,
credentials : true ,
allowMethods : [ "OPTIONS" , "GET" , "POST" , "DELETE" ] ,
allowHeaders : [ 'Content-Type' , 'Accept' ]
} )
) ;
ffmpeg transcoding
const ffmpeg = require ( 'fluent-ffmpeg' ) ;
... .
var command = ffmpeg ( _delPath . amr )
. audioBitrate ( '16k' ) //16k音频采样率
. audioFrequency ( 16000 ) //16比特音频信号
. audioQuality ( 10 ) //音频质量
. on ( 'end' , function ( ) {
console . log ( 'file has been converted succesfully' ) ;
resolve ( ) ;
} )
. on ( 'error' , function ( err ) {
reject ( err . message )
console . log ( 'an error happened: ' + err . message ) ;
} )
. save ( _delPath . fix ) ;
Submit voiceprint server
const rp = require ( "request-promise" ) ;
... . .
var vprData = {
method : "POST" ,
url : "http://www.xxxx.com" ,
headers : {
"cache-control" : "no-cache" ,
"x-udid" : "xxxxxx" ,
"x-session-key" : "xxxx" ,
"x-task-config" : "xxxxxx" ,
"x-request-date" : "xxxxxx" ,
"x-sdk-version" : "5.1" ,
"x-app-key" : "xxxxxxx"
} ,
formData : {
// Like <input type="file" name="file">
file : {
value : fs . createReadStream ( soundData . path ) ,
options : {
filename : soundData . name ,
contentType : soundData . type //mp3 = audio/mpeg, wav = audio/wav
}
}
}
} ;
var xml = await rp ( vprData ) ;
//xml to json
var resJson = { } ;
var parseString = require ( 'xml2js' ) . parseString ;
await new Promise ( ( resolve , reject ) => {
parseString ( xml . toString ( ) , async ( err , result ) => {
resJson = result . ResponseInfo ;
//do something
resolve ( ) ;
} ) ;
} ) ;
Technology stack vue + vue-router + axios.
Remove the long-press pop-up copied button on WeChat
mounted ( ) {
document . oncontextmenu = function ( e ) {
e . preventDefault ( ) ;
} ;
//初始化 微信jssdk
vm . wx_init ( ) ;
}
Get WeChat signature and register event
wx . config ( {
debug : false , // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId : res . appId , // 必填,公众号的唯一标识
timestamp : res . timestamp , // 必填,生成签名的时间戳
nonceStr : res . nonceStr , // 必填,生成签名的随机串
signature : res . signature , // 必填,签名,见附录1
jsApiList : [
"onMenuShareTimeline" ,
"onMenuShareAppMessage" ,
"uploadVoice" ,
"startRecord" ,
"playVoice" ,
"stopRecord" ,
"onVoicePlayEnd"
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
} ) ;
Prompt the user in advance to authorize the recording function in order to avoid being prompted for authorization at the same time when recording is officially started. At this time, the recording function status is out of control.
if ( ! localStorage . rainAllowRecord || localStorage . rainAllowRecord !== "true" ) {
wx . startRecord ( {
success : function ( ) {
localStorage . rainAllowRecord = "true" ;
wx . stopRecord ( ) ;
} ,
cancel : function ( ) {
alert ( "用户拒绝授权录音" ) ;
}
} ) ;
}
Okay, talk is cheap, show you the code.
How to use
git clone https: //github.com/ssttm169/tom-vpr.git
cd server
npm i / yarn
npm run dev //本地开发
npm start //服务器跑
//或者
cd client
npm i / yarn
npm run dev //本地开发
npm start //服务上跑客户端