magic 发布的文章

语音读题功能

为了更方便查看题目,我们加入读题功能。语音读题主要应用在智能客服机器人、电子有声读物、智慧教育等领域,了解到目前市场语音合成技术,主要有讯飞语音、百度语音、腾讯语音这几家大厂。 都支持男女生声,讯飞价格比较贵,这里发现腾讯语音合成暂时不收费,

腾讯云的语言合成介绍
https://cloud.tencent.com/product/tts#scenarios

语音合成(Text To Speech)满足已知文本生成语音的需求,打通人机交互闭环。多种音色选择,支持自定义音量、语速,为企业客户提供定制自有领域词库和个性化发音人服务,让发音更自然、更专业、更符合场景需求。语音合成广泛应用于语音导航、有声读物、标准发音领读、自动新闻播报等场景。

本以为这些API厂商,直接提供了API接口,小程序里请求就好了,现实不是的,做法跟做微信支付有点类似。必须自己实现一套服务端API,服务端实现接口加密等操作。

这几家都需要这样做,这里首先把需要的资料准备好。

  1. 开发语言 这里选Golang,官方有服务端SDK
  2. 腾讯云API密匙,自己在控制台查看并记录
  3. 开发文档地址:https://cloud.tencent.com/document/api/441/18086
  4. 选一台服务器,备案好域名,配置好https

编写代码

1.路由

    beego.Router("/1/textToVoice", &controllers.CloudController{}, "post:TextToVoice")

2.控制器

func (cloud *CloudController) TextToVoice() {
    body := cloud.Ctx.Input.CopyBody(beego.BConfig.MaxMemory)
    js, err := simplejson.NewJson(body)
    if err != nil {
        cloud.responseError(err)
    }
    //获取文本信息
    text := js.Get("text").MustString()
    //判断不能为空
    if strings.TrimSpace(text) == "" {
        cloud.responseError(fmt.Errorf("text param不能为空"))
    }
    
//这里初始化大家传入自己腾讯云的key信息
    client, _ := aai.NewClientWithSecretId(
        "id",
        "key",
        regions.Guangzhou)

    request := aai.NewTextToVoiceRequest()
    request.Text = common.StringPtr(text)
    request.SessionId = common.StringPtr(uuid.GetRandomString(16))
    request.ModelType = common.Int64Ptr(-1)
    request.ModelType = common.Int64Ptr(-1)
    request.Speed = common.Float64Ptr(0.8)
    response, err := client.TextToVoice(request)
    // 处理异常
    if _, ok := err.(*errors.TencentCloudSDKError); ok {
        cloud.responseError(fmt.Errorf("An API error has returned: %s", err))
    }
    // 非SDK异常,直接失败。实际代码中可以加入其他的处理。
    if err != nil {
        cloud.responseError(err)
    }
    // 打印返回的json字符串
    var base64Str *string = response.Response.Audio
    fileByte, err := models.Base64Decode([]byte(*base64Str))
    if err != nil {
        cloud.responseError(err)
    }

    fileName := uuid.GetRandomString(16) + "_" + fmt.Sprintf("%d", time.Now().Unix()) + ".wav"
    filePath := "/data/dyfsuda/app/restful/files/" + fileName
    url := "https://api.xxxx.com/files/" + fileName
//把文件写入目录
    if err = ioutil.WriteFile(filePath, fileByte, os.ModeAppend); err != nil {
        cloud.responseError(err)
    }
    if err = os.Chmod(filePath, 0777); err != nil {
        cloud.responseError(err)
    }
//返回文件路径给客户端
    cloud.Data["json"] = map[string]string{"url": url}
    cloud.ServeJSON()
}

这里每次的题目可能都不一样,所以就不更新到数据库了,需要的时候,调用一次接口,如果接口收费,这里就把语音文件路径保存到数据表里,每次判断数据表是否存在语音就可以了。

目前开发这读题还是需要自己有服务器,如果现成的API调用就好了。

image.png

这项目目前已经上线,大家可以体验下读题效果。

image.png

有开发者问Bmob能否用wepy,我的答案是可以的,也已经有数十位基于Bmob sdk开发者使用wepy做的小程序上线了

安装wepy
以下安装都通过npm安装,首先电脑安装npm

安装 wepy 命令行工具。
npm install wepy-cli -g
在开发目录生成开发DEMO。

wepy new myproject
切换至项目目录。

cd myproject

开发实时编译。
wepy build --watch

安装.png

这是安装过程

然后开发工具选择 dist 目录打开,默认情况下会报错,大家开启
image.png

记得这个操作,不然会报上面的错误,微信开发者工具 --> 项目 --> 关闭ES6转ES5。

然后就是建个文件引入Bmob了,跟之前引入Bmob并无差异。

├── dist                   微信开发者工具指定的目录(该目录由WePY的build指令自动编译生成,请不要直接修改该目录下的文件)
├── node_modules           
├── src                    代码编写的目录(该目录为使用WePY后的开发目录)
|   ├── components         WePY组件目录(组件不属于完整页面,仅供完整页面或其他组件引用)
|   |   ├── com_a.wpy      可复用的WePY组件a
|   |   └── com_b.wpy      可复用的WePY组件b
|   ├── pages              WePY页面目录(属于完整页面)
|   |   ├── index.wpy      index页面(经build后,会在dist目录下的pages目录生成index.js、index.json、index.wxml和index.wxss文件)
|   |   └── other.wpy      other页面(经build后,会在dist目录下的pages目录生成other.js、other.json、other.wxml和other.wxss文件)
|   └── app.wpy            小程序配置项(全局数据、样式、声明钩子等;经build后,会在dist目录下生成app.js、app.json和app.wxss文件)
└── package.json           项目的package配置

上面是项目目录结构说明,这里需要注意的是wepy引入js需要2份才不会报错,在目录src 下放一份BmobSDk , dist目录下面放一份。

小程序接下来就愉快的操作Bmob 云数据库了。

好久没更新mongo运维这块知识了,这次介绍 db.currentOp与db.killOp命令

数据库优化方法有很多,但所有数据库优化都离不开慢查询优化。mysql、mongodb都可以开启慢查询,来对数据库查询进行分析。开启慢查询日志,对性能会有一定的影响。mongoDB 有时我们只想临时看下慢查询日志,应该如何处理。

这时,我们可以用到mongdb的db.currentOp命令 ,他可以列出当前真正跑的op相关信息。

1.查看目前正在执行的所有查询语句

db.currentOp();

2.查询所有操作xxx集合并且执行时间已超过3s的请求

db.currentOp(
   {
     "active" : true,
     "secs_running" : { "$gt" : 3 },
     "ns" : /^xxx\./
   }
)

3. 当然,如果我当前db集群有非常多的集合,我也可以不限制xxx集合

db.currentOp(
   {
     "active" : true,
     "secs_running" : { "$gt" : 3 }
   }
)

currentOp的过滤条件包括

  1. 请求操作类型,insert、update、delete…
  2. 请求对应的connectionId,threadId
  3. 请求是否正在等待锁
  4. 请求执行时间
  5. 请求操作的DB或collection
  6. 请求query的内容
  7. …等等

返回结果如下

{
            "desc" : "conn44266",
            "threadId" : "140419266524928",
            "connectionId" : 44266,
            "client" : "10.10.68.209",
            "active" : true,
            "opid" : 4495651,
            "secs_running" : 25,
            "microsecs_running" : NumberLong(25459008),
            "op" : "command",
            "ns" : "xxxxx",
            "query" : {
                "count" : "c74dc2de71",
                "query" : {
                    "video" : {
                        "$exists" : true
                    },
                    "_isdel" : 0,
                    "deleted" : {
                        "$ne" : true
                    },
                    "verify" : {
                        "$ne" : false
                    }
                }
            },
            "planSummary" : "IXSCAN { video: 1 }",
            "numYields" : 189,
            "locks" : {
                "Global" : "r",
                "Database" : "r",
                "Collection" : "r"
            },
            "waitingForLock" : false,
            "lockStats" : {
                "Global" : {
                    "acquireCount" : {
                        "r" : NumberLong(380)
                    }
                },
                "Database" : {
                    "acquireCount" : {
                        "r" : NumberLong(190)
                    }
                },
                "Collection" : {
                    "acquireCount" : {
                        "r" : NumberLong(190)
                    }
                }
            }
        }

结果

当我们知道某条语句是锁库的罪魁祸首的时候,我们就可以通过另一条语句,干掉对应的请求。

killOp 停止正在执行的查询

用法:

db.killOp(opid)

目前Mongodb手册,还未有一次清掉当前所有查询,文档地址:https://docs.mongodb.com/manual/reference/method/db.killOp/

执行后返回

{ "info" : "attempting to kill op", "ok" : 1 }

image.png

db.killOp(opid)的实现原理如下

每个连接对应的服务线程存储了一个killPending的字段,当发送killOp时,会将该字段置1;请求在执行过程中,可以通过不断的调用OperationContext::checkForInterrupt()来检查killPending是否被设置,如果被设置,则线程退出。

一个请求要支持killOp,必须在请求的处理逻辑里加上checkForInterrupt()检查点才行,否则即使发送了killOp,也只能等待请求完全处理完毕线程才会退出。

比如createIndex的处理逻辑里包含了类似如下的代码,在createIndex的循环过程中,一旦killPending被置1了,createIndex的执行可以在当前循环结束时退出。

while (!createIndexFinished) {
    createIndexForOneElement();
    checkForInterupt();
}

所以发送killOp后,请求要执行到下一个『检查点』线程才会退出,MongoDB在很多可能耗时长的请求中,都加入了checkForInterrupt()检查点,如创建索引,repair database,mapreduce、aggregation等。

批量一次清楚当前慢查询

上面说还未有一次清掉当前所有查询, 不过我们可以通过手动写脚本实现。此脚本由diggzhang大神贡献。文章链接:http://yangcongchufang.com/kill-mongo-ops.html

实现功能:传入自己的IP地址,强制关停自己的异常查询。

打开家目录下的.mongorc.js拷贝下面的killMyRunningOps函数进去,重新打开mongoshell即可加载这个函数(mongoshell启动时会预读这个文件)。

➜  ~ cat ~/.mongorc.js
killMyRunningOps = function (clientIp) {
    var currOp = db.currentOp();
    for (op in currOp.inprog) {
        if (clientIp == currOp.inprog[op].client.split(":")[0]) {
            db.killOp(currentOp.inprog[op].opid)
        }
    }
}

用法很简单,知道自己IP后,调用这个函数:

> killMyRunningOps("12.23.32.21")

目标

开发一款H5与小程序共用的一套代码,带后端并且可以提交给微信审核。

简介:

Bmob 最近推出了混合平台 JSSDK库,支持 Node.js 、微信小程序 、Weex 、React Native 、Quick App 和浏览器等,写一份js可以以上任意平台,如果前端UI相关用法可以相同,也就是一份代码可以在多个平台使用。就拿行业微信小程序来说,目前支持跨平台的框架有mpvue、与wepy,分别是美团与腾讯出的。mpvue 有跨平台示例、wepy背后有腾讯为靠山各有优势。

混合数据SDK

文档地址: https://bmob.github.io/hydrogen-js-sdk/#/?id=登陆

这里使用的是Bmob后端云、如果使用自己的服务器,请求网络这块可以用Fly,同样是夸平台,具体请上github查看

框架的选择

由于mpvue官方有跨平台前端示例,这里为了少踩点坑,选择mpvue

实战开始一:分析页面请求

需要实现的页面
demo

从上图,我们可以看到这个页面,有2个数据来自后端,分表是广告图列表数据。这里有2个接口,他们的数据结构分别是

实战开始二:建立数据表

  1. 广告图
数据结构如下
[
  {
  "title": "1299元起,诺基亚X6正式发布",
  "image": "http://img.ithome.com/newsuploadfiles/focus/f3583ca6-9a52-461b-b2b2-0f649fab0516.jpg",
  "link": "/pages/news/detail?id=360077&title=1299元起,诺基亚X6正式发布"
  }
 ]
我在后台建个表slides,导入数据/src/db/slides.json
导入数据
  1. 列表数据

    我在后台建个表newslist,导入数据/src/db/newslist.json,跟上一步一样的操作
  2. 详细数据

    建立新闻内容表newscontent,导入数据/src/db/newscontent.json

实战开始三:修改请求代码到数据库

列表修改请求代码

//api.js,getNewsList改为

getNewsList: (r) => {
    //返回一个异步对象
    return new Promise((resolve, reject) => {
    //查询`newslist` 数据
      const query = Bmob.Query('newslist')
      query.find().then(res => {
           //返回一个json数据
        resolve({'newslist': res})
      }).catch(err => {
        reject(err)
      })
    })
  },
数据已经从Bmob数据库调用出来了
广告图操作一样,具体代码看github

点击一下连接查看
Github开源代码

这样就实现了,首页的广告、数据列表、内容展示功能,如果您的小程序只是一个展示功能,可以使用此代码进行修改。下一节我们再讲朋友圈的实现。

[Quikapp快应用【小程序】开发入门教程二] 调试器入门

1. 安装调试器

简书新规则不能有二维码,这个地方是一张运行起来内网的二维码图片

上一篇讲到二维码,显示出来后,手机应该安装调试器,教程网址

https://doc.quickapp.cn/tools/debugging-tools.html

2.扫描二维码

image

遇到问题1: 扫描后手机没反应,用另外一台华为手机扫描没问题。

解决方法:手机重新安装调试器,重启手机

遇到问题2:使用实时编译,报错,错误1:npm run watch 命令执行后提示错误 ,错误2:命令执行成功,手机调试器提示安装失败。

错误一:解决办法暂时没找到,过了几天,没动。命令就执行成功了,本环境为MAC。

错误二:最终找到原因,这里需要2个进程,单独执行npm run watch 是没效果的,需要把npm run server 也执行起来。

错误二提示截图:

image

3.成功实现自动刷新调试

npm run watch

解决调试问题,接下来就可以具体开发应用了。 这里目前有个神奇的问题是chrome浏览器中的devtools工具 是否显示需要看运气的,而且只支持chrome,我执行了好多次运行命令npm run server 只有一次弹出了调试地址。

http://192.168.1.38:12306/inspector/inspector.html?ws=192.168.1.43:42717/inspector&remoteFrontend=true&dockSide=undocked, 如果能拿到浏览器的调试地址,到这里就可以实现手机、电脑同步调试了。

image