package com.mazing.wx; import com.fasterxml.jackson.core.type.TypeReference; import com.mazing.CommonConstants; import com.mazing.commons.utils.HttpClientUtils; import com.mazing.commons.utils.JsonUtils; import com.mazing.commons.utils.cfg.DesPropertiesEncoder; import com.mazing.core.remote.config.Config; import com.mazing.core.web.RestResult; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 读取微信 access token 的线程 * */ public class WxAccessTokenReaderThread extends Thread { private static final Logger logger = LoggerFactory.getLogger(WxAccessTokenReaderThread.class); private boolean running = true; /** * 获取间隔 */ private static final int INTERVAL = 2 * 60 * 1000; // access token 在 7200秒过期, private long TOKEN_TIMEOUT = 7200 * 1000L - INTERVAL; private long lastReadTime = 0L; private String appid = null; private String secret = null; /** * access token */ static volatile String wxAccessToken = ""; /** * jsapi_ticket */ static volatile String wxJsapiTicket = ""; public WxAccessTokenReaderThread() { super("WxAccessTokenReaderThread"); } public void stopRunning() { running = false; interrupt(); } @Override public void run() { logger.info("WxAccessTokenReaderThread started ..."); if (!readConfig()) { logger.error("读取配置错误,线程结束,请配置 group=weixin_mp 的内容。 "); running = false; } while (running) { long now = System.currentTimeMillis(); if (now - lastReadTime >= TOKEN_TIMEOUT) { try { readToken(); readTicket(); lastReadTime = now; } catch (Exception e) { logger.error("读取微信 access token / jsapi ticket 错误", e); } } try { sleep(INTERVAL); } catch (InterruptedException e) { break; } } logger.info("WxAccessTokenReaderThread stopped ..."); } /** * 读取配置,只读一次 * @throws Exception */ public boolean readConfig() { String json = HttpClientUtils.doGet(CommonConstants.CONFIG_DOMAIN + "/api/base/allConfigs"); // logger.info("wx#readConfig | http response | result: {}", json.substring(0, 10) + "***"); RestResult<List<Config>> result = JsonUtils.parseObject(json, new TypeReference<RestResult<List<Config>>>() { }); if (!(result.isSuccess())) { logger.warn("wx#readConfig#Failure | Failure Request | result: {}", json); return false; } final String groupCode = "weixin_mp"; DesPropertiesEncoder decoder = new DesPropertiesEncoder(); int found = 0; for (Config config : result.getObject()) { if (groupCode.equals(config.getGroupCode())) { if ("appid".equalsIgnoreCase(config.getConfigKey())) { appid = decoder.decode(config.getConfigValue()); found++; } else if ("secret".equalsIgnoreCase(config.getConfigKey())) { secret = decoder.decode(config.getConfigValue()); found++; } } if (found >= 2) break; } return found >= 2; } /** * 读取 access token */ private void readToken() { 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 * 1000L - INTERVAL; if (TOKEN_TIMEOUT < 1000) { TOKEN_TIMEOUT = 10 * 60 * 1000L - INTERVAL; } } } else { logger.error("读取微信 access token 错误:" + json); } } /** * 读取 jsapi_ticket */ private void readTicket() { 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"); logger.info("读取微信 jsapi_ticket 成功!"); } } else { logger.error("读取微信 jsapi_ticket 错误:" + json); } } }
package com.mazing.commons.wx; import com.mazing.cfg.GlobalSetting; import com.mazing.commons.utils.HttpClientUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 获取微信 access token。 * 文档见:http://mp.weixin.qq.com/wiki/15/54ce45d8d30b6bf6758f68d2e95bc627.html * 接口:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET * 每天最多 2000 次 * @ */ public class AccessTokenCollector extends Thread { private static final Logger logger = LoggerFactory.getLogger(AccessTokenCollector.class); // true:不需要读取wx token(线程直接结束) private final boolean skip = Boolean.parseBoolean(GlobalSetting.getSysCongfig("noTokenWX")); private boolean stopped = false; /** * 间隔 */ private static final int INTERVAL = 2 * 60 * 1000; // 读取 access token 的地址 public static final String URL = "http://123.40.50.60:20090/"; public AccessTokenCollector() { super("accessTokenCollector"); } public void stopRunning() { this.stopped = true; interrupt(); } @Override public void run() { if (skip) { logger.info("mobile#wx#AccessTokenCollector | 不需要读取微信公众号Token信息"); return; } logger.info("mobile#wx#AccessTokenCollector | 读取微信公众号 token 线程启动 ..."); while (!stopped) { try { String str = HttpClientUtils.doGet(URL); str = StringUtils.trimToEmpty(str); if (StringUtils.isNotEmpty(str)) { String[] array = str.split("\\|"); WeixinAccessToken.ACCESS_TOKEN = array[0]; WeixinAccessToken.JSAPI_TICKET = array[1]; logger.info("mobile#wx#AccessTokenCollector | 读取微信公众号 token 成功"); } else { logger.error("mobile#wx#AccessTokenCollector | 读取微信公众号 token 出错,没有结果"); } } catch (Exception e) { logger.error("mobile#wx#AccessTokenCollector | 出错", e); } try { Thread.sleep(INTERVAL); } catch (InterruptedException e) { // ignore break; } } logger.info("mobile#wx#AccessTokenCollector | 读取微信公众号 token 线程结束 ..."); } }
微信JSSDK签名 数据
package com.mazing.mobile.web.utils; import java.io.IOException; import java.util.HashMap; 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.cfg.DesPropertiesEncoder; import com.mazing.commons.utils.encrypt.EncryptUtil; import com.mazing.commons.wx.WeixinAccessToken; import com.mazing.core.remote.config.ConfigGroupConstants; import com.mazing.core.remote.config.ConfigRemoteService; /** * 微信分享的工具类 * * */ public class WeiXinHelper { private static final Logger logger = LoggerFactory.getLogger(WeiXinHelper.class); /** * 微信分享的attr */ public static final String WX_CONFIG_ATTR = "wxConfig"; /** * 微信appid */ private static String WX_APPID = null; // /** // * 用户的请求是否是https的(进入到nginx的是否是https)(nginx upstream是http) // */ // public static boolean httpsFromNginx(HttpServletRequest request) { // String url = request.getRequestURL().toString(); // String scheme = request.getHeader("x-forwarded-proto"); // return (url.startsWith("https://") || (StringUtils.isNotBlank(scheme)) && "https".equals(scheme)); // } /** * 检查当前的请求是否是 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 (WX_APPID == null) { String encryptedId = ConfigRemoteService.getConfig(ConfigGroupConstants.WEIXIN_WP, "appid"); if (encryptedId != null) { try { DesPropertiesEncoder decoder = new DesPropertiesEncoder(); WX_APPID = decoder.decode(encryptedId); } catch (Exception e) { logger.error("mobile#share#getWxMpAppId | 解密 appid 出错", e); } } else { logger.error("mobile#share#getWxMpAppId | 没有配置 group_code: {} config_key: {} !!", ConfigGroupConstants.WEIXIN_WP, "appid"); } } return WX_APPID; } /** * 微信分享的 config * * @param url 当前页面完整的地址,包括参数 * @return */ public static String getWxMpConfig(String url) { String noncestr = RandomStringUtils.random(16, CommonConstants.LETTER_NUMBER_CHARS); long timestamp = System.currentTimeMillis() / 1000; // 秒 logger.info("mobile#share#getWxMpConfig | 设置微信分享配置 |url: {}", url); String appId = getWxMpAppId(); String sourceStr = "jsapi_ticket=" + WeixinAccessToken.JSAPI_TICKET + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; String signature = EncryptUtil.getSHA1(sourceStr).toLowerCase(); String[] jsApiList = new String[] { // "checkJsApi", // "onMenuShareTimeline", // "onMenuShareAppMessage",// "onMenuShareQQ",// "onMenuShareWeibo", // "onMenuShareQZone",// "hideAllNonBaseMenuItem",// "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); } /** * 设置微信分享JSSDK 需要的config配置信息<br> * * 凡是需要设置分享操作菜单(分享至朋友圈、QQ、微博, 或者屏蔽分享功能,只保留设置字体、刷新 等 菜单) 相关的页面, 都需要调用该方法进行config设置 * * @param request * */ public static void setWxJsConfig(HttpServletRequest request) { // 微信相关的内容 String url = WeiXinHelper.getFullUrl(request); request.setAttribute(WeiXinHelper.WX_CONFIG_ATTR, WeiXinHelper.getWxMpConfig(url)); } }
页面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() { 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>
隐藏微信的分享功能
<%@ 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)); }); wx.ready(function() { // 1 判断当前版本是否支持指定 JS 接口,支持批量判断 wx.checkJsApi({ jsApiList : [ 'hideAllNonBaseMenuItem' ], success : function(res) { MazingEnv.log('当前版本支持wx JS接口'); } }); // 8.3 批量隐藏菜单项 /* wx.hideMenuItems({ menuList: [ 'menuItem:readMode', // 阅读模式 'menuItem:share:timeline', // 分享到朋友圈 'menuItem:copyUrl' // 复制链接 ], success: function (res) { alert('已隐藏“阅读模式”,“分享到朋友圈”,“复制链接”等按钮'); }, fail: function (res) { alert(JSON.stringify(res)); } }); */ // 8.5 隐藏所有非基本菜单项 wx.hideAllNonBaseMenuItem({ success : function() { MazingEnv.log('已隐藏所有非基本菜单项'); } }); }); </script>