
记录微信小程序介入web-view时实现扫一扫功能
一、官方文档: web-view | 微信开放文档 概述 | 微信开放文档
二、准备工作
jssdk需要绑定域名,项目需要能用域名访问
微信开发者工具
微信公众号,需要公众号的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的工具类
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";
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";
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;
}
获取签名 概述 | 微信开放文档
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;
}
签名校验工具
微信 JS 接口签名校验工具
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、前端代码
也可以是
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中的时间戳要和生成签名是的时间戳保持一致