微信小程序接入web-view实现扫一扫功能

夏侯衡
2023-12-01

记录微信小程序介入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 + "&timestamp=" + 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、前端代码

<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中的时间戳要和生成签名是的时间戳保持一致

 类似资料: