| @@ -6,11 +6,15 @@ import com.iformall.common.Result; | |||
| import com.iformall.common.ResultData; | |||
| import com.iformall.domain.po.*; | |||
| import com.iformall.enums.*; | |||
| import com.iformall.mp.service.WxMpManager; | |||
| import com.iformall.mp.utils.MpUtil; | |||
| import com.iformall.service.*; | |||
| import com.iformall.service.wechat.WxOpenService; | |||
| import io.swagger.annotations.Api; | |||
| import io.swagger.annotations.ApiOperation; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.mp.api.WxMpMessageRouter; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| @@ -59,10 +63,13 @@ public class WechatCalllbackController extends BaseController { | |||
| @Autowired | |||
| private MailService mailService; | |||
| @Autowired | |||
| private WxMpManager mpManager; | |||
| @RequestMapping(value = "/notify", method = RequestMethod.POST) | |||
| @ApiOperation("接收ticket回调") | |||
| public Object receiveTicket(HttpServletRequest request) throws IOException { | |||
| logger.info("[" +getIpAddr() + "]接收微信回调 /wxOpen/notify"); | |||
| logger.info("[" + getIpAddr() + "]接收微信回调 /wxOpen/notify"); | |||
| InputStream inStream = request.getInputStream(); | |||
| ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); | |||
| byte[] buffer = new byte[1024]; | |||
| @@ -77,10 +84,10 @@ public class WechatCalllbackController extends BaseController { | |||
| inStream.close(); | |||
| String timestamp = request.getParameter("timestamp"); | |||
| String nonce = request.getParameter("nonce"); | |||
| String nonce = request.getParameter("nonce"); | |||
| String signature = request.getParameter("signature"); | |||
| String encType = request.getParameter("encrypt_type"); | |||
| String msgSignature= request.getParameter("msg_signature"); | |||
| String msgSignature = request.getParameter("msg_signature"); | |||
| /* | |||
| // 测试数据 | |||
| @@ -108,19 +115,19 @@ public class WechatCalllbackController extends BaseController { | |||
| WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(resultxml, | |||
| openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||
| this.logger.info("接收微信回调,消息解密后内容为:{} ", inMessage.toString()); | |||
| if(inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.COMPONENT_VERIFY_TICKET.getMessage())) { | |||
| if (inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.COMPONENT_VERIFY_TICKET.getMessage())) { | |||
| // 保存component_verify_ticket | |||
| componentVerifyTicketService.saveOrUpdate(new WxComponentVerifyTicket(){ | |||
| componentVerifyTicketService.saveOrUpdate(new WxComponentVerifyTicket() { | |||
| { | |||
| setComponentVerifyTicket(inMessage.getComponentVerifyTicket()); | |||
| setCreateTime(new Date(Long.valueOf(inMessage.getCreateTime() * 1000))); | |||
| setComponentAppid(inMessage.getAppId()); | |||
| setDeadline(new Date(Long.valueOf(System.currentTimeMillis() + (60*60*1000)))); | |||
| setDeadline(new Date(Long.valueOf(System.currentTimeMillis() + (60 * 60 * 1000)))); | |||
| } | |||
| }); | |||
| } else if (inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.AUTHORIZED.getMessage()) || | |||
| inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.UPDATEAUTHORIZED.getMessage()) | |||
| ) { | |||
| ) { | |||
| try { | |||
| WxOpenQueryAuthResult queryAuthResult = openService.getWxOpenComponentService().getQueryAuth(inMessage.getAuthorizationCode()); | |||
| logger.info("getQueryAuth", queryAuthResult); | |||
| @@ -129,7 +136,7 @@ public class WechatCalllbackController extends BaseController { | |||
| queryAuthResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
| logger.info(openAuthorizerInfoResult.toString()); | |||
| // save auth info | |||
| authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo(){ | |||
| authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() { | |||
| { | |||
| setAuthorizerAppid(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
| setAlias(openAuthorizerInfoResult.getAuthorizerInfo().getAlias()); | |||
| @@ -143,7 +150,7 @@ public class WechatCalllbackController extends BaseController { | |||
| logger.error(e.getMessage()); | |||
| } | |||
| } else if (inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.UNAUTHORIZED.getMessage())) { | |||
| authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo(){ | |||
| authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() { | |||
| { | |||
| setAuthorizerAppid(inMessage.getAuthorizerAppid()); | |||
| setAuthorizationStatus(EnumWxAuthorizationStatus.UNAUTHORIZED.getCode()); | |||
| @@ -163,8 +170,16 @@ public class WechatCalllbackController extends BaseController { | |||
| @RequestMapping(value = "/{appId}/callback") | |||
| @ApiOperation("消息回调") | |||
| public Object callback(@PathVariable("appId") String appId, HttpServletRequest request) throws IOException { | |||
| logger.info("[" +getIpAddr() + "]接收微信APP回调 /{appId}/callback"); | |||
| @ResponseBody | |||
| public String callback(@PathVariable("appId") String appId, HttpServletRequest request) throws IOException { | |||
| logger.info("[" + getIpAddr() + "]接收微信APP回调 /{appId}/callback"); | |||
| String timestamp = request.getParameter("timestamp"); | |||
| String nonce = request.getParameter("nonce"); | |||
| String signature = request.getParameter("signature"); | |||
| String encType = request.getParameter("encrypt_type"); | |||
| String openid = request.getParameter("openid"); | |||
| String msgSignature = request.getParameter("msg_signature"); | |||
| InputStream inStream = request.getInputStream(); | |||
| ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); | |||
| byte[] buffer = new byte[1024]; | |||
| @@ -178,27 +193,24 @@ public class WechatCalllbackController extends BaseController { | |||
| outSteam.close(); | |||
| inStream.close(); | |||
| String timestamp = request.getParameter("timestamp"); | |||
| String nonce = request.getParameter("nonce"); | |||
| String signature = request.getParameter("signature"); | |||
| String encType = request.getParameter("encrypt_type"); | |||
| String openid = request.getParameter("openid"); | |||
| String msgSignature= request.getParameter("msg_signature"); | |||
| logger.info("接收微信APP回调:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," | |||
| + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||
| appId, openid, signature, encType, msgSignature, timestamp, nonce, resultxml); | |||
| // 第三方平台一定接收的是加密的消息 | |||
| if (!StringUtils.equalsIgnoreCase("aes", encType) | |||
| || !openService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { | |||
| throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
| } | |||
| WxAppinfo appinfo = appinfoService.getByAppId(appId); | |||
| String out = ""; | |||
| // aes加密的消息 | |||
| WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(resultxml, | |||
| openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||
| logger.info("接收微信APP回调,消息解密后内容为:\n{}", inMessage.toString()); | |||
| // 全网发布测试用例 | |||
| /* | |||
| if (StringUtils.equalsAnyIgnoreCase(appId, "wx570bc396a51b8ff8", "wxd101a85aa106f53e")) { | |||
| try { | |||
| if (StringUtils.equals(inMessage.getMsgType(), "text")) { | |||
| @@ -222,9 +234,12 @@ public class WechatCalllbackController extends BaseController { | |||
| } catch (WxErrorException e) { | |||
| logger.error("callback", e); | |||
| } | |||
| } else { | |||
| } | |||
| */ | |||
| if(appinfo.getType().equals(EnumAppType.B.getCode()) || appinfo.getType().equals(EnumAppType.C.getCode())) { | |||
| // 小程序 | |||
| /**判断消息类型,调用对应的方法*/ | |||
| switch (inMessage.getMsgType()){ | |||
| switch (inMessage.getMsgType()) { | |||
| case "event": { | |||
| switch (inMessage.getEvent()) { | |||
| case "weapp_audit_success": { // 审核成功 | |||
| @@ -246,18 +261,6 @@ public class WechatCalllbackController extends BaseController { | |||
| out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(outMessage, openService.getWxOpenConfigStorage()); | |||
| logger.info(out); | |||
| } | |||
| /* | |||
| else { | |||
| WxMpXmlOutMessage outMsg = new WxMpXmlOutTextMessage(); | |||
| outMsg.setCreateTime(System.currentTimeMillis()); | |||
| outMsg.setToUserName(inMessage.getFromUser()); | |||
| outMsg.setFromUserName(inMessage.getToUser()); | |||
| outMsg.setMsgType("text"); | |||
| ((WxMpXmlOutTextMessage) outMsg).setContent(inMessage.getContent()); | |||
| out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(outMsg, wxOpenService.getWxOpenConfigStorage()); | |||
| } | |||
| */ | |||
| else { | |||
| out = "success"; | |||
| } | |||
| @@ -265,9 +268,21 @@ public class WechatCalllbackController extends BaseController { | |||
| default: | |||
| return "success"; | |||
| } | |||
| } else { | |||
| // 公众号 | |||
| WxMpService mpService = MpUtil.getMpService(appinfo); | |||
| try { | |||
| WxMpXmlOutMessage outMessage = mpManager.getRouter(mpService).route(inMessage); | |||
| if (outMessage == null) { | |||
| this.logger.info("2 return: success"); | |||
| return "success"; | |||
| } | |||
| out = outMessage.toEncryptedXml(mpService.getWxMpConfigStorage()); | |||
| } catch (Exception e) { | |||
| logger.error("路由消息时出现异常!", e); | |||
| } | |||
| } | |||
| return out; | |||
| } | |||
| @@ -275,8 +290,8 @@ public class WechatCalllbackController extends BaseController { | |||
| WxWeappAuditStatus auditStatus = weappAuditStatusService.getByAppId(appId); | |||
| if(auditStatus != null) { | |||
| if(success) { | |||
| if (auditStatus != null) { | |||
| if (success) { | |||
| auditStatus.setAuditStatus(EnumWeappAuditStatus.SUCCESS.getCode()); | |||
| auditStatus.setAuditErrCode(""); | |||
| } else { | |||
| @@ -289,21 +304,21 @@ public class WechatCalllbackController extends BaseController { | |||
| // 发邮件 | |||
| WxAppinfo appinfo = appinfoService.getByAppId(appId); | |||
| if(appinfo != null) { | |||
| if (appinfo != null) { | |||
| SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 | |||
| StringBuilder titleSB = new StringBuilder(); | |||
| if(appinfo.getType().equals(EnumAppType.C.getCode())) { | |||
| if (appinfo.getType().equals(EnumAppType.C.getCode())) { | |||
| titleSB.append("C端小程序"); | |||
| } else if(appinfo.getType().equals(EnumAppType.B.getCode())) { | |||
| } else if (appinfo.getType().equals(EnumAppType.B.getCode())) { | |||
| titleSB.append("B端小程序"); | |||
| } else if(appinfo.getType().equals(EnumAppType.MP_S.getCode())) { | |||
| } else if (appinfo.getType().equals(EnumAppType.MP_S.getCode())) { | |||
| titleSB.append("公众号-服务号"); | |||
| } else if(appinfo.getType().equals(EnumAppType.MP_P.getCode())) { | |||
| } else if (appinfo.getType().equals(EnumAppType.MP_P.getCode())) { | |||
| titleSB.append("公众号-订阅号"); | |||
| } | |||
| titleSB.append(appinfo.getName()); | |||
| if(success) { | |||
| if (success) { | |||
| titleSB.append("审核通过"); | |||
| } else { | |||
| titleSB.append("审核未通过"); | |||
| @@ -311,13 +326,13 @@ public class WechatCalllbackController extends BaseController { | |||
| StringBuilder sb = new StringBuilder(); | |||
| sb.append(df.format(new Date())).append("\n"); | |||
| if(success) { | |||
| if (success) { | |||
| sb.append("审核通过\n"); | |||
| } else { | |||
| sb.append("审核未通过\n").append("原因").append(reason); | |||
| } | |||
| String[] receivers = new String[] {"wangmei@iformall.com", "wuguoqiang@iformall.com", "huanghui@iformall.com", "sunmingming@iformall.com"}; | |||
| String[] receivers = new String[]{"wangmei@iformall.com", "wuguoqiang@iformall.com", "huanghui@iformall.com", "sunmingming@iformall.com"}; | |||
| //发送邮件 | |||
| mailService.sendSimpleMail(receivers, titleSB.toString(), sb.toString()); | |||
| @@ -326,15 +341,15 @@ public class WechatCalllbackController extends BaseController { | |||
| @GetMapping(value = "/createOpenPlatform") | |||
| @ApiOperation("创建微信开放平台") | |||
| public ResultData createOpenPlatform(@RequestParam(value="appId") String appId) { | |||
| public ResultData createOpenPlatform(@RequestParam(value = "appId") String appId) { | |||
| Gson gson = new GsonBuilder().create(); | |||
| try { | |||
| String responseStr = openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).createOpenPlatform(appId); | |||
| logger.info(responseStr); | |||
| WxOpenPlatformResult openPlatformResult = gson.fromJson(responseStr, WxOpenPlatformResult.class); | |||
| if(openPlatformResult.isSuccess()) { | |||
| if (openPlatformResult.isSuccess()) { | |||
| WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
| if(authorizerInfo != null) { | |||
| if (authorizerInfo != null) { | |||
| authorizerInfo.setId(authorizerInfo.getId()); | |||
| authorizerInfo.setOpenAppid(openPlatformResult.getOpenAppid()); | |||
| authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
| @@ -355,7 +370,7 @@ public class WechatCalllbackController extends BaseController { | |||
| String responseStr = openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).bindOpenPlatform(appId, openAppId); | |||
| logger.info(responseStr); | |||
| WxOpenResult openResult = gson.fromJson(responseStr, WxOpenResult.class); | |||
| if(openResult.isSuccess()) { | |||
| if (openResult.isSuccess()) { | |||
| return new ResultData(); | |||
| } | |||
| return new ResultData(Result.ERROR, responseStr); | |||
| @@ -373,7 +388,7 @@ public class WechatCalllbackController extends BaseController { | |||
| String responseStr = openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).unbindOpenPlatform(appId, openAppId); | |||
| logger.info(responseStr); | |||
| WxOpenResult openResult = gson.fromJson(responseStr, WxOpenResult.class); | |||
| if(openResult.isSuccess()) { | |||
| if (openResult.isSuccess()) { | |||
| return new ResultData(); | |||
| } | |||
| return new ResultData(Result.ERROR, responseStr); | |||
| @@ -391,7 +406,7 @@ public class WechatCalllbackController extends BaseController { | |||
| String responseStr = openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getBindOpenPlatform(appId); | |||
| logger.info(responseStr); | |||
| WxOpenPlatformResult openPlatformResult = gson.fromJson(responseStr, WxOpenPlatformResult.class); | |||
| if(openPlatformResult.isSuccess()) { | |||
| if (openPlatformResult.isSuccess()) { | |||
| return new ResultData(openPlatformResult.getOpenAppid()); | |||
| } | |||
| return new ResultData(Result.ERROR, responseStr); | |||
| @@ -0,0 +1,17 @@ | |||
| package com.iformall.mp.builder; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| public abstract class AbstractBuilder { | |||
| protected final Logger logger = LoggerFactory.getLogger(getClass()); | |||
| public abstract WxMpXmlOutMessage build(String content, | |||
| WxMpXmlMessage wxMessage, WxMpService service); | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package com.iformall.mp.builder; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| public class ImageBuilder extends AbstractBuilder { | |||
| @Override | |||
| public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, | |||
| WxMpService service) { | |||
| WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content) | |||
| .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) | |||
| .build(); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| package com.iformall.mp.builder; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| public class TextBuilder extends AbstractBuilder { | |||
| @Override | |||
| public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, | |||
| WxMpService service) { | |||
| WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) | |||
| .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) | |||
| .build(); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| package com.iformall.mp.controller; | |||
| import com.iformall.common.ResultData; | |||
| import com.iformall.mp.service.WxMpManager; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUserList; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.PathVariable; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import java.util.List; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| @RestController | |||
| @RequestMapping("/wx/{account}/mem") | |||
| public class WxMemController { | |||
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
| @Autowired | |||
| private WxMpManager wxMpManager; | |||
| @GetMapping("/syncAccountFansList") | |||
| public ResultData syncAccountFansList(@PathVariable String account) throws WxErrorException { | |||
| final WxMpService wxService = wxMpManager.getMpService(account); | |||
| WxMpUserList mpUserList = wxService.getUserService().userList(null); | |||
| logger.info(mpUserList.toString()); | |||
| List<WxMpUser> userList = wxService.getUserService().userInfoList(mpUserList.getOpenids()); | |||
| logger.info(userList.toString()); | |||
| while(StringUtils.isNotBlank(mpUserList.getNextOpenid())) { | |||
| mpUserList = wxService.getUserService().userList(mpUserList.getNextOpenid()); | |||
| logger.info(mpUserList.toString()); | |||
| if(mpUserList.getOpenids().size() > 0) { | |||
| userList = wxService.getUserService().userInfoList(mpUserList.getOpenids()); | |||
| logger.info(userList.toString()); | |||
| } | |||
| } | |||
| return new ResultData(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,190 @@ | |||
| package com.iformall.mp.controller; | |||
| import com.iformall.mp.service.WxMpManager; | |||
| import me.chanjar.weixin.common.api.WxConsts; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuButton; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.mp.bean.menu.WxMpGetSelfMenuInfoResult; | |||
| import me.chanjar.weixin.mp.bean.menu.WxMpMenu; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import org.springframework.web.context.request.RequestContextHolder; | |||
| import org.springframework.web.context.request.ServletRequestAttributes; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import java.net.MalformedURLException; | |||
| import java.net.URL; | |||
| import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| @RestController | |||
| @RequestMapping("/wx/menu/{account}") | |||
| public class WxMenuController { | |||
| @Autowired | |||
| private WxMpManager wxMpManager; | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单创建接口 | |||
| * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN | |||
| * 如果要创建个性化菜单,请设置matchrule属性 | |||
| * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @return 如果是个性化菜单,则返回menuid,否则返回null | |||
| */ | |||
| @PostMapping("/create") | |||
| public String menuCreate(@PathVariable String account, @RequestBody WxMenu menu) throws WxErrorException { | |||
| return wxMpManager.getMpService(account).getMenuService().menuCreate(menu); | |||
| } | |||
| @GetMapping("/create") | |||
| public String menuCreateSample(@PathVariable String account) throws WxErrorException, MalformedURLException { | |||
| WxMenu menu = new WxMenu(); | |||
| WxMenuButton button1 = new WxMenuButton(); | |||
| button1.setType(MenuButtonType.CLICK); | |||
| button1.setName("今日歌曲"); | |||
| button1.setKey("V1001_TODAY_MUSIC"); | |||
| // WxMenuButton button2 = new WxMenuButton(); | |||
| // button2.setType(WxConsts.BUTTON_MINIPROGRAM); | |||
| // button2.setName("小程序"); | |||
| // button2.setAppId("wx286b93c14bbf93aa"); | |||
| // button2.setPagePath("pages/lunar/index.html"); | |||
| // button2.setUrl("http://mp.weixin.qq.com"); | |||
| WxMenuButton button3 = new WxMenuButton(); | |||
| button3.setName("菜单"); | |||
| menu.getButtons().add(button1); | |||
| // menu.getButtons().add(button2); | |||
| menu.getButtons().add(button3); | |||
| WxMenuButton button31 = new WxMenuButton(); | |||
| button31.setType(MenuButtonType.VIEW); | |||
| button31.setName("搜索"); | |||
| button31.setUrl("http://www.soso.com/"); | |||
| WxMenuButton button32 = new WxMenuButton(); | |||
| button32.setType(MenuButtonType.VIEW); | |||
| button32.setName("视频"); | |||
| button32.setUrl("http://v.qq.com/"); | |||
| WxMenuButton button33 = new WxMenuButton(); | |||
| button33.setType(MenuButtonType.CLICK); | |||
| button33.setName("赞一下我们"); | |||
| button33.setKey("V1001_GOOD"); | |||
| WxMenuButton button34 = new WxMenuButton(); | |||
| button34.setType(MenuButtonType.VIEW); | |||
| button34.setName("获取用户信息"); | |||
| ServletRequestAttributes servletRequestAttributes = | |||
| (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); | |||
| if (servletRequestAttributes != null) { | |||
| HttpServletRequest request = servletRequestAttributes.getRequest(); | |||
| URL requestURL = new URL(request.getRequestURL().toString()); | |||
| String url = wxMpManager.getMpService(account) | |||
| .oauth2buildAuthorizationUrl( | |||
| String.format("%s://%s/wx/redirect/%s/greet", requestURL.getProtocol(), requestURL.getHost(), account), | |||
| WxConsts.OAuth2Scope.SNSAPI_USERINFO, null); | |||
| button34.setUrl(url); | |||
| } | |||
| button3.getSubButtons().add(button31); | |||
| button3.getSubButtons().add(button32); | |||
| button3.getSubButtons().add(button33); | |||
| button3.getSubButtons().add(button34); | |||
| return wxMpManager.getMpService(account).getMenuService().menuCreate(menu); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单创建接口 | |||
| * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN | |||
| * 如果要创建个性化菜单,请设置matchrule属性 | |||
| * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @param json | |||
| * @return 如果是个性化菜单,则返回menuid,否则返回null | |||
| */ | |||
| @PostMapping("/createByJson") | |||
| public String menuCreate(@PathVariable String account, @RequestBody String json) throws WxErrorException { | |||
| return wxMpManager.getMpService(account).getMenuService().menuCreate(json); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单删除接口 | |||
| * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN | |||
| * </pre> | |||
| */ | |||
| @GetMapping("/delete") | |||
| public void menuDelete(@PathVariable String account) throws WxErrorException { | |||
| wxMpManager.getMpService(account).getMenuService().menuDelete(); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 删除个性化菜单接口 | |||
| * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @param menuId 个性化菜单的menuid | |||
| */ | |||
| @GetMapping("/delete/{menuId}") | |||
| public void menuDelete(@PathVariable String account, @PathVariable String menuId) throws WxErrorException { | |||
| wxMpManager.getMpService(account).getMenuService().menuDelete(menuId); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单查询接口 | |||
| * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN | |||
| * </pre> | |||
| */ | |||
| @GetMapping("/get") | |||
| public WxMpMenu menuGet(@PathVariable String account) throws WxErrorException { | |||
| return wxMpManager.getMpService(account).getMenuService().menuGet(); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 测试个性化菜单匹配结果 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html | |||
| * </pre> | |||
| * | |||
| * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号。 | |||
| */ | |||
| @GetMapping("/menuTryMatch/{userid}") | |||
| public WxMenu menuTryMatch(@PathVariable String account, @PathVariable String userid) throws WxErrorException { | |||
| return wxMpManager.getMpService(account).getMenuService().menuTryMatch(userid); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 获取自定义菜单配置接口 | |||
| * 本接口将会提供公众号当前使用的自定义菜单的配置,如果公众号是通过API调用设置的菜单,则返回菜单的开发配置,而如果公众号是在公众平台官网通过网站功能发布菜单,则本接口返回运营者设置的菜单配置。 | |||
| * 请注意: | |||
| * 1、第三方平台开发者可以通过本接口,在旗下公众号将业务授权给你后,立即通过本接口检测公众号的自定义菜单配置,并通过接口再次给公众号设置好自动回复规则,以提升公众号运营者的业务体验。 | |||
| * 2、本接口与自定义菜单查询接口的不同之处在于,本接口无论公众号的接口是如何设置的,都能查询到接口,而自定义菜单查询接口则仅能查询到使用API设置的菜单配置。 | |||
| * 3、认证/未认证的服务号/订阅号,以及接口测试号,均拥有该接口权限。 | |||
| * 4、从第三方平台的公众号登录授权机制上来说,该接口从属于消息与菜单权限集。 | |||
| * 5、本接口中返回的图片/语音/视频为临时素材(临时素材每次获取都不同,3天内有效,通过素材管理-获取临时素材接口来获取这些素材),本接口返回的图文消息为永久素材素材(通过素材管理-获取永久素材接口来获取这些素材)。 | |||
| * 接口调用请求说明: | |||
| * http请求方式: GET(请使用https协议) | |||
| * https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| */ | |||
| @GetMapping("/getSelfMenuInfo") | |||
| public WxMpGetSelfMenuInfoResult getSelfMenuInfo(@PathVariable String account) throws WxErrorException { | |||
| return wxMpManager.getMpService(account).getMenuService().getSelfMenuInfo(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,150 @@ | |||
| package com.iformall.mp.controller; | |||
| import com.iformall.mp.service.WxMpManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Controller; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import javax.servlet.http.HttpServletResponse; | |||
| import java.io.ByteArrayOutputStream; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.PrintWriter; | |||
| import java.nio.charset.Charset; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| @Controller | |||
| @RequestMapping("/wx") | |||
| public class WxMpCallBackController { | |||
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
| @Autowired | |||
| private WxMpManager wxMpManager; | |||
| /** | |||
| * GET请求:进行URL、Tocken 认证; | |||
| * 1. 将token、timestamp、nonce三个参数进行字典序排序 | |||
| * 2. 将三个参数字符串拼接成一个字符串进行sha1加密 | |||
| * 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 | |||
| */ | |||
| @GetMapping(value = "/{account}/message") | |||
| @ResponseBody | |||
| public void authGet(@PathVariable String account, HttpServletRequest request, HttpServletResponse response) throws IOException { | |||
| String signature = request.getParameter("signature");// 微信加密签名 | |||
| String timestamp = request.getParameter("timestamp");// 时间戳 | |||
| String nonce = request.getParameter("nonce");// 随机数 | |||
| String echostr = request.getParameter("echostr");// 随机字符串 | |||
| if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { | |||
| throw new IllegalArgumentException("请求参数非法,请核实!"); | |||
| } | |||
| this.logger.info("接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", | |||
| signature, timestamp, nonce, echostr); | |||
| // 数据库 -> | |||
| final WxMpService wxService = wxMpManager.getMpService(account); | |||
| if (wxService == null) { | |||
| throw new IllegalArgumentException(String.format("未找到对应account=[%s]的配置,请核实!", account)); | |||
| } | |||
| if (wxService.checkSignature(timestamp, nonce, signature)) { | |||
| logger.info("微信服务验证成功"); | |||
| PrintWriter out = response.getWriter(); | |||
| out.print(echostr); | |||
| out.close(); | |||
| } | |||
| } | |||
| /** | |||
| * POST 请求:进行消息处理; | |||
| */ | |||
| @PostMapping(value = "/{account}/message") | |||
| @ResponseBody | |||
| public String post(@PathVariable String account, HttpServletRequest request) throws IOException { | |||
| // 处理用户和微信公众账号交互消息 | |||
| String signature = request.getParameter("signature");// 微信加密签名 | |||
| String timestamp = request.getParameter("timestamp");// 时间戳 | |||
| String nonce = request.getParameter("nonce");// 随机数 | |||
| String openid = request.getParameter("openid"); | |||
| String encType = request.getParameter("encrypt_type"); | |||
| String msgSignature = request.getParameter("msg_signature"); | |||
| if (StringUtils.isAnyBlank(signature, timestamp, nonce)) { | |||
| throw new IllegalArgumentException("请求参数非法,请核实!"); | |||
| } | |||
| final WxMpService wxService = wxMpManager.getMpService(account); | |||
| if (!wxService.checkSignature(timestamp, nonce, signature)) { | |||
| throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
| } | |||
| InputStream inStream = request.getInputStream(); | |||
| ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); | |||
| byte[] buffer = new byte[1024]; | |||
| int len = 0; | |||
| while ((len = inStream.read(buffer)) != -1) { | |||
| outSteam.write(buffer, 0, len); | |||
| } | |||
| String requestBody = new String(outSteam.toByteArray(), Charset.forName("UTF-8")); | |||
| logger.info(requestBody); | |||
| outSteam.close(); | |||
| inStream.close(); | |||
| this.logger.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," | |||
| + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||
| openid, signature, encType, msgSignature, timestamp, nonce, requestBody); | |||
| String out = null; | |||
| if (encType == null) { | |||
| // 明文传输的消息 | |||
| WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); | |||
| WxMpXmlOutMessage outMessage = this.route(inMessage, wxService); | |||
| if (outMessage == null) { | |||
| logger.info("1 return: success"); | |||
| return "success"; | |||
| } | |||
| out = outMessage.toXml(); | |||
| logger.info(out); | |||
| } else if ("aes".equalsIgnoreCase(encType)) { | |||
| // aes加密的消息 | |||
| WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), | |||
| timestamp, nonce, msgSignature); | |||
| this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); | |||
| WxMpXmlOutMessage outMessage = this.route(inMessage, wxService); | |||
| if (outMessage == null) { | |||
| this.logger.info("2 return: success"); | |||
| return "success"; | |||
| } | |||
| logger.info(outMessage.toXml()); | |||
| out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); | |||
| } | |||
| this.logger.info("\n组装回复信息:{}", out); | |||
| return out; | |||
| } | |||
| private WxMpXmlOutMessage route(WxMpXmlMessage message, WxMpService wxService) { | |||
| try { | |||
| return wxMpManager.getRouter(wxService).route(message); | |||
| } catch (Exception e) { | |||
| this.logger.error("路由消息时出现异常!", e); | |||
| } | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package com.iformall.mp.controller; | |||
| import com.iformall.mp.service.WxMpManager; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Controller; | |||
| import org.springframework.ui.ModelMap; | |||
| import org.springframework.web.bind.annotation.PathVariable; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RequestParam; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| @Controller | |||
| @RequestMapping("/wx/redirect/{account}") | |||
| public class WxRedirectController { | |||
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
| @Autowired | |||
| private WxMpManager wxMpManager; | |||
| @RequestMapping("/greet") | |||
| public String greetUser(@PathVariable String account, @RequestParam String code, ModelMap map) { | |||
| WxMpService mpService = wxMpManager.getMpService(account); | |||
| try { | |||
| WxMpOAuth2AccessToken accessToken = mpService.oauth2getAccessToken(code); | |||
| WxMpUser user = mpService.oauth2getUserInfo(accessToken, null); | |||
| map.put("user", user); | |||
| } catch (WxErrorException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return "greet_user"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| package com.iformall.mp.error; | |||
| import org.springframework.stereotype.Controller; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| /** | |||
| * <pre> | |||
| * 出错页面控制器 | |||
| * Created by Binary Wang on 2018/8/25. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @Controller | |||
| @RequestMapping("/error") | |||
| public class ErrorController { | |||
| @GetMapping(value = "/404") | |||
| public String error404() { | |||
| return "error"; | |||
| } | |||
| @GetMapping(value = "/500") | |||
| public String error500() { | |||
| return "error"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| package com.iformall.mp.error; | |||
| import org.springframework.boot.web.server.ErrorPage; | |||
| import org.springframework.boot.web.server.ErrorPageRegistrar; | |||
| import org.springframework.boot.web.server.ErrorPageRegistry; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.stereotype.Component; | |||
| /** | |||
| * <pre> | |||
| * 配置错误状态与对应访问路径 | |||
| * Created by Binary Wang on 2018/8/25. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @Component | |||
| public class ErrorPageConfiguration implements ErrorPageRegistrar { | |||
| @Override | |||
| public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { | |||
| errorPageRegistry.addErrorPages( | |||
| new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"), | |||
| new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500") | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.mp.api.WxMpMessageHandler; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| public abstract class AbstractHandler implements WxMpMessageHandler { | |||
| protected Logger logger = LoggerFactory.getLogger(getClass()); | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class KfSessionHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| //TODO 对会话做处理 | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package com.iformall.mp.handler; | |||
| import com.iformall.mp.builder.TextBuilder; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class LocationHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) { | |||
| //TODO 接收处理用户发送的地理位置消息 | |||
| try { | |||
| String content = "感谢反馈,您的的地理位置已收到!"; | |||
| return new TextBuilder().build(content, wxMessage, null); | |||
| } catch (Exception e) { | |||
| this.logger.error("位置消息接收处理失败", e); | |||
| return null; | |||
| } | |||
| } | |||
| //上报地理位置事件 | |||
| this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}", | |||
| wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision())); | |||
| //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| package com.iformall.mp.handler; | |||
| import com.iformall.mp.utils.JsonUtils; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class LogHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class MenuHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService weixinService, | |||
| WxSessionManager sessionManager) { | |||
| String msg = String.format("type:%s, event:%s, key:%s", | |||
| wxMessage.getMsgType(), wxMessage.getEvent(), | |||
| wxMessage.getEventKey()); | |||
| if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { | |||
| return null; | |||
| } | |||
| return WxMpXmlOutMessage.TEXT().content(msg) | |||
| .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) | |||
| .build(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| package com.iformall.mp.handler; | |||
| import com.iformall.mp.builder.TextBuilder; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class MsgHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService weixinService, | |||
| WxSessionManager sessionManager) { | |||
| if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) { | |||
| //TODO 可以选择将消息保存到本地 | |||
| } | |||
| //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 | |||
| try { | |||
| if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") | |||
| && weixinService.getKefuService().kfOnlineList() | |||
| .getKfOnlineList().size() > 0) { | |||
| return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() | |||
| .fromUser(wxMessage.getToUser()) | |||
| .toUser(wxMessage.getFromUser()).build(); | |||
| } | |||
| } catch (WxErrorException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| //TODO 组装回复消息 | |||
| String content = "您好,有什么可以帮您";// "收到信息内容:" + JsonUtils.toJson(wxMessage); | |||
| return new TextBuilder().build(content, wxMessage, weixinService); | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class NullHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| package com.iformall.mp.handler; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| public abstract class ScanHandler extends AbstractHandler { | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * 门店审核事件处理 | |||
| * | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class StoreCheckNotifyHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| // TODO 处理门店审核事件 | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| package com.iformall.mp.handler; | |||
| import com.iformall.mp.builder.TextBuilder; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class SubscribeHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService weixinService, | |||
| WxSessionManager sessionManager) throws WxErrorException { | |||
| this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser()); | |||
| // 获取微信用户基本信息 | |||
| try { | |||
| WxMpUser userWxInfo = weixinService.getUserService() | |||
| .userInfo(wxMessage.getFromUser(), null); | |||
| if (userWxInfo != null) { | |||
| // TODO 可以添加关注用户到本地数据库 | |||
| } | |||
| } catch (WxErrorException e) { | |||
| if (e.getError().getErrorCode() == 48001) { | |||
| this.logger.info("该公众号没有获取用户信息权限!"); | |||
| } | |||
| } | |||
| WxMpXmlOutMessage responseResult = null; | |||
| try { | |||
| responseResult = this.handleSpecial(wxMessage); | |||
| } catch (Exception e) { | |||
| this.logger.error(e.getMessage(), e); | |||
| } | |||
| if (responseResult != null) { | |||
| return responseResult; | |||
| } | |||
| try { | |||
| return new TextBuilder().build("感谢关注", wxMessage, weixinService); | |||
| } catch (Exception e) { | |||
| this.logger.error(e.getMessage(), e); | |||
| } | |||
| return null; | |||
| } | |||
| /** | |||
| * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 | |||
| */ | |||
| private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage) | |||
| throws Exception { | |||
| //TODO | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| package com.iformall.mp.handler; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.Map; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Component | |||
| public class UnsubscribeHandler extends AbstractHandler { | |||
| @Override | |||
| public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||
| Map<String, Object> context, WxMpService wxMpService, | |||
| WxSessionManager sessionManager) { | |||
| String openId = wxMessage.getFromUser(); | |||
| this.logger.info("取消关注用户 OPENID: " + openId); | |||
| // TODO 可以更新本地数据库为取消关注状态 | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,116 @@ | |||
| package com.iformall.mp.manager; | |||
| import com.iformall.domain.po.WxAppinfo; | |||
| import com.iformall.mp.handler.*; | |||
| import com.iformall.mp.utils.MpUtil; | |||
| import com.iformall.service.WxAppinfoService; | |||
| import me.chanjar.weixin.mp.api.WxMpMessageRouter; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.constant.WxMpEventConstants; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Service; | |||
| import static me.chanjar.weixin.common.api.WxConsts.*; | |||
| /** | |||
| * wechat mp configuration | |||
| * | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Service | |||
| public class WxMpManager { | |||
| @Autowired | |||
| private WxAppinfoService appinfoService; | |||
| @Autowired | |||
| private LogHandler logHandler; | |||
| @Autowired | |||
| private NullHandler nullHandler; | |||
| @Autowired | |||
| private KfSessionHandler kfSessionHandler; | |||
| @Autowired | |||
| private StoreCheckNotifyHandler storeCheckNotifyHandler; | |||
| @Autowired | |||
| private LocationHandler locationHandler; | |||
| @Autowired | |||
| private MenuHandler menuHandler; | |||
| @Autowired | |||
| private MsgHandler msgHandler; | |||
| @Autowired | |||
| private UnsubscribeHandler unsubscribeHandler; | |||
| @Autowired | |||
| private SubscribeHandler subscribeHandler; | |||
| public WxMpMessageRouter getRouter(WxAppinfo appinfo) { | |||
| WxMpService mpService = MpUtil.getMpService(appinfo); | |||
| if(mpService != null) { | |||
| return newRouter(mpService); | |||
| } | |||
| return null; | |||
| } | |||
| public WxMpMessageRouter getRouter(WxMpService mpService) { | |||
| return newRouter(mpService); | |||
| } | |||
| private WxMpMessageRouter newRouter(WxMpService wxMpService) { | |||
| final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); | |||
| // 记录所有事件的日志 (异步执行) | |||
| newRouter.rule().handler(this.logHandler).next(); | |||
| // 接收客服会话管理事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) | |||
| .handler(this.kfSessionHandler).end(); | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) | |||
| .handler(this.kfSessionHandler) | |||
| .end(); | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) | |||
| .handler(this.kfSessionHandler).end(); | |||
| // 门店审核事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.POI_CHECK_NOTIFY) | |||
| .handler(this.storeCheckNotifyHandler).end(); | |||
| // 自定义菜单事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(MenuButtonType.CLICK).handler(this.menuHandler).end(); | |||
| // 点击菜单连接事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(MenuButtonType.VIEW).handler(this.nullHandler).end(); | |||
| // 关注事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.SUBSCRIBE).handler(this.subscribeHandler) | |||
| .end(); | |||
| // 取消关注事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.UNSUBSCRIBE) | |||
| .handler(this.unsubscribeHandler).end(); | |||
| // 上报地理位置事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.LOCATION).handler(this.locationHandler) | |||
| .end(); | |||
| // 接收地理位置消息 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.LOCATION) | |||
| .handler(this.locationHandler).end(); | |||
| // 扫码事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.SCAN).handler(this.nullHandler).end(); | |||
| // 默认 | |||
| newRouter.rule().async(false).handler(this.msgHandler).end(); | |||
| return newRouter; | |||
| } | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| package com.iformall.mp.service; | |||
| import com.iformall.domain.po.WxAppinfo; | |||
| import com.iformall.mp.handler.*; | |||
| import com.iformall.mp.utils.MpUtil; | |||
| import com.iformall.service.WxAppinfoService; | |||
| import me.chanjar.weixin.mp.api.WxMpMessageRouter; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.constant.WxMpEventConstants; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Service; | |||
| import static me.chanjar.weixin.common.api.WxConsts.*; | |||
| /** | |||
| * wechat mp configuration | |||
| * | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| @Service | |||
| public class WxMpManager { | |||
| @Autowired | |||
| private WxAppinfoService appinfoService; | |||
| @Autowired | |||
| private LogHandler logHandler; | |||
| @Autowired | |||
| private NullHandler nullHandler; | |||
| @Autowired | |||
| private KfSessionHandler kfSessionHandler; | |||
| @Autowired | |||
| private StoreCheckNotifyHandler storeCheckNotifyHandler; | |||
| @Autowired | |||
| private LocationHandler locationHandler; | |||
| @Autowired | |||
| private MenuHandler menuHandler; | |||
| @Autowired | |||
| private MsgHandler msgHandler; | |||
| @Autowired | |||
| private UnsubscribeHandler unsubscribeHandler; | |||
| @Autowired | |||
| private SubscribeHandler subscribeHandler; | |||
| public WxMpMessageRouter getRouter(String appId) { | |||
| WxMpService mpService = getMpService(appId); | |||
| if(mpService != null) { | |||
| return newRouter(mpService); | |||
| } | |||
| return null; | |||
| } | |||
| public WxMpMessageRouter getRouter(WxMpService mpService) { | |||
| return newRouter(mpService); | |||
| } | |||
| public WxMpService getMpService(String appId) { | |||
| WxAppinfo appinfo = appinfoService.getByAppId(appId); | |||
| if(appinfo == null) | |||
| return null; | |||
| return MpUtil.getMpService(appinfo); | |||
| } | |||
| private WxMpMessageRouter newRouter(WxMpService wxMpService) { | |||
| final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); | |||
| // 记录所有事件的日志 (异步执行) | |||
| newRouter.rule().handler(this.logHandler).next(); | |||
| // 接收客服会话管理事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) | |||
| .handler(this.kfSessionHandler).end(); | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) | |||
| .handler(this.kfSessionHandler) | |||
| .end(); | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) | |||
| .handler(this.kfSessionHandler).end(); | |||
| // 门店审核事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(WxMpEventConstants.POI_CHECK_NOTIFY) | |||
| .handler(this.storeCheckNotifyHandler).end(); | |||
| // 自定义菜单事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(MenuButtonType.CLICK).handler(this.menuHandler).end(); | |||
| // 点击菜单连接事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(MenuButtonType.VIEW).handler(this.nullHandler).end(); | |||
| // 关注事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.SUBSCRIBE).handler(this.subscribeHandler) | |||
| .end(); | |||
| // 取消关注事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.UNSUBSCRIBE) | |||
| .handler(this.unsubscribeHandler).end(); | |||
| // 上报地理位置事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.LOCATION).handler(this.locationHandler) | |||
| .end(); | |||
| // 接收地理位置消息 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.LOCATION) | |||
| .handler(this.locationHandler).end(); | |||
| // 扫码事件 | |||
| newRouter.rule().async(false).msgType(XmlMsgType.EVENT) | |||
| .event(EventType.SCAN).handler(this.nullHandler).end(); | |||
| // 默认 | |||
| newRouter.rule().async(false).handler(this.msgHandler).end(); | |||
| return newRouter; | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.iformall.mp.utils; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.GsonBuilder; | |||
| /** | |||
| * @author Binary Wang(https://github.com/binarywang) | |||
| */ | |||
| public class JsonUtils { | |||
| public static String toJson(Object obj) { | |||
| Gson gson = new GsonBuilder() | |||
| .setPrettyPrinting() | |||
| .create(); | |||
| return gson.toJson(obj); | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| package com.iformall.mp.utils; | |||
| import com.iformall.domain.po.WxAppinfo; | |||
| import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; | |||
| /** | |||
| * @author Stormeye | |||
| */ | |||
| public class MpUtil { | |||
| static public WxMpService getMpService(WxAppinfo appinfo) { | |||
| WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage(); | |||
| configStorage.setAppId(appinfo.getAppId()); | |||
| configStorage.setSecret(appinfo.getSecret()); | |||
| configStorage.setToken(appinfo.getToken()); | |||
| configStorage.setAesKey(appinfo.getAesKey()); | |||
| WxMpService service = new WxMpServiceImpl(); | |||
| service.setWxMpConfigStorage(configStorage); | |||
| return service; | |||
| } | |||
| } | |||