关于项目
之前做的微信网页开发项目,用vue写的,前后端分离项目,使用vue-cli3搭建,路由模式history。客户要求销售员登录后可分享相关文章网页,并且分享标题内容自定,而客户需要授权登录,客户可以转发给客户,并且带上销售员信息。手机微信浏览器,电脑微信浏览器打开可以获取用户信息,记录用户行为等,非微信浏览器下打开,能正常访问网页内容。在手机微信浏览器下选用普通浏览器打开(即带上参数,用普通浏览器访问页面),再用普通浏览器分享给微信好用,打开后仍然可以获取用户信息,记录用户行为(即参数不丢失)。
思路
- 由于销售员是直接输入用户名密码登录,因此在销售员登录后将销售员编号,以及文章编号存入本地;
- 在页面加载,即mounted中,取出本地的销售员编号、文章编号和路径的销售员编号、文章编号,如果本地存在销售员编号则为销售员,反之为客户;
- .用qs.parse()取出路径的参数,并且对浏览器进行判断,在本地没有销售员编号,网页路径没有code,微信浏览器情况下,进行微信授权登录;
- 微信浏览器时进行签名,如果是客户,记录客户行为;
准备
微信网页开发首先得配置,准备好相关的东西,不然根本没法开始,就算写了也没法测试,需要准备什么呢?
- 首先你得申请一个微信公众号,并且进行微信认证
- 一个https的域名,如果确实没有,那也可以先使用内网穿透。关于内网穿透,本人开始使用的是花生壳的内网穿透,坑爹啊,冲了6块钱不说,域名还不能固定。推荐使用sunny-ngrok内网穿透,真心比花生壳好用,而且可以免费用一个
- 微信公众号配置,到微信公众号设置——功能设置里面配置js安全域名,以及回调域名。到基本配置里面配置ip白名单(ip白名单如果不知道,可以先不配)。
- 微信开发者工具,切换到微信网页开发模式,将项目部署域名粘贴到地址栏中,可以用来模拟微信浏览器调试微信签名,授权,分享等微信接口
上代码
这是分享授权的代码,其他省略,另外this.$globalMethods是本人的一个公用文件,一些公用的方法,url,数据等都存在这里面,为使代码更简洁,使用了async,await等ES6以上的语法,不熟悉ES6语法的可自行百度
项目使用了vuex,并对ajax请求进行了封装,相关代码省略
import {
mapState} from 'vuex'
import wx from 'weixin-js-sdk'
import qs from 'qs'
import {
subNewsMyForwardsRecord,saveUserScanTime,getOpenid,reqNewsDetail,getConfigData,addUserScanTimeCount} from '../../api'
data(){
return{
newsList:{
},
salesnum:'',//销售员编号
newsDetail:[],//新闻详情
}
},
async mounted() {
document.title='资讯详情'
let cookiSalesnum = localStorage.getItem('salesnum')//获取本地缓存的销售员编号
let cookiConnum=localStorage.getItem('connum')//获取本地缓存的文章编号
let windowUrl=window.location.href
windowUrl=decodeURIComponent(windowUrl)
let paramsStr=windowUrl.split('?')[1]
let paramsObj=qs.parse(paramsStr)//使用qs库解析路径
let {
connum,salesnum,code}=paramsObj
salesnum=salesnum || cookiSalesnum
connum=connum || cookiConnum
let {
appid,redirect_uri} = this.$globalMethods.wxPublicData()
let iswx=this.isWeixin()
if(!cookiSalesnum && !code && iswx){
/*没有销售员编号,没有code,微信浏览器,进入的是别人分享的页面需要授权登录*/
redirect_uri= `${
redirect_uri}/newsdetail?connum=${
connum}&salesnum=${
salesnum}`
redirect_uri=encodeURIComponent(redirect_uri)//此处必须要转码
window.location.href=`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${
appid}&redirect_uri=${
redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
return
}
let newsDetail=await reqNewsDetail(connum)//获取资讯详情
this.newsDetail=newsDetail//将资讯详情渲染到页面
iswx && this.wxSignFun({
windowUrl,appid,salesnum,connum,newsDetail,cookiSalesnum})//微信浏览器下,签名
cookiSalesnum && this.$store.dispatch('getMyCardInfo',{
salesnum})//销售员,显示销售员信息
if(!cookiSalesnum && iswx){
// 不是销售员,微信浏览器,获取用户信息,记录用户行为
let openidResult=await getOpenid(code,salesnum)
let openid=openidResult.openid
/*websocket计时*/
let websocketUrl=this.$globalMethods.getWbsocketUrl()
let ws = new WebSocket(websocketUrl)
ws.onopen = ()=> {
addUserScanTimeCount(salesnum,openid,connum)
}
}
},
methods:{
async wxSignFun(params){
let {
windowUrl,appid,salesnum,connum,newsDetail,cookiSalesnum }=params
let congigResult=await getConfigData(windowUrl)
let {
nonceStr,signature,timestamp}=congigResult
wx.config({
debug:false,
appId:appid,
nonceStr,
signature,
timestamp,
jsApiList: [
'onMenuShareAppMessage', //分享到朋友接口,旧接口,即将废弃
'updateTimelineShareData', // 分享到朋友接口,新接口
'checkJsApi',
]
})
//处理验证失败的信息
wx.error(function (res) {
alert('微信签名失败')
})
let desc=this.ToText(newsDetail.context).substring(0,20)
let link=`${
windowUrl}?connum=${
connum}&salesnum=${
salesnum}`
let shareData={
title: newsDetail.title, // 分享标题
desc, // 分享描述
link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: this.$globalMethods.getBaseUrl_router() + '/logo.png', // 分享图标
type: 'link', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function (res) {
//签名成功,记录用户行为
;( res.errMsg==='sendAppMessage:ok') && !cookiSalesnum && subNewsMyForwardsRecord(salesnum,connum)
},
}
wx.ready(result=>{
wx.onMenuShareAppMessage(shareData)
wx.updateTimelineShareData(shareData)
wx.checkJsApi({
jsApiList: [
'onMenuShareAppMessage',
'updateTimelineShareData',
],
success: function (res) {
//alert('分享成功')
console.log(res)
}
})
})
},
//富文本数据转纯文本函数
ToText(HTML){
var input = HTML;
return input.replace(/<(style|script|iframe)[^>]*?>[\s\S]+?<\/\1\s*>/gi,'').replace(/<[^>]+?>/g,'').replace(/\s+/g,' ').replace(/ /g,' ').replace(/>/g,' ');
},
/*判断是否是微信浏览器*/
isWeixin() {
//window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,这个属性可以用来判断浏览器类型
var ua = window.navigator.userAgent.toLowerCase();
//通过正则表达式匹配ua中是否含有MicroMessenger字符串
console.log(ua.match())//micromessenger
return ua.match(/MicroMessenger/i) == 'micromessenger'
},
}
微信网页开发的坑
微信签名:
微信签名可以说是微信网页开发中最繁琐的了。签名失败的原因有很多。签名成功不是指后端返回了签名信息就成功,而是后端返回了签名信息,并且前端的微信相关接口调用成功,即wx.ready执行,才算是成功
- 微信公众号没有做相关的配置或者配置出错
- ip白名单没有配置,如果在开发者调试工具中报错为某ip不在白名单内,那就把这个ip配到公众号后台白名单即可
- 后端签名算法错误,这里要注意,签名算法加密方式是MD5或HMAC-SHA256。另外排序规则等也容易出错,可仔细阅读微信开发文档。
- 前后端appid不一致,appid不是项目公众号的appid。这其实是一个低级错误,但是又是存在的,有些童鞋手里项目好多个,结果搞错了相关的appid,签名肯定失败
- 签名url错误。注意签名的url一定用window.location.href来取,另外,项目必须部署以后才能测试签名,不管域名是已经申请好的https域名,还是内网穿透的https域名都行,直接在开发工具地址栏输入localhost:8080,签名肯定失败.
- 如果用vue开发,路由模式一定选用history 模式,不可使用hash模式,用hash模式就算成功返回签名信息,分享的时候还是会失败,因为hash模式的地址里带有#号,而微信签名的url是不能有#号的,这会导致分享接口调用失败
- 子页面的签名。本人的项目里面,有多个签名页面,而且是从父级页面跳转到子级页面后才签名,这里有个地方要注意,跳转的时候请用window.location.href来进行子页面的跳转,用this. r o u t e r . r e p l a c e 或 者 t h i s . router.replace或者this. router.replace或者this.router.push等vue的跳转方式,在安卓机签名可以成功,但是在苹果机ios系统下,就会失败,这和ios的路由缓存机制有关
window.location.href=`${
this.$globalMethods.getBaseUrl_router()}/newsdetail`//需要签名的子页面的正确跳转方式
本人亦探索过用vue路由守卫解决ios的这个问题,但最终测试时发现无效,因此放弃
微信授权
微信授权相对来说简单很多,按照微信给定的链接拼接号参数,使用window.location.href跳转即可,需要携带的参数,拼接到rederect_uri上。
redirect_uri= `${
redirect_uri}/newsdetail?connum=${
connum}&salesnum=${
salesnum}`
redirect_uri=encodeURIComponent(redirect_uri)
window.location.href=`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${
appid}&redirect_uri=${
redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
这里要强调的一点是,如果想在分享是携带多个参数,那么redirect_uri必须转码,使用encodeURIComponent来进行转码,如果不转码,无论你在rederect_uri上拼接多少个参数,你会发现最终都只会剩下一个参数(这里容我吐槽下微信,是不是应该改成如果不转码,直接授权登录失败??)
分享页面的标题
由于vue开发是单页面应用,如果想让分享出去的页面单独显示自己的标题,最有效的办法就是在mounted里面写上这句
document.title='你的自定义标题'//
本人踩坑时亦尝试过路由守卫,但是在ios上还是不尽人意,而且还比较麻烦
