WeChatServiceImpl.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. package com.cyksj.service.wechat.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.CharsetUtil;
  5. import cn.hutool.core.util.RandomUtil;
  6. import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
  7. import cn.hutool.json.JSONObject;
  8. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  9. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  10. import com.cyksj.common.exception.BusinessRuntimeException;
  11. import com.cyksj.common.util.*;
  12. import com.cyksj.config.BaseWeChatConfig;
  13. import com.cyksj.config.WeChatConfig;
  14. import com.cyksj.config.WeChatFactory;
  15. import com.cyksj.enums.WeChatAuthRedirectUrl;
  16. import com.cyksj.mapper.WxAppMapper;
  17. import com.cyksj.mapper.channel.ShopConfigMapper;
  18. import com.cyksj.model.dto.*;
  19. import com.cyksj.model.entity.ShopConfig;
  20. import com.cyksj.model.entity.WxApp;
  21. import com.cyksj.model.response.WxGzhQrCodeTicketRep;
  22. import com.cyksj.redis.RedisService;
  23. import com.cyksj.service.wechat.WeChatService;
  24. import com.cyksj.service.wechat.WxMpAccountOps;
  25. import lombok.RequiredArgsConstructor;
  26. import lombok.extern.slf4j.Slf4j;
  27. import org.apache.commons.lang3.StringUtils;
  28. import org.apache.logging.log4j.util.Strings;
  29. import org.springframework.stereotype.Service;
  30. import javax.crypto.Mac;
  31. import javax.crypto.spec.SecretKeySpec;
  32. import javax.net.ssl.KeyManagerFactory;
  33. import java.io.File;
  34. import java.io.FileInputStream;
  35. import java.io.InputStream;
  36. import java.math.BigDecimal;
  37. import java.net.URLEncoder;
  38. import java.net.http.HttpRequest;
  39. import java.net.http.HttpResponse;
  40. import java.security.KeyStore;
  41. import java.util.Arrays;
  42. import java.util.Date;
  43. import java.util.Map;
  44. import java.util.Optional;
  45. /**
  46. * @author chan
  47. * @description
  48. * @ClassName WeChatServiceImpl
  49. * @date 2020-08-05 11:00 上午
  50. */
  51. @Slf4j
  52. @Service
  53. @RequiredArgsConstructor
  54. public class WeChatServiceImpl implements WeChatService {
  55. private final WeChatConfig weChatConfig;
  56. private final WxMpAccountOps wxMpAccountOps;
  57. private final WxAppMapper wxAppMapper;
  58. private final ShopConfigMapper shopConfigMapper;
  59. private final RedisService redisService;
  60. @Override
  61. public String createUrl(String state, String redirectUrl) {
  62. return createUrl(state,redirectUrl,weChatConfig.getAppid(), WeChatAuthRedirectUrl.BASE.getScope());
  63. }
  64. @Override
  65. public String createUrl(String state, String redirectUrl,String appId,String scope) {
  66. return "https://open.weixin.qq.com/connect/oauth2/authorize" +
  67. "?appid=" + appId +
  68. "&redirect_uri=" + URLEncoder.encode(redirectUrl, IoKit.Charsets.UTF_8.getCharset()) +
  69. "&response_type=code" +
  70. "&scope=" + scope +
  71. "&state=" + state + "#wechat_redirect";
  72. }
  73. @Override
  74. public AuthToken getAuthToken(String code) throws Exception {
  75. return getAuthToken(code, weChatConfig.getAppid());
  76. }
  77. public String getSecret(String appId){
  78. WxApp wxApp = wxAppMapper.selectOne(new QueryWrapper<WxApp>().lambda().eq(WxApp::getAppId, appId));
  79. if (Optional.ofNullable(wxApp).isPresent()) {
  80. return wxApp.getAppSecret();
  81. }else {
  82. return Strings.EMPTY;
  83. }
  84. }
  85. @Override
  86. public AuthToken getAuthToken(String code,String appId) throws Exception {
  87. String secret = getSecret(appId);
  88. if(StringUtils.isBlank(secret)){
  89. throw BusinessRuntimeException.getInstance("授权公众号不存在!");
  90. }
  91. String url = "https://api.weixin.qq.com/sns/oauth2/access_token" +
  92. "?appid=" + appId +
  93. "&secret=" + secret +
  94. "&code=" + code +
  95. "&grant_type=authorization_code";
  96. HttpResponse<byte[]> res =
  97. J11HttpC.custom()
  98. .ofGet()
  99. .url(url)
  100. .send(HttpResponse.BodyHandlers.ofByteArray());
  101. if (200 != res.statusCode()) {
  102. throw BusinessRuntimeException.getInstance("请求user_token失败: " + IoKit.toString(res.body()));
  103. }
  104. return Jsons.parseObject(res.body(), AuthToken.class);
  105. }
  106. @Override
  107. public GzhOAuth2UserInfo getUserInfo(String accessToken, String openid) throws Exception {
  108. String url = "https://api.weixin.qq.com/sns/userinfo" +
  109. "?access_token=" + accessToken +
  110. "&openid=" + openid +
  111. "&lang=zh_CN";
  112. HttpResponse<byte[]> res =
  113. J11HttpC.custom()
  114. .ofGet()
  115. .url(url)
  116. .send(HttpResponse.BodyHandlers.ofByteArray());
  117. if (200 != res.statusCode()) {
  118. throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body()));
  119. }
  120. return Jsons.parseObject(res.body(), GzhOAuth2UserInfo.class);
  121. }
  122. @Override
  123. public GzhUnionidUserinfo getUserInfo2(String accessToken, String openid) throws Exception {
  124. String url = "https://api.weixin.qq.com/cgi-bin/user/info" +
  125. "?access_token=" + accessToken +
  126. "&openid=" + openid +
  127. "&lang=zh_CN";
  128. HttpResponse<byte[]> res =
  129. J11HttpC.custom()
  130. .ofGet()
  131. .url(url)
  132. .send(HttpResponse.BodyHandlers.ofByteArray());
  133. if (200 != res.statusCode()) {
  134. throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body()));
  135. }
  136. GzhUnionidUserinfo info = Jsons.parseObject(res.body(), GzhUnionidUserinfo.class);
  137. if (null == info.getSubscribe() || info.getSubscribe() == 0) {
  138. return null;
  139. }
  140. return info;
  141. }
  142. @Override
  143. public String getJsTicket() throws Exception {
  144. return getJsTicket(null);
  145. }
  146. @Override
  147. public String getJsTicket(String appId) throws Exception {
  148. appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid());
  149. String jsTicket = wxMpAccountOps.jsTicket(appId);
  150. if (StringUtils.isBlank(jsTicket)) {
  151. throw BusinessRuntimeException.getInstance("获取用户信息失败");
  152. }
  153. return jsTicket;
  154. }
  155. @Override
  156. public String getAccessToken() throws Exception {
  157. return getAccessToken(null);
  158. }
  159. @Override
  160. public String getAccessToken(String appId) throws Exception {
  161. appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid());
  162. String accessToken = wxMpAccountOps.accessToken(appId);
  163. log.info("------------------accessToken:{} \n",accessToken);
  164. if (StringUtils.isBlank(accessToken)) {
  165. throw BusinessRuntimeException.getInstance("获取用户信息失败:");
  166. }
  167. return accessToken;
  168. }
  169. @Override
  170. public H5JsPayParams getJsPayParams(String appid, String shopId, String openid, Integer totalFee, String outTradeNo, String notifyUrl, String attach, String body, String tradeType, Date expireTime) throws Exception {
  171. log.info("------------------- 调用微信h5支付 开始 -------------------");
  172. ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1"));
  173. // 拼接统一下单实体类
  174. UnifiedOrder order = new UnifiedOrder()
  175. .setAppid(appid)
  176. .setAttach(attach)
  177. .setBody(body)
  178. .setMchId(shopConfig.getAppId())
  179. .setNonceStr("chan.123")
  180. .setNotifyUrl(notifyUrl)
  181. .setOutTradeNo(outTradeNo)
  182. .setSpbillCreateIp("127.0.0.1")
  183. .setTotalFee(totalFee)
  184. .setTradeType(tradeType);
  185. if (expireTime != null) {
  186. //5分钟过期
  187. order.setTimeExpire(DateUtil.format(DateUtil.offsetMinute(expireTime, 5), "yyyyMMddHHmmss"));
  188. }
  189. if (!"NATIVE".equals(tradeType)) {
  190. order.setOpenid(openid);
  191. }
  192. String sign = Codec.DoDigest.custom()
  193. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  194. .setStringData(order.getPrintln(tradeType, shopConfig.getSecret()))
  195. .toHexString()
  196. .toUpperCase();
  197. order.setSign(sign);
  198. // xml
  199. String xml = Xmls.toXml(order);
  200. log.info("调用微信h5支付, 请求参数: \n{}", xml);
  201. HttpResponse<byte[]> res =
  202. J11HttpC.custom()
  203. .ofPost()
  204. .url("https://api.mch.weixin.qq.com/pay/unifiedorder")
  205. .cacheURI()
  206. .headers(J11HttpC.ReqType.raw_xml)
  207. .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset()))
  208. .send(HttpResponse.BodyHandlers.ofByteArray());
  209. if (200 != res.statusCode()) {
  210. throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body()));
  211. }
  212. Map<String, String> wxResult = Xmls.toMap(res.body());
  213. log.info("微信返回值 xml转map: \n{}", wxResult);
  214. if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  215. throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult);
  216. }
  217. if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  218. throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]");
  219. }
  220. if (StringUtils.isBlank(wxResult.get("prepay_id"))) {
  221. throw BusinessRuntimeException.getInstance("无法获取prepay_id.");
  222. }
  223. // 校验签名
  224. boolean f = this.checkSignWithMd5(wxResult,shopConfig.getSecret());
  225. if (!f) {
  226. throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败.");
  227. }
  228. H5JsPayParams params = new H5PayParams();
  229. params.setAppId(appid);
  230. params.setNonceStr(order.getNonceStr());
  231. params.setSignType("MD5");
  232. params.setTimestamp(System.currentTimeMillis());
  233. params.setPrepayId("prepay_id=" + wxResult.get("prepay_id"));
  234. sign = Codec.DoDigest.custom()
  235. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  236. .setStringData(params.println(shopConfig.getSecret()))
  237. .toHexString()
  238. .toUpperCase();
  239. params.setPaySign(sign);
  240. //二维码链接
  241. params.setCodeUrl(wxResult.get("code_url"));
  242. log.info("------------------- 调用微信h5支付 结束 -------------------");
  243. return params;
  244. }
  245. @Override
  246. public boolean checkSignWithMd5(Map<String, String> map) throws Exception {
  247. return checkSignWithMd5(map,weChatConfig.getShopKey());
  248. }
  249. @Override
  250. public boolean checkSignWithMd5(Map<String, String> map,String shopKey) throws Exception {
  251. // 将map的key按ASCii升序排列
  252. String[] keys = map.keySet().toArray(new String[0]);
  253. Arrays.sort(keys);
  254. // 将参数有序排列
  255. StringBuilder str = new StringBuilder();
  256. for (int i = 0; i < keys.length; i++) {
  257. if (StringUtils.equals(keys[i], "sign")) {
  258. continue;
  259. }
  260. if (StringUtils.isBlank(map.get(keys[i]))) {
  261. continue;
  262. }
  263. str.append(keys[i]).append("=").append(map.get(keys[i])).append("&");
  264. }
  265. str.append("key=").append(shopKey);
  266. log.info("签名验证, 排列好的有序字符串: {}", str.toString());
  267. String sign = Codec.DoDigest.custom().setAlgorithm(Codec.DoDigest.Algorithm.MD5).setStringData(str.toString()).toHexString().toUpperCase();
  268. return StringUtils.equals(map.get("sign"), sign);
  269. }
  270. @Override
  271. public H5PayParams getH5PayParams(String appid, Integer totalFee, String outTradeNo, String notifyUrl, String attach, String ip,String body) throws Exception {
  272. log.info("------------------- 调用微信h5支付 开始 -------------------");
  273. BaseWeChatConfig weChatConfigStrategy = WeChatFactory.getWeChatConfigStrategy(appid);
  274. // 拼接统一下单实体类
  275. UnifiedOrder order = new UnifiedOrder();
  276. order.setAppid(appid);
  277. order.setAttach(attach);
  278. order.setBody(body);
  279. order.setMchId(weChatConfigStrategy.getShopid());
  280. order.setNonceStr("chan123");
  281. order.setNotifyUrl(notifyUrl);
  282. order.setOutTradeNo(outTradeNo);
  283. order.setSpbillCreateIp(ip);
  284. order.setTotalFee(totalFee);
  285. order.setTradeType("MWEB");
  286. String sceneInfo= "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"" + weChatConfigStrategy.getDomain() +"\",\"wap_name\": \"善行\"}}";
  287. order.setSceneInfo(sceneInfo);
  288. // order.setSignType("MD5");
  289. String sign = Codec.DoDigest.custom()
  290. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  291. .setStringData(order.H5Println(weChatConfigStrategy.getShopKey()))
  292. .toHexString()
  293. .toUpperCase();
  294. order.setSign(sign);
  295. // xml
  296. String xml = Xmls.toXml(order);
  297. log.info("调用微信h5支付, 请求参数: \n{}", xml);
  298. HttpResponse<byte[]> res =
  299. J11HttpC.custom()
  300. .ofPost()
  301. .url("https://api.mch.weixin.qq.com/pay/unifiedorder")
  302. .cacheURI()
  303. .headers(J11HttpC.ReqType.raw_xml)
  304. .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset()))
  305. .send(HttpResponse.BodyHandlers.ofByteArray());
  306. if (200 != res.statusCode()) {
  307. throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body()));
  308. }
  309. Map<String, String> wxResult = Xmls.toMap(res.body());
  310. log.info("微信返回值 xml转map: \n{}", wxResult);
  311. if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  312. throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult);
  313. }
  314. if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  315. throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]");
  316. }
  317. if (StringUtils.isBlank(wxResult.get("prepay_id"))) {
  318. throw BusinessRuntimeException.getInstance("无法获取prepay_id.");
  319. }
  320. // 校验签名
  321. boolean f = this.checkSignWithMd5(wxResult,weChatConfigStrategy.getShopKey());
  322. if (!f) {
  323. throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败.");
  324. }
  325. H5PayParams params = new H5PayParams();
  326. params.setAppId(appid);
  327. params.setNonceStr(order.getNonceStr());
  328. params.setSignType("MD5");
  329. params.setTimestamp(System.currentTimeMillis());
  330. params.setPrepayId("prepay_id=" + wxResult.get("prepay_id"));
  331. params.setMWebUrl(wxResult.get("mweb_url"));
  332. sign = Codec.DoDigest.custom()
  333. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  334. .setStringData(params.println(weChatConfigStrategy.getShopKey()))
  335. .toHexString()
  336. .toUpperCase();
  337. params.setPaySign(sign);
  338. log.info("------------------- 调用微信h5支付 结束 -------------------");
  339. return params;
  340. }
  341. @Override
  342. public void sendTemplateMessage(String accessToken, String json) throws Exception{
  343. String url = "https://api.weixin.qq.com/cgi-bin/message/template/send" +
  344. "?access_token=" + accessToken;
  345. HttpResponse<String> send = J11HttpC.custom()
  346. .ofPost()
  347. .url(url)
  348. .headers(J11HttpC.ReqType.raw_json)
  349. .body(HttpRequest.BodyPublishers.ofString(json))
  350. .send(HttpResponse.BodyHandlers.ofString());
  351. log.info("发送模板消息结束,返回值:{}", send != null ? send.body() : null);
  352. }
  353. @Override
  354. public void refundWxOrder(String appId,String shopId, String transactionId, String outRefundNo, BigDecimal totalFee, BigDecimal refundMoney, String notifyUrl) throws Exception {
  355. ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1"));
  356. WxRefundOrder wxRefundOrder = new WxRefundOrder();
  357. wxRefundOrder.setAppid(appId);
  358. //商户号
  359. wxRefundOrder.setMchId(shopConfig.getAppId());
  360. wxRefundOrder.setOutRefundNo(outRefundNo);
  361. wxRefundOrder.setNonceStr("chan123");
  362. wxRefundOrder.setTotalFee(totalFee.intValue());
  363. //退款金额
  364. wxRefundOrder.setRefundFee(refundMoney.intValue());
  365. wxRefundOrder.setTransactionId(transactionId);
  366. // wxRefundOrder.setRefundDesc("订单退款");
  367. wxRefundOrder.setNotifyUrl(notifyUrl);
  368. String sign = Codec.DoDigest.custom()
  369. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  370. .setStringData(wxRefundOrder.println(shopConfig.getSecret()))
  371. .toHexString()
  372. .toUpperCase();
  373. wxRefundOrder.setSign(sign);
  374. String xml = Xmls.toXml(wxRefundOrder);
  375. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  376. File file = new File("cert/" + shopId + ".p12");
  377. InputStream inputStream = new FileInputStream(file);
  378. keyStore.load(inputStream, shopId.toCharArray());
  379. // 初始化密钥库
  380. KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  381. factory.init(keyStore, shopId.toCharArray());
  382. cn.hutool.http.HttpRequest request = cn.hutool.http.HttpRequest.post("https://api.mch.weixin.qq.com/secapi/pay/refund")
  383. // 自定义返回编码
  384. .charset(CharsetUtil.CHARSET_UTF_8)
  385. // 禁用缓存
  386. .disableCache()
  387. .setSSLSocketFactory(SSLSocketFactoryBuilder.create()
  388. .setKeyManagers(factory.getKeyManagers())
  389. .setProtocol(SSLSocketFactoryBuilder.TLSv1).build());
  390. cn.hutool.http.HttpResponse res = request.body(xml).execute();
  391. if (200 != res.getStatus()) {
  392. throw BusinessRuntimeException.getInstance("请求wx_refund失败: " + res.body());
  393. }
  394. Map<String, String> wxRefund = Xmls.toMap(res.body());
  395. log.info("微信返回值 xml 转 map: \n{}", wxRefund);
  396. if (!StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") || !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) {
  397. throw BusinessRuntimeException.getInstance("调用微信申请退款失败. 微信返回: " + wxRefund);
  398. }
  399. if (StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") && !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) {
  400. throw BusinessRuntimeException.getInstance("订单异常: " + wxRefund.get("err_code") + "[" + wxRefund.get("err_code_des") + "]");
  401. }
  402. // 校验签名
  403. boolean f = this.checkSignWithMd5(wxRefund, shopConfig.getSecret());
  404. if (!f) {
  405. throw BusinessRuntimeException.getInstance("调用微信h5申请退款, 微信返回值的签名验证失败.");
  406. }
  407. log.info("------------------- 调用微信申请退款 结束 -------------------");
  408. }
  409. @Override
  410. public WxGzhQrCodeTicketRep getWxGzhQrCode(Long sharedId, Long popularizeId) throws Exception {
  411. String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken());
  412. //scene_str:场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
  413. String scene_str = RandomUtil.randomString(16);
  414. DateTime dateTime = DateUtil.offsetSecond(DateTime.now(), 60);
  415. String body = String.format("{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
  416. 60,
  417. "QR_STR_SCENE",
  418. scene_str);
  419. HttpResponse<String> response = J11HttpC.custom()
  420. .url(gzhQrCodeUrl)
  421. .ofPost()
  422. .body(HttpRequest.BodyPublishers.ofString(body))
  423. .send(HttpResponse.BodyHandlers.ofString());
  424. if (200 != response.statusCode()) {
  425. throw BusinessRuntimeException.getInstance("获取微信登录二维码失败: " + response.body());
  426. }
  427. GzhBodyDto gzhBodyDto = new GzhBodyDto()
  428. .setSharedId(sharedId)
  429. .setPopularizeId(popularizeId);
  430. if (sharedId != null) {
  431. redisService.set(RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getNameFormat(scene_str), Jsons.toJson(gzhBodyDto), RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getTimeout());
  432. }
  433. JSONObject re = Jsons.parseObject(response.body(), JSONObject.class);
  434. String ticket = re.getStr("ticket");
  435. WxGzhQrCodeTicketRep wxGzhQrCodeTicketRep = new WxGzhQrCodeTicketRep();
  436. wxGzhQrCodeTicketRep.setQrCodeUrl(String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket));
  437. wxGzhQrCodeTicketRep.setSceneStr(scene_str);
  438. wxGzhQrCodeTicketRep.setExpireTime(dateTime.getTime());
  439. return wxGzhQrCodeTicketRep;
  440. }
  441. @Override
  442. public String getNotExpiredWxGzhQrCode(String sceneKey) throws Exception {
  443. String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken());
  444. String body = String.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
  445. "QR_LIMIT_STR_SCENE",
  446. sceneKey);
  447. HttpResponse<String> response = J11HttpC.custom()
  448. .url(gzhQrCodeUrl)
  449. .ofPost()
  450. .body(HttpRequest.BodyPublishers.ofString(body))
  451. .send(HttpResponse.BodyHandlers.ofString());
  452. if (200 != response.statusCode()) {
  453. throw BusinessRuntimeException.getInstance("获取永久渠道二维码失败: " + response.body());
  454. }
  455. JSONObject re = Jsons.parseObject(response.body(), JSONObject.class);
  456. String ticket = re.getStr("ticket");
  457. String url = String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket);
  458. return url;
  459. }
  460. /**
  461. * sha256_HMAC加密
  462. * @param message 消息
  463. * @param secret 秘钥
  464. * @return 加密后字符串
  465. */
  466. public static String sha256_HMAC(String message,String secret) {
  467. String hash = "";
  468. try {
  469. Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
  470. SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(IoKit.Charsets.UTF_8.getCharset()), "HmacSHA256");
  471. sha256_HMAC.init(secret_key);
  472. byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
  473. hash = byteArrayToHexString(bytes);
  474. } catch (Exception e) {
  475. System.out.println("Error HmacSHA256 ===========" + e.getMessage());
  476. }
  477. return hash;
  478. }
  479. /**
  480. * 将加密后的字节数组转换成字符串
  481. *
  482. * @param b 字节数组
  483. * @return 字符串
  484. */
  485. public static String byteArrayToHexString(byte[] b) {
  486. StringBuilder hs = new StringBuilder();
  487. String stmp;
  488. for (int n = 0; b!=null && n < b.length; n++) {
  489. stmp = Integer.toHexString(b[n] & 0XFF);
  490. if (stmp.length() == 1) {
  491. hs.append('0');
  492. }
  493. hs.append(stmp);
  494. }
  495. return hs.toString().toLowerCase();
  496. }
  497. }