赞
踩
最近一直在做微信小程序的项目,使用到了微信扫码的功能。原本可以直接使用小程序提供的api,但是由于涉及到一些其他原因,需要在h5页面中来调用该功能。所以就需要使用微信公众平台提供的JSSDK来调用扫一扫功能。仅以此来记录自己的学习过程,也希望能帮助到有此需求的一些朋友。
过程中参考文章有:https://blog.csdn.net/u011327333/article/details/50439462
https://www.jianshu.com/p/a4aea4d12c23
长话短说,我们先来做一些准备工作!
一、准备工作
官方接口文档:微信JS-SDK说明文档
1,首先看一下说明文档,第一步,绑定域名:
登录公众号 -》公众号设置 -》功能设置 -》JS接口安全域名
由于微信开发中很多地方都用了域名,所以这里提供一个不用阿里云或者腾讯云备案的方法,利用natapp内网穿透,直接将本机设置为服务器并获得域名。填写域名时不要带着http://,例如http://www.baidu.com,只写www.baidu.com就行。
https://natapp.cn/member/dashborad
具体设置方法可以看里面的教程,不过这里需要购买VIP_1型隧道,免费的会被微信屏蔽。
直接映射本地的80端口,然后用nginx做内部转发,就可以访问h5页面或者本地的后台服务。
2,引入JS文件
根据文档说明,在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js、
3,通过config接口注入权限验证配置
这个配置需要在调用扫一扫功能之前就配置好,例如在页面初始化的时候。
其余的在教程中说的很明白,下面我们直接上代码,这里的前后端代码只是写了个demo,真要用到项目中肯定还需要好好封装一下。
我的前端页面与后台服务是前后端分离的,前端页面就是一个单独的html页面,后台服务用的是springboot,都是在本机上启动的,通过nginx做的反向代理,natapp将域名映射到本机80端口,后台服务为8080端口,指定带有某个字符串的请求路由到8080端口去请求后台,而前端页面则直接放到nginx下进行访问。
二、前端页面
需要把当前页面的url传到后台去,生成签名信息时需要使用到,其他信息都可以在后台设置,最终配置的时候config中的各字段与后台生成签名时的字段值能对应起来就行了。
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=0">
- <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"> </script>
- <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
- </head>
- <body>
- <div class="lbox_close wxapi_form">
- <h3>微信扫一扫</h3>
- <input id="codeValue">
- <button id="scanQRCode">扫码</button>
-
- </div>
- <script type="text/javascript">
- $(function() {
- //需要把当前页面的url地址传到后台,生成签名信息时需要使用到
- var tokenUrl= location.href;
- //获取签名的后台接口
- var _getWechatSignUrl = '/orange/jsapi/getSign';
-
- $(document).ready(function(){
- //获取签名
- $.ajax({
- url:_getWechatSignUrl,
- data:{tokenUrl:tokenUrl},
- dataType:"json",
- success:function(res){
- console.log(res);
- //获得签名之后传入配置中进行配置
- wxConfig(res.appId,res.timestamp,res.nonceStr,res.signature);
- }
- })
- })
-
- function wxConfig(_appId,_timestamp, _nonceStr, _signature) {
- console.log('获取数据:' + _timestamp +'\n' + _nonceStr +'\n' + _signature);
- wx.config({
- debug:true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
- appId: _appId,// 必填,公众号的唯一标识
- timestamp: _timestamp,// 必填,生成签名的时间戳
- nonceStr: _nonceStr,// 必填,生成签名的随机串
- signature: _signature,// 必填,签名,见附录1
- jsApiList: ['checkJsApi','scanQRCode']
- // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
- });
- }
-
- $("#scanQRCode").click(function(event){
- wx.scanQRCode({
- desc: 'scanQRCode desc',
- needResult : 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
- scanType : [ "qrCode", "barCode" ], // 可以指定扫二维码还是一维码,默认二者都有
- success : function(res) {
- console.log("调用扫描成功",res);
- var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
- $("#codeValue").val(result);//显示结果
- alert("扫码结果为:" + result);
- },
- error:function(res){
- console.log(res)
- }
- });
- })
-
- });
- </script>
-
- </body>
- </html>

三、后台服务
生成签名和其他信息的controller
- import com.imooc.service.JsApiService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.data.repository.query.Param;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- import javax.servlet.http.HttpServletRequest;
- import java.util.Map;
-
-
- @Controller
- @RequestMapping("/jsapi")
- public class jsApiController {
-
- @Autowired
- @Qualifier("wxJsService")
- private JsApiService jsApiService;
-
- @GetMapping(value = "/getSign")
- @ResponseBody
- public Map<String, String> scanJsApi(@Param("tokenUrl") String tokenUrl, HttpServletRequest request) {
- Map<String, String> res = jsApiService.sign(tokenUrl);
- return res;
- }
- }

处理请求的service
- import com.imooc.common.CacheObjectType;
- import com.imooc.common.Constants;
- import com.imooc.common.RedisOperator;
- import com.imooc.common.WechatUtil;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.stereotype.Component;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.io.UnsupportedEncodingException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.Formatter;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.UUID;
-
- @Component("wxJsService")
- @Transactional
- public class JsApiService{
-
- @Autowired
- private RedisOperator redis;
-
- /**
- * 获取签名
- * @param url
- * @return
- */
- public Map<String, String> sign(String url) {
- Map<String, String> resultMap = new HashMap<>(16);
-
- //这里的jsapi_ticket是获取的jsapi_ticket。
- String jsapiTicket = this.getJsApiTicket();
- //这里签名中的nonceStr要与前端页面config中的nonceStr保持一致,所以这里获取并生成签名之后,还要将其原值传到前端
- String nonceStr = createNonceStr();
- //nonceStr
- String timestamp = createTimestamp();
- String string1;
- String signature = "";
-
- //注意这里参数名必须全部小写,且必须有序
- string1 = "jsapi_ticket=" + jsapiTicket +
- "&noncestr=" + nonceStr +
- "×tamp=" + timestamp +
- "&url=" + url;
- System.out.println("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();
- }
-
- resultMap.put("url", url);
- resultMap.put("jsapi_ticket", jsapiTicket);
- resultMap.put("nonceStr", nonceStr);
- resultMap.put("timestamp", timestamp);
- resultMap.put("signature", signature);
- resultMap.put("appId", Constants.GZH_APPID);
-
- return resultMap;
- }
-
- 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 createNonceStr() {
- return UUID.randomUUID().toString();
- }
-
- private static String createTimestamp() {
- return Long.toString(System.currentTimeMillis() / 1000);
- }
-
- public String getJsApiTicket(){
- CacheObjectType cacheObject = CacheObjectType.WX_TOKEN;
- String ticket = redis.get(cacheObject.getPrefix()+"jsapi_ticket");
- if(StringUtils.isBlank(ticket)){
- ticket = WechatUtil.getJsApiTicket(getToken());
- redis.set(cacheObject.getPrefix()+"jsapi_ticket",ticket,
- cacheObject.getExpiredTime());
- }
- return ticket;
- }
-
- private String getToken(){
- String token = redis.get(Constants.GZH_TOKEN);
- return token;
- }
- }

CacheObjectType
- public enum CacheObjectType {
- VERIFY_CODE("sms:S:code:", 60 * 5),
-
- WX_TOKEN("wx:S:token", 7000);
-
- private String prefix;
- private int expiredTime;
-
- CacheObjectType(String prefix, int expiredTime) {
- this.prefix = prefix;
- this.expiredTime = expiredTime;
- }
-
- public String getPrefix() {
- return prefix;
- }
-
- public int getExpiredTime() {
- return expiredTime;
- }
- }

Constants
- public class Constants {
- //换取ticket的url
- public static final String JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
- //微信公众号token在redis中存储时的key值
- public static final String GZH_TOKEN = "wxgzh-access-token";
- //手动写死一下域名
- public static final String AppDomain = "XXX.natapp1.cc";
- public static final String GZH_APPID = "XXXXXXXXX";
- public static final String GZH_SECURET = "XXXXXXXX";
- }
RedisOperator,token不一定必须存到redis中
-
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.TimeUnit;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.stereotype.Component;
-
- /**
- * @Description: 使用redisTemplate的操作实现类
- */
- @Component
- public class RedisOperator {
-
- // @Autowired
- // private RedisTemplate<String, Object> redisTemplate;
-
- @Autowired
- private StringRedisTemplate redisTemplate;
-
-
- // String(字符串)
-
- /**
- * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
- *
- * @param key
- * @param value
- */
- public void set(String key, String value) {
- redisTemplate.opsForValue().set(key, value);
- }
-
- /**
- * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
- *
- * @param key
- * @param value
- * @param timeout (以秒为单位)
- */
- public void set(String key, String value, long timeout) {
- redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 实现命令:GET key,返回 key所关联的字符串值。
- *
- * @param key
- * @return value
- */
- public String get(String key) {
- return (String) redisTemplate.opsForValue().get(key);
- }
- }

WechatUtil
- import com.alibaba.fastjson.JSONObject;
- import org.apache.commons.lang3.StringUtils;
-
- /**
- * 微信工具类
- */
- public class WechatUtil {
-
- /**
- * 获得jsapi_ticket
- */
- public static String getJsApiTicket(String token) {
- String url = Constants.JSAPI_TICKET
- + "?access_token=" + token
- + "&type=jsapi";
-
- String response = HttpClientUtil.doGet(url);
-
- // WXSessionModel model = JsonUtils.jsonToPojo(response, WXSessionModel.class);
-
- // String response = OkHttpUtil.doGet(url);
- if (StringUtils.isBlank(response)) {
- return null;
- }
- JSONObject jsonObject = JSONObject.parseObject(response);
- System.out.println(response);
- String ticket = jsonObject.getString("ticket");
- return ticket;
- }
-
- }
-

HttpClientUtil
- import java.io.IOException;
- import java.net.URI;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
-
- import org.apache.http.NameValuePair;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.client.utils.URIBuilder;
- import org.apache.http.entity.ContentType;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.impl.client.HttpClients;
- import org.apache.http.message.BasicNameValuePair;
- import org.apache.http.util.EntityUtils;
-
- public class HttpClientUtil {
-
- public static String doGet(String url, Map<String, String> param) {
-
- // 创建Httpclient对象
- CloseableHttpClient httpclient = HttpClients.createDefault();
-
- String resultString = "";
- CloseableHttpResponse response = null;
- try {
- // 创建uri
- URIBuilder builder = new URIBuilder(url);
- if (param != null) {
- for (String key : param.keySet()) {
- builder.addParameter(key, param.get(key));
- }
- }
- URI uri = builder.build();
-
- // 创建http GET请求
- HttpGet httpGet = new HttpGet(uri);
-
- // 执行请求
- response = httpclient.execute(httpGet);
- // 判断返回状态是否为200
- if (response.getStatusLine().getStatusCode() == 200) {
- resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (response != null) {
- response.close();
- }
- httpclient.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return resultString;
- }
-
- public static String doGet(String url) {
- return doGet(url, null);
- }
-
- public static String doPost(String url, Map<String, String> param) {
- // 创建Httpclient对象
- CloseableHttpClient httpClient = HttpClients.createDefault();
- CloseableHttpResponse response = null;
- String resultString = "";
- try {
- // 创建Http Post请求
- HttpPost httpPost = new HttpPost(url);
- // 创建参数列表
- if (param != null) {
- List<NameValuePair> paramList = new ArrayList<>();
- for (String key : param.keySet()) {
- paramList.add(new BasicNameValuePair(key, param.get(key)));
- }
- // 模拟表单
- UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
- httpPost.setEntity(entity);
- }
- // 执行http请求
- response = httpClient.execute(httpPost);
- resultString = EntityUtils.toString(response.getEntity(), "utf-8");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- response.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- return resultString;
- }
-
- public static String doPost(String url) {
- return doPost(url, null);
- }
-
- public static String doPostJson(String url, String json) {
- // 创建Httpclient对象
- CloseableHttpClient httpClient = HttpClients.createDefault();
- CloseableHttpResponse response = null;
- String resultString = "";
- try {
- // 创建Http Post请求
- HttpPost httpPost = new HttpPost(url);
- // 创建请求内容
- StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
- httpPost.setEntity(entity);
- // 执行http请求
- response = httpClient.execute(httpPost);
- resultString = EntityUtils.toString(response.getEntity(), "utf-8");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- response.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- return resultString;
- }
- }

后端代码略多,因为这里使用了一些工具类。
获取jsApiTicket的时候还需要当前公众号的access_token,可以用下面这个方法获取,这里写入redis的时候的key要与获取jsApiTicket时取值的key对应起来。
- /**
- * 获取公众号的access_token
- * @return
- */
- @GetMapping("/getAccessToken")
- public IMoocJSONResult getAccessToken() {
-
- String url = "https://api.weixin.qq.com/cgi-bin/token";
- Map<String, String> param = new HashMap<>(16);
- param.put("grant_type", "client_credential");
- param.put("appid", Constants.GZH_APPID);
- param.put("secret", Constants.GZH_SECURET);
- String wxResult = HttpClientUtil.doGet(url, param);
- System.out.println(wxResult);
-
- WXTokenModel model = JsonUtils.jsonToPojo(wxResult, WXTokenModel.class);
-
- redis.set(Constants.GZH_TOKEN, model.getAccess_token(), Long.parseLong(model.getExpires_in()));
-
- return IMoocJSONResult.ok(model);
- }

WXTokenModel
- public class WXTokenModel {
-
- private String access_token;
- private String expires_in;
- private String errcode;
- private String errmsg;
- public String getAccess_token() {
- return access_token;
- }
- public void setAccess_token(String access_token) {
- this.access_token = access_token;
- }
- public String getExpires_in() {
- return expires_in;
- }
- public void setExpires_in(String expires_in) {
- this.expires_in = expires_in;
- }
- public String getErrcode() {
- return errcode;
- }
- public void setErrcode(String errcode) {
- this.errcode = errcode;
- }
- public String getErrmsg() {
- return errmsg;
- }
- public void setErrmsg(String errmsg) {
- this.errmsg = errmsg;
- }
-
- }

四,效果
在自己的微信中访问前端页面,因为开启了debug模式,所以每一个请求都弹出请求结果,如果不想查看请求结果,可以将
wx.config({
debug:true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
});
中的debug设置为false
弹出config:ok,表示我们签名和配置信息验证成功,点击扫码,扫码成功之后会将结果会先到前面的输入框中。
大功告成!手动耶!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。