From f5c77d68f54a3654006c560cad7b011010012299 Mon Sep 17 00:00:00 2001 From: ronger Date: Mon, 16 Mar 2020 21:17:58 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E5=BE=AE=E4=BF=A1=E5=85=AC?= =?UTF-8?q?=E4=BC=97=E5=8F=B7=E6=9C=8D=E5=8A=A1=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 + .../wx/mp/builder/AbstractBuilder.java | 17 ++ .../vertical/wx/mp/builder/ImageBuilder.java | 24 +++ .../vertical/wx/mp/builder/TextBuilder.java | 22 ++ .../wx/mp/config/WxMpConfiguration.java | 111 +++++++++++ .../vertical/wx/mp/config/WxMpProperties.java | 46 +++++ .../wx/mp/controller/WxMenuController.java | 188 ++++++++++++++++++ .../wx/mp/controller/WxPortalController.java | 116 +++++++++++ .../mp/controller/WxRedirectController.java | 42 ++++ .../vertical/wx/mp/error/ErrorController.java | 29 +++ .../wx/mp/error/ErrorPageConfiguration.java | 27 +++ .../wx/mp/handler/AbstractHandler.java | 12 ++ .../wx/mp/handler/KfSessionHandler.java | 25 +++ .../wx/mp/handler/LocationHandler.java | 44 ++++ .../vertical/wx/mp/handler/LogHandler.java | 25 +++ .../vertical/wx/mp/handler/MenuHandler.java | 35 ++++ .../vertical/wx/mp/handler/MsgHandler.java | 52 +++++ .../vertical/wx/mp/handler/NullHandler.java | 24 +++ .../vertical/wx/mp/handler/ScanHandler.java | 25 +++ .../mp/handler/StoreCheckNotifyHandler.java | 27 +++ .../wx/mp/handler/SubscribeHandler.java | 71 +++++++ .../wx/mp/handler/UnsubscribeHandler.java | 27 +++ .../rymcu/vertical/wx/mp/utils/JsonUtils.java | 9 + 23 files changed, 1003 insertions(+) create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/builder/AbstractBuilder.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/builder/ImageBuilder.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/builder/TextBuilder.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/config/WxMpConfiguration.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/config/WxMpProperties.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/controller/WxMenuController.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/controller/WxPortalController.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/controller/WxRedirectController.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/error/ErrorController.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/error/ErrorPageConfiguration.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/AbstractHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/KfSessionHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/LocationHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/LogHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/MenuHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/MsgHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/NullHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/ScanHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/StoreCheckNotifyHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/SubscribeHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/handler/UnsubscribeHandler.java create mode 100644 src/main/java/com/rymcu/vertical/wx/mp/utils/JsonUtils.java diff --git a/pom.xml b/pom.xml index 27c3657..c8b3a9c 100644 --- a/pom.xml +++ b/pom.xml @@ -169,6 +169,11 @@ jodd-http 5.0.13 + + com.github.binarywang + weixin-java-mp + 3.7.0 + diff --git a/src/main/java/com/rymcu/vertical/wx/mp/builder/AbstractBuilder.java b/src/main/java/com/rymcu/vertical/wx/mp/builder/AbstractBuilder.java new file mode 100644 index 0000000..57207d4 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/builder/AbstractBuilder.java @@ -0,0 +1,17 @@ +package com.rymcu.vertical.wx.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 Binary Wang(https://github.com/binarywang) + */ +public abstract class AbstractBuilder { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + public abstract WxMpXmlOutMessage build(String content, + WxMpXmlMessage wxMessage, WxMpService service); +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/builder/ImageBuilder.java b/src/main/java/com/rymcu/vertical/wx/mp/builder/ImageBuilder.java new file mode 100644 index 0000000..922b3bb --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/builder/ImageBuilder.java @@ -0,0 +1,24 @@ +package com.rymcu.vertical.wx.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; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/builder/TextBuilder.java b/src/main/java/com/rymcu/vertical/wx/mp/builder/TextBuilder.java new file mode 100644 index 0000000..83f0ea9 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/builder/TextBuilder.java @@ -0,0 +1,22 @@ +package com.rymcu.vertical.wx.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; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpConfiguration.java b/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpConfiguration.java new file mode 100644 index 0000000..94c1402 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpConfiguration.java @@ -0,0 +1,111 @@ +package com.rymcu.vertical.wx.mp.config; + +import com.rymcu.vertical.wx.mp.handler.*; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.stream.Collectors; + +import static me.chanjar.weixin.common.api.WxConsts.EventType; +import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY; + +/** + * wechat mp configuration + * + * @author Binary Wang(https://github.com/binarywang) + */ +@AllArgsConstructor +@Configuration +@EnableConfigurationProperties(WxMpProperties.class) +public class WxMpConfiguration { + private final LogHandler logHandler; + private final NullHandler nullHandler; + private final KfSessionHandler kfSessionHandler; + private final StoreCheckNotifyHandler storeCheckNotifyHandler; + private final LocationHandler locationHandler; + private final MenuHandler menuHandler; + private final MsgHandler msgHandler; + private final UnsubscribeHandler unsubscribeHandler; + private final SubscribeHandler subscribeHandler; + private final ScanHandler scanHandler; + private final WxMpProperties properties; + + @Bean + public WxMpService wxMpService() { + // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! + final List configs = this.properties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + WxMpService service = new WxMpServiceImpl(); + service.setMultiConfigStorages(configs + .stream().map(a -> { + WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl(); + configStorage.setAppId(a.getAppId()); + configStorage.setSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + return configStorage; + }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); + return service; + } + + @Bean + public WxMpMessageRouter messageRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.logHandler).next(); + + // 接收客服会话管理事件 + newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION) + .handler(this.kfSessionHandler).end(); + + // 门店审核事件 + newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); + + // 自定义菜单事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.CLICK).handler(this.menuHandler).end(); + + // 点击菜单连接事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.VIEW).handler(this.nullHandler).end(); + + // 关注事件 + newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); + + // 取消关注事件 + newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); + + // 上报地理位置事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.LOCATION).handler(this.locationHandler).end(); + + // 接收地理位置消息 + newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end(); + + // 扫码事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpProperties.java b/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpProperties.java new file mode 100644 index 0000000..de1ee9d --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/config/WxMpProperties.java @@ -0,0 +1,46 @@ +package com.rymcu.vertical.wx.mp.config; + +import com.rymcu.vertical.wx.mp.utils.JsonUtils; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * wechat mp properties + * + * @author Binary Wang(https://github.com/binarywang) + */ +@Data +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + private List configs; + + @Data + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + } + + @Override + public String toString() { + return JsonUtils.toJson(this); + } +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/controller/WxMenuController.java b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxMenuController.java new file mode 100644 index 0000000..03c2372 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxMenuController.java @@ -0,0 +1,188 @@ +package com.rymcu.vertical.wx.mp.controller; + +import lombok.AllArgsConstructor; +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.api.WxMpService; +import me.chanjar.weixin.mp.bean.menu.WxMpGetSelfMenuInfoResult; +import me.chanjar.weixin.mp.bean.menu.WxMpMenu; +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 Binary Wang(https://github.com/binarywang) + */ +@AllArgsConstructor +@RestController +@RequestMapping("/wx/menu/{appid}") +public class WxMenuController { + private final WxMpService wxService; + + /** + *
+     * 自定义菜单创建接口
+     * 详情请见: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
+     * 
+ * + * @return 如果是个性化菜单,则返回menuid,否则返回null + */ + @PostMapping("/create") + public String menuCreate(@PathVariable String appid, @RequestBody WxMenu menu) throws WxErrorException { + return this.wxService.switchoverTo(appid).getMenuService().menuCreate(menu); + } + + @GetMapping("/create") + public String menuCreateSample(@PathVariable String appid) 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 = this.wxService.switchoverTo(appid).oauth2buildAuthorizationUrl( + String.format("%s://%s/wx/redirect/%s/greet", requestURL.getProtocol(), requestURL.getHost(), appid), + WxConsts.OAuth2Scope.SNSAPI_USERINFO, null); + button34.setUrl(url); + } + + button3.getSubButtons().add(button31); + button3.getSubButtons().add(button32); + button3.getSubButtons().add(button33); + button3.getSubButtons().add(button34); + + this.wxService.switchover(appid); + return this.wxService.getMenuService().menuCreate(menu); + } + + /** + *
+     * 自定义菜单创建接口
+     * 详情请见: 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
+     * 
+ * + * @return 如果是个性化菜单,则返回menuid,否则返回null + */ + @PostMapping("/createByJson") + public String menuCreate(@PathVariable String appid, @RequestBody String json) throws WxErrorException { + return this.wxService.switchoverTo(appid).getMenuService().menuCreate(json); + } + + /** + *
+     * 自定义菜单删除接口
+     * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN
+     * 
+ */ + @GetMapping("/delete") + public void menuDelete(@PathVariable String appid) throws WxErrorException { + this.wxService.switchoverTo(appid).getMenuService().menuDelete(); + } + + /** + *
+     * 删除个性化菜单接口
+     * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
+     * 
+ * + * @param menuId 个性化菜单的menuid + */ + @GetMapping("/delete/{menuId}") + public void menuDelete(@PathVariable String appid, @PathVariable String menuId) throws WxErrorException { + this.wxService.switchoverTo(appid).getMenuService().menuDelete(menuId); + } + + /** + *
+     * 自定义菜单查询接口
+     * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN
+     * 
+ */ + @GetMapping("/get") + public WxMpMenu menuGet(@PathVariable String appid) throws WxErrorException { + return this.wxService.switchoverTo(appid).getMenuService().menuGet(); + } + + /** + *
+     * 测试个性化菜单匹配结果
+     * 详情请见: http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html
+     * 
+ * + * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号。 + */ + @GetMapping("/menuTryMatch/{userid}") + public WxMenu menuTryMatch(@PathVariable String appid, @PathVariable String userid) throws WxErrorException { + return this.wxService.switchoverTo(appid).getMenuService().menuTryMatch(userid); + } + + /** + *
+     * 获取自定义菜单配置接口
+     * 本接口将会提供公众号当前使用的自定义菜单的配置,如果公众号是通过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
+     * 
+ */ + @GetMapping("/getSelfMenuInfo") + public WxMpGetSelfMenuInfoResult getSelfMenuInfo(@PathVariable String appid) throws WxErrorException { + return this.wxService.switchoverTo(appid).getMenuService().getSelfMenuInfo(); + } +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/controller/WxPortalController.java b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxPortalController.java new file mode 100644 index 0000000..524549a --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxPortalController.java @@ -0,0 +1,116 @@ +package com.rymcu.vertical.wx.mp.controller; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("/wx/portal/{appid}") +public class WxPortalController { + private final WxMpService wxService; + private final WxMpMessageRouter messageRouter; + + @GetMapping(produces = "text/plain;charset=utf-8") + public String authGet(@PathVariable String appid, + @RequestParam(name = "signature", required = false) String signature, + @RequestParam(name = "timestamp", required = false) String timestamp, + @RequestParam(name = "nonce", required = false) String nonce, + @RequestParam(name = "echostr", required = false) String echostr) { + + log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, + timestamp, nonce, echostr); + if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { + throw new IllegalArgumentException("请求参数非法,请核实!"); + } + + if (!this.wxService.switchover(appid)) { + throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); + } + + if (wxService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } + + return "非法请求"; + } + + @PostMapping(produces = "application/xml; charset=UTF-8") + public String post(@PathVariable String appid, + @RequestBody String requestBody, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("openid") String openid, + @RequestParam(name = "encrypt_type", required = false) String encType, + @RequestParam(name = "msg_signature", required = false) String msgSignature) { + log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + openid, signature, encType, msgSignature, timestamp, nonce, requestBody); + + if (!this.wxService.switchover(appid)) { + throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); + } + + if (!wxService.checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + String out = null; + if (encType == null) { + // 明文传输的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toXml(); + } else if ("aes".equalsIgnoreCase(encType)) { + // aes加密的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), + timestamp, nonce, msgSignature); + log.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); + } + + log.debug("\n组装回复信息:{}", out); + return out; + } + + private WxMpXmlOutMessage route(WxMpXmlMessage message) { + try { + return this.messageRouter.route(message); + } catch (Exception e) { + log.error("路由消息时出现异常!", e); + } + + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/controller/WxRedirectController.java b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxRedirectController.java new file mode 100644 index 0000000..35bf6b4 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/controller/WxRedirectController.java @@ -0,0 +1,42 @@ +package com.rymcu.vertical.wx.mp.controller; + +import lombok.AllArgsConstructor; +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.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; + +import javax.xml.ws.Action; + +/** + * @author Edward + */ +@AllArgsConstructor +@Controller +@RequestMapping("/wx/redirect/{appid}") +public class WxRedirectController { + private final WxMpService wxService; + + @RequestMapping("/greet") + public String greetUser(@PathVariable String appid, @RequestParam String code, ModelMap map) { + if (!this.wxService.switchover(appid)) { + throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); + } + + try { + WxMpOAuth2AccessToken accessToken = wxService.oauth2getAccessToken(code); + WxMpUser user = wxService.oauth2getUserInfo(accessToken, null); + map.put("user", user); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return "greet_user"; + } +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorController.java b/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorController.java new file mode 100644 index 0000000..03b8bad --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorController.java @@ -0,0 +1,29 @@ +package com.rymcu.vertical.wx.mp.error; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + *
+ * 出错页面控制器
+ * Created by Binary Wang on 2018/8/25.
+ * 
+ * + * @author Binary Wang + */ +@Controller +@RequestMapping("/error") +public class ErrorController { + + @GetMapping(value = "/404") + public String error404() { + return "error"; + } + + @GetMapping(value = "/500") + public String error500() { + return "error"; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorPageConfiguration.java b/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorPageConfiguration.java new file mode 100644 index 0000000..2502a00 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/error/ErrorPageConfiguration.java @@ -0,0 +1,27 @@ +package com.rymcu.vertical.wx.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; + +/** + *
+ * 配置错误状态与对应访问路径
+ * Created by Binary Wang on 2018/8/25.
+ * 
+ * + * @author Binary Wang + */ +@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") + ); + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/AbstractHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/AbstractHandler.java new file mode 100644 index 0000000..2149b1c --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/AbstractHandler.java @@ -0,0 +1,12 @@ +package com.rymcu.vertical.wx.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()); +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/KfSessionHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/KfSessionHandler.java new file mode 100644 index 0000000..0423997 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/KfSessionHandler.java @@ -0,0 +1,25 @@ +package com.rymcu.vertical.wx.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 context, WxMpService wxMpService, + WxSessionManager sessionManager) { + //TODO 对会话做处理 + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/LocationHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/LocationHandler.java new file mode 100644 index 0000000..fd0330f --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/LocationHandler.java @@ -0,0 +1,44 @@ +package com.rymcu.vertical.wx.mp.handler; + +import com.rymcu.vertical.wx.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 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; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/LogHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/LogHandler.java new file mode 100644 index 0000000..f405a19 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/LogHandler.java @@ -0,0 +1,25 @@ +package com.rymcu.vertical.wx.mp.handler; + +import com.rymcu.vertical.wx.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 context, WxMpService wxMpService, + WxSessionManager sessionManager) { + this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/MenuHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/MenuHandler.java new file mode 100644 index 0000000..e100914 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/MenuHandler.java @@ -0,0 +1,35 @@ +package com.rymcu.vertical.wx.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.EventType; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class MenuHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + String msg = String.format("type:%s, event:%s, key:%s", + wxMessage.getMsgType(), wxMessage.getEvent(), + wxMessage.getEventKey()); + if (EventType.VIEW.equals(wxMessage.getEvent())) { + return null; + } + + return WxMpXmlOutMessage.TEXT().content(msg) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/MsgHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/MsgHandler.java new file mode 100644 index 0000000..74cde99 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/MsgHandler.java @@ -0,0 +1,52 @@ +package com.rymcu.vertical.wx.mp.handler; + +import com.rymcu.vertical.wx.mp.builder.TextBuilder; +import com.rymcu.vertical.wx.mp.utils.JsonUtils; +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 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); + + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/NullHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/NullHandler.java new file mode 100644 index 0000000..23bbdad --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/NullHandler.java @@ -0,0 +1,24 @@ +package com.rymcu.vertical.wx.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 context, WxMpService wxMpService, + WxSessionManager sessionManager) { + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/ScanHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/ScanHandler.java new file mode 100644 index 0000000..99bc85b --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/ScanHandler.java @@ -0,0 +1,25 @@ +package com.rymcu.vertical.wx.mp.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +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; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class ScanHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + // 扫码事件处理 + return null; + } +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/StoreCheckNotifyHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/StoreCheckNotifyHandler.java new file mode 100644 index 0000000..d036ea7 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/StoreCheckNotifyHandler.java @@ -0,0 +1,27 @@ +package com.rymcu.vertical.wx.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 context, WxMpService wxMpService, + WxSessionManager sessionManager) { + // TODO 处理门店审核事件 + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/SubscribeHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/SubscribeHandler.java new file mode 100644 index 0000000..d4fac61 --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/SubscribeHandler.java @@ -0,0 +1,71 @@ +package com.rymcu.vertical.wx.mp.handler; + +import java.util.Map; + +import com.rymcu.vertical.wx.mp.builder.TextBuilder; +import org.springframework.stereotype.Component; + +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; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class SubscribeHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map 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; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/handler/UnsubscribeHandler.java b/src/main/java/com/rymcu/vertical/wx/mp/handler/UnsubscribeHandler.java new file mode 100644 index 0000000..49cfa3d --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/handler/UnsubscribeHandler.java @@ -0,0 +1,27 @@ +package com.rymcu.vertical.wx.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 context, WxMpService wxMpService, + WxSessionManager sessionManager) { + String openId = wxMessage.getFromUser(); + this.logger.info("取消关注用户 OPENID: " + openId); + // TODO 可以更新本地数据库为取消关注状态 + return null; + } + +} diff --git a/src/main/java/com/rymcu/vertical/wx/mp/utils/JsonUtils.java b/src/main/java/com/rymcu/vertical/wx/mp/utils/JsonUtils.java new file mode 100644 index 0000000..bda28aa --- /dev/null +++ b/src/main/java/com/rymcu/vertical/wx/mp/utils/JsonUtils.java @@ -0,0 +1,9 @@ +package com.rymcu.vertical.wx.mp.utils; + +import com.alibaba.fastjson.JSONObject; + +public class JsonUtils { + public static String toJson(Object obj) { + return JSONObject.toJSONString(obj); + } +}