WeChatUtil.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. package com.ylx.massage.utils;
  2. /**
  3. * @author jianlong
  4. * @date 2024-06-04 10:13
  5. */
  6. import cn.hutool.http.HttpUtil;
  7. import cn.hutool.json.JSONArray;
  8. import cn.hutool.json.JSONUtil;
  9. import com.alibaba.fastjson2.JSONObject;
  10. import com.ylx.common.config.WechatAccountConfig;
  11. import com.ylx.common.constant.MassageConstants;
  12. import com.ylx.common.core.redis.RedisCache;
  13. import com.ylx.common.exception.ServiceException;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Component;
  17. import org.w3c.dom.Node;
  18. import org.w3c.dom.NodeList;
  19. import javax.annotation.Resource;
  20. import javax.xml.parsers.DocumentBuilder;
  21. import javax.xml.parsers.DocumentBuilderFactory;
  22. import javax.xml.transform.OutputKeys;
  23. import javax.xml.transform.Transformer;
  24. import javax.xml.transform.TransformerFactory;
  25. import javax.xml.transform.dom.DOMSource;
  26. import javax.xml.transform.stream.StreamResult;
  27. import java.io.ByteArrayInputStream;
  28. import java.io.InputStream;
  29. import java.io.StringWriter;
  30. import java.io.UnsupportedEncodingException;
  31. import java.net.URLEncoder;
  32. import java.util.HashMap;
  33. import java.util.Map;
  34. import java.util.concurrent.TimeUnit;
  35. /**
  36. * 微信登录工具类
  37. */
  38. @Component
  39. @Slf4j
  40. public class WeChatUtil {
  41. private final static String ERROR_CODE = "errcode";
  42. @Resource
  43. private WechatAccountConfig wxPayProperties;
  44. @Autowired
  45. private RedisCache redisCache;
  46. /**
  47. * 获取微信授权code
  48. *
  49. * @param state state信息
  50. * @return String 返回微信授权code的URL
  51. */
  52. public String getCode(String state) {
  53. try {
  54. StringBuffer url = new StringBuffer();
  55. url.append(wxPayProperties.getGetCodeUrl())
  56. .append("?appid=")
  57. .append(wxPayProperties.getMpAppId())
  58. .append("&redirect_uri=")
  59. .append(URLEncoder.encode(wxPayProperties.getRedirectUrl(), "UTF-8"))
  60. // .append(URLEncoder.encode("https://city.baoxianzhanggui.com/fragrance/#/pages/my/wxLogin", "UTF-8"))
  61. .append("&response_type=code&scope=snsapi_userinfo&state=")
  62. .append("STATE")
  63. .append("#wechat_redirect");
  64. return url.toString();
  65. } catch (UnsupportedEncodingException e) {
  66. throw new ServiceException("URL格式化异常");
  67. }
  68. }
  69. /**
  70. * 获取微信AccessToken
  71. *
  72. * @param code 用户code
  73. * @return Map<?, ?> 返回包含微信AccessToken的Map
  74. */
  75. public Map<?, ?> getAccessToken(String code) {
  76. StringBuffer url = new StringBuffer();
  77. url.append(wxPayProperties.getAccessTokenUrl())
  78. .append("?appid=").append(wxPayProperties.getMpAppId())
  79. .append("&secret=").append(wxPayProperties.getMpAppSecret())
  80. .append("&code=").append(code)
  81. .append("&grant_type=authorization_code");
  82. log.info("URL的值:{}", url.toString());
  83. String rs = HttpUtil.get(url.toString());
  84. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  85. if (null == map.get(ERROR_CODE)) {
  86. return map;
  87. } else {
  88. log.error("getAccessToken-获取access_token出错:{}", map);
  89. throw new ServiceException("获取access_token出错");
  90. }
  91. }
  92. /**
  93. * 获取微信AccessToken
  94. *
  95. * @return String 返回包含微信AccessToken的Map
  96. */
  97. public String getToken() {
  98. if (redisCache.getCacheObject(MassageConstants.ACCESS_TOKEN_KEY + wxPayProperties.getMpAppId()) != null) {
  99. return redisCache.getCacheObject(MassageConstants.ACCESS_TOKEN_KEY + wxPayProperties.getMpAppId());
  100. }
  101. StringBuffer url = new StringBuffer();
  102. url.append("https://api.weixin.qq.com/cgi-bin/token")
  103. .append("?grant_type=").append("client_credential")
  104. .append("&appid=").append(wxPayProperties.getMpAppId())
  105. .append("&secret=").append(wxPayProperties.getMpAppSecret());
  106. log.info("URL的值:{}", url.toString());
  107. String rs = HttpUtil.get(url.toString());
  108. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  109. log.info("map的值:{}", map);
  110. if (null == map.get(ERROR_CODE)) {
  111. redisCache.setCacheObject(MassageConstants.ACCESS_TOKEN_KEY + wxPayProperties.getMpAppId(), map.get("access_token").toString(), 1, TimeUnit.HOURS);
  112. return map.get("access_token").toString();
  113. } else {
  114. log.error("getToken-获取access_token出错:{}", map);
  115. throw new ServiceException("getToken-获取access_token出错");
  116. }
  117. }
  118. /**
  119. * 获取新增关注数量
  120. *
  121. * @return 返回包含增关注数量
  122. */
  123. public String getFollowers(String beginDate, String endDate) {
  124. String token = this.getToken();
  125. String urlStr = "https://api.weixin.qq.com/datacube/getusersummary?access_token=" + token;
  126. cn.hutool.json.JSONObject param1 = JSONUtil.createObj();
  127. param1.set("begin_date", beginDate);
  128. param1.set("end_date", endDate);
  129. String result = HttpUtil.post(urlStr, param1.toString());
  130. log.info("getFollowers-获取关注量请求参数:{}", param1);
  131. cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(result);
  132. log.info("getFollowers-获取关注量返回:{}", jsonObject);
  133. Object listObj = jsonObject.get("list");
  134. if (listObj == null || JSONUtil.isNull(listObj)) {
  135. return "0";
  136. }
  137. JSONArray list;
  138. try {
  139. if (listObj instanceof JSONArray) {
  140. list = (JSONArray) listObj;
  141. } else if (listObj instanceof String) {
  142. list = JSONUtil.parseArray(listObj.toString());
  143. } else if (listObj.getClass().getName().contains("ArrayList")) {
  144. // 如果是 FastJSON 返回的 ArrayList,转换为 JSON 字符串后再解析
  145. String jsonArrayStr = JSONUtil.toJsonStr(listObj);
  146. list = JSONUtil.parseArray(jsonArrayStr);
  147. } else {
  148. throw new ServiceException("返回数据格式错误,list字段无法识别");
  149. }
  150. } catch (Exception e) {
  151. log.error("解析list字段失败: {}", listObj, e);
  152. return "0";
  153. }
  154. if (list.isEmpty()) {
  155. return "0";
  156. }
  157. cn.hutool.json.JSONObject jsonObjectItem = list.getJSONObject(0);
  158. return jsonObjectItem.getStr("new_user");
  159. }
  160. /**
  161. * 获取jsticke
  162. */
  163. public String getJsapiTicket(String token) {
  164. //todo 缓存公众号全局token凭证
  165. StringBuffer url = new StringBuffer();
  166. url.append("https://api.weixin.qq.com/cgi-bin/ticket/getticket")
  167. .append("?access_token=").append(token)
  168. .append("&type=").append("jsapi");
  169. String rs = HttpUtil.get(url.toString());
  170. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  171. if ("ok".equals(map.get("errmsg"))) {
  172. return map.get("ticket").toString();
  173. } else {
  174. log.error("获取jsticke出错:{}", map);
  175. throw new ServiceException("获取jsticke出错");
  176. }
  177. }
  178. /**
  179. * 刷新AccessToken
  180. *
  181. * @param refreshToken 用户刷新token
  182. * @return 返回包含刷新后的微信AccessToken的Map
  183. */
  184. public Map<?, ?> refreshToken(String refreshToken) {
  185. StringBuffer url = new StringBuffer();
  186. url.append("https://api.weixin.qq.com/sns/oauth2/refresh_token")
  187. .append("?appid=").append(wxPayProperties.getMpAppId())
  188. .append("&grant_type=refresh_token&refresh_token=").append(refreshToken);
  189. String rs = HttpUtil.get(url.toString());
  190. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  191. if (null == map.get(ERROR_CODE)) {
  192. return map;
  193. } else {
  194. log.error("刷新access_token出错:{}", map);
  195. throw new ServiceException("刷新access_token出错");
  196. }
  197. }
  198. /**
  199. * 获取用户信息
  200. *
  201. * @param accessToken 微信AccessToken
  202. * @param openid 用户的openid
  203. * @return 返回包含用户信息的JSON字符串
  204. */
  205. public String getUserInfo(String accessToken, String openid) {
  206. StringBuffer url = new StringBuffer();
  207. url.append("https://api.weixin.qq.com/sns/userinfo")
  208. .append("?access_token=").append(accessToken)
  209. .append("&openid=").append(openid)
  210. .append("&lang=zh_CN");
  211. return HttpUtil.get(url.toString());
  212. }
  213. /**
  214. * 生成公众号码ticket
  215. *
  216. * @param token access_token
  217. * @param openId
  218. * @return Map<?, ?> 返回包含微信服务号ticket的Map
  219. */
  220. public Map<?, ?> getTicket(String token, String openId) {
  221. String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + token;
  222. String param = "{\"action_name\": \"QR_LIMIT_STR_SCENE\",\"action_info\": {\"scene\": {\"scene_str\": \"" + openId + "\"}}}";
  223. String rs = HttpUtil.post(url, param);
  224. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  225. // String ticket = map.get("ticket").toString();
  226. //
  227. // String encode = null;
  228. // try {
  229. // encode = URLEncoder.encode(ticket, "UTF-8");
  230. // } catch (UnsupportedEncodingException e) {
  231. // throw new RuntimeException(e);
  232. // }
  233. //
  234. // String url1 = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+ encode;
  235. //
  236. // String s = HttpUtil.get(url1);
  237. //
  238. //
  239. //
  240. //
  241. // Map<?, ?> rsmap = JSONObject.parseObject(s, Map.class);
  242. // return rsmap;
  243. return map;
  244. }
  245. /**
  246. * 生成公众号码技师 ticket
  247. *
  248. * @param token access_token
  249. * @param jsId 技师id
  250. * @return 返回包含微信公众号码ticket的Map
  251. */
  252. public Map<?, ?> getJsTicket(String token, String jsId) {
  253. String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + token;
  254. String param = "{\"action_name\": \"QR_LIMIT_STR_SCENE\",\"action_info\": {\"scene\": {\"scene_str\": \"" + jsId + "\"}}}";
  255. String rs = HttpUtil.post(url, param);
  256. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  257. return map;
  258. }
  259. /**
  260. * 生成公众号码ticket
  261. *
  262. * @param token access_token
  263. * @return 返回包含微信公众号码ticket的Map
  264. */
  265. public Map<?, ?> menuUtil(String token) {
  266. String url = " https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + token;
  267. //String param = "{\"button\":[{\"type\":\"view\",\"name\":\"立即下单\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/\"},{\"type\":\"view\",\"name\":\"查看商户\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/#/pages/identify/identify\"},{\"name\":\"更多\",\"sub_button\":[{\"type\":\"view\",\"name\":\"技师招募\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/#/pages/join/first_join\"},{\"type\":\"view\",\"name\":\"招商合作\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/#/pages/join/teamwork\"},{\"type\":\"view\",\"name\":\"投诉举报\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/#/pages/join/feedback\"},{\"type\":\"view\",\"name\":\"了解我们\",\"url\":\"https://city.baoxianzhanggui.com/fragrance/#/pages/join/understand\"}]}]}";
  268. String param = wxPayProperties.getMenu();
  269. String rs = HttpUtil.post(url, param);
  270. Map<?, ?> map = JSONObject.parseObject(rs, Map.class);
  271. return map;
  272. }
  273. /**
  274. * 服务号消息通知
  275. *
  276. * @param openid 用户的openid
  277. * @param templateId 模板id
  278. * @param data 消息数据
  279. * @return Map<?, ?> 返回包含微信公众号码ticket的Map
  280. */
  281. public Map<?, ?> notification(String openid, String templateId, cn.hutool.json.JSONObject data) {
  282. String token = this.getToken();
  283. log.info("token的值:{}", token);
  284. log.info("模版ID的值:{}", templateId);
  285. String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + token;
  286. cn.hutool.json.JSONObject param1 = JSONUtil.createObj();
  287. param1.put("touser", openid);
  288. param1.put("template_id", templateId);
  289. param1.put("url", "https://test.baoxianzhanggui.com/fragrance/");
  290. param1.put("data", data);
  291. log.info("notification-消息通知请求参数:{}", param1.toString());
  292. String result = HttpUtil.post(url, param1.toString());
  293. Map<?, ?> map = JSONObject.parseObject(result, Map.class);
  294. log.info("notification-消息通知返回参数:{}", map);
  295. return map;
  296. }
  297. /**
  298. * XML格式字符串转换为Map
  299. *
  300. * @param strXML XML字符串
  301. * @return XML数据转换后的Map
  302. * @throws Exception
  303. */
  304. public static Map<String, String> xmlToMap(String strXML) throws Exception {
  305. try {
  306. Map<String, String> data = new HashMap<>();
  307. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  308. DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
  309. InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
  310. org.w3c.dom.Document doc = documentBuilder.parse(stream);
  311. doc.getDocumentElement().normalize();
  312. NodeList nodeList = doc.getDocumentElement().getChildNodes();
  313. for (int idx = 0; idx < nodeList.getLength(); ++idx) {
  314. Node node = nodeList.item(idx);
  315. if (node.getNodeType() == Node.ELEMENT_NODE) {
  316. org.w3c.dom.Element element = (org.w3c.dom.Element) node;
  317. data.put(element.getNodeName(), element.getTextContent());
  318. }
  319. }
  320. try {
  321. stream.close();
  322. } catch (Exception ex) {
  323. // do nothing
  324. }
  325. return data;
  326. } catch (Exception ex) {
  327. log.error("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
  328. throw ex;
  329. }
  330. }
  331. /**
  332. * 将Map转换为XML格式的字符串
  333. *
  334. * @param data Map类型数据
  335. * @return XML格式的字符串
  336. * @throws Exception
  337. */
  338. public static String mapToXml(Map<String, String> data) throws Exception {
  339. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  340. DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
  341. org.w3c.dom.Document document = documentBuilder.newDocument();
  342. org.w3c.dom.Element root = document.createElement("xml");
  343. document.appendChild(root);
  344. for (String key : data.keySet()) {
  345. String value = data.get(key);
  346. if (value == null) {
  347. value = "";
  348. }
  349. value = value.trim();
  350. org.w3c.dom.Element filed = document.createElement(key);
  351. filed.appendChild(document.createTextNode(value));
  352. root.appendChild(filed);
  353. }
  354. TransformerFactory tf = TransformerFactory.newInstance();
  355. Transformer transformer = tf.newTransformer();
  356. DOMSource source = new DOMSource(document);
  357. transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
  358. transformer.setOutputProperty(OutputKeys.INDENT, "yes");
  359. StringWriter writer = new StringWriter();
  360. StreamResult result = new StreamResult(writer);
  361. transformer.transform(source, result);
  362. String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
  363. try {
  364. writer.close();
  365. } catch (Exception ex) {
  366. }
  367. return output;
  368. }
  369. }