WeixinUtil.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. package com.iamberry.wechat.utils;
  2. import java.io.BufferedReader;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. import java.io.OutputStream;
  6. import java.io.UnsupportedEncodingException;
  7. import java.net.ConnectException;
  8. import java.net.URL;
  9. import java.net.URLEncoder;
  10. import java.util.Date;
  11. import java.util.logging.Logger;
  12. import javax.net.ssl.HttpsURLConnection;
  13. import javax.net.ssl.SSLContext;
  14. import javax.net.ssl.SSLSocketFactory;
  15. import javax.net.ssl.TrustManager;
  16. import net.sf.json.JSONObject;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.stereotype.Component;
  19. import com.iamberry.app.tool.log.RatFWLogger;
  20. import com.iamberry.wechat.core.entity.wx.AccessToken;
  21. import com.iamberry.wechat.core.entity.wx.ITTempLate;
  22. import com.iamberry.wechat.core.entity.wx.Menu;
  23. import com.iamberry.wechat.core.entity.wx.QRCJson;
  24. import com.iamberry.wechat.core.entity.wx.Ticket;
  25. import com.iamberry.wechat.core.entity.wx.Token;
  26. import com.iamberry.wechat.face.wechat.TokenService;
  27. import com.iamberry.wechat.tools.MyX509TrustManager;
  28. import com.iamberry.wechat.tools.NameUtils;
  29. /**
  30. * @company 深圳爱贝源科技有限公司
  31. * @website www.iamberry.com
  32. * @author 献
  33. * @tel 18271840547
  34. * @date 2016年11月3日
  35. * @explain 微信公众平台通用接口工具类
  36. */
  37. @Component
  38. public class WeixinUtil {
  39. @Autowired
  40. private RatFWLogger logger;
  41. @Autowired
  42. private TokenService tokenService;
  43. private static String sendTemplate = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
  44. /**
  45. * 网页授权获取access_token
  46. *
  47. * @param code
  48. * @return
  49. */
  50. public JSONObject getParamByOauth2(String code) {
  51. JSONObject jsonObject = null;
  52. // 得到access_token和openid
  53. jsonObject = httpRequest(NameUtils.oauth2_getToken_url.replaceAll("CODE", code).replaceAll("APPID", NameUtils.appId).replaceAll("SECRET", NameUtils.appSecret), "GET", null);
  54. if (null != jsonObject) {
  55. return jsonObject;
  56. }
  57. // 获取token失败
  58. return null;
  59. }
  60. /**
  61. * 发起https请求并获取结果
  62. *
  63. * @param requestUrl
  64. * 请求地址
  65. * @param requestMethod
  66. * 请求方式(GET/POST)
  67. * @param outputStr
  68. * 提交的数据
  69. * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
  70. * java.lang.ClassCastException:
  71. * sun.net.www.protocol.http.HttpURLConnection cannot be cast to
  72. * javax.net.ssl.HttpsURLConnection
  73. */
  74. public JSONObject httpRequest(String requestUrl,
  75. String requestMethod, String outputStr) {
  76. JSONObject jsonObject = null;
  77. StringBuffer buffer = new StringBuffer();
  78. try {
  79. // 创建SSLContext对象,并使用我们指定的信任管理器初始化
  80. TrustManager[] tm = { new MyX509TrustManager() };
  81. SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
  82. sslContext.init(null, tm, new java.security.SecureRandom());
  83. // 从上述SSLContext对象中得到SSLSocketFactory对象
  84. SSLSocketFactory ssf = sslContext.getSocketFactory();
  85. URL url = new URL(requestUrl);
  86. HttpsURLConnection httpUrlConn = (HttpsURLConnection) url
  87. .openConnection();
  88. httpUrlConn.setSSLSocketFactory(ssf);
  89. httpUrlConn.setDoOutput(true);
  90. httpUrlConn.setDoInput(true);
  91. httpUrlConn.setUseCaches(false);
  92. // 设置请求方式(GET/POST)
  93. httpUrlConn.setRequestMethod(requestMethod);
  94. if ("GET".equalsIgnoreCase(requestMethod))
  95. httpUrlConn.connect();
  96. // 当有数据需要提交时
  97. if (null != outputStr) {
  98. OutputStream outputStream = httpUrlConn.getOutputStream();
  99. // 注意编码格式,防止中文乱码
  100. outputStream.write(outputStr.getBytes("UTF-8"));
  101. outputStream.close();
  102. }
  103. // 将返回的输入流转换成字符串
  104. InputStream inputStream = httpUrlConn.getInputStream();
  105. InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
  106. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  107. String str = null;
  108. while ((str = bufferedReader.readLine()) != null) {
  109. buffer.append(str);
  110. }
  111. bufferedReader.close();
  112. inputStreamReader.close();
  113. // 释放资源
  114. inputStream.close();
  115. inputStream = null;
  116. httpUrlConn.disconnect();
  117. jsonObject = JSONObject.fromObject(buffer.toString());
  118. } catch (ConnectException ce) {
  119. Logger.getAnonymousLogger().info("Weixin server connection timed out.");
  120. } catch (Exception e) {
  121. Logger.getAnonymousLogger().info("信任管理器请求时..." + e);
  122. Logger.getAnonymousLogger().info("jsonObject..." + jsonObject);
  123. }
  124. return jsonObject;
  125. }
  126. /**
  127. * 获取access_token
  128. * @param appid 凭证
  129. * @param appsecret 密钥
  130. * @return
  131. */
  132. public AccessToken getAccessToken(String appid, String appsecret) {
  133. // 查询缓存,如果是第一次,绝对为空
  134. Token accessTokenMemory = StaticCacheMemory.getAccessToken();
  135. if (accessTokenMemory != null) {
  136. if (new Date().getTime() < accessTokenMemory.getEndDate().getTime()) {
  137. AccessToken accessToken = new AccessToken();
  138. accessToken.setExpiresIn(7200);
  139. accessToken.setToken(accessTokenMemory.getToken());
  140. return accessToken;
  141. }
  142. }
  143. // 根据public No查询access_token
  144. Token token = tokenService.selectOne(NameUtils.pubNo);
  145. // 查询到了植入环境中
  146. StaticCacheMemory.setAccessToken(token);
  147. AccessToken accessToken = null;
  148. //判断数据库中是否存在token
  149. if (token != null) {
  150. //判断token是否失效
  151. Date date = new Date();
  152. if (date.getTime() < token.getEndDate().getTime()) {
  153. accessToken = new AccessToken();
  154. accessToken.setExpiresIn(7200);
  155. accessToken.setToken(token.getToken());
  156. return accessToken;
  157. } else {
  158. String requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.appId).replace("APPSECRET", NameUtils.appSecret);
  159. JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
  160. // 如果请求成功
  161. if (null != jsonObject) {
  162. try {
  163. accessToken = new AccessToken();
  164. accessToken.setToken(jsonObject.getString("access_token"));
  165. accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
  166. token.setToken(accessToken.getToken());
  167. token.setEndDate(new Date(date.getTime() + 6500000));
  168. token.setAppid(NameUtils.pubNo);
  169. try {
  170. tokenService.updateToken(token);
  171. StaticCacheMemory.setAccessToken(token);
  172. } catch (Exception e) {
  173. System.out.println("更新token失败:" + e.getMessage());
  174. }
  175. return accessToken;
  176. } catch (Exception e) {
  177. e.printStackTrace();
  178. accessToken = null;
  179. // 获取token失败
  180. Logger.getAnonymousLogger().info("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  181. }
  182. }
  183. }
  184. } else {
  185. String requestUrl = NameUtils.access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
  186. JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
  187. // 如果请求成功
  188. if (null != jsonObject) {
  189. try {
  190. accessToken = new AccessToken();
  191. accessToken.setToken(jsonObject.getString("access_token"));
  192. accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
  193. token = new Token();
  194. token.setToken(accessToken.getToken());
  195. token.setEndDate(new Date(new Date().getTime() + 6500000));
  196. token.setAppid(NameUtils.pubNo);
  197. try {
  198. tokenService.updateToken(token);
  199. StaticCacheMemory.setAccessToken(token);
  200. } catch (Exception e) {
  201. // 更新失败
  202. }
  203. return accessToken;
  204. } catch (Exception e) {
  205. accessToken = null;
  206. // 获取token失败
  207. Logger.getAnonymousLogger().info("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  208. }
  209. }
  210. }
  211. return accessToken;
  212. }
  213. /**
  214. * 获取jsapi_ticket(jsapi_ticket的有效期为7200秒)
  215. * @param token
  216. * @return
  217. */
  218. public Ticket getTicket(String token) {
  219. Ticket ticket = null;
  220. String requestUrl = NameUtils.jsapi_ticket_url.replace("ACCESS_TOKEN", token);
  221. JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
  222. if (null == jsonObject) {
  223. // 网络异常
  224. return null;
  225. }
  226. try {
  227. ticket = new Ticket();
  228. ticket.setTicket(jsonObject.getString("ticket"));
  229. ticket.setExpiresIn(jsonObject.getInt("expires_in"));
  230. } catch (Exception e) {
  231. // 获取token失败
  232. Logger.getAnonymousLogger().info("获取ticket失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  233. // 判断错误信息是否为 access_token 失效的问题,只有access失效,我们才重新获取,否则每次获取可能导致access_token资源耗尽
  234. if (jsonObject.getInt("errcode") != 40001)
  235. return null;
  236. // 强制刷新access_token,存在多线程安全隐患!
  237. requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.appId).replace("APPSECRET", NameUtils.appSecret);
  238. JSONObject accessTokenJsonObject = httpRequest(requestUrl, "GET", null);
  239. // 报错access_token信息,不管报错与否
  240. Token wechatToken = new Token();
  241. wechatToken.setEndDate(new Date(new Date().getTime() + 6500000));
  242. wechatToken.setAppid(NameUtils.pubNo);
  243. wechatToken.setToken(accessTokenJsonObject.getString("access_token"));
  244. tokenService.updateToken(wechatToken);
  245. StaticCacheMemory.setAccessToken(wechatToken);
  246. // 重新获取一次
  247. requestUrl = NameUtils.jsapi_ticket_url.replace("ACCESS_TOKEN", wechatToken.getToken());
  248. jsonObject = httpRequest(requestUrl, "GET", null);
  249. ticket = new Ticket();
  250. ticket.setTicket(jsonObject.getString("ticket"));
  251. ticket.setExpiresIn(jsonObject.getInt("expires_in"));
  252. }
  253. return ticket;
  254. }
  255. /**
  256. * 创建菜单
  257. * @param menu 菜单实例
  258. * @param accessToken 有效的access_token
  259. * @return 0表示成功,其他值表示失败
  260. */
  261. public int createMenu(Menu menu, String accessToken) {
  262. int result = 0;
  263. // 拼装创建菜单的url
  264. String url = NameUtils.menu_create_url.replace("ACCESS_TOKEN", accessToken);
  265. // 将菜单对象转换成json字符串
  266. String jsonMenu = JSONObject.fromObject(menu).toString();
  267. // 调用接口创建菜单
  268. JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);
  269. if (null != jsonObject) {
  270. if (0 != jsonObject.getInt("errcode")) {
  271. result = jsonObject.getInt("errcode");
  272. Logger.getAnonymousLogger().info("创建菜单失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  273. }
  274. }
  275. return result;
  276. }
  277. /**
  278. * 创建菜单
  279. * @param jsonMenu
  280. * @param accessToken
  281. */
  282. public int createMenu(String jsonMenu, String accessToken) {
  283. int result = 0;
  284. // 拼装创建菜单的url
  285. String url = NameUtils.menu_create_url.replace("ACCESS_TOKEN", accessToken);
  286. // 调用接口创建菜单
  287. JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);
  288. if (null != jsonObject) {
  289. if (0 != jsonObject.getInt("errcode")) {
  290. result = jsonObject.getInt("errcode");
  291. Logger.getAnonymousLogger().info("创建菜单失败 errcode:{} errmsg:{} " + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  292. }
  293. }
  294. return result;
  295. }
  296. /**
  297. * 创建二维码
  298. * @param qrCode
  299. * @return
  300. */
  301. public QRCJson createQrcode(String json) {
  302. AccessToken at = this.getAccessToken(NameUtils.appId, NameUtils.appSecret);
  303. if (at == null) {
  304. return null;
  305. }
  306. String token = at.getToken();
  307. JSONObject jsonObject = httpRequest(NameUtils.get_token_url.replaceAll("TOKEN", token), "POST", json);
  308. if (jsonObject == null) {
  309. return null;
  310. }
  311. QRCJson qrcJson = new QRCJson();
  312. String ticket = null;
  313. try {
  314. ticket = URLEncoder.encode(jsonObject.getString("ticket"), "UTF-8");
  315. } catch (UnsupportedEncodingException e) {
  316. e.printStackTrace();
  317. }
  318. qrcJson.setTicket(ticket);
  319. qrcJson.setUrl(NameUtils.show_qrcode_url + ticket);
  320. if (json.contains("expire_seconds")) {// 临时二维码才有过期时间
  321. qrcJson.setExpire_seconds(jsonObject.getString("expire_seconds"));
  322. }
  323. return qrcJson;
  324. }
  325. /**
  326. * 生成网页授权链接
  327. *
  328. * @param type
  329. * @param redirectURI
  330. */
  331. public String getOauth2Url(String type, String redirectURI) {
  332. String url = "";
  333. try {
  334. redirectURI = URLEncoder.encode(redirectURI, "utf-8");
  335. } catch (UnsupportedEncodingException e) {
  336. e.printStackTrace();
  337. }
  338. if ("snsapi_base".equals(type)) {
  339. url = NameUtils.oauth2_url.replace("SCOPE", "snsapi_base").replace("APPID", NameUtils.appId).replace("REDIRECT_URI", redirectURI);
  340. } else {
  341. url = NameUtils.oauth2_url.replace("SCOPE", "snsapi_userinfo").replace("APPID", NameUtils.appId).replace("REDIRECT_URI", redirectURI);
  342. }
  343. return url;
  344. }
  345. /**
  346. * (网页授权)拉取用户信息
  347. *
  348. * @param code
  349. * @param type
  350. * @return
  351. */
  352. public JSONObject getUserInfoByOauth2(String code, String type) {
  353. JSONObject jsonObject = null;
  354. String openid = "", access_token = "";
  355. // 得到access_token和openid
  356. jsonObject = httpRequest(NameUtils.oauth2_getToken_url.replaceAll("CODE", code).replaceAll("APPID", NameUtils.appId).replaceAll("SECRET", NameUtils.appSecret), "GET", null);
  357. if (null == jsonObject) {
  358. return null;
  359. }
  360. openid = jsonObject.getString("openid");
  361. access_token = jsonObject.getString("access_token");
  362. // 获取用户信息
  363. if ("info".equals(type) && !"".equals(openid) && !"".equals(access_token)) {
  364. jsonObject = null;
  365. jsonObject = httpRequest(NameUtils.oauth2_getUserInfo_url.replaceAll("ACCESS_TOKEN", access_token).replaceAll("OPENID", openid), "GET", null);
  366. if (jsonObject != null)
  367. return jsonObject;
  368. }
  369. return jsonObject;
  370. }
  371. /**
  372. * 拉取用户信息
  373. *
  374. * @param code
  375. * @param appid
  376. * @param secret
  377. */
  378. public String[] getOpenId(String code, String appid, String secret) {
  379. String[] strs = new String[2];
  380. String url = "", openid = "", access_token = "";
  381. Logger.getAnonymousLogger().info("code = " + code + ", appid = " + appid + ", secret = " + secret);
  382. // 得到access_token和openid
  383. url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
  384. JSONObject jsonObject = httpRequest(url.replaceAll("CODE", code).replaceAll("APPID", appid).replaceAll("SECRET", secret), "GET", null);
  385. if (null == jsonObject) {
  386. return null;
  387. }
  388. openid = jsonObject.getString("openid");
  389. access_token = jsonObject.getString("access_token");
  390. strs[0] = openid;
  391. strs[1] = access_token;
  392. return strs;
  393. }
  394. /**
  395. * 根据openid拉取用户信息
  396. * @param openid
  397. * @return
  398. */
  399. public JSONObject getUserInfo(String openid) {
  400. AccessToken token = this.getAccessToken(NameUtils.appId, NameUtils.appSecret);
  401. if (token == null) {
  402. // 此处如果出错,只能说明程序逻辑改变,存在多线程安全隐患
  403. return null;
  404. }
  405. // 获取信息
  406. JSONObject jsonObject = httpRequest(NameUtils.userinfo_url.replaceAll("ACCESS_TOKEN", token.getToken()).replaceAll("OPENID", openid), "GET", null);
  407. if (null == jsonObject) {
  408. // 网络错误
  409. return null;
  410. }
  411. // 对数据进行get(防止出现问题,能够让程序及时补救), 如果抛出异常,表示access_token可能失效,强制获取一次。
  412. try {
  413. jsonObject.getString("sex");
  414. } catch (Exception e) {
  415. // 记录日志
  416. logger.error(this, "getUserInfo method throw exception:" + e.getMessage() + "[" + jsonObject + "]");
  417. // 判断错误信息是否为 access_token 失效的问题,只有access失效,我们才重新获取,否则每次获取可能导致access_token资源耗尽
  418. if (jsonObject.getInt("errcode") != 40001)
  419. return jsonObject;
  420. // 强制刷新access_token,存在多线程安全隐患!
  421. String requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.appId).replace("APPSECRET", NameUtils.appSecret);
  422. JSONObject accessTokenJsonObject = httpRequest(requestUrl, "GET", null);
  423. // 报错access_token信息,不管报错与否
  424. Token wechatToken = new Token();
  425. wechatToken.setEndDate(new Date(new Date().getTime() + 6500000));
  426. wechatToken.setAppid(NameUtils.pubNo);
  427. wechatToken.setToken(accessTokenJsonObject.getString("access_token"));
  428. tokenService.updateToken(wechatToken);
  429. StaticCacheMemory.setAccessToken(wechatToken);
  430. // 再次获取用户信息
  431. return httpRequest(NameUtils.userinfo_url.replaceAll("ACCESS_TOKEN", wechatToken.getToken()).replaceAll("OPENID", openid), "GET", null);
  432. }
  433. return jsonObject;
  434. }
  435. /**
  436. * 发送模板消息 appId 公众账号的唯一标识 appSecret 公众账号的密钥 openId 用户标识
  437. */
  438. public boolean sendTemplateMessage(String appId, String appSecret,
  439. String openId, String template_id, Object data, String redirectUrl) {
  440. // 准备数据
  441. AccessToken token = this.getAccessToken(appId, appSecret);
  442. String access_token = token.getToken();
  443. String url = sendTemplate + access_token;
  444. //json格式转换
  445. String jsonObj = JSONObject.fromObject(data).toString();
  446. data = "="+jsonObj.toString()+"=";
  447. // 准备模版
  448. ITTempLate temp = new ITTempLate();
  449. temp.setTouser(openId);
  450. temp.setTemplate_id(template_id);
  451. temp.setUrl(redirectUrl);
  452. temp.setData(data);
  453. // format
  454. String jsonString = JSONObject.fromObject(temp).toString();
  455. jsonString = jsonString.replace("\"=", "").replace("=\"", "").replaceAll("\\\\", "");
  456. // send
  457. JSONObject jsonObject = httpRequest(url, "POST", jsonString);
  458. if (null == jsonObject) {
  459. return false;
  460. }
  461. if (0 != jsonObject.getInt("errcode")) {
  462. logger.info("错误 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
  463. return false;
  464. }
  465. return true;
  466. }
  467. }