微信公众号中获取openid与调用扫一扫功能

微信公众号中实现获取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 + "&timestamp=" + 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了。

这是我踩的坑,希望大家看到后有所帮助吧。如果大家有什么好的建议希望在评论区留言,我也学习学习,谢谢!

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值