记录微信小程序介入web-view时实现扫一扫功能
一、官方文档: web-view | 微信开放文档 概述 | 微信开放文档
二、准备工作
1、开发使用者必须是已认证的服务号,服务号绑定“JS接口安全域名”下的网页可使用此标签跳转任意合法合规的小程序。
2、必须部署到正式服务器。=
3、必须是在已认证的服务号中做JS接口安全域名验证。
4、微信公众号,需要公众号的appid、appsecret
三、开发流程
1、微信公众号--设置与开发--公众号设置--功能设置--js接口安全域名--添加项目域名
微信公众号--设置与开发--基本配置--获取appid、appsecret,添加ip访问白名单
2、后台用到的jar包
import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.servlet.http.HttpServletRequest; import net.hjyzg.util.DateUtil; //自己写的关于时间判断的工具类 import net.sf.json.JSONException; import net.sf.json.JSONObject; import net.shopxx.Setting; //自己项目的一些基础配置信息 import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.protocol.HTTP; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
3、基础类
发送https的工具类
/** * 发送https请求 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(ssf); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod(requestMethod); // 当outputStr不为null时向输出流写数据 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意编码格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 释放资源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("连接超时:{}", ce); } catch (Exception e) { log.error("https请求异常:{}", e); } return jsonObject; }
AccessToken 类 public class AccessToken { // 获取到的凭证 public static String token; //过期时间 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") public static Date expiresTime; }
JsapiTicket 类 public class JsapiTicket { // 获取到的凭证 public static String ticket; //过期时间 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") public static Date expiresTime; }
4、获取微信信息的基础方法
获取Access token
//access_token凭证获取(GET) public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/** * 获取接口访问凭证 * * @param appid 凭证 * @param appsecret 密钥 * @return * @throws ParseException */ public static String getToken(String appid, String appsecret) throws ParseException { String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 String token = ""; Date expiresTime = AccessToken.expiresTime; //判断是否过期,防止获取Access token信息几秒后过期,判断在过期前半小时内就重新获取一次 if(expiresTime == null || DateUtil.differenceMinute(expiresTime,new Date()) < 30){ // 发起GET请求获取凭证 JSONObject jsonObject = httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { AccessToken.token = jsonObject.getString("access_token"); String expiresIn = jsonObject.getString("expires_in"); expiresTime =df.parse(DateUtil.getNewTimeByMinute(df.format(new Date()), Integer.valueOf(expiresIn)/3600)); AccessToken.expiresTime = expiresTime; token = AccessToken.token; } catch (JSONException e) { // 获取token失败 log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); } } } else { token = AccessToken.token; } return token; }
获取jsapi_ticket
//获取jsapi_ticket public final static String ticket_url= "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
/** * 获取ticket * @param access_token * @return * @throws ParseException */ public static String getTicket(String access_token) throws ParseException { String requestUrl = ticket_url.replace("ACCESS_TOKEN", access_token); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 String ticket = ""; Date expiresTime = JsapiTicket.expiresTime; //判断是否过期,防止获取Access token信息几秒后过期,判断在过期前半小时内就重新获取一次 if(expiresTime == null || DateUtil.differenceMinute(expiresTime,new Date()) < 30){ // 发起GET请求获取凭证 JSONObject jsonObject = httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { JsapiTicket.ticket = jsonObject.getString("ticket"); String expiresIn = jsonObject.getString("expires_in"); expiresTime =df.parse(DateUtil.getNewTimeByMinute(df.format(new Date()), Integer.valueOf(expiresIn)/3600)); JsapiTicket.expiresTime = expiresTime; ticket = JsapiTicket.ticket; } catch (JSONException e) { // 获取token失败 log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); } } } else { ticket = JsapiTicket.ticket; } return ticket; }
获取签名 概述 | 微信开放文档
/** * 获取SHA1签名 * @param timestamp 时间戳 * @param nonceStr 随机字符串 * @param url 访问的url * @return * @throws ParseException */ public static String getSignature(String timestamp,String nonceStr,String url) throws ParseException { String jsapi_ticket = JsapiTicket.ticket; String str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url="+url; String signature = DigestUtils.sha1Hex(str); return signature; }
签名校验工具
5、获取微信信息,返回前端
String appId = "公众号的appId"; String appSecret = "公众号的appSecret "; String timestamp = String.valueOf(new Date().getTime()).substring(0,10);//10位 String nonceStr = "huijiayunzhigou"; String signature = ""; try { String accessToken = WxCommonUtil.getToken(appId,appSecret); String ticket = WxCommonUtil.getTicket(accessToken); String url = setting.getSiteUrl()+"/member/index"; //当前网页的URL,不包含#及其后面部分,域名访问路径 signature = WxCommonUtil.getSignature(timestamp,nonceStr,url); } catch (ParseException e) { e.printStackTrace(); } model.addAttribute("appId",appId); model.addAttribute("timestamp",timestamp); model.addAttribute("nonceStr",nonceStr); model.addAttribute("signature",signature);
6、前端代码
<script src="http://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script> 也可以是<script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '"${appId}", // 必填,公众号的唯一标识
timestamp: "${timestamp}", // 必填,生成签名的时间戳
nonceStr: '"${noncestr}", // 必填,生成签名的随机串
signature: "${signnature}",// 必填,签名
jsApiList: ["checkJsApi","scanQRCode"] // 必填,需要使用的JS接口列表
});
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
alert(JSON.stringify(res));
});
wx.checkJsApi({
jsApiList: ['chooseImage'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function(res) {
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
}
});
//点击扫一扫 function scan(){ wx.scanQRCode({ needResult: 1,// 默认为0,扫描结果由微信处理,1则直接返回扫描结果, scanType: ["qrCode", "barCode"],// 可以指定扫二维码还是条形码 success: function (res) { alert(JSON.stringify(res)) }, fail: function (res) { alert(JSON.stringify(res)) } }) }
7、注意事项
公众号配置js接口安全域名和ip白名单
js引入版本jweixin-1.3.2.js
使用公众号的appid、appsecret
access_token不是微信网页授权得到的那个access_token,而是公众号的全局唯一接口调用凭据的access_token
签名的url为当前网页的URL,不包含#及其后面部分,域名访问路径
签名的时间戳是10位
config中的时间戳要和生成签名是的时间戳保持一致