
公众号开发(十四)实现公众号的H5微信支付
微信支付商户配置
首先在之前的章节:公众号参数配置到uni-config-center 里一定要配置好支付的参数
"mchId": "微信支付商户ID",
"mchKey": "微信支付API密钥",
"notify_url": "微信支付回调地址"
如果之前没配置,这次配置好后要重新上传uni-config-center
生成微信支付回调地址
由于使用云函数的原因,回调地址就需要让云函数支持URL访问。
进入uniCloud的web后台,点击 函数列表
--云函数域名绑定
点击启用域名,就会自动生成一个域名
然后云函数点bctos-weixin
的详情按钮进入详情界面,因为我们的支付回调写在这个云函数里
在详情页面最下面点编辑
请求路径必须以 /http开头,而且不能以 / 结尾,这里我填写:/http/notice
保存后,点右边的复制路径
即可得到上面notify_url
参数需要填写的微信支付回调地址,如我的是这样的
https://4d82a040-4004-4008-8e38-83351e4f7e03.bspapp.com/http/notice
然后在云函数的回调里只需要把订单由未支付状态修改成已支付状态即可。
前端实现微信支付
下面是封装好的支付方法,先通过后端云函数生成统一下单,并返回前端需要的支付参数,最后调起支付操作。
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async weixinPay(money, out_trade_no, ip) {
let openid = await this.getOpenid()
if (!openid) {
this.error('openid获取失败')
return false
}
this.showLoading()
let data = {
openid,
ip,
out_trade_no,
money,
action: "payment"
}
console.log('微信支付前端参数', data)
let e = await uniCloud.callFunction({
name: 'bctos-weixin',
data: data
})
if (e.result.code != 0) {
this.error(e.result.msg)
return false
}
let payment = e.result.data;
let param = {
"appId": payment.appId, //公众号名称,由商户传入
"timeStamp": payment.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": payment.nonceStr, //随机串
"package": payment.package,
"signType": payment.signType, //微信签名方式:
"paySign": payment.paySign //微信签名
}
console.log('微信支付前端得到的结果', param);
WeixinJSBridge.invoke('getBrandWCPayRequest', param)
const db = uniCloud.database()
//由于最新的微信支付推出了点金计划,导致支付成功后不再回调,因此需要主动查询支付状态
let time = setInterval(() => {
db.collection('bctos-order').where({
out_trade_no
}).get().then((res) => {
console.log('bctos-order', res.result.data[0].is_pay)
// res 为数据库查询结果
if (res.result.data[0].is_pay == 1) {
uni.hideLoading()
clearInterval(time)
this.success('支付成功', () => {
uni.redirectTo({
url: "/pages/index/index"
})
})
}
})
}, 1000)
},
最终效果
至此开发全部完成。
微信支付商户配置
首先在之前的章节:公众号参数配置到uni-config-center 里一定要配置好支付的参数
"mchId": "微信支付商户ID",
"mchKey": "微信支付API密钥",
"notify_url": "微信支付回调地址"
如果之前没配置,这次配置好后要重新上传uni-config-center
生成微信支付回调地址
由于使用云函数的原因,回调地址就需要让云函数支持URL访问。
进入uniCloud的web后台,点击 函数列表
--云函数域名绑定
点击启用域名,就会自动生成一个域名
然后云函数点bctos-weixin
的详情按钮进入详情界面,因为我们的支付回调写在这个云函数里
在详情页面最下面点编辑
请求路径必须以 /http开头,而且不能以 / 结尾,这里我填写:/http/notice
保存后,点右边的复制路径
即可得到上面notify_url
参数需要填写的微信支付回调地址,如我的是这样的
https://4d82a040-4004-4008-8e38-83351e4f7e03.bspapp.com/http/notice
然后在云函数的回调里只需要把订单由未支付状态修改成已支付状态即可。
前端实现微信支付
下面是封装好的支付方法,先通过后端云函数生成统一下单,并返回前端需要的支付参数,最后调起支付操作。
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async weixinPay(money, out_trade_no, ip) {
let openid = await this.getOpenid()
if (!openid) {
this.error('openid获取失败')
return false
}
this.showLoading()
let data = {
openid,
ip,
out_trade_no,
money,
action: "payment"
}
console.log('微信支付前端参数', data)
let e = await uniCloud.callFunction({
name: 'bctos-weixin',
data: data
})
if (e.result.code != 0) {
this.error(e.result.msg)
return false
}
let payment = e.result.data;
let param = {
"appId": payment.appId, //公众号名称,由商户传入
"timeStamp": payment.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": payment.nonceStr, //随机串
"package": payment.package,
"signType": payment.signType, //微信签名方式:
"paySign": payment.paySign //微信签名
}
console.log('微信支付前端得到的结果', param);
WeixinJSBridge.invoke('getBrandWCPayRequest', param)
const db = uniCloud.database()
//由于最新的微信支付推出了点金计划,导致支付成功后不再回调,因此需要主动查询支付状态
let time = setInterval(() => {
db.collection('bctos-order').where({
out_trade_no
}).get().then((res) => {
console.log('bctos-order', res.result.data[0].is_pay)
// res 为数据库查询结果
if (res.result.data[0].is_pay == 1) {
uni.hideLoading()
clearInterval(time)
this.success('支付成功', () => {
uni.redirectTo({
url: "/pages/index/index"
})
})
}
})
}, 1000)
},
最终效果
至此开发全部完成。
收起阅读 »
升级中心 uni-upgrade-center - Admin 【bug】
menu.json 配置文件
配置项
"url": "uni_modules/uni-upgrade-center/pages/app/list"
前面少了个 /
添加菜单后 点击升级中心菜单 菜单不会自动展开
应该为:
"url": "/uni_modules/uni-upgrade-center/pages/app/list"
menu.json 配置文件
配置项
"url": "uni_modules/uni-upgrade-center/pages/app/list"
前面少了个 /
添加菜单后 点击升级中心菜单 菜单不会自动展开
应该为:
"url": "/uni_modules/uni-upgrade-center/pages/app/list"

公众号开发(十三)表单界面实现
导入uni-ui
进入uni-ui插件界面
https://ext.dcloud.net.cn/plugin?id=55
点使用 HBuilderX 导入插件
按钮进入bctos-weixin-h5项目,后面我们需要使用到里面的uniForms, uniEasyinput等组件
界面模板实现
可以先使用schema2code插件生成前端界面,再在这基础上修改会省事很多。
下面直接上模板代码
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
<template>
<view class="uni-container">
<view class="top-ad">
<image style="width: 100%; height: 100px;" mode="aspectFit" src="../../static/head.jpg"></image>
</view>
<uni-forms ref="form" :value="formData" validate-trigger="submit" err-show-type="toast">
<view class="white-rounded">
<view style="padding: 1px 15px;">
<view class="nei-romkuan">
<uni-easyinput disabled placeholder="服务内容: 工商注册代办 (经营范围:服务类)"></uni-easyinput>
</view>
<view class="nei-romkuan">
<view class="uni-column">
<text>*</text>电子邮箱 (xxx@xxx.com)
</view>
<uni-easyinput v-model="formData.email" placeholder=" 请输入" />
</view>
<view class="nei-romkuan">
<view class="uni-column">
<text>*</text>手机号码 ( 本人身份证实名的手机号码 )
</view>
<uni-easyinput v-model="formData.mobile" placeholder="请输入" />
</view>
</view>
</view>
<view class="white-rounded white-rounded2">
<view class="kuan50">
<uni-file-picker file-mediatype="image" return-type="object" v-model="formData.idcard_0"
:list-styles="styles" :image-styles="styles2"></uni-file-picker>
<view class="shenfen-zheng">上传身份证正面</view>
</view>
<view class="kuan50">
<uni-file-picker file-mediatype="image" return-type="object" v-model="formData.idcard_1"
:list-styles="styles" :image-styles="styles2" />
<view class="shenfen-zheng">上传身份证背面</view>
</view>
</view>
<view class="uni-button-group">
<button type="primary" class="uni-button" @click="submit">微信支付(788.00元)</button>
</view>
</uni-forms>
</view>
</template>
内容比较明了,就是一个表单页面,只不过加了些排版。
导入uni-ui
进入uni-ui插件界面
https://ext.dcloud.net.cn/plugin?id=55
点使用 HBuilderX 导入插件
按钮进入bctos-weixin-h5项目,后面我们需要使用到里面的uniForms, uniEasyinput等组件
界面模板实现
可以先使用schema2code插件生成前端界面,再在这基础上修改会省事很多。
下面直接上模板代码
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
<template>
<view class="uni-container">
<view class="top-ad">
<image style="width: 100%; height: 100px;" mode="aspectFit" src="../../static/head.jpg"></image>
</view>
<uni-forms ref="form" :value="formData" validate-trigger="submit" err-show-type="toast">
<view class="white-rounded">
<view style="padding: 1px 15px;">
<view class="nei-romkuan">
<uni-easyinput disabled placeholder="服务内容: 工商注册代办 (经营范围:服务类)"></uni-easyinput>
</view>
<view class="nei-romkuan">
<view class="uni-column">
<text>*</text>电子邮箱 (xxx@xxx.com)
</view>
<uni-easyinput v-model="formData.email" placeholder=" 请输入" />
</view>
<view class="nei-romkuan">
<view class="uni-column">
<text>*</text>手机号码 ( 本人身份证实名的手机号码 )
</view>
<uni-easyinput v-model="formData.mobile" placeholder="请输入" />
</view>
</view>
</view>
<view class="white-rounded white-rounded2">
<view class="kuan50">
<uni-file-picker file-mediatype="image" return-type="object" v-model="formData.idcard_0"
:list-styles="styles" :image-styles="styles2"></uni-file-picker>
<view class="shenfen-zheng">上传身份证正面</view>
</view>
<view class="kuan50">
<uni-file-picker file-mediatype="image" return-type="object" v-model="formData.idcard_1"
:list-styles="styles" :image-styles="styles2" />
<view class="shenfen-zheng">上传身份证背面</view>
</view>
</view>
<view class="uni-button-group">
<button type="primary" class="uni-button" @click="submit">微信支付(788.00元)</button>
</view>
</uni-forms>
</view>
</template>
内容比较明了,就是一个表单页面,只不过加了些排版。
收起阅读 »
公众号开发(十二)JS-SDK配置及自定义分享
在网页中要使用分享等一系列的微信JS-SDK功能,要先配置好JS-SDK参数。
安装jweixin-module
要使用公众号H5的JS-SDK功能,先要安装jweixin-module
在bctos-weixin-h5根目录下,执行npm初始化(如果你的根目录下已经有package.json文件可省这一步)
npm init
一直默认回车即可
然后正式安装jweixin-module
npm install jweixin-module --save
安装好后在App.vue文件中引入
var wx = require('jweixin-module')
生成JS-SDK配置参数
然后在pages/index/index
页面的onReady方法中加入生成JS-SDK配置参数,让页面一加载进来就完成配置
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
uniCloud.callFunction({
name: 'bctos-weixin',
data: {
"action": "getJsApiParams",
"url": location.href.split('#')[0]
},
success(res) {
console.log('getJsApiParams get res', res)
if (res.result.code != 0) {
getApp().error(res.result.message)
} else {
getApp().share(res.result)
}
}
})
一定要注意上面url的参数,一定要填写location.href.split('#')[0]
在这一步最容易出现invalid signature错误,我们也是调试了很久才修改好,如果真出现这个问题,先按官方的建议排查一下
1. 确认签名算法正确,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
2. 确认config中nonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。
3. 确认url是页面完整的url(请在当前页面alert(location.href.split('#')[0])确认),包括'http(s)://'部分,以及'?'后面的GET参数部分,但不包括'#'hash后面的部分。
4. 确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
5. 确保一定缓存access_token和jsapi_ticket。
6. 确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
后端云函数实现getJsApiParams方法的代码
async function getApiTicket() {
//先从缓存获取,如果存在就要不重复从微信获取
let key = '' + appId
let ticket = await cache(key)
if (ticket !== false) {
console.log('ticket缓存已存在', ticket);
return ticket
}
let access_token = await getAccessToken()
if (access_token == false) {
return {
code: 1,
message: '获取access_token失败'
}
} else {
console.log('获取access_token成功', access_token);
}
//这里也要使用代理网址请求
let url = 'http://weixin-agent.bctos.cn/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi'
let res = await uniCloud.httpclient.request(url, {
method: 'GET',
data: {}
})
let result = JSON.parse(res.data.toString())
if (typeof(result.ticket) == "undefined") {
console.log('重新获取的Ticket失败了', result)
return false
} else {
console.log('重新获取的Ticket的结果:', result)
await cache(key, result.ticket, result.expires_in)
return result.ticket
}
}
//获取ticket
let ticket = await getApiTicket()
if (ticket == false) {
return {
code: 2,
message: '获取ticket失败'
}
} else {
console.log('获取ticket成功', ticket);
}
let ret = {
jsapi_ticket: ticket,
noncestr: createNonceStr(),
timestamp: createTimestamp(),
url: event.url
};
let string = raw(ret)
console.log('排序后加加密前的字段串', string)
ret.signature = sha1(string);
ret.appId = appId
ret.code = 0
console.log('最终返回的参数', ret)
return ret
注意上面获取ticket的地址,特别是&type=jsapi
这个参数,由于微信文档里还有一个获取会员卡ticket的地方,名字都是相同的,URL前面的地址也是一样的,只是后面&type=card
不一样。之前不知道,就是这个问题导致我们整整调试了两天,反复按官方上面提到的6点调试了很久,还是报invalid signature报错,所以真的如果报这个错误,要一个字母一个字母都仔细比对才行。
实现自定义分享
成功获取到JS-SDK参数后,就可以在前端使用wx.config进行配置了,下面是配置加自定义分享的内容,如果需要加其它功能,如地理位置等信息,需要有jsApiList里加入对于的接口名
share(jsapi_params) {
let config = {
debug: false,
appId: jsapi_params.appId, // 必填,公众号的唯一标识
timestamp: jsapi_params.timestamp, // 必填,生成签名的时间戳
nonceStr: jsapi_params.noncestr, // 必填,生成签名的随机串
signature: jsapi_params.signature, // 必填,签名,见附录1
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData'
]
}
console.log('配置参数', config);
wx.config(config);
wx.error(function(res) {
console.log('JS-SDK配置出问题了', res)
});
wx.ready(function() {
//自定义分享的内容,开发者可以自主修改这部分内容
let shareData1 = {
title: '个体工商户在线注册',
link: "https://caiwu.bctos.cn/index.html?suid=" + uni.getStorageSync('share_uid') +
'#pages/config/detail',
imgUrl: "https://caiwu.bctos.cn/static/share.jpg",
success: function(res) {
console.log('分享设置成功', res);
}
}
let shareData2 = {
...shareData1,
desc: '通过深牛财务线上操作,轻松,快捷办理个体营业执照。'
}
wx.updateAppMessageShareData(shareData2)
wx.updateTimelineShareData(shareData1)
});
},
要看效果不能在浏览器里预览,一定要在微信开发者工具
里看,因为浏览器无法调用微信的JS接口。这是在微信开发者工具
的公众号网页调试里看到的效果,看到分享设置成功说明js-sdk参数已经配置正常,在手机里分享的话就可以看到自定义的分享内容了
在网页中要使用分享等一系列的微信JS-SDK功能,要先配置好JS-SDK参数。
安装jweixin-module
要使用公众号H5的JS-SDK功能,先要安装jweixin-module
在bctos-weixin-h5根目录下,执行npm初始化(如果你的根目录下已经有package.json文件可省这一步)
npm init
一直默认回车即可
然后正式安装jweixin-module
npm install jweixin-module --save
安装好后在App.vue文件中引入
var wx = require('jweixin-module')
生成JS-SDK配置参数
然后在pages/index/index
页面的onReady方法中加入生成JS-SDK配置参数,让页面一加载进来就完成配置
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
uniCloud.callFunction({
name: 'bctos-weixin',
data: {
"action": "getJsApiParams",
"url": location.href.split('#')[0]
},
success(res) {
console.log('getJsApiParams get res', res)
if (res.result.code != 0) {
getApp().error(res.result.message)
} else {
getApp().share(res.result)
}
}
})
一定要注意上面url的参数,一定要填写location.href.split('#')[0]
在这一步最容易出现invalid signature错误,我们也是调试了很久才修改好,如果真出现这个问题,先按官方的建议排查一下
1. 确认签名算法正确,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
2. 确认config中nonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。
3. 确认url是页面完整的url(请在当前页面alert(location.href.split('#')[0])确认),包括'http(s)://'部分,以及'?'后面的GET参数部分,但不包括'#'hash后面的部分。
4. 确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
5. 确保一定缓存access_token和jsapi_ticket。
6. 确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
后端云函数实现getJsApiParams方法的代码
async function getApiTicket() {
//先从缓存获取,如果存在就要不重复从微信获取
let key = '' + appId
let ticket = await cache(key)
if (ticket !== false) {
console.log('ticket缓存已存在', ticket);
return ticket
}
let access_token = await getAccessToken()
if (access_token == false) {
return {
code: 1,
message: '获取access_token失败'
}
} else {
console.log('获取access_token成功', access_token);
}
//这里也要使用代理网址请求
let url = 'http://weixin-agent.bctos.cn/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi'
let res = await uniCloud.httpclient.request(url, {
method: 'GET',
data: {}
})
let result = JSON.parse(res.data.toString())
if (typeof(result.ticket) == "undefined") {
console.log('重新获取的Ticket失败了', result)
return false
} else {
console.log('重新获取的Ticket的结果:', result)
await cache(key, result.ticket, result.expires_in)
return result.ticket
}
}
//获取ticket
let ticket = await getApiTicket()
if (ticket == false) {
return {
code: 2,
message: '获取ticket失败'
}
} else {
console.log('获取ticket成功', ticket);
}
let ret = {
jsapi_ticket: ticket,
noncestr: createNonceStr(),
timestamp: createTimestamp(),
url: event.url
};
let string = raw(ret)
console.log('排序后加加密前的字段串', string)
ret.signature = sha1(string);
ret.appId = appId
ret.code = 0
console.log('最终返回的参数', ret)
return ret
注意上面获取ticket的地址,特别是&type=jsapi
这个参数,由于微信文档里还有一个获取会员卡ticket的地方,名字都是相同的,URL前面的地址也是一样的,只是后面&type=card
不一样。之前不知道,就是这个问题导致我们整整调试了两天,反复按官方上面提到的6点调试了很久,还是报invalid signature报错,所以真的如果报这个错误,要一个字母一个字母都仔细比对才行。
实现自定义分享
成功获取到JS-SDK参数后,就可以在前端使用wx.config进行配置了,下面是配置加自定义分享的内容,如果需要加其它功能,如地理位置等信息,需要有jsApiList里加入对于的接口名
share(jsapi_params) {
let config = {
debug: false,
appId: jsapi_params.appId, // 必填,公众号的唯一标识
timestamp: jsapi_params.timestamp, // 必填,生成签名的时间戳
nonceStr: jsapi_params.noncestr, // 必填,生成签名的随机串
signature: jsapi_params.signature, // 必填,签名,见附录1
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData'
]
}
console.log('配置参数', config);
wx.config(config);
wx.error(function(res) {
console.log('JS-SDK配置出问题了', res)
});
wx.ready(function() {
//自定义分享的内容,开发者可以自主修改这部分内容
let shareData1 = {
title: '个体工商户在线注册',
link: "https://caiwu.bctos.cn/index.html?suid=" + uni.getStorageSync('share_uid') +
'#pages/config/detail',
imgUrl: "https://caiwu.bctos.cn/static/share.jpg",
success: function(res) {
console.log('分享设置成功', res);
}
}
let shareData2 = {
...shareData1,
desc: '通过深牛财务线上操作,轻松,快捷办理个体营业执照。'
}
wx.updateAppMessageShareData(shareData2)
wx.updateTimelineShareData(shareData1)
});
},
要看效果不能在浏览器里预览,一定要在微信开发者工具
里看,因为浏览器无法调用微信的JS接口。这是在微信开发者工具
的公众号网页调试里看到的效果,看到分享设置成功说明js-sdk参数已经配置正常,在手机里分享的话就可以看到自定义的分享内容了

公众号开发(十一)实现access_token获取
要获取access_token必须先完成前面几节的配置:
前端网页托管域名配置:https://bctos.cn/doc/18/1955
公众号域名配置:https://bctos.cn/doc/18/1959
公众号IP白名单配置:https://bctos.cn/doc/18/1963
云函数缓存机制实现:https://bctos.cn/doc/18/1964
确保上面的配置都好了之后,就可以进行access_token的获取了。
由于access_token每天请求的次数有限,必须统一在后端云函数里获取并缓存起来,因此我们的实现代码如下
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async function getAccessToken() {
//先从缓存获取,如果存在就要不重复从微信获取
let key = '' + appId
let token = await cache(key)
if (token !== false) {
console.log('token缓存已存在', token);
return token
}
//这里要使用代理网址请求
let url = 'http://weixin-agent.bctos.cn/cgi-bin/token?grant_type=client_credential'
url += '&secret=' + appSecret + '&appId=' + appId
let res = await uniCloud.httpclient.request(url, {
method: 'GET',
data: {}
})
let result = JSON.parse(res.data.toString())
if (typeof(result.access_token) == "undefined") {
console.log('重新获取的AccessToken失败了', result)
return false
} else {
console.log('重新获取的AccessToken的结果:', result)
await cache(key, result.access_token, result.expires_in)
return result.access_token
}
}
如果获取失败,可以在uniCloud的WEB后台里的云函数日志中看到微信返回了什么错误信息
要获取access_token必须先完成前面几节的配置:
前端网页托管域名配置:https://bctos.cn/doc/18/1955
公众号域名配置:https://bctos.cn/doc/18/1959
公众号IP白名单配置:https://bctos.cn/doc/18/1963
云函数缓存机制实现:https://bctos.cn/doc/18/1964
确保上面的配置都好了之后,就可以进行access_token的获取了。
由于access_token每天请求的次数有限,必须统一在后端云函数里获取并缓存起来,因此我们的实现代码如下
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async function getAccessToken() {
//先从缓存获取,如果存在就要不重复从微信获取
let key = '' + appId
let token = await cache(key)
if (token !== false) {
console.log('token缓存已存在', token);
return token
}
//这里要使用代理网址请求
let url = 'http://weixin-agent.bctos.cn/cgi-bin/token?grant_type=client_credential'
url += '&secret=' + appSecret + '&appId=' + appId
let res = await uniCloud.httpclient.request(url, {
method: 'GET',
data: {}
})
let result = JSON.parse(res.data.toString())
if (typeof(result.access_token) == "undefined") {
console.log('重新获取的AccessToken失败了', result)
return false
} else {
console.log('重新获取的AccessToken的结果:', result)
await cache(key, result.access_token, result.expires_in)
return result.access_token
}
}
如果获取失败,可以在uniCloud的WEB后台里的云函数日志中看到微信返回了什么错误信息
收起阅读 »
uni-app 获取已链接的蓝牙列表
官方没有找到如何获取已链接的蓝牙列表,使用如下方法实现,节省大家的时间。
getConnetedDevices(){
var main = plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
var BManager = main.getSystemService(Context.BLUETOOTH_SERVICE);
plus.android.importClass(BManager); //引入相关的method函数
var BAdapter = BManager.getAdapter();
plus.android.importClass(BAdapter);//引入相关的method函数,这样之后才会有isEna
var lists = BAdapter.getBondedDevices();
plus.android.importClass(lists);
var iterator = lists.iterator();
plus.android.importClass(iterator);
while(iterator.hasNext()) {
var d = iterator.next();
plus.android.importClass(d);
console.log("名称:"+d.getName()+",地址:"+d.getAddress());
}
}
官方没有找到如何获取已链接的蓝牙列表,使用如下方法实现,节省大家的时间。
getConnetedDevices(){
var main = plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
var BManager = main.getSystemService(Context.BLUETOOTH_SERVICE);
plus.android.importClass(BManager); //引入相关的method函数
var BAdapter = BManager.getAdapter();
plus.android.importClass(BAdapter);//引入相关的method函数,这样之后才会有isEna
var lists = BAdapter.getBondedDevices();
plus.android.importClass(lists);
var iterator = lists.iterator();
plus.android.importClass(iterator);
while(iterator.hasNext()) {
var d = iterator.next();
plus.android.importClass(d);
console.log("名称:"+d.getName()+",地址:"+d.getAddress());
}
}
收起阅读 »

公众号开发(十)云函数缓存机制实现
由于云函数每次运行完都会注销,不像其它后端(如PHP)可以使用session或者文件缓存数据。理论上自己部署redis也是用来缓存数据,但redis不可能部署到云函数的内网中,走外网的话性能肯定大打折扣。因此只能使用云数据库来缓存数组,虽然这会增加数据库的访问次数,但也是没办法的事,好在云数据库的性能比较高,不像mysql那样会成为性能瓶颈
数据库就用之前建好的bctos-cache,它只有三个字段:键名(key),缓存内容(value)和过期时间(expired)
其中为了能保存对象数据,所有保存的数据在入库前先经过 JSON.stringify, 出库时会使用 JSON.parse 进行还原。所以不建议直接查询bctos-cache,而是调用云函数处理。
为方便使用,我们直接在APP.vue里封装了cache方法
async function cache(key, value, expired) {
console.log('缓存的参数', {
key,
value,
expired
});
let res = await uniCloud.callFunction({
name: 'bctos-common',
data: {
action: "cache",
key,
value,
expired
}
})
console.log('缓存的结果', res);
if (typeof(res.result.data) == "undefined") {
return res.result
} else {
return res.result.data
}
}
可以这样方便的调用
let app = getApp();
//写入缓存,并且长期有效
app.cache('test','123456')
//写入缓存,而且只缓存300秒,到期自动删除
app.cache('test','123456', 300)
//读取缓存
let res = await app.cache('test')
//删除缓存, value设为null表示删除
app.cache('test', null)
如果是在云函数中使用缓存,建议把上面的cache方法复制到云函数中使用
有了这缓存的功能,后面就可以用来缓存access_token和ticket了
由于云函数每次运行完都会注销,不像其它后端(如PHP)可以使用session或者文件缓存数据。理论上自己部署redis也是用来缓存数据,但redis不可能部署到云函数的内网中,走外网的话性能肯定大打折扣。因此只能使用云数据库来缓存数组,虽然这会增加数据库的访问次数,但也是没办法的事,好在云数据库的性能比较高,不像mysql那样会成为性能瓶颈
数据库就用之前建好的bctos-cache,它只有三个字段:键名(key),缓存内容(value)和过期时间(expired)
其中为了能保存对象数据,所有保存的数据在入库前先经过 JSON.stringify, 出库时会使用 JSON.parse 进行还原。所以不建议直接查询bctos-cache,而是调用云函数处理。
为方便使用,我们直接在APP.vue里封装了cache方法
async function cache(key, value, expired) {
console.log('缓存的参数', {
key,
value,
expired
});
let res = await uniCloud.callFunction({
name: 'bctos-common',
data: {
action: "cache",
key,
value,
expired
}
})
console.log('缓存的结果', res);
if (typeof(res.result.data) == "undefined") {
return res.result
} else {
return res.result.data
}
}
可以这样方便的调用
let app = getApp();
//写入缓存,并且长期有效
app.cache('test','123456')
//写入缓存,而且只缓存300秒,到期自动删除
app.cache('test','123456', 300)
//读取缓存
let res = await app.cache('test')
//删除缓存, value设为null表示删除
app.cache('test', null)
如果是在云函数中使用缓存,建议把上面的cache方法复制到云函数中使用
有了这缓存的功能,后面就可以用来缓存access_token和ticket了
收起阅读 »
公众号开发(九)公众号IP白名单配置
之前无论是获取openid还是获取用户信息,都是不需要使用普通接口需要的access_token参数的。但后面的接口就需要用到access_token了,而要获取微信的access_token,是需要配置IP白名单的,但我们的uniCloud云函数是运行在不同的容器中,IP地址是变化的,不是固定的。因此要完全免费实现微信公众号功能,目前看来不大可能。
想要有固定IP地址,方法有三个:
一是使用付费云空间
有钱就是任性,直接购买能提供固定IP的付费云空间就好,这个最简单
二是使用宝塔在自己服务器搭建反向代理
针对上面第二种自己有服务器的情况,并且服务器已经安装了宝塔管理系统,可以按下面流程来增加
增加站点
输入你的域名(以weixin-agent.bctos.cn为例),PHP版本选择静态即可
点网站名进入配置界面
进入反向代理--增加代理
目标URL固定填写https://api.weixin.qq.com
,发送域名固定为api.weixin.qq.com
,其它内容随意
保存即可生效
然后把自己服务器的外网IP地址配置到公众号平台(mp.weixin.qq.com)的基本配置页面的IP白名单中
然后后面调用微信接口的网址由https://api.weixin.qq.com/xxx
修改为代理网址http://weixin-agent.bctos.cn/xxx
即可。
如获取access_token的网址:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
那么使用代理的网址是:
http://weixin-agent.bctos.cn/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
注意我没有配置代理域名的证书,因此只能使用http,如果你配置好证书,也可以使用https
三是共享代理
如果你没有自己的服务器,专门买一台服务器作代理的话花钱多而且还浪费资源。这时可以选择我们的共享代理。
共享代理就是我们出一台服务器,已经按上面的方法搭好了反向代理,你只需要付一小笔费用,就可以使用我们的服务器作代理了。
要使用共享代理请加微信(bctos-cn)咨询,付费后我们会给你服务器IP地址和代理网址(不是上面演示的网址),你只需要配置到你的公众号上即可,我们会提供长期的技术支持的哦。
扫码加微信
之前无论是获取openid还是获取用户信息,都是不需要使用普通接口需要的access_token参数的。但后面的接口就需要用到access_token了,而要获取微信的access_token,是需要配置IP白名单的,但我们的uniCloud云函数是运行在不同的容器中,IP地址是变化的,不是固定的。因此要完全免费实现微信公众号功能,目前看来不大可能。
想要有固定IP地址,方法有三个:
一是使用付费云空间
有钱就是任性,直接购买能提供固定IP的付费云空间就好,这个最简单
二是使用宝塔在自己服务器搭建反向代理
针对上面第二种自己有服务器的情况,并且服务器已经安装了宝塔管理系统,可以按下面流程来增加
增加站点
输入你的域名(以weixin-agent.bctos.cn为例),PHP版本选择静态即可
点网站名进入配置界面
进入反向代理--增加代理
目标URL固定填写https://api.weixin.qq.com
,发送域名固定为api.weixin.qq.com
,其它内容随意
保存即可生效
然后把自己服务器的外网IP地址配置到公众号平台(mp.weixin.qq.com)的基本配置页面的IP白名单中
然后后面调用微信接口的网址由https://api.weixin.qq.com/xxx
修改为代理网址http://weixin-agent.bctos.cn/xxx
即可。
如获取access_token的网址:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
那么使用代理的网址是:
http://weixin-agent.bctos.cn/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
注意我没有配置代理域名的证书,因此只能使用http,如果你配置好证书,也可以使用https
三是共享代理
如果你没有自己的服务器,专门买一台服务器作代理的话花钱多而且还浪费资源。这时可以选择我们的共享代理。
共享代理就是我们出一台服务器,已经按上面的方法搭好了反向代理,你只需要付一小笔费用,就可以使用我们的服务器作代理了。
要使用共享代理请加微信(bctos-cn)咨询,付费后我们会给你服务器IP地址和代理网址(不是上面演示的网址),你只需要配置到你的公众号上即可,我们会提供长期的技术支持的哦。
扫码加微信

公众号开发(八)从微信中获取用户昵称头像
上一节我们使用openid完成了注册和登录,但uni-id里的用户信息都是空的,这一节我们将实现在微信中获取用户昵称头像。
类似获取openid流程,获取用户信息也是需要跳转页面的,并且还需要用户确认授权才能获取到信息
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
代码已加详细说明
async getWeixinUserInfo() {
//如果缓存存在,则直接返回
let user = uni.getStorageSync('wx-user')
if (user) return user
//本地无法跳转,不能获取user
if (window.location.href.indexOf('localhost') != -1) return false;
//先取uid,保存获取用户信息之前已经通过openid完成注册登录
let uid = await this.getUid()
if (!uid) return false;
//先读取公众号配置
let config = await this.bctosCommon('config')
let appid = config.result.appId
let secret = config.result.appSecret
//获取微信跳转带回来的参数
let option = this.getGetParam()
if (typeof(option.state) == "undefined" || option.state != 'bctos') { //第一步:用户同意授权,获取code
let redirect_uri = encodeURIComponent(location.href.split('#')[0])
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/' +
`authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=bctos#wechat_redirect`
} else if (typeof(option.state) != "undefined" && option.state == 'bctos') {
//第二步:通过code换取网页授权access_token
if (!typeof(option.code) || option.code == '') {
this.error('获取code失败')
} else {
//前端不能跨域请求微信服务器,只能通过云函数代请求, 获取openid和access_token
let res = await this.bctosCommon('jsonp', {
url: 'https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code' +
`&appid=${appid}&secret=${secret}&code=${option.code}`
})
let data = res.result.data
if (typeof(data) != "object" || typeof(data.openid) == "undefined") {
this.error(JSON.stringify(data))
} else {
uni.setStorageSync('openid', data.openid)
//获取用户信息
res = await this.bctosCommon('jsonp', {
url: `https://api.weixin.qq.com/sns/userinfo?access_token=${data.access_token}&openid=${data.openid}&lang=zh_CN`
})
if (typeof(res.result.data) != "object" || typeof(res.result.data.openid) == "undefined") {
this.error(JSON.stringify(res.result.data))
} else {
//获取的结果缓存在前端
uni.setStorageSync('wx-user', res.result.data)
//获取的结果保存到uni-id的用户表中
this.bctosCommon('saveUserInfo', {
uid,
...res.result.data
})
return res.result.data
}
}
}
}
return false
}
虽然它调用它后端云函数jsonp,但它只是一个代获取远程信息的作用,代码如下
if (!event.method) {
event.method = 'GET'
}
if (!event.data) {
event.data = {}
}
if (!event.dataType) {
event.dataType = 'json'
}
res = await uniCloud.httpclient.request(event.url, {
method: event.method,
data: event.data,
dataType: event.dataType
})
因为如果直接在前端使用uni.request请求的话,会报跨域错误,只能使用后端uniCloud.httpclient.request方法来请求。
得到结果后保存到uni-id的代码如下
let save = {
uid: event.uid,
wx_openid: event.openid,
nickname: event.nickname,
gender: event.sex,
province: event.province,
city: event.city,
country: event.country,
avatar: event.headimgurl
}
if (typeof(event.unionid) != "undefined") save.wx_unionid = event.unionid
res = await uniID.updateUser(save)
其中国家(country),省(province),市(city)这三个字段有uni-id的用户表结构中是没有定义的,但不影响数据的保存
上一节我们使用openid完成了注册和登录,但uni-id里的用户信息都是空的,这一节我们将实现在微信中获取用户昵称头像。
类似获取openid流程,获取用户信息也是需要跳转页面的,并且还需要用户确认授权才能获取到信息
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
代码已加详细说明
async getWeixinUserInfo() {
//如果缓存存在,则直接返回
let user = uni.getStorageSync('wx-user')
if (user) return user
//本地无法跳转,不能获取user
if (window.location.href.indexOf('localhost') != -1) return false;
//先取uid,保存获取用户信息之前已经通过openid完成注册登录
let uid = await this.getUid()
if (!uid) return false;
//先读取公众号配置
let config = await this.bctosCommon('config')
let appid = config.result.appId
let secret = config.result.appSecret
//获取微信跳转带回来的参数
let option = this.getGetParam()
if (typeof(option.state) == "undefined" || option.state != 'bctos') { //第一步:用户同意授权,获取code
let redirect_uri = encodeURIComponent(location.href.split('#')[0])
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/' +
`authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=bctos#wechat_redirect`
} else if (typeof(option.state) != "undefined" && option.state == 'bctos') {
//第二步:通过code换取网页授权access_token
if (!typeof(option.code) || option.code == '') {
this.error('获取code失败')
} else {
//前端不能跨域请求微信服务器,只能通过云函数代请求, 获取openid和access_token
let res = await this.bctosCommon('jsonp', {
url: 'https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code' +
`&appid=${appid}&secret=${secret}&code=${option.code}`
})
let data = res.result.data
if (typeof(data) != "object" || typeof(data.openid) == "undefined") {
this.error(JSON.stringify(data))
} else {
uni.setStorageSync('openid', data.openid)
//获取用户信息
res = await this.bctosCommon('jsonp', {
url: `https://api.weixin.qq.com/sns/userinfo?access_token=${data.access_token}&openid=${data.openid}&lang=zh_CN`
})
if (typeof(res.result.data) != "object" || typeof(res.result.data.openid) == "undefined") {
this.error(JSON.stringify(res.result.data))
} else {
//获取的结果缓存在前端
uni.setStorageSync('wx-user', res.result.data)
//获取的结果保存到uni-id的用户表中
this.bctosCommon('saveUserInfo', {
uid,
...res.result.data
})
return res.result.data
}
}
}
}
return false
}
虽然它调用它后端云函数jsonp,但它只是一个代获取远程信息的作用,代码如下
if (!event.method) {
event.method = 'GET'
}
if (!event.data) {
event.data = {}
}
if (!event.dataType) {
event.dataType = 'json'
}
res = await uniCloud.httpclient.request(event.url, {
method: event.method,
data: event.data,
dataType: event.dataType
})
因为如果直接在前端使用uni.request请求的话,会报跨域错误,只能使用后端uniCloud.httpclient.request方法来请求。
得到结果后保存到uni-id的代码如下
let save = {
uid: event.uid,
wx_openid: event.openid,
nickname: event.nickname,
gender: event.sex,
province: event.province,
city: event.city,
country: event.country,
avatar: event.headimgurl
}
if (typeof(event.unionid) != "undefined") save.wx_unionid = event.unionid
res = await uniID.updateUser(save)
其中国家(country),省(province),市(city)这三个字段有uni-id的用户表结构中是没有定义的,但不影响数据的保存
收起阅读 »
公众号开发(七)uni-id自动注册和登录
在上一节我们得到openid之后,就可以在uni-id绑定用户,实现注册和登录功能了。
前端比较简单,只需要把openid提交给后端,由后端操作uni-id实现注册和登录,得到用户ID后缓存起来供后面的使用
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async getUid() {
//如果缓存存在,则直接返回
let user = uniCloud.getCurrentUserInfo()
console.log('user', user);
if (user.uid) return user.uid
//如果openid不存在,自动先去获取openid
let openid = await this.getOpenid()
console.log('openid', openid);
if (!openid) return false
//后端使用openid实现自动注册和登录功能
let res = await this.bctosCommon('loginOrRegister', {
openid
})
console.log('get res', res)
if (res.result.code === 0) {
//返回成功,缓存uni-id相关信息
uni.setStorageSync('uni_id_token', res.result.token)
uni.setStorageSync('uni_id_token_expired', res.result.tokenExpired)
uni.setStorageSync('uid', res.result.uid)
return res.result.uid
} else {
this.error(res.result.message)
return false
}
}
后面的实现代码,直接使用openid作为用户名和密码进行注册,当然这个开发者也可以加点其它信息让密码复杂些。
let param = {
username: event.openid,
password: event.openid
}
res = await uniID.login({
...param,
queryField: ['username']
})
if (res.code == 10101 || res.code == 10002) {
console.log('need register')
param.wx_openid = event.openid
if (typeof(event.unionid) != "undefined") param.wx_unionid = event.unionid
res = await uniID.register(param)
}
它先使用uniID.login登录,如果错误提示码为10101或10002表示用户不存在,就先使用uniID.register完成注册和登录
这就是openid与用户ID实现绑定的逻辑,比较简单。
在上一节我们得到openid之后,就可以在uni-id绑定用户,实现注册和登录功能了。
前端比较简单,只需要把openid提交给后端,由后端操作uni-id实现注册和登录,得到用户ID后缓存起来供后面的使用
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
async getUid() {
//如果缓存存在,则直接返回
let user = uniCloud.getCurrentUserInfo()
console.log('user', user);
if (user.uid) return user.uid
//如果openid不存在,自动先去获取openid
let openid = await this.getOpenid()
console.log('openid', openid);
if (!openid) return false
//后端使用openid实现自动注册和登录功能
let res = await this.bctosCommon('loginOrRegister', {
openid
})
console.log('get res', res)
if (res.result.code === 0) {
//返回成功,缓存uni-id相关信息
uni.setStorageSync('uni_id_token', res.result.token)
uni.setStorageSync('uni_id_token_expired', res.result.tokenExpired)
uni.setStorageSync('uid', res.result.uid)
return res.result.uid
} else {
this.error(res.result.message)
return false
}
}
后面的实现代码,直接使用openid作为用户名和密码进行注册,当然这个开发者也可以加点其它信息让密码复杂些。
let param = {
username: event.openid,
password: event.openid
}
res = await uniID.login({
...param,
queryField: ['username']
})
if (res.code == 10101 || res.code == 10002) {
console.log('need register')
param.wx_openid = event.openid
if (typeof(event.unionid) != "undefined") param.wx_unionid = event.unionid
res = await uniID.register(param)
}
它先使用uniID.login登录,如果错误提示码为10101或10002表示用户不存在,就先使用uniID.register完成注册和登录
这就是openid与用户ID实现绑定的逻辑,比较简单。
收起阅读 »
公众号开发(六)获取微信公众号的用户openid
从现在开始正式进入公众号H5开发,首先需要先获取用户openid。
由于获取openid需要先跳转到微信然后带参数跳转回来,因此只能在前端实现openid的获取。
实现获取openid代码
可能以后很多页面都需要获取openid,因此把获取方法直接写在根目录的App.vue文件的methods里
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
代码已经加了详细的说明
methods: {
async getOpenid() {
//如果缓存存在,则直接返回
let openid = uni.getStorageSync('openid')
if (openid) return openid
//本地无法跳转,不能获取openid
if (window.location.href.indexOf('localhost') != -1) return false;
//先读取公众号配置
let config = await this.bctosCommon('config')
let appid = config.result.appId
let secret = config.result.appSecret
//获取微信跳转带回来的参数
let option = this.getGetParam()
if (typeof(option.state) == "undefined" || option.state != 'bctos') {
//第一步:用户同意授权,获取code
let redirect_uri = encodeURIComponent(location.href.split('#')[0])
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/'
`authorize?appid=${appid}
从现在开始正式进入公众号H5开发,首先需要先获取用户openid。
由于获取openid需要先跳转到微信然后带参数跳转回来,因此只能在前端实现openid的获取。
实现获取openid代码
可能以后很多页面都需要获取openid,因此把获取方法直接写在根目录的App.vue文件的methods里
完整的代码请在官方插件市场下载:https://ext.dcloud.net.cn/plugin?id=4829
代码已经加了详细的说明
methods: {
async getOpenid() {
//如果缓存存在,则直接返回
let openid = uni.getStorageSync('openid')
if (openid) return openid
//本地无法跳转,不能获取openid
if (window.location.href.indexOf('localhost') != -1) return false;
//先读取公众号配置
let config = await this.bctosCommon('config')
let appid = config.result.appId
let secret = config.result.appSecret
//获取微信跳转带回来的参数
let option = this.getGetParam()
if (typeof(option.state) == "undefined" || option.state != 'bctos') {
//第一步:用户同意授权,获取code
let redirect_uri = encodeURIComponent(location.href.split('#')[0])
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/'
`authorize?appid=${appid}
收起阅读 »