现实项目中业务需要开发微信公众号,业务场景其中有需求要使用微信扫一扫并且能够把二维码的值展现出来,微信公众号本身是可以兼容java开发的所以一下的代码都是java为主。

一.想要调用微信中的扫一扫功能,就必须要使用到签名。

二.本篇主要是讲签名的生成规则 并且实现在公众号当中能够正常的调用并且把值展示在页面中。

 

作为一个纯前端来说开发公众号需要后台人员的配合
开发者文档地址(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115)

微信js-sdk demo 

https://www.weixinsxy.com/jssdk/ 手机端打开

1.绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

备注:登录后可在“开发者中心”查看对应的接口权限。

2.引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js

备注:支持使用 AMD/CMD 标准模块加载方法加载

3.后台生成签名,可以在微信公众号中调用扫码功能。

完整的页面代码展示

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@page import="org.jeecgframework.core.util.SysThemesUtil,org.jeecgframework.core.enums.SysThemesEnum"%>
<%@include file="/context/mytags.jsp"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta charset="utf-8" />
<title>扫描二维码</title>
<meta name="viewport"
	content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<meta name="save" content="history">
 <script type="text/javascript" src="<%=basePath%>/plug-in/jquery/jquery-1.8.3.min.js"></script>
 <script src = "<%=basePath%>/webpage/WeChat/js/jweixin-1.2.0.js"></script>
 <script type="text/javascript">
 $(document).ready(function () {
   	 var timestamp = parseInt('${timestamp}');//因为服务端是String类型,此处转化成数值类型
   	 var nonceStr = '${nonceStr}';
   	 var signature = '${signature}';
     wx.config({
         debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
         appId: '自己公众的标识', // 必填,公众号的唯一标识
         timestamp: timestamp, // 必填,生成签名的时间戳    
         nonceStr: nonceStr, // 必填,生成签名的随机串
         signature:signature, // 必填,签名
         jsApiList: ['checkJsApi','scanQRCode'] // 必填,需要使用的JS接口列表, 这里只需要调用扫一扫
     });

    
     $(".test").click(function () {
         var ua = navigator.userAgent.toLowerCase();
         var isWeixin = ua.indexOf('micromessenger') !== -1;
         if (!isWeixin) {
             alert('请用微信打开连接,才可使用扫一扫');
         }

         wx.scanQRCode({
             needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
             scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
             success: function (res) {
                 // 扫码成功,跳转到二维码指定页面(res.resultStr为扫码返回的结果)            
                 //  location.href = res.resultStr;
                 $("#name").val(JSON.stringify(res));
                 var scan = res.resultStr;
                 $("#name2").val(scan);

             },
             error: function (res) {
            	 alert(JSON.stringify(res));
                 if (res.errMsg.indexOf('function_not_exist') > 0) {
                     alert('当前版本过低,请进行升级');
                 }
             }
         });
     });
  });
</script>
</head>
 <body>
 <input type="button" class="test"  value="微信扫一扫"/>
 <input type="text" id="name">
<input type="text" id="name2">
 </body>
</html>

js文件:链接:https://pan.baidu.com/s/1W39FYso8vvc1q0SuGScLYA 
提取码:r01r

签名的生成规则: jsapi_ticket、noncestr、timestamp、url拼接,使用SHA1加密算法,最终生成签名

一. jsapi_ticket:

生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是企业号号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket。

  1. 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8
  2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKE

成功返回如下JSON:

{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}

获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。

1. 获取access_token, 因为一般有效期是7200s,所以添加到了ehcache中,默认先从缓存中获取,失效后再发送GET请求获取:

Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
if (null == act) {

    String url = "https://api.weixin.qq.com/cgi-bin/token";
    String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);

    logger.debug("[jsonStrToken] = " + jsonStrToken);

    JSONObject json = JSONObject.fromObject(jsonStrToken);

    access_token = (String) json.getString("access_token");
    if (access_token == null) {
        return null;
    }
    EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
} else {
    access_token = (String) act;
}

2.根据上一步中的access_token获取jsapi_ticket:

if (null == apiticket) {
    String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
    String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");

    logger.debug("[jsonStrTicket] = " + jsonStrTicket);

    JSONObject json = JSONObject.fromObject(jsonStrTicket);
    ticket = (String) json.get("ticket");

} else {
    ticket = (String) apiticket;
}

 

二.noncestr 获取随机字符串:

private static String create_nonce_str() {
    return UUID.randomUUID().toString();
}

 

三.timestamp

private static String create_timestamp() {
    return Long.toString(System.currentTimeMillis() / 1000);
}

四. url

String url = request.getRequestURL().toString();

 

后台完整代码

springMVC+spring+hibernate 架构项目

每个人的项目配置都有区别这里也本项目为例 配置为了页面跳转。

java电子签章实现原理 电子签章sdk_java电子签章实现原理

 

WeiXin 类

package com.szerp.wechat.util;
import net.sf.json.JSONObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.szerp.wmsreceipt.util.Tools;


@Component("WeiXin")
public class WeiXin {
	
	public static String appId = "自己公众号ip";
    public static String appSecret = "自己公众号应用秘钥";

    public static Log logger = LogFactory.getLog(WeiXin.class);

    public  static String  getWeiXinTicket() throws Exception {
        String access_token = "";
        String ticket = "";
        Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
        Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
        logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
        if (null == act) {

            String url = "https://api.weixin.qq.com/cgi-bin/token";
            String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);

            logger.debug("[jsonStrToken] = " + jsonStrToken);

            JSONObject json = JSONObject.fromObject(jsonStrToken);

            access_token = (String) json.getString("access_token");
            if (access_token == null) {
                return null;
            }
            EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
        } else {
            access_token = (String) act;
        }

        if (null == apiticket) {
            String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
            String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");

            logger.debug("[jsonStrTicket] = " + jsonStrTicket);

            JSONObject json = JSONObject.fromObject(jsonStrTicket);
            ticket = (String) json.get("ticket");

        } else {
            ticket = (String) apiticket;
        }

        return ticket;
        // 断开连接
    }
}

 

 

CurrencyUtil 通用工具类

package com.szerp.wechat.util;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSONObject;

/**
 * 微信公众号业务通用工具类
 * @author Administrator
 *
 */
public class CurrencyUtil {

	
	public static JSONObject doGetStr(String url) {
		DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
		HttpGet httpGet = new HttpGet(url);
		JSONObject jsonObject = null;
		try {
			HttpResponse response = defaultHttpClient.execute(httpGet);
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				String result = EntityUtils.toString(entity, "UTF-8");
				jsonObject = JSONObject.parseObject(result);
			}
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return jsonObject;
	}
}

 

 

EhcacheUtil 缓存工具类

package com.szerp.wechat.util;

import java.net.URL;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class EhcacheUtil {
    private static final String path = "/ehcache.xml";  
    
    private URL url;  
  
    private CacheManager manager;  
  
    private static EhcacheUtil ehCache;  
  
    private EhcacheUtil(String path) {  
        url = getClass().getResource(path);  
        manager = CacheManager.create(url);  
    }  
  
    public static EhcacheUtil getInstance() {  
        if (ehCache== null) {  
            ehCache= new EhcacheUtil(path);  
        }  
        return ehCache;  
    }  
  
    public void put(String cacheName, String key, Object value) {  
        Cache cache = manager.getCache(cacheName);  
        Element element = new Element(key, value);  
        cache.put(element);  
    }  
  
    public Object get(String cacheName, String key) {  
        Cache cache = manager.getCache(cacheName);  
        Element element = cache.get(key);  
        return element == null ? null : element.getObjectValue();  
    }  
  
    public Cache get(String cacheName) {  
        return manager.getCache(cacheName);  
    }  
  
    public void remove(String cacheName, String key) {  
        Cache cache = manager.getCache(cacheName);  
        cache.remove(key);  
    }  
}

ehcache.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false">

	<diskStore path="java.io.tmpdir" />

	<!-- Cluster localhost setting -->
	<cacheManagerPeerProviderFactory
		class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
	    properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
    	multicastGroupPort=4446, timeToLive=32"/>
	
	<cacheManagerPeerListenerFactory
		class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
		properties="hostName=localhost, port=40001,socketTimeoutMillis=2000" />


	<cache name="dictCache" maxElementsInMemory="500" overflowToDisk="true"
		eternal="true">
		<cacheEventListenerFactory
			class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
			properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
	</cache>

	<cache name="eternalCache" maxElementsInMemory="500"
		overflowToDisk="true" eternal="false" timeToIdleSeconds="1200"
		timeToLiveSeconds="1200">
		<cacheEventListenerFactory
			class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
			properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
	</cache>
	<!-- DefaultCache setting. Modify ehcache-safe.xml for timeToIdleSeconds,timeToLiveSecond,diskExpiryThreadIntervalSeconds 
		Use ehcache-safe.xml default for maxElementsInMemory,maxElementsOnDisk,overflowToDisk,eternal 
		Use ehcache default for memoryStoreEvictionPolicy,diskPersistent,. -->
	<defaultCache maxElementsInMemory="10000" overflowToDisk="true"
		eternal="false" memoryStoreEvictionPolicy="LRU" maxElementsOnDisk="10000000"
		diskExpiryThreadIntervalSeconds="600" timeToIdleSeconds="3600"
		timeToLiveSeconds="100000" diskPersistent="false" />
</ehcache>

 

Sign 签名生成类

 

package com.szerp.wechat.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("Sign")
public class Sign {

    @Autowired
    public WeiXin weiXinRequest;

    public static Log logger = LogFactory.getLog(Sign.class);

    public Map<String, String> test(HttpServletRequest requesturl) throws Exception {
        String ticket = weiXinRequest.getWeiXinTicket();

        // 注意 URL 一定要动态获取,不能 hardcode
        String url = requesturl.getRequestURL().toString();
        Map<String, String> ret = sign(ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
//      ret.put("appId", weiXinRequest.appId);
        return ret;
    };

    public static Map<String, String> sign(String jsapi_ticket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        // 注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
                + "×tamp=" + timestamp + "&url=" + url;

        logger.debug("[string1] = " + string1);

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());

            logger.debug("[signature] = " + signature);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        logger.debug("[ret] = " + ret);

        return ret;
    }

    public 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;
    }

    public static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 用SHA1算法生成安全签名
     * 
     * @param token
     *            票据
     * @param timestamp
     *            时间戳
     * @param nonce
     *            随机字符串
     * @param encrypt
     *            密文
     * @return 安全签名
     * @throws NoSuchAlgorithmException
     * @throws AesException
     */
    public String getSHA1(String token, String timestamp, String nonce)
            throws NoSuchAlgorithmException {
        String[] array = new String[] { token, timestamp, nonce };
        StringBuffer sb = new StringBuffer();
        // 字符串排序
        Arrays.sort(array);
        for (int i = 0; i < 3; i++) {
            sb.append(array[i]);
        }
        String str = sb.toString();
        // SHA1签名生成
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(str.getBytes());
        byte[] digest = md.digest();

        StringBuffer hexstr = new StringBuffer();
        String shaHex = "";
        for (int i = 0; i < digest.length; i++) {
            shaHex = Integer.toHexString(digest[i] & 0xFF);
            if (shaHex.length() < 2) {
                hexstr.append(0);
            }
            hexstr.append(shaHex);
        }
        return hexstr.toString();
    }
    
    
    
}


controller 类

package com.szerp.wechat.contoller;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.jeecgframework.core.common.exception.BusinessException;
import org.jeecgframework.core.common.hibernate.qbc.CriteriaQuery;
import org.jeecgframework.core.common.model.json.DataGrid;
import org.jeecgframework.core.util.StringUtil;
import org.jeecgframework.web.system.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;
import com.szerp.wechat.service.UpperShelfServiceI;
import com.szerp.wechat.util.CurrencyUtil;
import com.szerp.wechat.util.Sign;
import com.szerp.wmstask.entity.LWmsTaskdetailsEntity;
import com.szerp.wmstask.entity.LWmsTasksEntity;
import com.szerp.wmstask.service.LWmsTasksServiceI;
import com.szerp.wmstask.service.ReceiptTask;

import net.sf.json.JSONArray;

/**
 * 微信接口实现类;
 * 
 * @author zhanguoqiang
 * @date 2020-02-17
 * 
 */
@Scope("prototype")
@Controller			
@RequestMapping("/UpperShelf")
public class UpperShelfController{
    @Autowired
    private LWmsTasksServiceI lWmsTasksService;
	@Autowired
	private SystemService systemService;
	
	@Autowired
	@Qualifier("sessionFactory")
	private SessionFactory sessionFactory;

	private static final Logger logger = Logger.getLogger(UpperShelfController.class); // 日志类
	private String message; // 接口返回提示信息

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	public Session getSession()
	  {
	     return sessionFactory.getCurrentSession();
	  }
	
	/**
	 * 微信扫一扫
	 */
	@RequestMapping(value = "saoma")
	public ModelAndView list(HttpServletRequest request, HttpServletResponse response) {
		String access_token = null;
		String apiticket = null;
		try {
			//将jsapi_ticket、noncestr、timestamp、url拼接,使用SHA1加密算法,最终生成签名
		     if (access_token == null) {
					String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
							+ "自己公众号ip" + "&secret=" + "自己公众号应用秘钥";
					JSONObject jsonStrToken = CurrencyUtil.doGetStr(url) ;
					access_token = jsonStrToken.getString("access_token");
				}

				if (null == apiticket) {
					String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="
							+ access_token + "&type=jsapi";
					JSONObject jsonStrTicket = CurrencyUtil.doGetStr(url);
					apiticket = jsonStrTicket.getString("ticket");
				} 
				System.out.println(apiticket + "-----------apiticket-------------");
				String url = request.getRequestURL().toString();
				String	jsapi_ticket = apiticket;
				Map<String, String>  signMap = Sign.sign( jsapi_ticket,  url);
				System.out.println(signMap);
				request.setAttribute("timestamp", signMap.get("timestamp"));
				request.setAttribute("nonceStr", signMap.get("nonceStr"));
				request.setAttribute("signature", signMap.get("signature"));
				
		} catch (Exception e) {
			e.printStackTrace();
		}

		return new ModelAndView("WeChat/test");
	}
	
	
	
	
	
	
	
	
	
	
	
	
}

123生成的二维码

java电子签章实现原理 电子签章sdk_apache_02

 

最后页面展示结果

java电子签章实现原理 电子签章sdk_apache_03

java电子签章实现原理 电子签章sdk_apache_04

java电子签章实现原理 电子签章sdk_json_05