You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

416 lines
19 KiB

  1. package com.iformall.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.google.gson.Gson;
  6. import com.google.gson.GsonBuilder;
  7. import com.iformall.common.Result;
  8. import com.iformall.common.ResultData;
  9. import com.iformall.controller.base.BaseController;
  10. import com.iformall.domain.po.*;
  11. import com.iformall.enums.*;
  12. import com.iformall.service.*;
  13. import com.iformall.service.wechat.FmOpenService;
  14. import io.swagger.annotations.Api;
  15. import io.swagger.annotations.ApiOperation;
  16. import me.chanjar.weixin.common.error.WxErrorException;
  17. import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
  18. import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
  19. import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
  20. import me.chanjar.weixin.open.bean.WxOpenCreateResult;
  21. import me.chanjar.weixin.open.bean.WxOpenGetResult;
  22. import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage;
  23. import me.chanjar.weixin.open.bean.result.*;
  24. import org.apache.commons.lang3.StringUtils;
  25. import org.slf4j.Logger;
  26. import org.slf4j.LoggerFactory;
  27. import org.springframework.beans.factory.annotation.Autowired;
  28. import org.springframework.stereotype.Controller;
  29. import org.springframework.web.bind.annotation.*;
  30. import javax.servlet.http.HttpServletRequest;
  31. import javax.servlet.http.HttpServletResponse;
  32. import java.io.ByteArrayOutputStream;
  33. import java.io.IOException;
  34. import java.io.InputStream;
  35. import java.io.PrintWriter;
  36. import java.nio.charset.Charset;
  37. import java.util.Date;
  38. import java.util.Map;
  39. @Controller
  40. @RequestMapping("/wxOpen")
  41. @Api(description = "微信第三方开放平台回调相关接口")
  42. public class WechatCalllbackController extends BaseController {
  43. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  44. @Autowired
  45. protected FmOpenService openService;
  46. @Autowired
  47. private WxComponentVerifyTicketService componentVerifyTicketService;
  48. @Autowired
  49. private WxAuthorizerInfoService authorizerInfoService;
  50. @PostMapping(value = "/notify")
  51. @ApiOperation("接收ticket回调")
  52. @ResponseBody
  53. public String receiveTicket(HttpServletRequest request) throws IOException {
  54. logger.info("[" + getIpAddr() + "]接收微信回调 /wxOpen/notify");
  55. String timestamp = request.getParameter("timestamp");
  56. String nonce = request.getParameter("nonce");
  57. String signature = request.getParameter("signature");
  58. String encType = request.getParameter("encrypt_type");
  59. String msgSignature = request.getParameter("msg_signature");
  60. if (!StringUtils.equalsIgnoreCase("aes", encType)
  61. || !openService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {
  62. throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
  63. }
  64. String resultxml = getRequestXML(request);
  65. logger.info("\n接收微信回调:[signature=[{}], encType=[{}], msgSignature=[{}],"
  66. + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
  67. signature, encType, msgSignature, timestamp, nonce, resultxml);
  68. // aes加密的消息, 解密
  69. WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(resultxml,
  70. openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);
  71. this.logger.info("接收微信回调,消息解密后内容为:{} ", inMessage.toString());
  72. // 处理
  73. handleTicket(inMessage);
  74. return "success";
  75. }
  76. /**
  77. * 处理ticket消息
  78. * @param inMessage
  79. * @return
  80. */
  81. private void handleTicket(WxOpenXmlMessage inMessage) {
  82. try {
  83. String out = openService.getWxOpenComponentService().route(inMessage);
  84. this.logger.info("组装回复信息:{}", out);
  85. } catch (WxErrorException e) {
  86. this.logger.error("notify", e);
  87. }
  88. if (inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.COMPONENT_VERIFY_TICKET.getMessage())) {
  89. // 保存component_verify_ticket
  90. componentVerifyTicketService.saveOrUpdate(new WxComponentVerifyTicket() {
  91. {
  92. setComponentVerifyTicket(inMessage.getComponentVerifyTicket());
  93. setCreateTime(new Date(Long.valueOf(inMessage.getCreateTime() * 1000)));
  94. setComponentAppid(inMessage.getAppId());
  95. setDeadline(new Date(Long.valueOf(System.currentTimeMillis() + (60 * 60 * 1000))));
  96. }
  97. });
  98. } else if (inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.AUTHORIZED.getMessage()) ||
  99. inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.UPDATEAUTHORIZED.getMessage())) {
  100. // 审核通过
  101. handleWeappAuditedMessage(inMessage);
  102. } else if (inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.UNAUTHORIZED.getMessage())) {
  103. // 授权取消
  104. authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() {
  105. {
  106. setAuthorizerAppid(inMessage.getAuthorizerAppid());
  107. setAuthorizationStatus(EnumWxAuthorizationStatus.UNAUTHORIZED.getCode());
  108. setAuthTime(new Date());
  109. }
  110. });
  111. }
  112. }
  113. /**
  114. * 处理小程序审核消息
  115. * @param inMessage
  116. * @return
  117. */
  118. private boolean handleWeappAuditedMessage(WxOpenXmlMessage inMessage) {
  119. // 审核通过
  120. try {
  121. WxOpenQueryAuthResult queryAuthResult = openService.getWxOpenComponentService().getQueryAuth(inMessage.getAuthorizationCode());
  122. logger.info("getQueryAuth", queryAuthResult);
  123. WxOpenAuthorizerInfoResult openAuthorizerInfoResult = openService.getWxOpenComponentService().getAuthorizerInfo(
  124. queryAuthResult.getAuthorizationInfo().getAuthorizerAppid());
  125. logger.info(openAuthorizerInfoResult.toString());
  126. // save auth info
  127. authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() {
  128. {
  129. setAuthorizerAppid(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAppid());
  130. setAlias(openAuthorizerInfoResult.getAuthorizerInfo().getAlias());
  131. setHeadImg(openAuthorizerInfoResult.getAuthorizerInfo().getHeadImg());
  132. setQrcodeUrl(openAuthorizerInfoResult.getAuthorizerInfo().getQrcodeUrl());
  133. setRefreshToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerRefreshToken());
  134. setAccessToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAccessToken());
  135. setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + openAuthorizerInfoResult.getAuthorizationInfo().getExpiresIn()*1000)));
  136. setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode());
  137. setAuthTime(new Date());
  138. }
  139. });
  140. return true;
  141. } catch (WxErrorException e) {
  142. logger.error(e.getMessage());
  143. }
  144. return false;
  145. }
  146. @RequestMapping(value = "/{appId}/callback")
  147. @ApiOperation("消息回调")
  148. public void callback(@PathVariable("appId") String appId, HttpServletRequest request, HttpServletResponse response) throws IOException {
  149. logger.info("[" + getIpAddr() + "]接收微信APP回调 /{appId}/callback");
  150. String timestamp = request.getParameter("timestamp");
  151. String nonce = request.getParameter("nonce");
  152. String signature = request.getParameter("signature");
  153. String encType = request.getParameter("encrypt_type");
  154. String openid = request.getParameter("openid");
  155. String msgSignature = request.getParameter("msg_signature");
  156. // 第三方平台一定接收的是加密的消息
  157. if (!StringUtils.equalsIgnoreCase("aes", encType)
  158. || !openService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {
  159. throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
  160. }
  161. String resultxml = getRequestXML(request);
  162. logger.info("接收微信APP回调:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}],"
  163. + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
  164. appId, openid, signature, encType, msgSignature, timestamp, nonce, resultxml);
  165. String out = "";
  166. // aes加密的消息, 解密
  167. WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(resultxml,
  168. openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);
  169. logger.info("接收微信APP回调,消息解密后内容为:\n{}", inMessage.toString());
  170. // 全网发布测试用例
  171. if (StringUtils.equalsAnyIgnoreCase(appId, "wx570bc396a51b8ff8", "wxd101a85aa106f53e")) {
  172. handleWechatTest(appId, response, inMessage);
  173. PrintWriter printWriter = response.getWriter();
  174. wechatWriteSuccess(printWriter);
  175. return;
  176. }
  177. // 实际 消息 处理
  178. //handleCallbackMessage(appId, response, inMessage);
  179. WxMpXmlOutMessage outMessage = openService.getWxOpenMessageRouter().route(inMessage, appId);
  180. if(outMessage == null) {
  181. PrintWriter printWriter = response.getWriter();
  182. wechatWriteSuccess(printWriter);
  183. }
  184. PrintWriter printWriter = response.getWriter();
  185. wechatWriteSuccess(printWriter);
  186. }
  187. private void wechatWriteSuccess(PrintWriter printWriter) {
  188. printWriter.print("success");
  189. printWriter.flush();
  190. printWriter.close();
  191. }
  192. /**
  193. * 测试账号特殊处理
  194. * @param appId
  195. * @param response
  196. * @param inMessage
  197. * @throws IOException
  198. */
  199. private void handleWechatTest(@PathVariable("appId") String appId, HttpServletResponse response, WxMpXmlMessage inMessage) throws IOException {
  200. String out;
  201. try {
  202. if (StringUtils.equals(inMessage.getMsgType(), "text")) {
  203. if (StringUtils.equals(inMessage.getContent(), "TESTCOMPONENT_MSG_TYPE_TEXT")) {
  204. out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(
  205. WxMpXmlOutMessage.TEXT().content("TESTCOMPONENT_MSG_TYPE_TEXT_callback")
  206. .fromUser(inMessage.getToUser())
  207. .toUser(inMessage.getFromUser())
  208. .build(),
  209. openService.getWxOpenConfigStorage()
  210. );
  211. } else if (StringUtils.startsWith(inMessage.getContent(), "QUERY_AUTH_CODE:")) {
  212. String msg = inMessage.getContent().replace("QUERY_AUTH_CODE:", "") + "_from_api";
  213. WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(msg).toUser(inMessage.getFromUser()).build();
  214. openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);
  215. }
  216. } else if (StringUtils.equals(inMessage.getMsgType(), "event")) {
  217. WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(inMessage.getEvent() + "from_callback").toUser(inMessage.getFromUser()).build();
  218. openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);
  219. }
  220. } catch (WxErrorException e) {
  221. logger.error("callback", e);
  222. }
  223. }
  224. private String getRequestXML(HttpServletRequest request) throws IOException {
  225. InputStream inStream = request.getInputStream();
  226. ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
  227. byte[] buffer = new byte[1024];
  228. int len = 0;
  229. while ((len = inStream.read(buffer)) != -1) {
  230. outSteam.write(buffer, 0, len);
  231. }
  232. String resultxml = new String(outSteam.toByteArray(), Charset.forName("UTF-8"));
  233. logger.info(resultxml);
  234. outSteam.close();
  235. inStream.close();
  236. return resultxml;
  237. }
  238. private void getThreadCount( int i) {
  239. ThreadGroup group = Thread.currentThread().getThreadGroup();
  240. ThreadGroup topGroup = group;
  241. // 遍历线程组树,获取根线程组
  242. while (group != null) {
  243. topGroup = group;
  244. group = group.getParent();
  245. }
  246. // 激活的线程数加倍
  247. int activeCount = topGroup.activeCount();
  248. // copy into a list that is the exact size
  249. logger.info("Thread list size == " + i + " : " + activeCount);
  250. }
  251. @GetMapping(value = "/createOpenPlatform")
  252. @ApiOperation("创建微信开放平台")
  253. public ResultData createOpenPlatform(@RequestParam(value = "appId") String appId) {
  254. Gson gson = new GsonBuilder().create();
  255. try {
  256. WxOpenCreateResult ret = openService.getWxOpenComponentService().createOpenAccount(appId);
  257. logger.info(ret.toString());
  258. if (ret.isSuccess()) {
  259. WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId);
  260. if (authorizerInfo != null) {
  261. authorizerInfo.setId(authorizerInfo.getId());
  262. authorizerInfo.setOpenAppid(ret.getOpenAppid());
  263. authorizerInfoService.updateOpenAppid(authorizerInfo);
  264. }
  265. }
  266. return new ResultData(ret.toString());
  267. } catch (WxErrorException e) {
  268. logger.error(e.getMessage());
  269. return new ResultData(Result.ERROR, e.getMessage());
  270. }
  271. }
  272. @GetMapping(value = "/bindOpenPlatform")
  273. @ApiOperation("绑定微信开放平台")
  274. public ResultData bindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) {
  275. Gson gson = new GsonBuilder().create();
  276. try {
  277. Boolean ret = openService.getWxOpenComponentService().bindOpenAccount(appId, openAppId);
  278. if (ret) {
  279. WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId);
  280. if (authorizerInfo != null) {
  281. authorizerInfo.setId(authorizerInfo.getId());
  282. authorizerInfo.setOpenAppid(openAppId);
  283. authorizerInfoService.updateOpenAppid(authorizerInfo);
  284. }
  285. return new ResultData();
  286. }
  287. return new ResultData(Result.ERROR, ret.toString());
  288. } catch (WxErrorException e) {
  289. logger.error(e.getMessage());
  290. return new ResultData(Result.ERROR, e.getMessage());
  291. }
  292. }
  293. @GetMapping(value = "/unbindOpenPlatform")
  294. @ApiOperation("解绑微信开放平台")
  295. public ResultData unbindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) {
  296. Gson gson = new GsonBuilder().create();
  297. try {
  298. Boolean ret = openService.getWxOpenComponentService().unbindOpenAccount(appId, openAppId);
  299. if (ret) {
  300. WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo();
  301. authorizerInfo.setAuthorizerAppid(appId);
  302. authorizerInfoService.removeOpenAppid(authorizerInfo);
  303. return new ResultData();
  304. }
  305. return new ResultData(Result.ERROR, ret.toString());
  306. } catch (WxErrorException e) {
  307. logger.error(e.getMessage());
  308. return new ResultData(Result.ERROR, e.getMessage());
  309. }
  310. }
  311. @GetMapping(value = "/getOpenPlatform")
  312. @ApiOperation("获取微信开放平台appId")
  313. public ResultData getOpenPlatform(@RequestParam(value = "appId") String appId) {
  314. Gson gson = new GsonBuilder().create();
  315. try {
  316. WxOpenGetResult ret = openService.getWxOpenComponentService().getOpenAccount(appId);
  317. logger.info(ret.toString());
  318. if (ret.isSuccess()) {
  319. WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId);
  320. if (authorizerInfo != null) {
  321. if(StringUtils.isBlank(authorizerInfo.getOpenAppid()) && StringUtils.isNotBlank(ret.getOpenAppid())) {
  322. authorizerInfo.setId(authorizerInfo.getId());
  323. authorizerInfo.setOpenAppid(ret.getOpenAppid());
  324. authorizerInfoService.updateOpenAppid(authorizerInfo);
  325. }
  326. }
  327. return new ResultData(ret.getOpenAppid());
  328. }
  329. return new ResultData(Result.ERROR, ret.toString());
  330. } catch (WxErrorException e) {
  331. logger.error(e.getMessage());
  332. return new ResultData(Result.ERROR, e.getMessage());
  333. }
  334. }
  335. @GetMapping(value = "/clearQuota")
  336. @ApiOperation("代公众号清零quota,每个公众号每个月有10次清零机会,包括在微信公众平台上的清零以及调用API进行清零")
  337. public ResultData clearQuota(@RequestParam(value = "appId") String appId) {
  338. try {
  339. openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).clearQuota(appId);
  340. return new ResultData();
  341. } catch (WxErrorException e) {
  342. logger.error(e.getMessage());
  343. return new ResultData(Result.ERROR, e.getMessage());
  344. }
  345. }
  346. @GetMapping(value = "/getAuthorizerList")
  347. @ApiOperation("拉取当前所有已授权的帐号基本信息")
  348. public ResultData getAuthorizerList() {
  349. try {
  350. WxOpenAuthorizerListResult authorizerListResult = openService.getWxOpenComponentService().getAuthorizerList(0, 100);
  351. logger.info("getAuthorizerList: " + authorizerListResult);
  352. if(authorizerListResult.getTotalCount() > 0) {
  353. for (Map<String, String> data : authorizerListResult.getList()) {
  354. String appId = data.get("authorizer_appid");
  355. String refreshToken = data.get("refresh_token");
  356. Long authTime = Long.valueOf(data.get("auth_time"));
  357. Date authDate = new Date(authTime*1000);
  358. if(StringUtils.isBlank(refreshToken)) {
  359. continue;
  360. }
  361. WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo();
  362. authorizerInfo.setAuthorizerAppid(appId);
  363. authorizerInfo.setRefreshToken(refreshToken);
  364. authorizerInfo.setAuthTime(authDate);
  365. authorizerInfoService.updateAuthAppidInfo(authorizerInfo);
  366. openService.getWxOpenComponentService().getWxOpenConfigStorage().setAuthorizerRefreshToken(appId, refreshToken);
  367. }
  368. }
  369. return new ResultData(authorizerListResult.toString());
  370. } catch (WxErrorException e) {
  371. logger.error(e.getMessage());
  372. return new ResultData(Result.ERROR, e.getMessage());
  373. }
  374. }
  375. }