This commit is contained in:
2025-04-20 18:46:48 +08:00
parent e66f7ce00e
commit a696bc3bbe
34 changed files with 9681 additions and 328 deletions

5
backend/demo/index.html Normal file
View File

@@ -0,0 +1,5 @@
<script src="recorder.mp3.min.js"></script> <!--已包含recorder-core和mp3格式支持, CDN不稳定仅供测试: https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/recorder.mp3.min.js-->
<script src="./index.js"></script>

393
backend/demo/index.js Normal file
View File

@@ -0,0 +1,393 @@
/******************
《【教程】【ASR】实时语音识别、音频文件转文字-阿里云版》
作者:高坚果
时间2020-7-22 22:37:09
通过阿里云语音识别(语音转文字)插件 /src/extensions/asr.aliyun.short.js可实现实时语音识别、单个语音文件转文字。
只需要后端提供一个Token生成接口就能进行语音识别可直接参考或本地运行此NodeJs后端测试程序/assets/demo-asr/NodeJsServer_asr.aliyun.short.js配置好代码里的阿里云账号后在目录内直接命令行执行`node NodeJsServer_asr.aliyun.short.js`即可运行提供本地测试接口。
目前暂未提供其他版本的语音识别插件比如腾讯云、讯飞等搭配使用Recorder的onProcess实时处理可根据自己的业务需求选择对应厂家自行对接即可如需定制开发请联系作者。
******************/
var asr;
/*************单个语音文件转文字例子,你也可以录完音后再一次性进行识别***************/
//将录音文件进行语音识别支持的录音格式取决于浏览器的支持兼容性mp3最好wav次之其他格式不一定能够解码
var fileToText=function(audioBlob,fileName){
if(asr){
Runtime.Log("上次asr未关闭",1);
return;
};
Runtime.Log("开始识别文件:《"+fileName+"》asrProcess中已限制最多识别前60*3-5*(3-1)=170秒 ...");
$(".recAsrTxt").text("");
$(".recAsrTime").html("");
var url=$(".asrTokenApi").val();
var urlReq=null;
if(/^\s*\{.*\}\s*$/.test(url)){
//这里是输入框里面填的json数据直接success回调即可
urlReq=function(url,args,success,fail){
var data; try{ data=JSON.parse(url); }catch(e){};
if(!data || !data.appkey || !data.token){
fail("填写的json数据"+(!data?"解析失败":"中缺少appkey或token"));
}else{
success({ appkey:data.appkey, token:data.token });
}
}
};
var asr2=asr=Recorder.ASR_Aliyun_Short({
tokenApi:url
,apiArgs:{
lang:$("[name=arsLang]:checked").val()
,xxx:"其他请求参数"
}
,apiRequest:urlReq //如果提供了token数据可不发起api请求
,asrProcess:function(text,nextDuration,abortMsg){
/***识别中间结果实回调必须返回true才能继续识别否则立即超时停止识别***/
if(abortMsg){
//语音识别中途出错,单个文件识别无需任何处理,会自动回调结果
Runtime.Log("[asrProcess回调]被终止:"+abortMsg,1);
return false;
};
$(".recAsrTxt").text(text);
$(".recAsrTime").html("识别时长: "+formatTime(asr2.asrDuration())
+" 已发送数据时长: "+formatTime(asr2.sendDuration()));
return nextDuration<=3*60*1000;//允许识别3分钟的识别时长比音频时长小5*2秒
}
,log:function(msg,color){
Runtime.Log('<span style="opacity:0.15">'+msg+'</span>',color);
}
});
Runtime.Log("语言:"+asr.set.apiArgs.lang);
//语音文件识别只需调用audioToText即可完成识别简单快速
asr.audioToText(audioBlob,function(text,abortMsg){
asr=null;
if(abortMsg){
Runtime.Log("发现识别中途被终止(一般无需特别处理)"+abortMsg,"#fb8");
};
Runtime.Log("文件识别最终结果:"+text, 2);
},function(errMsg){
asr=null;
Runtime.Log("文件识别结束失败:"+errMsg, 1);
});
};
/*************单个语音文件转文字例子 END***************/
/*************实时语音识别例子*************************/
/******界面交互处理、打开录音权限******/
//使用长按录音的方式,可有效控制转换时长,避免不必要的资源浪费
//长按按钮功能已经封装好了,直接调用 BindTouchButton 即可快速实现长按
var bindTouchButton=function(){
DemoFragment.BindTouchButton(
"recTouchBtn"
,"按住进行录音+识别"
,"松开结束"
,{}
,asrOnTouchStart
,asrOnTouchEnd
);
};
var rec;
/**打开录音,先得到录音权限**/
function recOpenClick(){
$(".recOpenBtn").hide();
$(".recCloseBtn").show();
var end=function(isOk){
if(isOk){
$(".asrStartBtns").show();
$(".asrStopBtn").show();
};
};
rec=Recorder({
type:"wav"
,sampleRate:16000
,bitRate:16
,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){
Runtime.Process.apply(null,arguments);
//实时推入asr处理。asr.input随时都可以调用就算asr并未start会缓冲到asr.start完成然后将已input的数据进行识别
if(asr){
//buffers是从录音开头到现在的缓冲因此需要提供 buffersOffset=newBufferIdx
asr.input(buffers, bufferSampleRate, newBufferIdx);
};
}
});
var t=setTimeout(function(){
recAsrStatus("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)",1);
rec=null;
end(false);
},8000);
recAsrStatus("正在打开录音权限,请稍后...");
rec.open(function(){//打开麦克风授权获得相关资源
clearTimeout(t);
recAsrStatus("录音已打开,可以长按录音+识别了",2);
end(true);
},function(msg,isUserNotAllow){//用户拒绝未授权或不支持
clearTimeout(t);
recAsrStatus((isUserNotAllow?"UserNotAllow":"")+"无法录音:"+msg, 1);
rec=null;
end(false);
});
};
var recCloseClick=function(){
$(".recOpenBtn").show();
$(".recCloseBtn").hide();
$(".asrStartBtns").hide();
if(rec){
Runtime.Log("已关闭录音");
rec.close();
rec=null;
}else{
Runtime.Log("未打开录音",1);
}
};
//免长按,这里就是调用的长按时的两个状态方法 功能是一样的
var asrStartClick_NoTouch=function(){
asrOnTouchStart(function(){});
};
var asrStopClick_NoTouch=function(){
if(!asr){
Runtime.Log("未开始识别",1);
return;
};
asrOnTouchEnd(false,true);
};
/******核心的长按录音识别******/
/**长按开始录音**/
var asrOnTouchStart=function(cancel){
if(!rec){
cancel("未打开录音");
recAsrStatus("未打开录音",1);
return;
};
rec.s_isStart=false;
if(asr){
cancel("上次asr未关闭");
recAsrStatus("上次asr未关闭",1);
return;
};
$(".recAsrTxt").text("");
$(".recAsrTime").html("");
var url=$(".asrTokenApi").val();
var urlReq=null;
if(/^\s*\{.*\}\s*$/.test(url)){
//这里是输入框里面填的json数据直接success回调即可
urlReq=function(url,args,success,fail){
var data; try{ data=JSON.parse(url); }catch(e){};
if(!data || !data.appkey || !data.token){
fail("填写的json数据"+(!data?"解析失败":"中缺少appkey或token"));
}else{
success({ appkey:data.appkey, token:data.token });
}
}
};
//创建语音识别对象每次识别都要新建asr不能共用
var asr2=asr=Recorder.ASR_Aliyun_Short({
tokenApi:url
,apiArgs:{
lang:$("[name=arsLang]:checked").val()
,xxx:"其他请求参数"
}
,apiRequest:urlReq //如果提供了token数据可不发起api请求
,asrProcess:function(text,nextDuration,abortMsg){
/***实时识别结果必须返回true才能继续识别否则立即超时停止识别***/
if(abortMsg){
//语音识别中途出错
recAsrStatus("[asrProcess回调]被终止:"+abortMsg,1);
cancel("语音识别出错");//立即结束录音,就算继续录音也不会识别
return false;
};
$(".recAsrTxt").text(text);
$(".recAsrTime").html("识别时长: "+formatTime(asr2.asrDuration())
+" 已发送数据时长: "+formatTime(asr2.sendDuration()));
return nextDuration<=2*60*1000;//允许识别2分钟的识别时长比录音时长小5秒
}
,log:function(msg,color){
Runtime.Log('<span style="opacity:0.15">'+msg+'</span>',color);
}
});
Runtime.Log("语言:"+asr.set.apiArgs.lang);
recAsrStatus("连接服务器中,请稍后...");
//打开语音识别建议先打开asr成功后再开始录音
asr.start(function(){//无需特殊处理start和stop的关系只要调用了stop会阻止未完成的start不会执行回调
//开始录音
Runtime.Log("开始录音...");
rec.start();
rec.s_isStart=true;
recAsrStatus("滴~~ 已开始语音识别请讲话asrProcess中已限制最多识别60*2-5*(2-1)=115秒...",2);
},function(errMsg){
recAsrStatus("语音识别开始失败,请重试:"+errMsg,1);
cancel("语音识别开始失败");
});
};
/**松开停止录音**/
var asrOnTouchEnd=function(isCancel,isUser){
recAsrStatus(isCancel?"已取消":isUser?"已松开":"长按被取消",isUser?0:1);
var asr2=asr;asr=null;//先干掉asr防止重复stop
if(!asr2){
Runtime.Log("未开始识别",1);
}else{
//asr.stop 和 rec.stop 无需区分先后同时执行就ok了
asr2.stop(function(text,abortMsg){
if(abortMsg){
abortMsg="发现识别中途被终止(一般无需特别处理)"+abortMsg;
};
recAsrStatus("语音识别完成"+(abortMsg?""+abortMsg:""),abortMsg?"#f60":2);
Runtime.Log("识别最终结果:"+text, 2);
},function(errMsg){
recAsrStatus("语音识别"+(!isUser?"被取消":"结束失败")+""+errMsg, 1);
});
};
var rec2=rec;
if(rec2.s_isStart){
rec2.s_isStart=false;
rec2.stop(function(blob,duration){
Runtime.LogAudio(blob,duration,rec2);
},function(errMsg){
Runtime.Log("录音失败:"+errMsg, 1);
});
};
};
/**更新状态**/
var recAsrStatus=function(html,color){
var elem=document.querySelector(".recAsrStatus");
elem.style.color=color==1?"red":color==2?"#0b1":(color||null);
elem.innerHTML=html;
Runtime.Log(html,color);
};
/*************实时语音识别例子 END***************/
//破坏环境,测试错误是否被正确处理
var killToken=function(){
if(!asr){
Runtime.Log("未开始语音识别",1);
return;
}
asr.set.apiRequest=function(url,args,success,fail){
fail("不让获取Token");
}
Runtime.Log("已设置ASR的apiRequest下一分钟将无法获得Token");
};
var killWs=function(){
if(!asr || !asr.wsCur){
Runtime.Log("未开始语音识别",1);
return;
}
asr.wsCur.close();
Runtime.Log("已强制关闭了ASR的WebSocket连接");
};
//=====以下代码无关紧要,音频数据源和界面==================
//加载录音框架
Runtime.Import([
{url:RootFolder+"/src/recorder-core.js",check:function(){return !window.Recorder}}
,{url:RootFolder+"/src/engine/wav.js",check:function(){return !Recorder.prototype.wav}}
,{url:RootFolder+"/src/extensions/asr.aliyun.short.js",check:function(){return !Recorder.ASR_Aliyun_Short}}
,{url:RootFolder+"/assets/runtime-codes/fragment.touch_button.js",check:function(){return !window.DemoFragment||!DemoFragment.BindTouchButton}}//引入BindTouchButton
]);
//显示控制按钮
Runtime.Ctrls([
{html:'\
<hr/><div class="arsLangs" style="padding:5px 0">识别语言模型:\
<style>.arsLangs label{cursor: pointer;margin-right:10px}</style>\
<label><input type="radio" name="arsLang" value="普通话" checked>普通话</label>\
<label><input type="radio" name="arsLang" value="粤语">粤语</label>\
<label><input type="radio" name="arsLang" value="英语">英语</label>\
<label><input type="radio" name="arsLang" value="日语">日语</label>\
</div>\
<hr/><div style="padding:5px 0">Token Api\
<input class="asrTokenApi" value="'+localTokenApi(1).replace(/"/g,"&quot;")+'" placeholder="请填写api地址 或 token的json数据" style="width:240px"/>\
<div style="font-size:13px;color:#999;word-break:break-all;">\
<div>你可以下载Recorder仓库<a href="https://gitee.com/xiangyuecn/Recorder/tree/master/assets/demo-asr" target="_blank">/assets/demo-asr</a>内的nodejs服务器端脚本到电脑上配置好代码里的阿里云账号然后运行此服务器端脚本即可提供本地测试接口</div>\
<div>如果无法访问此api地址比如手机上你可以根据服务器脚本中的提示在电脑上打http地址手动复制或自行提供 {appkey:"...",token:"..."} 先删掉上面输入框中的url再粘贴json进去即可使用</div>\
</div>\
</div>\
'}
,{html:'<hr/>\
<div>\
<span style="margin-right:170px">\
<button class="mainBtn recOpenBtn" onclick="recOpenClick()">单击此处打开 录音+识别 功能</button>\
<button class="mainBtn recCloseBtn" onclick="recCloseClick()" style="display:none;">关闭录音</button>\
</span>\
\
<button class="mainBtn asrStartBtns" style="display:none;" onclick="asrStartClick_NoTouch()">免按住开始录音+识别</button>\
<button class="mainBtn asrStopBtn" onclick="asrStopClick_NoTouch()" style="display:none;">结束语音识别</button>\
</div>\
\
<div>\
<button class="mainBtn recTouchBtn asrStartBtns" style="width:260px;height:60px;line-height:60px;display:none;"></button>\
</div>\
\
<div class="recAsrStatus"></div>\
\
<hr/>\
<div style="margin:12px 0 6px;font-size:12px">实时识别结果: <span class="recAsrTime"></span></div>\
<div class="recAsrTxt" style="padding:15px 10px;min-height:50px;margin-bottom:12px;border:3px dashed #a2a1a1"></div>\
'}
,{name:"破坏ASR配置下一分钟得不到Token",click:"killToken"}
,{name:"强制断开ASR的WebSocket连接",click:"killWs"}
,{choiceFile:{
multiple:false
,name:"音频已限制最多前3分钟内转成文字"
,mime:"audio/*"
,process:function(fileName,arrayBuffer,filesCount,fileIdx,endCall){
fileToText(new Blob([arrayBuffer]),fileName);
endCall();
}
}}
]);
bindTouchButton();
var formatTime=function(n){//格式化毫秒成 分:秒
n=Math.round(n/1000);
var s=n%60;
var m=(n-s)/60;
return m+":"+("0"+s).substr(-2);
};
function localTokenApi(useSet){
var url="http://127.0.0.1:9527/token";
if(useSet){
url=localStorage["ASR_Aliyun_Short_TokenApi"]||url;
}
return url;
};
$(".asrTokenApi").bind("change",function(){
localStorage["ASR_Aliyun_Short_TokenApi"]=this.value==localTokenApi()?"":this.value;
});

6
backend/demo/recorder.mp3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

14
backend/package.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "backend",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.7.1",
"type": "module"
}

View File

@@ -0,0 +1,312 @@
/*******************************************
ASR 阿里云-智能语音交互-一句话识别 生成token的api接口本地测试http后端服务
https://github.com/xiangyuecn/Recorder
【运行】直接在当前文件夹内用命令行执行命令,即可运行:
node NodeJsServer_asr.aliyun.short.js
【运行前先修改以下配置必填项】:
*******************************************/
global.Config = {
//请到阿里云开通 一句话识别 服务可试用一段时间正式使用时应当开通商用版很便宜得到AccessKey、Secret参考https://help.aliyun.com/document_detail/324194.html
AccessKey: 'LTAI5tDazeiB3pYYUwnMuLAt', //【必填】根据文档创建RAM用户分配好权限生成访问密钥
Secret: '2UnGJO6GLZIVXS249B4wgi9uTKknLY', //【必填】
//请到阿里云智能语音交互控制台创建相应的语音识别项目每个项目可以设置一种语言模型要支持多种语言就创建多个项目然后填写每个项目对应的Appkey到这里前端调用本接口的时候传入lang参数来获取到对应的Appkey
Langs: {
普通话: 'xGuvvqhseLDauN2r', //【必填】至少创建一个普通话语言模型的项目用于测试填对应的Appkey
粤语: '',
英语: '',
日语: '',
//...... 按需提供语言对应的项目Appkey
},
};
global.Port = 9527; //http服务端口
global.SSL = {
CertFile: '', //cert.pem 证书路径不为空将开启https
KeyFile: '', //cert.key 密钥路径
};
const Config = global.Config;
//如果提供了这个本地文件就加载过来可覆盖global下挂载的对象
var fs = require('fs');
var localFile = 'NodeJsServer_asr.aliyun.short__local.cjs';
fs.existsSync(localFile) && require('./' + localFile);
var GetLangKeys = function () {
var keys = [];
for (var k in Config.Langs) if (Config.Langs[k]) keys.push(k);
return keys;
};
/***************一句话识别Token生成***************/
var GetAsrToken = async function (ctx, args) {
var lang = args.lang; //unsafe
var rtv = {};
ctx.setValue(rtv);
var errTag = '[GetAsrToken]';
if (!Config.AccessKey || !Config.Secret) {
return ctx.error(errTag + '未配置AccessKey');
}
console.log('Config', lang);
//根据前端需要的语言获得appkey一种语言模型对应一个项目
var appkey = Config.Langs[lang];
if (!appkey) {
var errMsg = 'lang[' + lang + ']未配置Appkey';
if (!lang) errMsg = '请求需要提供lang参数';
return ctx.error(errTag + errMsg + '已配置的可用lang值' + JSON.stringify(GetLangKeys()));
}
rtv.appkey = appkey;
//获得Access TokenToken有效期内可以重复使用实际使用时应当全局共享并做好并发控制
var cache = AccessToken_RedisCache; //假装读取Redis缓存数据
if (!cache || cache.ExpireTime - Date.now() / 1000 < 60) {
//缓存无效
/*RedisLock()*/ {
//假装加锁得到锁的允许更新Token
cache = AccessToken_RedisCache; //假装得到锁后,二次确认是否抢占到更新机会
if (!cache || cache.ExpireTime - Date.now() / 1000 < 60) {
cache = await NewAccessToken(ctx);
if (ctx.isError()) {
return ctx.error(errTag + ctx.getMsg());
}
console.log('NewAccessToken', cache);
AccessToken_RedisCache = cache; //假装写入Redis缓存
}
}
}
rtv.token = cache.Id;
ctx.endLog = function () {
console.log(
' \x1B[32m' +
lang +
'token:' +
JSON.stringify(rtv) +
'\x1B[0m' +
'\n 此token可重复使用有效期到' +
new Date(cache.ExpireTime * 1000).toLocaleString(),
);
};
return ctx;
};
var AccessToken_RedisCache = null;
//调用阿里云接口获得新的Access Token文档https://help.aliyun.com/document_detail/113251.html
var NewAccessToken = async function (ctx) {
var params = {
AccessKeyId: Config.AccessKey,
Action: 'CreateToken',
Version: '2019-02-28',
Format: 'JSON',
RegionId: 'cn-shanghai',
Timestamp: JSON.stringify(new Date()).replace(/"/g, ''),
SignatureMethod: 'HMAC-SHA1',
SignatureVersion: '1.0',
SignatureNonce: RandomHEX(32).replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5'),
};
var arr = [];
for (var k in params) {
arr.push(urlEncodeX(k) + '=' + urlEncodeX(params[k]));
}
arr.sort();
params = arr.join('&');
var signStr = 'GET&' + urlEncodeX('/') + '&' + urlEncodeX(params);
var sign = HMAC('sha1', Config.Secret + '&', signStr, 'base64');
params = 'Signature=' + urlEncodeX(sign) + '&' + params;
//直接发起get请求
var load = LoadRequest('http://nls-meta.cn-shanghai.aliyuncs.com/?' + params);
await load.getString(false);
var respTxt = load.response.data;
var data = {};
try {
data = JSON.parse(respTxt);
} catch (e) {}
if (!data.Token) {
ctx.error('获取Token失败[' + data.Code + ']:' + data.Message);
return null;
}
return data.Token;
};
//一些函数
var LoadRequest = require('./lib.load.cjs');
var Crypto = require('crypto');
var urlEncodeX = function (val) {
return encodeURIComponent(val).replace(/[^\w\-\.\~\%]/g, function (a) {
return '%' + ('0' + a.charCodeAt(0).toString(16)).substr(-2).toUpperCase();
});
};
var RandomHEX = function (len) {
var s = [];
for (var i = 0, r; i < len; i++) {
r = Math.floor(Math.random() * 16);
if (r < 10) {
s.push(String.fromCharCode(r + 48));
} else {
s.push(String.fromCharCode(r - 10 + 97));
}
}
return s.join('');
};
var HMAC = function (hash, key, data, resType) {
var hmac = Crypto.createHmac(hash, key);
hmac.update(data);
return hmac.digest(resType === null ? null : resType || 'hex');
};
/***************提供接口***************/
var Api = {}; //async method_path0(ctx,args)
//内容回显
Api.GET_ = Api.GET_echo = async function (ctx, args) {
ctx.setValue('<h1>阿里云一句话识别TokenApi服务正在运行...</h1>', 'html');
};
Api.POST_ = Api.POST_echo = async function (ctx, args) {
ctx.setValue({ url: ctx.req.url, method: ctx.req.method, headers: ctx.req.headers, args: args });
};
//token接口
Api.GET_token = Api.POST_token = async function (ctx, args) {
await GetAsrToken(ctx, args);
};
/***开启http服务***/
var http = require('http');
var https = require('https');
var querystring = require('querystring');
var reqProcess = function (req, rep) {
var ctx = {
req: req,
rep: req,
data: { c: 0, m: '', v: '' },
status: 200,
setCT: false,
contentType: 'text/json; charset=utf-8',
};
ctx.setValue = function (val, contentType) {
if (contentType != null) {
ctx.setCT = true;
ctx.contentType = !contentType || contentType == 'html' ? 'text/html; charset=utf-8' : contentType;
}
ctx.data.v = val;
};
ctx.error = function (msg) {
ctx.data.c = 1;
ctx.data.m = msg;
return ctx;
};
ctx.isError = function () {
return ctx.data.c != 0;
};
ctx.getMsg = function () {
return ctx.data.m;
};
ctx.endLog = function () {};
var post = Buffer.alloc(0);
req.on('data', function (chunk) {
post = Buffer.concat([post, chunk], post.length + chunk.length);
});
req.on('end', function () {
(async function () {
if (req.method != 'GET' && req.method != 'POST') {
ctx.setValue('Method: ' + req.method, 'html');
} else
try {
//解析url查询参数 和 post表单
var params = querystring.parse((/\?(.+)/.exec(req.url) || [])[1] || '');
params = Object.assign(params, querystring.parse(post.toString()));
var path0 = (/^\/([^\/\?]+)/.exec(req.url) || [])[1] || '';
var fn = Api[req.method + '_' + path0];
if (fn) {
await fn(ctx, params);
} else {
ctx.status = 404;
ctx.error(req.method + '路径不存在:' + req.url);
}
} catch (e) {
ctx.error('执行出错:' + e.stack);
}
if (!ctx.setCT) {
try {
var sendData = JSON.stringify(ctx.data);
} catch (e) {
sendData = JSON.stringify({ c: 1, m: '返回数据失败:' + e.stack });
}
} else {
var sendData = ctx.data.v;
}
rep.writeHead(ctx.status, {
'Content-Type': ctx.contentType,
'Access-Control-Allow-Origin': '*',
});
rep.end(sendData);
console.log(
'[' +
new Date().toLocaleString() +
']:' +
Port +
' ' +
req.method +
' ' +
ctx.status +
' ' +
Buffer.byteLength(sendData) +
' ' +
post.length +
' ' +
req.url,
);
if (ctx.isError()) {
console.log(' \x1B[31m' + ctx.getMsg() + '\x1B[0m');
}
ctx.endLog();
})();
});
};
var UrlProtocol = 'http';
var UrlPort = Port == 80 ? '' : ':' + Port;
if (SSL.CertFile) {
UrlProtocol = 'https';
UrlPort = Port == 443 ? '' : ':' + Port;
}
console.log('正在创建' + UrlProtocol + '服务(port:' + Port + ')...');
if (SSL.CertFile) {
https
.createServer(
{
cert: fs.readFileSync(SSL.CertFile),
key: fs.readFileSync(SSL.KeyFile),
},
reqProcess,
)
.listen(Port);
} else {
http.createServer(reqProcess).listen(Port);
}
console.log('\x1B[32m%s\x1B[0m', UrlProtocol.toUpperCase() + '服务正在运行...');
console.log(`
请在语音识别测试页面填写接口地址: \x1B[33m
${UrlProtocol}://127.0.0.1${UrlPort}/token
${UrlProtocol}://localhost${UrlPort}/token
${UrlProtocol}://你的局域网IP${UrlPort}/token
${UrlProtocol}://你的域名${UrlPort}/token
\x1B[0m
如果你的测试页面\x1B[35m无法访问http地址\x1B[0m或\x1B[35m存在跨域问题\x1B[0m时请在本电脑上直接访问 \x1B[33m${UrlProtocol}://127.0.0.1${UrlPort}/token?lang=你需要lang语言\x1B[0m
然后手动复制 \x1B[33m{appkey:'...',token:'...'}\x1B[0m json到页面中使用复制的token可以重复使用本控制台中会显示有效期
已配置的可用lang语言\x1B[33m${JSON.stringify(GetLangKeys())}\x1B[0m
`);

199
backend/src/lib.load.cjs Normal file
View File

@@ -0,0 +1,199 @@
//请求数据封装
var LoadFn=module.exports=function(url){
return new Load(url);
};
var QueryString = require('querystring');
var Http=require('http');
var Https=require('https');
var Load=function(url){
this.response={//响应数据
url:"" //最终响应的url如果发生了30x跳转为跳转后的地址
,status:0
,statusDesc:"" //Not Found
,headers:[] //["k: v",...] k大小写未知
,data:null //string或buffer
};
this.params={};//表单参数 GET或POST表单
this.rawPost=null;//POST数据 string或buffer
this.headers={};
this.contentType="application/x-www-form-urlencoded";
this.timeout=15019;//默认GET的POST为30000
this.redirect=true;//是否允许重定向
this.debug=false;//调试模式,会忽略证书的校验
this._url=url;
var m=/((\w+:)?\/\/([^\/:]+)(\:\d+)?)(.*)/.exec(url)||[];
this._url_left=m[1];
this._url_protocol=(m[2]||"http:").toLowerCase();
this._url_isHttps=this._url_protocol=="https:";
this._url_host=m[3]||"";
this._url_port=+(m[4]||":"+(this._url_isHttps?443:80)).substr(1);
this._url_pq=m[5]||"";
};
Load.prototype={
_redirect:async function(url,checkSuccessStatus,method,encode){
if(/(^https?:\/\/)|(^\/)/i.test(url)){
if(RegExp.$2){
url=this._url_left+url;
};
}else{
throw new Error("未支持的重定向地址格式:"+url);
};
var redirectCount=this.redirectCount||0;
if(redirectCount>=10){
throw new Error("重定向超过次数");
};
var load=new Load(url);
load.params=this.params;
load.rawPost=this.rawPost;
load.headers=this.headers;
load.contentType=this.contentType;
load.timeout=this.timeout;
load.redirect=this.redirect;
load.debug=this.debug;
load.redirectCount=redirectCount+1;
await load.send(checkSuccessStatus,method,encode);
return load;
}
,set:function(k,v){
this[k]=v;
return this;
}
,send:async function(checkSuccessStatus,method,encode){
var This=this;
return await new Promise(function(resolve, reject){
var isGET=method=="GET";
var url=This._url_pq;
var params=QueryString.stringify(This.params);
if(isGET && params){
if(url.indexOf("?")+1){
url+="&"+params;
}else{
url+="?"+params;
};
};
if(!isGET){
This.headers["Content-Length"]=Buffer.byteLength(This.rawPost||params);
if(!This.headers["Content-Type"]){
This.headers["Content-Type"]=This.contentType;
};
};
var scope=This._url_isHttps?Https:Http;
var obj={
method:method
,headers:This.headers
,timeout:This.timeout!=15019?This.timeout:isGET?15000:30000
,rejectUnauthorized:This.debug?false:true
,protocol:This._url_protocol
,host:This._url_host
,port:This._url_port
,path:url
};
var req=scope.request(obj,function(resp){
var status=resp.statusCode;
var headers=[],location="";
var arr=resp.rawHeaders;
for(var i=0;i<arr.length;i+=2){
var k=arr[i], v=arr[i+1];
headers.push(k+": "+v);
if(k.toLowerCase()=="location"){
location=v;
};
};
This.response={
url:This._url
,status:status
,statusDesc:resp.statusMessage
,headers:headers
};
if((status==301||status==302) && This.redirect){
This._redirect(location,checkSuccessStatus,method,encode)
.then(function(o){
This.response=o.response;
resolve(This.response.data);
},reject);
return;
};
var buffers=[];
resp.on('data',function(buffer){
buffers.push(buffer);
});
resp.on('end',function(){
if(checkSuccessStatus!==false){
if(!( status>=200 && status<300 )){
reject(new Error("请求失败["+status+"]"));
return;
};
};
This.response.data=Buffer.concat(buffers);
if(encode!=null){
var enc=(encode=="json"?"":encode)||'utf8';
var val=This.response.data.toString(enc);
if(encode=="json"){
try{
val=JSON.parse(val);
}catch(e){
reject(new Error("请求结果解析失败"));
return;
};
};
This.response.data=val;
};
resolve(This.response.data);
});
});
req.on("error",function(e){
reject(new Error("请求错误:"+e.message));
});
if(!isGET){
if(This.rawPost){
req.write(This.rawPost);
}else if(params){
req.write(params);
};
};
req.end();
});
}
,getBytes:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"GET");
}
,getString:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"GET","");
}
,getJson:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"GET","json");
}
,postBytes:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"POST");
}
,postString:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"POST","");
}
,postJson:async function(checkSuccessStatus){
return await this.send(checkSuccessStatus,"POST","json");
}
};