diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34292da --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target/ +/.settings/ +*.project +*.classpath +application.properties diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4d30df2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.4.1.RELEASE + + + com.github.binarywang.wechat + 1.0.0-SNAPSHOT + weixin-mp-demo-springboot + jar + + spring-boot-demo-for-wechat-mp + Spring Boot Demo with wechat MP + + + UTF-8 + zh_CN + 1.7 + 2.2.4 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + com.github.binarywang + weixin-java-mp + ${weixin-java-mp.version} + + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${project.build.jdk} + ${project.build.jdk} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-surefire-plugin + + com.testwithspring.starter.springboot.UnitTest + + + + + + diff --git a/src/main/java/com/github/binarywang/demo/wechat/WechatMpDemoApplication.java b/src/main/java/com/github/binarywang/demo/wechat/WechatMpDemoApplication.java new file mode 100644 index 0000000..6ccebde --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/WechatMpDemoApplication.java @@ -0,0 +1,16 @@ +package com.github.binarywang.demo.wechat; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author Binary Wang + */ +@SpringBootApplication +public class WechatMpDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(WechatMpDemoApplication.class, args); + } +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/builder/AbstractBuilder.java b/src/main/java/com/github/binarywang/demo/wechat/builder/AbstractBuilder.java new file mode 100644 index 0000000..33461d9 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/builder/AbstractBuilder.java @@ -0,0 +1,20 @@ +package com.github.binarywang.demo.wechat.builder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +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/github/binarywang/demo/wechat/builder/ImageBuilder.java b/src/main/java/com/github/binarywang/demo/wechat/builder/ImageBuilder.java new file mode 100644 index 0000000..628d525 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/builder/ImageBuilder.java @@ -0,0 +1,26 @@ +package com.github.binarywang.demo.wechat.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutImageMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +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/github/binarywang/demo/wechat/builder/TextBuilder.java b/src/main/java/com/github/binarywang/demo/wechat/builder/TextBuilder.java new file mode 100644 index 0000000..a8baf54 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/builder/TextBuilder.java @@ -0,0 +1,24 @@ +package com.github.binarywang.demo.wechat.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage; + +/** + * + * @author Binary Wang + * + */ +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/github/binarywang/demo/wechat/config/WechatMpConfiguration.java b/src/main/java/com/github/binarywang/demo/wechat/config/WechatMpConfiguration.java new file mode 100644 index 0000000..cd04918 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/config/WechatMpConfiguration.java @@ -0,0 +1,172 @@ +package com.github.binarywang.demo.wechat.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.github.binarywang.demo.wechat.handler.AbstractHandler; +import com.github.binarywang.demo.wechat.handler.KfSessionHandler; +import com.github.binarywang.demo.wechat.handler.LocationHandler; +import com.github.binarywang.demo.wechat.handler.LogHandler; +import com.github.binarywang.demo.wechat.handler.MenuHandler; +import com.github.binarywang.demo.wechat.handler.MsgHandler; +import com.github.binarywang.demo.wechat.handler.NullHandler; +import com.github.binarywang.demo.wechat.handler.StoreCheckNotifyHandler; +import com.github.binarywang.demo.wechat.handler.SubscribeHandler; +import com.github.binarywang.demo.wechat.handler.UnsubscribeHandler; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; + +/** + * wechat mp configuration + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnClass(WxMpService.class) +@EnableConfigurationProperties(WechatMpProperties.class) +public class WechatMpConfiguration { + @Autowired + private WechatMpProperties properties; + + @Bean + @ConditionalOnMissingBean + public WxMpConfigStorage configStorage() { + WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage(); + configStorage.setAppId(this.properties.getAppId()); + configStorage.setSecret(this.properties.getSecret()); + configStorage.setToken(this.properties.getToken()); + configStorage.setAesKey(this.properties.getAesKey()); + configStorage.setPartnerId(this.properties.getPartnerId()); + configStorage.setPartnerKey(this.properties.getPartnerKey()); + return configStorage; + } + + @Bean + @ConditionalOnMissingBean + public WxMpService wxMpService(WxMpConfigStorage configStorage) { + WxMpService wxMpService = new WxMpServiceImpl(); + wxMpService.setWxMpConfigStorage(configStorage); + return wxMpService; + } + + @Bean + public WxMpMessageRouter router(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 + newRouter.rule().handler(this.logHandler).next(); + + // 接收客服会话管理事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_KF_CREATE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_KF_CLOSE_SESSION).handler(this.kfSessionHandler) + .end(); + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_KF_SWITCH_SESSION) + .handler(this.kfSessionHandler).end(); + + // 门店审核事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_POI_CHECK_NOTIFY) + .handler(this.storeCheckNotifyHandler).end(); + + // 自定义菜单事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.BUTTON_CLICK).handler(this.getMenuHandler()).end(); + + // 点击菜单连接事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.BUTTON_VIEW).handler(this.nullHandler).end(); + + // 关注事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_SUBSCRIBE).handler(this.getSubscribeHandler()) + .end(); + + // 取消关注事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_UNSUBSCRIBE) + .handler(this.getUnsubscribeHandler()).end(); + + // 上报地理位置事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_LOCATION).handler(this.getLocationHandler()) + .end(); + + // 接收地理位置消息 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_LOCATION) + .handler(this.getLocationHandler()).end(); + + // 扫码事件 + newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT) + .event(WxConsts.EVT_SCAN).handler(this.getScanHandler()).end(); + + // 默认 + newRouter.rule().async(false).handler(this.getMsgHandler()).end(); + + return newRouter; + } + + @Autowired + private LocationHandler locationHandler; + + @Autowired + private MenuHandler menuHandler; + + @Autowired + private MsgHandler msgHandler; + + @Autowired + protected LogHandler logHandler; + + @Autowired + protected NullHandler nullHandler; + + @Autowired + protected KfSessionHandler kfSessionHandler; + + @Autowired + protected StoreCheckNotifyHandler storeCheckNotifyHandler; + + @Autowired + private UnsubscribeHandler unsubscribeHandler; + + @Autowired + private SubscribeHandler subscribeHandler; + + protected MenuHandler getMenuHandler() { + return this.menuHandler; + } + + protected SubscribeHandler getSubscribeHandler() { + return this.subscribeHandler; + } + + protected UnsubscribeHandler getUnsubscribeHandler() { + return this.unsubscribeHandler; + } + + protected AbstractHandler getLocationHandler() { + return this.locationHandler; + } + + protected MsgHandler getMsgHandler() { + return this.msgHandler; + } + + protected AbstractHandler getScanHandler() { + return null; + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/config/WechatMpProperties.java b/src/main/java/com/github/binarywang/demo/wechat/config/WechatMpProperties.java new file mode 100644 index 0000000..83da5a7 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/config/WechatMpProperties.java @@ -0,0 +1,97 @@ +package com.github.binarywang.demo.wechat.config; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * wechat mp properties + * + * @author Binary Wang + */ +@ConfigurationProperties(prefix = "wechat.mp") +public class WechatMpProperties { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 微信支付partner id + */ + private String partnerId; + + /** + * 微信支付partner key + */ + private String partnerKey; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + + public String getAppId() { + return this.appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getSecret() { + return this.secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getPartnerId() { + return this.partnerId; + } + + public void setPartnerId(String partnerId) { + this.partnerId = partnerId; + } + + public String getPartnerKey() { + return this.partnerKey; + } + + public void setPartnerKey(String partnerKey) { + this.partnerKey = partnerKey; + } + + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getAesKey() { + return this.aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, + ToStringStyle.MULTI_LINE_STYLE); + } +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/controller/WechatController.java b/src/main/java/com/github/binarywang/demo/wechat/controller/WechatController.java new file mode 100644 index 0000000..00210c5 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/controller/WechatController.java @@ -0,0 +1,110 @@ +package com.github.binarywang.demo.wechat.controller; + +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@RestController +@RequestMapping("/wechat/portal") +public class WechatController { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private WxMpService wxService; + + @Autowired + private WxMpMessageRouter router; + + @RequestMapping(method = RequestMethod.GET, + produces = "text/plain;charset=utf-8") + public @ResponseBody String authGet( + @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) { + + this.logger.info("\n接收到来自微信服务器的认证消息:[{},{},{},{}]", signature, + timestamp, nonce, echostr); + + if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { + throw new IllegalArgumentException("请求参数非法,请核实~"); + } + + if (this.wxService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } + + return "非法请求"; + } + + @RequestMapping(method = RequestMethod.POST, + produces = "application/xml; charset=UTF-8") + public @ResponseBody String post(@RequestBody String requestBody, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam(name = "encrypt_type", + required = false) String encType, + @RequestParam(name = "msg_signature", + required = false) String msgSignature) { + this.logger.info("\n接收微信请求:[{},{},{},{},{}]\n{} ", signature, encType, + msgSignature, timestamp, nonce, requestBody); + + 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".equals(encType)) { + // aes加密的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml( + requestBody, this.wxService.getWxMpConfigStorage(), timestamp, + nonce, msgSignature); + this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage + .toEncryptedXml(this.wxService.getWxMpConfigStorage()); + } + + this.logger.debug("\n组装回复信息:{}", out); + + return out; + } + + private WxMpXmlOutMessage route(WxMpXmlMessage message) { + try { + return this.router.route(message); + } catch (Exception e) { + this.logger.error(e.getMessage(), e); + } + + return null; + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/controller/WechatErrorController.java b/src/main/java/com/github/binarywang/demo/wechat/controller/WechatErrorController.java new file mode 100644 index 0000000..5d47e93 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/controller/WechatErrorController.java @@ -0,0 +1,121 @@ +package com.github.binarywang.demo.wechat.controller; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ErrorAttributes; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author 王彬 (Binary Wang) + * + */ +@Controller +public class WechatErrorController implements ErrorController { + + private static final Logger logger = LoggerFactory + .getLogger(WechatErrorController.class); + + private static WechatErrorController appErrorController; + + /** + * Error Attributes in the Application + */ + @Autowired + private ErrorAttributes errorAttributes; + + private final static String ERROR_PATH = "/error"; + + /** + * Controller for the Error Controller + * + * @param errorAttributes + */ + + public WechatErrorController(ErrorAttributes errorAttributes) { + this.errorAttributes = errorAttributes; + } + + public WechatErrorController() { + if (appErrorController == null) { + appErrorController = new WechatErrorController(this.errorAttributes); + } + } + + /** + * Supports the HTML Error View + * @param request + */ + @RequestMapping(value = ERROR_PATH, produces = "text/html") + public ModelAndView errorHtml(HttpServletRequest request) { + return new ModelAndView("error", + this.getErrorAttributes(request, false)); + } + + /** + * Supports other formats like JSON, XML + * @param request + */ + @RequestMapping(value = ERROR_PATH) + @ResponseBody + public ResponseEntity> error( + HttpServletRequest request) { + Map body = this.getErrorAttributes(request, + this.getTraceParameter(request)); + HttpStatus status = this.getStatus(request); + return new ResponseEntity<>(body, status); + } + + @Override + public String getErrorPath() { + return ERROR_PATH; + } + + @SuppressWarnings("static-method") + private boolean getTraceParameter(HttpServletRequest request) { + String parameter = request.getParameter("trace"); + if (parameter == null) { + return false; + } + + return !"false".equals(parameter.toLowerCase()); + } + + private Map getErrorAttributes(HttpServletRequest request, + boolean includeStackTrace) { + RequestAttributes requestAttributes = new ServletRequestAttributes( + request); + Map map = this.errorAttributes + .getErrorAttributes(requestAttributes, includeStackTrace); + logger.error("map is [{}]", map); + String url = request.getRequestURL().toString(); + map.put("URL", url); + logger.error("[error info]: status-{}, request url-{}", + map.get("status"), url); + return map; + } + + @SuppressWarnings("static-method") + private HttpStatus getStatus(HttpServletRequest request) { + Integer statusCode = (Integer) request + .getAttribute("javax.servlet.error.status_code"); + if (statusCode != null) { + return HttpStatus.valueOf(statusCode); + } + + return HttpStatus.INTERNAL_SERVER_ERROR; + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/AbstractHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/AbstractHandler.java new file mode 100644 index 0000000..8958d37 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/AbstractHandler.java @@ -0,0 +1,20 @@ +package com.github.binarywang.demo.wechat.handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import me.chanjar.weixin.mp.api.WxMpMessageHandler; + +/** + * + * @author Binary Wang + * + */ +public abstract class AbstractHandler implements WxMpMessageHandler { + + protected Logger logger = LoggerFactory.getLogger(getClass()); + protected final Gson gson = new Gson(); + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/KfSessionHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/KfSessionHandler.java new file mode 100644 index 0000000..ac628f6 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/KfSessionHandler.java @@ -0,0 +1,28 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +@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/github/binarywang/demo/wechat/handler/LocationHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/LocationHandler.java new file mode 100644 index 0000000..62a543b --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/LocationHandler.java @@ -0,0 +1,52 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.github.binarywang.demo.wechat.builder.TextBuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class LocationHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + if (wxMessage.getMsgType().equals(WxConsts.XML_MSG_LOCATION)) { + //TODO 接收处理用户发送的地理位置消息 + try { + String content = "感谢反馈,您的的地理位置已收到!"; + return new TextBuilder().build(content, wxMessage, null); + } catch (Exception e) { + this.logger.error("位置消息接收处理失败", e); + return null; + } + } + + //上报地理位置事件 + this.logger.info("\n上报地理位置 。。。 "); + this.logger.info("\n纬度 : " + wxMessage.getLatitude()); + this.logger.info("\n经度 : " + wxMessage.getLongitude()); + this.logger.info("\n精度 : " + String.valueOf(wxMessage.getPrecision())); + + //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 + + return null; + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/LogHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/LogHandler.java new file mode 100644 index 0000000..55f2e13 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/LogHandler.java @@ -0,0 +1,43 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +@Component +public class LogHandler extends AbstractHandler { + private static final ObjectMapper JSON = new ObjectMapper(); + static { + JSON.setSerializationInclusion(Include.NON_NULL); + JSON.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE); + } + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + try { + this.logger.info("\n接收到请求消息,内容:{}", JSON.writeValueAsString(wxMessage)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + return null; + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/MenuHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/MenuHandler.java new file mode 100644 index 0000000..d36de04 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/MenuHandler.java @@ -0,0 +1,39 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +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 (WxConsts.BUTTON_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/github/binarywang/demo/wechat/handler/MsgHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/MsgHandler.java new file mode 100644 index 0000000..960ce33 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/MsgHandler.java @@ -0,0 +1,56 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.github.binarywang.demo.wechat.builder.TextBuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class MsgHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + + if (!wxMessage.getMsgType().equals(WxConsts.XML_MSG_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 = "回复信息内容"; + return new TextBuilder().build(content, wxMessage, weixinService); + + } + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/NullHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/NullHandler.java new file mode 100644 index 0000000..3822215 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/NullHandler.java @@ -0,0 +1,27 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * + * @author Binary Wang + * + */ +@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/github/binarywang/demo/wechat/handler/ScanHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/ScanHandler.java new file mode 100644 index 0000000..bca97fd --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/ScanHandler.java @@ -0,0 +1,10 @@ +package com.github.binarywang.demo.wechat.handler; + +/** + * + * @author Binary Wang + * + */ +public abstract class ScanHandler extends AbstractHandler { + +} diff --git a/src/main/java/com/github/binarywang/demo/wechat/handler/StoreCheckNotifyHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/StoreCheckNotifyHandler.java new file mode 100644 index 0000000..e59eb04 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/StoreCheckNotifyHandler.java @@ -0,0 +1,28 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * 门店审核事件处理 + * + * @author 王彬 (Binary Wang) + */ +@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/github/binarywang/demo/wechat/handler/SubscribeHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/SubscribeHandler.java new file mode 100644 index 0000000..cc5b831 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/SubscribeHandler.java @@ -0,0 +1,69 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.github.binarywang.demo.wechat.builder.TextBuilder; + +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; + +/** + * @author Binary Wang + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +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()); + + // 获取微信用户基本信息 + WxMpUser userWxInfo = weixinService.getUserService() + .userInfo(wxMessage.getFromUser(), null); + + if (userWxInfo != null) { + // TODO 可以添加关注用户到本地 + } + + WxMpXmlOutMessage responseResult = null; + try { + responseResult = 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/github/binarywang/demo/wechat/handler/UnsubscribeHandler.java b/src/main/java/com/github/binarywang/demo/wechat/handler/UnsubscribeHandler.java new file mode 100644 index 0000000..ca758e8 --- /dev/null +++ b/src/main/java/com/github/binarywang/demo/wechat/handler/UnsubscribeHandler.java @@ -0,0 +1,31 @@ +package com.github.binarywang.demo.wechat.handler; + +import java.util.Map; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +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/resources/application.properties.template b/src/main/resources/application.properties.template new file mode 100644 index 0000000..88c7112 --- /dev/null +++ b/src/main/resources/application.properties.template @@ -0,0 +1,5 @@ +logging.level.org.springframework.web=DEBUG +logging.level.com.github.binarywang.demo.wechat=DEBUG +wechat.mp.appId= +wechat.mp.secret= +wechat.mp.token= \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..00055b9 --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,16 @@ + + + + 出错啦! + + + +

+

+

+

+

+

+

+ + \ No newline at end of file