微信公众号中实现获取openid与调用扫一扫功能
环境:微信公众号,vue,脚手架,vuex(可以不用)。
问题:history模式下ios端要同时获取openid与调用微信扫一扫功能非常麻烦。建议将模式修改成hash。
需要提前处理的问题:1,在微信公众平台进行服务器配置,2,配置JS接口安全域名和网页授权域名,3,配置IP白名单。
关键词:appid,appsecret,code,redirect_uri,openid,扫一扫。
使用hash路由模式的情况下,这种方式在微信公众号里边要同时获取微信openid和调用扫一扫功能还是比较简单的。下面是我解决问题的思路。
一,首先是获取用户的openid
第一步:需要在微信公众号首页根据微信提供的请求url获取当前用户的code信息。大概实现逻辑就是,先通过微信提供的url进行信息配置。其中redirect_uri是向微信发起请求之后需要跳转的地址(最好设为当前页),oppid微信公众平台提供给你的oppid。redirect_uri本案例配置的是首页地址,也就是进入首页之后根据location.href获取首页的url地址。然后进行如下配置(特别注意的是url需要encodeURIComponent处理)
var redirect_uri= location.href;
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx77845299abcf81f1&redirect_uri=' encodeURIComponent(redirect_uri) + '&response_type=code&scope=snsapi_base&state=0#wechat_redirect。
第二步:通过上面的配置地址向微信发起请求(此请求必须是要在微信客户端发起,否则会提示你需要在微信客户端打此开连接),请求发出后微信服务器会根据当前用户信息生成一个code的参数附加在你设置的跳转地址里边。我设置的跳转页就是首页,所以会刷新首页,然后就可以在刷新后的页面地址里边拿到code信息。
第三步:拿到code信息后便可以向后端发起获取用户openid的请求了(其中oppId,appsecret就是你微信公众平台给你的):
后台代码如下 :
/**
* 根据code 获取用户openID
*/
public BaseResp getUserOpenId(String code) {
BaseResp resp = new BaseResp();
String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";;
String params = "?appid=" + this.oppId + "&secret=" + this.appsecret + "&code=" + code
+ "&grant_type=authorization_code";
String result = this.httpGet(requestUrl + params);
log.info("################h5获取当前用户信息:" + JSON.toJSONString(result));
String openid = JSONObject.parseObject(result).getString("openid");
resp.setCode(MessageEnum.SUCCESS.toString());
resp.setMsg(openid);// 当前用户openid
return resp;
}
前端代码如下(可以直接copy使用)
methods: {
getOpenId: function() {
var this_ = this;
//第一步从地址中获取code
var access_code = this_.getQueryString('code');
if (access_code == null) {
var fromurl = location.href; // 获取授权code的回调地址,获取到code,直接返回到当前页
var url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=appid&redirect_uri=' +
encodeURIComponent(fromurl) +
'&response_type=code&scope=snsapi_base&state=0#wechat_redirect';
location.href = url;
} else {
let api = this_.requestUrl+"/wx/open/getOpenId?code=" + access_code;
this.$axios.get(api).then((response) => {
this_.openId = response.data.msg;
//
this.$store.commit('updateOpenId',response.data.msg)
}).catch(function(error) {
alert(error);
});
}
},
getQueryString: function(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = location.search.substr(1).match(reg);
if (r != null)
return unescape(decodeURI(r[2]));
return null;
},
},
created() {
let openId=this.$store.state.globalOpenId;
if(openId==null){
this.getOpenId();
}
}
这样就完成在微信公众号里边获取用户的openid了。
二,实现调用微信扫一扫
关键字:jsapi_ticket
逻辑:微信公众号在调用扫一扫功能之前需要对当前页授权才行。所以后端根据当前页的url和后端获取其他一系列的参数生成一个signature的签名,用于做前端授权验证,只要验证通过了就可以调用扫一扫功能了(官方文档说明)。
第一步:首先获取当前页的url,将url使用encodeURIComponent处理。
第二步:根据当前页的url从服务器端异步获取授权页配置信息(后端实现需要通过微信公众号的openid与secret获取access_token,再根据access_token获取jsApi-ticket票据;然后再根据票据,noncestr,timestamp,和前端传入的url进行sha-1加密处理得到授权页需要的signature验证信息;最后将微信公众号oppid和生成的timestamp,nonceStr,signature返回给前端;前端获取到这些信息后做如下图配置就可以。。。这一步稍微有一点繁琐,但是都是后端实现,前端不用管,在附录一中我会将后端实现代码粘出来)。
第三步:配置需要调用的控件,下面我配置的是scanQRCode扫一扫。
created: function() {
//alert(this.$route.query.openId)
this.openId = this.$store.state.globalOpenId;//this.$route.query.openId;
let url=encodeURIComponent(location.href.split('#')[0]);
this.$axios({
method: 'post',
url: this.requestUrl + '/wx/open/getSign?url=' + url,
}).then(function(response) {
var data = response.data;
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.appId, // 必填,公众号的唯一标识
timestamp: data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature, // 必填,签名,见附录1
jsApiList: ['scanQRCode']
});
});
}
第四步:如果上一步验证成功,那么接下来就直接调用下面定义的函数就可以打开扫一扫了。
openScan: function() { //扫一扫
var _this = this
wx.scanQRCode({
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: function(res) {
var msg = res.resultStr;
}
});
},
附录一
需要用到的url
public static final String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
public static final String token_url = "https://api.weixin.qq.com/cgi-bin/token";
public static final String getticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
public static final String template_url = "https://api.weixin.qq.com/cgi-bin/message/template/send";
获取签名
/**
* 获取sign签名
*
* @return
*/
public Map sign(HttpServletRequest request, HttpServletResponse response) {
// HttpServletRequest request = ServletActionContext.getRequest();
Map ret = new HashMap();
String url = request.getParameter("url");
String jsapi_ticket = getJsapiTicket(request, response);
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
int length = url.indexOf("#");
String uri = url;
if (length > 0) {
uri = url.substring(0, length);// 当前网页的URL,不包含#及其后面部分
}
// 注意顺序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url;
log.info("string1=" + string1);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ret.put("appId", this.oppId);
ret.put("url", uri);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
System.out.println(signature);
// this.setJsonString(JSON.toJSONString(ret));
log.info("获取sign结果:" + JSON.toJSONString(ret));
return ret;
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
/**
* 发送get请求
*
* @param url
* 路径
* @return
*/
public static String httpGet(String url) {
String strResult = null;
try {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() == org.apache.http.HttpStatus.SC_OK) {
strResult = EntityUtils.toString(response.getEntity());
} else {
log.info("get请求提交失败:" + url);
}
} catch (IOException e) {
log.info("请求异常:" + e.getMessage());
}
return strResult;
}
获取access_token
public String getWxToken() {//每次获取新最新token
// 当过期时间超过两小时,则重新获取新的access_token
String requestUrl = WxOpenBusi.token_url;
String params = "?grant_type=client_credential&appid=" + this.oppId + "&secret=" + this.appsecret + "";
String result = WxOpenBusi.httpGet(requestUrl + params);
log.info("################获取微信token结果:" + JSON.toJSONString(result));
String accessToken_t = JSONObject.parseObject(result).getString("access_token");
return accessToken_t;
}
获取临时票据
/**
* 得到jsApi-ticket
*
* @return
*/
@SuppressWarnings({ "static-access", "unused" })
private String getJsapiTicket(HttpServletRequest request, HttpServletResponse response) {
// String code = request.getParameter("code");
// String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?";
// String params = "grant_type=client_credential&appid=" + oppId +
// "&secret=" + appsecret + "";
// String result = this.httpGet(requestUrl + params);
// log.info("################平台获取微信调用接口token结果:" +
// JSON.toJSONString(result));
String accessToken = this.getWxToken();
String jsapi_ticket = WxOpenBusi.jsapi_ticket;
Long curTime = System.currentTimeMillis();
if ((WxOpenBusi.ticket_expiration_time + 7140000) < curTime) {
WxOpenBusi.ticket_expiration_time = System.currentTimeMillis();
String requestUrl = WxOpenBusi.getticket_url;
String params = "?access_token=" + accessToken + "&type=jsapi";
String result = this.httpGet(requestUrl + params);
log.info("################平台调微信jsApi-ticket接口获取ticket结果:" + JSON.toJSONString(result));
jsapi_ticket = JSONObject.parseObject(result).getString("ticket");
WxOpenBusi.jsapi_ticket = jsapi_ticket;
}
return jsapi_ticket;
}
响应给前端的对象
public class BaseResp implements Serializable {
private static final long serialVersionUID = -901432767858826220L;
@Setter
@Getter
private String code;
@Setter
@Getter
private String msg;
@Setter
@Getter
private String status;//预留属性
@Setter
@Getter
private Object data;
到这里实现微信公众号中获取用户oppid和实现调用扫一扫功能的全部代码和实现逻辑就完了。谢谢大家阅读!一年多没有写博客了,若有不足支持还请多多提点。
当然,签名都是基于hash模式下实现的。如果要在history模式下实现的话ios兼容性就很难控制了。我直接最初使用的就是history模式,结果花了一天多去处理ios的兼容问题,结果也很不如意。问题就出在在实现调用扫一扫功能的时候向后台传递的url的问题上,因为这会涉及到多个页面跳转时ios系统对当前页url不像安卓一样只需要用location.href就可以。而是它存在依赖关系。就比如:现在有两个页面A,B。A是首页,B是通过A跳转去的页面。那么这个时候你想要在B页面中实现调用扫一扫功能,你需要传的地址应该是A的地址,而不是当前页B的地址,具体解决方案可以参考微信 jssdk 签名错误 invalid signature。
然而,真正的问题却不是A跳转到B页面能不能实现微信扫一扫的问题,而是在一般情况下我们都会在微信首页中去先获取用户的oppid,然后把他存储起来。
问题就出在我们在获取用户oppid的时候是需要先去获取code参数的,而获取code这个参数就需要刷新当前的页面,就如我最上面说的,会根据微信提供获取code的url配置跳转的地址,而这个跳转地址基本上都是当前页页就是首页,这样就会存在首页需要刷新才可以获取得到code的信息。问题就出在刷新之后,首页A在跳转到B页面去后端获取sign配置参数的时候配置的当前页的url就非常难以控制。。。
如果你不相信你去验证之后就会发现,你传最初进入首页的这个首页地址是不对的,然后你尝试传递首页刷新之后的地址(这时的地址会带有code,state等参数)的时候也不对,最后你只能再尝试一下传递B页面的当前页地址,最后你会发现都不行。。。。这就就很痛苦了吧,它不像安卓那样很简单,你那个页面需要调扫一扫功能你就传那个页面的url就可以了,但是ios只要有页面刷新或者页面跳转就不行。所以最后未来解决这个问题我之后把vuecli的路由模式修改为hash了。
这是我踩的坑,希望大家看到后有所帮助吧。如果大家有什么好的建议希望在评论区留言,我也学习学习,谢谢!