微信JSSDK之添加微信卡券
微信卡券的使用,是在之前的微信jsapi基础上,再加上一次卡券的单独验签,
这里对上篇微信JSSDK的使用稍作修改:
1.微信的accessToken的获取有时间限制,之前是将token的读取放在一个单独的服务上,单控
2.基于开个别的服务比较繁琐,现在使用redis缓存,来控制访问频率,至于并发,由锁来控制
代码如下:
package com.mazing.commons.wx; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.type.TypeReference; import com.mazing.commons.cache.redis.Redis; import com.mazing.commons.cache.redis.RedisLock; import com.mazing.commons.server.context.MainframeBeanFactory; import com.mazing.commons.utils.HttpClientUtils; import com.mazing.commons.utils.JsonUtils; import com.mazing.commons.utils.LogUtil; import com.mazing.commons.utils.cfg.EncryptionPropertyPlaceholderConfigurer; /** * access token 改为缓存读取方式,失效再读取 * * @author sky * */ public class KFwxAccessTokenReader { private static final Logger logger = LoggerFactory.getLogger(KFwxAccessTokenReader.class); /** * 获取间隔 */ private static final int INTERVAL = 2 * 60 ; // access token 在 7200秒过期, private static int TOKEN_TIMEOUT = 7200 - INTERVAL; private static String appid = null; private static String secret = null; /** * access token */ public String wxAccessToken = ""; /** * jsapi_ticket */ public String wxJsapiTicket = ""; /** * 卡券api_ticket */ public String wxCardApiTicket = ""; private static Redis redis; static { readConfig(); //clearCache(); } public static Map<String, String> readAccessToken() { Map<String, String> map = new HashMap<>(); String atKey = getJsAccessTokenCacheKey(); String accessToken = redis().get(atKey); String jsapiTicket = ""; String cardApiTicket = ""; if (StringUtils.isBlank(accessToken)) { accessToken = readToken(); jsapiTicket = readTicket(accessToken); cardApiTicket = readCardTicket(accessToken); logger.info("kf#wx#accessToken | cache为空,通过接口重新获取数据 | accessToken: {}, jsapiTicket: {},cardApiTicket: {}", LogUtil.hidePassport(accessToken), LogUtil.hidePassport(jsapiTicket), LogUtil.hidePassport(cardApiTicket)); } else { jsapiTicket = redis().get(getJsapiTicketCacheKey()); cardApiTicket = redis().get(getCardApiTicketCacheKey()); logger.info("kf#wx#accessToken | cache不为空, 获取缓存数据 | accessToken: {}, jsapiTicket: {},cardApiTicket: {}", LogUtil.hidePassport(accessToken), LogUtil.hidePassport(jsapiTicket), LogUtil.hidePassport(cardApiTicket)); } map.put(WeixinAccessToken.KF_ACCESS_TOKEN, accessToken); map.put(WeixinAccessToken.KF_JSAPI_TICKET, jsapiTicket); map.put(WeixinAccessToken.KF_CARD_API_TICKET, cardApiTicket); return map; } private static String getJsAccessTokenCacheKey() { return "KF_wxAccessTokenKey"; } /** * jsapi_ticket cache key * * @return */ private static String getJsapiTicketCacheKey() { return "KF_wxJSapiTicketKey"; } /** * 卡券 api_ticket cache key * * @return */ private static String getCardApiTicketCacheKey() { return "KF_wxCardApiTicketKey"; } public static void clearCache() { redis().del(getJsAccessTokenCacheKey()); redis().del(getJsapiTicketCacheKey()); redis().del(getCardApiTicketCacheKey()); logger.info("清除微信卡券相关缓存...."); } private static Redis redis() { if (null == redis) redis = (Redis) MainframeBeanFactory.getBean("redis"); return redis; } /** * 读取配置,只读一次 * * @throws Exception */ public static void readConfig() { appid = (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get("kf_appid"); secret = (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get("kf_appsecret"); } /** * 读取 access token */ private static String readToken() { RedisLock lock = new RedisLock(redis, "KFWxAccessTokenKey"); String wxAccessToken = ""; if (lock.lock()) { Map<String, String> params = new HashMap<>(4); params.put("grant_type", "client_credential"); params.put("appid", appid); params.put("secret", secret); String json = HttpClientUtils.doGet("https://api.weixin.qq.com/cgi-bin/token", 10000, params); if (StringUtils.isBlank(json)) { logger.error("读取微信 access token 错误"); } Map<String, Object> map = JsonUtils.parseObject(json.trim(), new TypeReference<Map<String, Object>>() { }); if (map.containsKey("access_token")) { wxAccessToken = (String) map.get("access_token"); logger.info("读取微信 access token 成功"); if (map.containsKey("expires_in")) { int expiresIn = ((Number) map.get("expires_in")).intValue(); logger.info("过期时间 (s):" + expiresIn); TOKEN_TIMEOUT = expiresIn - INTERVAL; // 缓存,并设置过期时间 redis().set(getJsAccessTokenCacheKey(), wxAccessToken, TOKEN_TIMEOUT); } } else { logger.error("读取微信 access token 错误:" + json); } lock.unlock(); } return wxAccessToken; } /** * 读取 jsapi_ticket */ private static String readTicket(String wxAccessToken) { String wxJsapiTicket = ""; Map<String, String> params = new HashMap<>(4); params.put("access_token", wxAccessToken); params.put("type", "jsapi"); String json = HttpClientUtils.doGet("https://api.weixin.qq.com/cgi-bin/ticket/getticket", 10000, params); Map<String, Object> map = JsonUtils.parseObject(json.trim(), new TypeReference<Map<String, Object>>() { }); int errcode = -1; if (map.get("errcode") != null) { errcode = ((Number) map.get("errcode")).intValue(); logger.info("读取微信 jsapi_ticket 结果,errcode: {}, errmsg: {}", errcode, map.get("errmsg")); if (errcode == 0) { wxJsapiTicket = (String) map.get("ticket"); // set to cache redis().set(getJsapiTicketCacheKey(), wxJsapiTicket); logger.info("读取微信 jsapi_ticket 成功!"); } } else { logger.error("读取微信 jsapi_ticket 错误:" + json); } return wxJsapiTicket; } /** * 获取卡券 api_ticket */ private static String readCardTicket(String wxAccessToken) { String wxCardApiTicket = ""; Map<String, String> params = new HashMap<>(4); params.put("access_token", wxAccessToken); params.put("type", "wx_card"); String json = HttpClientUtils.doGet("https://api.weixin.qq.com/cgi-bin/ticket/getticket", 10000, params); Map<String, Object> map = JsonUtils.parseObject(json.trim(), new TypeReference<Map<String, Object>>() { }); int errcode = -1; if (map.get("errcode") != null) { errcode = ((Number) map.get("errcode")).intValue(); logger.info("读取微信 卡券 api_ticket 结果,errcode: {}, errmsg: {}", errcode, map.get("errmsg")); if (errcode == 0) { wxCardApiTicket = (String) map.get("ticket"); // set to cache redis().set(getCardApiTicketCacheKey(), wxCardApiTicket); logger.info("读取微信 卡券 api_ticket 成功! ticket: {}", LogUtil.hidePassport(wxCardApiTicket)); } } else { logger.error("读取微信 卡券 api_ticket 错误:" + json); } return wxCardApiTicket; } }
util类
package com.mazing.mobile.web.kaifun; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mazing.CommonConstants; import com.mazing.commons.utils.JsonUtils; import com.mazing.commons.utils.LogUtil; import com.mazing.commons.utils.cfg.EncryptionPropertyPlaceholderConfigurer; import com.mazing.commons.utils.encrypt.EncryptUtil; import com.mazing.commons.wx.KFwxAccessTokenReader; import com.mazing.commons.wx.WeixinAccessToken; import com.mazing.core.web.ResultCode; import com.mazing.core.web.exception.ServiceException; /** * 对接公众号的微信平台 工具类(验签参数获取) * * @author sky 2016-05-57 * */ public class KaiFunWXUtil { private static final Logger logger = LoggerFactory.getLogger(KaiFunWXUtil.class); /** * 微信分享的attr */ public static final String WX_CONFIG_ATTR = "wxConfig"; /** * 开饭 微信appid */ private static String KF_WX_APPID = null; // 原始值 public static String KF_WX_CARDID = null; /** * 检查当前的请求是否是 https 的,如果不是则重定向成为https<br> * 如果执行了重定向,返回true */ public static boolean redirectFullUrl2Https(HttpServletRequest request, HttpServletResponse response) throws IOException { String params = request.getQueryString(); String url = request.getRequestURL().toString(); String scheme = request.getHeader("x-forwarded-proto"); boolean need2https = (url.startsWith("http://") // 如果是http的请求 && (StringUtils.isBlank(scheme) || !("https".equals(scheme))));// nginx上报的不是https协议 // 重定向请求为https,并返回true if (need2https) { url = url.replaceFirst("http://", "https://"); if (params != null) url += "?" + params; response.sendRedirect(url); return true; } return false; } /** * 返回当前页面完整的 URL,包括“?”后面的参数 * * @param request * @return */ public static String getFullUrl(HttpServletRequest request) { String params = request.getQueryString(); String url = request.getRequestURL().toString(); // nginx 的 upstream配置使用的是 http,导致外部是https的请求,来到代码却是http // nginx 会带上scheme参数(443端口才设置) String scheme = request.getHeader("x-forwarded-proto"); if (StringUtils.isNotBlank(scheme) && "https".equals(scheme)) url = url.replaceFirst("http://", "https://"); if (params != null) { url += "?" + params; } return url; } public static void main(String[] args) { String url = "http://xxx.com?callback=http://123.com"; System.out.println(url.replaceFirst("http", "https")); } /** * 微信公众号的appid * * @return */ private static String getWxMpAppId() { if (KF_WX_APPID == null) { KF_WX_APPID = (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get("kf_appid"); } return KF_WX_APPID; } private static String getWxMpCardId() { if (KF_WX_CARDID == null) { KF_WX_CARDID = (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get("kf_cardId"); } return KF_WX_CARDID; } /** * 微信分享的 config * * @param url 当前页面完整的地址,包括参数 * @return */ public static String getWxMpConfig(String url) { String noncestr = RandomStringUtils.random(16, CommonConstants.LETTER_NUMBER_CHARS); long timestamp = System.currentTimeMillis() / 1000; // 秒 String appId = getWxMpAppId(); Map<String, String> amap = KFwxAccessTokenReader.readAccessToken(); String jsapiTicket = amap.get(WeixinAccessToken.KF_JSAPI_TICKET); logger.info("mobile#share#getWxMpConfig | 设置微信分享配置 |url: {}, KF_JSAPI_TICKET: {}", url, LogUtil.hidePassport(jsapiTicket)); String sourceStr = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; String signature = EncryptUtil.getSHA1(sourceStr).toLowerCase(); String[] jsApiList = new String[] { // "checkJsApi", // "onMenuShareTimeline", // "onMenuShareAppMessage",// "onMenuShareQQ",// "onMenuShareWeibo", // "onMenuShareQZone",// "hideAllNonBaseMenuItem",// "addCard",// "showAllNonBaseMenuItem" }; Map<String, Object> map = new HashMap<>(8); map.put("nonceStr", noncestr); map.put("timestamp", timestamp); map.put("appId", appId); map.put("signature", signature); map.put("debug", false); map.put("jsApiList", jsApiList); return JsonUtils.toJSONString(map); } /** * 获取卡券的签名 * * @return */ public static void setCardSignature(HttpServletRequest request) { long timestamp = System.currentTimeMillis() / 1000; // 秒 String cardId = getWxMpCardId(); String timeStr = timestamp + ""; Map<String, String> map = KFwxAccessTokenReader.readAccessToken(); String apiTicket = map.get(WeixinAccessToken.KF_CARD_API_TICKET); if (StringUtils.isBlank(apiTicket)) { throw new ServiceException(ResultCode.FAILURE, "系统繁忙, 请稍后再试"); } logger.info("kaifun#wx#signature | 验签参数 | cardId: {}, timestamp: {}, apiTicket: {}", cardId, timeStr, LogUtil.hidePassport(apiTicket)); List<String> list = new ArrayList<>(); list.add(timeStr); list.add(cardId); list.add(apiTicket); Collections.sort(list); String sourceStr = ""; for (Object obj : list) { sourceStr += obj; } String signature = EncryptUtil.getSHA1(sourceStr).toLowerCase(); //logger.info("kaifun#wx#signature | 验签参数排序后 | sourceStr: {}, signature: {}", sourceStr, signature); Map<String, Object> ext = new HashMap<>(); ext.put("timestamp", timeStr); ext.put("signature", signature); request.setAttribute("ext", JsonUtils.toJSONString(ext)); request.setAttribute("_cardid", KF_WX_CARDID); } /** * 设置微信分享JSSDK 需要的config配置信息<br> * * 凡是需要设置分享操作菜单(分享至朋友圈、QQ、微博, 或者屏蔽分享功能,只保留设置字体、刷新 等 菜单) 相关的页面, 都需要调用该方法进行config设置 * * @param model * @param request * @author sky 2016-04-19 */ public static void setWxJsConfig(HttpServletRequest request) { // 微信相关的内容 String url = getFullUrl(request); request.setAttribute(WX_CONFIG_ATTR, getWxMpConfig(url)); setCardSignature(request); } }
controller下发
package com.mazing.mobile.web.kaifun; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/open/wx/card") public class KFCardController { @RequestMapping("obtain/index") public String searchIndex(Model model, HttpServletRequest request) { // 微信相关的内容 KaiFunWXUtil.setWxJsConfig(request); return "/open/kaifun/obtain_index"; } }
jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script type="text/javascript"> // 微信分享的配置 wx.config(<c:out value="${wxConfig}" escapeXml="false"/>); wx.error(function(res) { //alert(JSON.stringify(res)); }); /** http://www.xiaomeiti.com/note/3561 **/ function WeixinShare(shareData) { this.shareData = shareData; if (wx && wx.checkJsApi) { this.shareType = "api"; this.initByAPI(); } else { this.shareType = "bridge"; this.initByBridge(); } } WeixinShare.prototype.initByAPI = function() { var me = this; wx.ready(function() { document.querySelector('#addCard').onclick = function () { wx.addCard({ cardList: [ { cardId: '${_cardid}', cardExt: '<c:out value="${ext}" escapeXml="false"/>' } ], success: function (res) { alert('已添加卡券:' + JSON.stringify(res.cardList)); } }); }; /* */ var shareData = { title : me.getParam("title"), desc : me.getParam("desc"), link : me.getParam("link"), imgUrl : me.getParam("imgUrl"), trigger : function(res) { /* this.title = me.getParam("title"); this.desc = me.getParam("desc"); this.link = me.getParam("link"); this.imgUrl = me.getParam("imgUrl"); */ } }; wx.onMenuShareAppMessage(shareData); var shareData2 = { title : me.getParam("title"), desc : me.getParam("desc"), link : me.getParam("link"), imgUrl : me.getParam("imgUrl"), trigger : function(res) { } }; var timelineTitle = me.getParam("timelineTitle"); if (timelineTitle) { shareData2.title = timelineTitle; } wx.onMenuShareTimeline(shareData2);//分享朋友圈 wx.onMenuShareQQ(shareData);//分享至QQ wx.onMenuShareWeibo(shareData);//分享到微博 wx.onMenuShareQZone(shareData);//分享到QZone //这里调用show all,是因为其他页面可能会调用了hide all(貌似是全局生效) 后跳转至 需要show all的页面 //wx.showAllNonBaseMenuItem({ // success : function() { // //MazingEnv.log('已显示所有非基本菜单项'); // } //}); }); }; WeixinShare.prototype.initByBridge = function() { var me = this; document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() { window.alert('Bridge triggered.'); WeixinJSBridge.on('menu:share:appmessage', function(argv) { me.shareFriend() }); WeixinJSBridge.on('menu:share:timeline', function(argv) { me.shareTimeline() }); }, false); }; WeixinShare.prototype.getParam = function(name) { var val = this.shareData[name]; if (typeof val == "function") { return val(); } return val; }; WeixinShare.prototype.shareFriend = function() { WeixinJSBridge.invoke('sendAppMessage', { appid : this.getParam("appid"), img_url : this.getParam("imgUrl"), img_width : 120, img_height : 120, link : this.getParam("link"), title : this.getParam("title"), desc : this.getParam("desc") }, function(res) { _report('send_msg', res.err_msg); }); }; WeixinShare.prototype.shareTimeline = function() { WeixinJSBridge.invoke('shareTimeline', { appid : this.getParam("appid"), img_url : this.getParam("imgUrl"), img_width : 120, img_height : 120, link : this.getParam("link"), title : this.getParam("title"), desc : this.getParam("desc") }, function(res) { _report('timeline', res.err_msg); }); }; var wxApi = new WeixinShare(shareData); </script>
相关推荐
jieq 2020-11-09
znbvx 2020-11-13
liduote 2020-11-13
umengren 2020-11-11
郑贺腾讯社交广告 2020-11-06
wuxiaobo 2020-11-06
jcwang 2020-11-03
Dayer 2020-10-27
Elyn 2020-10-24
虞凌云 2020-09-28
huavhuahua 2020-09-21
lookat 2020-09-18
QSmile 2020-09-17
fanxiaoxuan 2020-09-17
Allinputs 2020-08-30
xjygabc00 2020-09-11
gxq 2020-09-10
Tomato 2020-09-10
taiyangyu 2020-09-10