├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── README.md ├── commit.sh ├── pom.xml └── src ├── main ├── docker │ └── Dockerfile ├── java │ └── com │ │ └── github │ │ └── binarywang │ │ └── demo │ │ └── wx │ │ └── mp │ │ ├── WxMpDemoApplication.java │ │ ├── builder │ │ ├── AbstractBuilder.java │ │ ├── ImageBuilder.java │ │ └── TextBuilder.java │ │ ├── config │ │ ├── WxMpConfiguration.java │ │ └── WxMpProperties.java │ │ ├── controller │ │ ├── WxJsapiController.java │ │ ├── WxMenuController.java │ │ ├── WxPortalController.java │ │ └── WxRedirectController.java │ │ ├── error │ │ ├── ErrorController.java │ │ └── ErrorPageConfiguration.java │ │ ├── handler │ │ ├── AbstractHandler.java │ │ ├── KfSessionHandler.java │ │ ├── LocationHandler.java │ │ ├── LogHandler.java │ │ ├── MenuHandler.java │ │ ├── MsgHandler.java │ │ ├── NullHandler.java │ │ ├── ScanHandler.java │ │ ├── StoreCheckNotifyHandler.java │ │ ├── SubscribeHandler.java │ │ └── UnsubscribeHandler.java │ │ └── utils │ │ └── JsonUtils.java └── resources │ ├── META-INF │ └── additional-spring-configuration-metadata.json │ ├── application.yml.template │ └── templates │ ├── error.html │ └── greet_user.html └── test └── java └── com └── github └── binarywang └── demo └── wx └── mp └── controller ├── BaseControllerTest.java ├── WxJsapiControllerTest.java └── WxMenuControllerTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ binarywang ] 4 | custom: https://github.com/Wechat-Group/WxJava/blob/master/images/qrcodes/wepay.jpg?raw=true 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.project 4 | *.classpath 5 | application.yml 6 | /.idea/ 7 | *.iml 8 | /ngrok/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | script: "mvn clean package -Dmaven.test.skip=true" 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | notifications: 12 | email: 13 | - binarywang@vip.qq.com 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![码云Gitee](https://gitee.com/binary/weixin-java-mp-demo-springboot/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-mp-demo-springboot) 2 | [![Github](http://github-svg-buttons.herokuapp.com/star.svg?user=binarywang&repo=weixin-java-mp-demo-springboot&style=flat&background=1081C1)](https://github.com/binarywang/weixin-java-mp-demo-springboot) 3 | [![Build Status](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot) 4 | ----------------------- 5 | 6 | ### 本Demo基于Spring Boot构建,实现微信公众号后端开发功能。 7 | ### 本项目为WxJava的Demo演示程序,更多Demo请[查阅此处](https://github.com/Wechat-Group/WxJava/blob/master/demo.md)。 8 | #### 如有问题请[【在此提问】](https://github.com/binarywang/weixin-java-mp-demo-springboot/issues),谢谢配合。 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 25 | 26 |
15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 |
27 | 28 | ## 使用步骤: 29 | 1. 请注意,本demo为简化代码编译时加入了lombok支持,如果不了解lombok的话,请先学习下相关知识,比如可以阅读[此文章](https://mp.weixin.qq.com/s/cUc-bUcprycADfNepnSwZQ); 30 | 1. 另外,新手遇到问题,请务必先阅读[【开发文档首页】](https://github.com/Wechat-Group/WxJava/wiki)的常见问题部分,可以少走很多弯路,节省不少时间。 31 | 1. 配置:复制 `/src/main/resources/application.yml.template` 或修改其扩展名生成 `application.yml` 文件,根据自己需要填写相关配置(需要注意的是:yml文件内的属性冒号后面的文字之前需要加空格,可参考已有配置,否则属性会设置不成功); 32 | 2. 主要配置说明如下: 33 | ``` 34 | wx: 35 | mp: 36 | useRedis: false 37 | redisConfig: 38 | host: 127.0.0.1 39 | port: 6379 40 | timeout: 2000 41 | configs: 42 | - appId: 1111 # 第一个公众号的appid 43 | secret: 1111 # 公众号的appsecret 44 | token: 111 # 接口配置里的Token值 45 | aesKey: 111 # 接口配置里的EncodingAESKey值 46 | - appId: 2222 # 第二个公众号的appid,以下同上 47 | secret: 1111 48 | token: 111 49 | aesKey: 111 50 | 51 | ``` 52 | 3. 运行Java程序:`WxMpDemoApplication`; 53 | 4. 配置微信公众号中的接口地址:http://公网可访问域名/wx/portal/xxxxx (注意,xxxxx为对应公众号的appid值); 54 | 5. 根据自己需要修改各个handler的实现,加入自己的业务逻辑。 55 | 56 | -------------------------------------------------------------------------------- /commit.sh: -------------------------------------------------------------------------------- 1 | git commit -a -m ":arrow_up: 升级sdk版本为4.7.0" 2 | git pull --rebase 3 | 4 | git push origin master 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.6.7 10 | 11 | 12 | com.github.binarywang 13 | 1.0.0-SNAPSHOT 14 | weixin-java-mp-demo 15 | jar 16 | 17 | Wechat mp demo with Spring Boot and WxJava 18 | 基于 WxJava 和 Spring Boot 实现的微信公众号后端开发演示项目 19 | 20 | 21 | 4.7.0 22 | 23 | 8 24 | 25 | 26 | UTF-8 27 | UTF-8 28 | zh_CN 29 | wechat-mp-demo 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-thymeleaf 46 | 47 | 48 | 49 | com.github.binarywang 50 | weixin-java-mp 51 | ${weixin-java-mp.version} 52 | 53 | 54 | 55 | org.projectlombok 56 | lombok 57 | provided 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | redis.clients 69 | jedis 70 | 3.3.0 71 | 72 | 73 | 74 | com.github.jedis-lock 75 | jedis-lock 76 | 1.0.0 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-configuration-processor 81 | true 82 | 83 | 84 | 85 | org.testng 86 | testng 87 | 7.5.1 88 | test 89 | 90 | 91 | io.rest-assured 92 | rest-assured 93 | 3.1.1 94 | test 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-maven-plugin 103 | 104 | 105 | 106 | com.spotify 107 | docker-maven-plugin 108 | 1.0.0 109 | 110 | ${docker.image.prefix}/${project.artifactId} 111 | src/main/docker 112 | 113 | 114 | / 115 | ${project.build.directory} 116 | ${project.build.finalName}.jar 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | VOLUME /tmp 3 | ADD weixin-java-mp-demo-springboot-1.0.0-SNAPSHOT.jar app.jar 4 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 5 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/WxMpDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author Binary Wang 8 | */ 9 | @SpringBootApplication 10 | public class WxMpDemoApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(WxMpDemoApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/builder/AbstractBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.builder; 2 | 3 | import me.chanjar.weixin.mp.api.WxMpService; 4 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * @author Binary Wang 11 | */ 12 | public abstract class AbstractBuilder { 13 | protected final Logger logger = LoggerFactory.getLogger(getClass()); 14 | 15 | public abstract WxMpXmlOutMessage build(String content, 16 | WxMpXmlMessage wxMessage, WxMpService service); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/builder/ImageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.builder; 2 | 3 | import me.chanjar.weixin.mp.api.WxMpService; 4 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | 8 | /** 9 | * @author Binary Wang 10 | */ 11 | public class ImageBuilder extends AbstractBuilder { 12 | 13 | @Override 14 | public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, 15 | WxMpService service) { 16 | 17 | WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content) 18 | .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) 19 | .build(); 20 | 21 | return m; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/builder/TextBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.builder; 2 | 3 | import me.chanjar.weixin.mp.api.WxMpService; 4 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage; 7 | 8 | /** 9 | * @author Binary Wang 10 | */ 11 | public class TextBuilder extends AbstractBuilder { 12 | 13 | @Override 14 | public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, 15 | WxMpService service) { 16 | WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) 17 | .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) 18 | .build(); 19 | return m; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/config/WxMpConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.config; 2 | 3 | import com.github.binarywang.demo.wx.mp.handler.*; 4 | import lombok.AllArgsConstructor; 5 | import me.chanjar.weixin.common.redis.JedisWxRedisOps; 6 | import me.chanjar.weixin.mp.api.WxMpMessageRouter; 7 | import me.chanjar.weixin.mp.api.WxMpService; 8 | import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; 9 | import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; 10 | import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; 11 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import redis.clients.jedis.JedisPool; 15 | import redis.clients.jedis.JedisPoolConfig; 16 | 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | import static me.chanjar.weixin.common.api.WxConsts.EventType; 21 | import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE; 22 | import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE; 23 | import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; 24 | import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; 25 | import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*; 26 | import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY; 27 | 28 | /** 29 | * wechat mp configuration 30 | * 31 | * @author Binary Wang 32 | */ 33 | @AllArgsConstructor 34 | @Configuration 35 | @EnableConfigurationProperties(WxMpProperties.class) 36 | public class WxMpConfiguration { 37 | private final LogHandler logHandler; 38 | private final NullHandler nullHandler; 39 | private final KfSessionHandler kfSessionHandler; 40 | private final StoreCheckNotifyHandler storeCheckNotifyHandler; 41 | private final LocationHandler locationHandler; 42 | private final MenuHandler menuHandler; 43 | private final MsgHandler msgHandler; 44 | private final UnsubscribeHandler unsubscribeHandler; 45 | private final SubscribeHandler subscribeHandler; 46 | private final ScanHandler scanHandler; 47 | private final WxMpProperties properties; 48 | 49 | @Bean 50 | public WxMpService wxMpService() { 51 | // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! 52 | final List configs = this.properties.getConfigs(); 53 | if (configs == null) { 54 | throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); 55 | } 56 | 57 | WxMpService service = new WxMpServiceImpl(); 58 | service.setMultiConfigStorages(configs 59 | .stream().map(a -> { 60 | WxMpDefaultConfigImpl configStorage; 61 | if (this.properties.isUseRedis()) { 62 | final WxMpProperties.RedisConfig redisConfig = this.properties.getRedisConfig(); 63 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 64 | JedisPool jedisPool = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(), 65 | redisConfig.getTimeout(), redisConfig.getPassword()); 66 | configStorage = new WxMpRedisConfigImpl(new JedisWxRedisOps(jedisPool), a.getAppId()); 67 | } else { 68 | configStorage = new WxMpDefaultConfigImpl(); 69 | } 70 | 71 | configStorage.setAppId(a.getAppId()); 72 | configStorage.setSecret(a.getSecret()); 73 | configStorage.setToken(a.getToken()); 74 | configStorage.setAesKey(a.getAesKey()); 75 | return configStorage; 76 | }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); 77 | return service; 78 | } 79 | 80 | @Bean 81 | public WxMpMessageRouter messageRouter(WxMpService wxMpService) { 82 | final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); 83 | 84 | // 记录所有事件的日志 (异步执行) 85 | newRouter.rule().handler(this.logHandler).next(); 86 | 87 | // 接收客服会话管理事件 88 | newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION) 89 | .handler(this.kfSessionHandler).end(); 90 | newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION) 91 | .handler(this.kfSessionHandler).end(); 92 | newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION) 93 | .handler(this.kfSessionHandler).end(); 94 | 95 | // 门店审核事件 96 | newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); 97 | 98 | // 自定义菜单事件 99 | newRouter.rule().async(false).msgType(EVENT).event(EventType.CLICK).handler(this.menuHandler).end(); 100 | 101 | // 点击菜单连接事件 102 | newRouter.rule().async(false).msgType(EVENT).event(EventType.VIEW).handler(this.nullHandler).end(); 103 | 104 | // 关注事件 105 | newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); 106 | 107 | // 取消关注事件 108 | newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); 109 | 110 | // 上报地理位置事件 111 | newRouter.rule().async(false).msgType(EVENT).event(EventType.LOCATION).handler(this.locationHandler).end(); 112 | 113 | // 接收地理位置消息 114 | newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end(); 115 | 116 | // 扫码事件 117 | newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); 118 | 119 | // 默认 120 | newRouter.rule().async(false).handler(this.msgHandler).end(); 121 | 122 | return newRouter; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/config/WxMpProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.config; 2 | 3 | import com.github.binarywang.demo.wx.mp.utils.JsonUtils; 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * wechat mp properties 11 | * 12 | * @author Binary Wang 13 | */ 14 | @Data 15 | @ConfigurationProperties(prefix = "wx.mp") 16 | public class WxMpProperties { 17 | /** 18 | * 是否使用redis存储access token 19 | */ 20 | private boolean useRedis; 21 | 22 | /** 23 | * redis 配置 24 | */ 25 | private RedisConfig redisConfig; 26 | 27 | @Data 28 | public static class RedisConfig { 29 | /** 30 | * redis服务器 主机地址 31 | */ 32 | private String host; 33 | 34 | /** 35 | * redis服务器 端口号 36 | */ 37 | private Integer port; 38 | 39 | /** 40 | * redis服务器 密码 41 | */ 42 | private String password; 43 | 44 | /** 45 | * redis 服务连接超时时间 46 | */ 47 | private Integer timeout; 48 | } 49 | 50 | /** 51 | * 多个公众号配置信息 52 | */ 53 | private List configs; 54 | 55 | @Data 56 | public static class MpConfig { 57 | /** 58 | * 设置微信公众号的appid 59 | */ 60 | private String appId; 61 | 62 | /** 63 | * 设置微信公众号的app secret 64 | */ 65 | private String secret; 66 | 67 | /** 68 | * 设置微信公众号的token 69 | */ 70 | private String token; 71 | 72 | /** 73 | * 设置微信公众号的EncodingAESKey 74 | */ 75 | private String aesKey; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return JsonUtils.toJson(this); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/controller/WxJsapiController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import me.chanjar.weixin.common.bean.WxJsapiSignature; 5 | import me.chanjar.weixin.common.error.WxErrorException; 6 | import me.chanjar.weixin.mp.api.WxMpService; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.net.MalformedURLException; 13 | 14 | /** 15 | * jsapi 演示接口的 controller. 16 | * 17 | * @author Binary Wang 18 | * @date 2020-04-25 19 | */ 20 | @AllArgsConstructor 21 | @RestController 22 | @RequestMapping("/wx/jsapi/{appid}") 23 | public class WxJsapiController { 24 | private final WxMpService wxService; 25 | 26 | @GetMapping("/getJsapiTicket") 27 | public String getJsapiTicket(@PathVariable String appid) throws WxErrorException { 28 | final WxJsapiSignature jsapiSignature = this.wxService.switchoverTo(appid).createJsapiSignature("111"); 29 | System.out.println(jsapiSignature); 30 | return this.wxService.getJsapiTicket(true); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/controller/WxMenuController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import me.chanjar.weixin.common.api.WxConsts; 5 | import me.chanjar.weixin.common.bean.menu.WxMenu; 6 | import me.chanjar.weixin.common.bean.menu.WxMenuButton; 7 | import me.chanjar.weixin.common.error.WxErrorException; 8 | import me.chanjar.weixin.mp.api.WxMpService; 9 | import me.chanjar.weixin.mp.bean.menu.WxMpGetSelfMenuInfoResult; 10 | import me.chanjar.weixin.mp.bean.menu.WxMpMenu; 11 | import org.springframework.web.bind.annotation.*; 12 | import org.springframework.web.context.request.RequestContextHolder; 13 | import org.springframework.web.context.request.ServletRequestAttributes; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.net.MalformedURLException; 17 | import java.net.URL; 18 | 19 | import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; 20 | 21 | /** 22 | * @author Binary Wang 23 | */ 24 | @AllArgsConstructor 25 | @RestController 26 | @RequestMapping("/wx/menu/{appid}") 27 | public class WxMenuController { 28 | private final WxMpService wxService; 29 | 30 | /** 31 | *
 32 |      * 自定义菜单创建接口
 33 |      * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN
 34 |      * 如果要创建个性化菜单,请设置matchrule属性
 35 |      * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
 36 |      * 
37 | * 38 | * @return 如果是个性化菜单,则返回menuid,否则返回null 39 | */ 40 | @PostMapping("/create") 41 | public String menuCreate(@PathVariable String appid, @RequestBody WxMenu menu) throws WxErrorException { 42 | return this.wxService.switchoverTo(appid).getMenuService().menuCreate(menu); 43 | } 44 | 45 | @GetMapping("/create") 46 | public String menuCreateSample(@PathVariable String appid) throws WxErrorException, MalformedURLException { 47 | WxMenu menu = new WxMenu(); 48 | WxMenuButton button1 = new WxMenuButton(); 49 | button1.setType(MenuButtonType.CLICK); 50 | button1.setName("今日歌曲"); 51 | button1.setKey("V1001_TODAY_MUSIC"); 52 | 53 | // WxMenuButton button2 = new WxMenuButton(); 54 | // button2.setType(WxConsts.BUTTON_MINIPROGRAM); 55 | // button2.setName("小程序"); 56 | // button2.setAppId("wx286b93c14bbf93aa"); 57 | // button2.setPagePath("pages/lunar/index.html"); 58 | // button2.setUrl("http://mp.weixin.qq.com"); 59 | 60 | WxMenuButton button3 = new WxMenuButton(); 61 | button3.setName("菜单"); 62 | 63 | menu.getButtons().add(button1); 64 | // menu.getButtons().add(button2); 65 | menu.getButtons().add(button3); 66 | 67 | WxMenuButton button31 = new WxMenuButton(); 68 | button31.setType(MenuButtonType.VIEW); 69 | button31.setName("搜索"); 70 | button31.setUrl("http://www.soso.com/"); 71 | 72 | WxMenuButton button32 = new WxMenuButton(); 73 | button32.setType(MenuButtonType.VIEW); 74 | button32.setName("视频"); 75 | button32.setUrl("http://v.qq.com/"); 76 | 77 | WxMenuButton button33 = new WxMenuButton(); 78 | button33.setType(MenuButtonType.CLICK); 79 | button33.setName("赞一下我们"); 80 | button33.setKey("V1001_GOOD"); 81 | 82 | WxMenuButton button34 = new WxMenuButton(); 83 | button34.setType(MenuButtonType.VIEW); 84 | button34.setName("获取用户信息"); 85 | 86 | ServletRequestAttributes servletRequestAttributes = 87 | (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 88 | if (servletRequestAttributes != null) { 89 | HttpServletRequest request = servletRequestAttributes.getRequest(); 90 | URL requestURL = new URL(request.getRequestURL().toString()); 91 | String url = this.wxService.switchoverTo(appid).getOAuth2Service().buildAuthorizationUrl( 92 | String.format("%s://%s/wx/redirect/%s/greet", requestURL.getProtocol(), requestURL.getHost(), appid), 93 | WxConsts.OAuth2Scope.SNSAPI_USERINFO, null); 94 | button34.setUrl(url); 95 | } 96 | 97 | button3.getSubButtons().add(button31); 98 | button3.getSubButtons().add(button32); 99 | button3.getSubButtons().add(button33); 100 | button3.getSubButtons().add(button34); 101 | 102 | this.wxService.switchover(appid); 103 | return this.wxService.getMenuService().menuCreate(menu); 104 | } 105 | 106 | /** 107 | *
108 |      * 自定义菜单创建接口
109 |      * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN
110 |      * 如果要创建个性化菜单,请设置matchrule属性
111 |      * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
112 |      * 
113 | * 114 | * @return 如果是个性化菜单,则返回menuid,否则返回null 115 | */ 116 | @PostMapping("/createByJson") 117 | public String menuCreate(@PathVariable String appid, @RequestBody String json) throws WxErrorException { 118 | return this.wxService.switchoverTo(appid).getMenuService().menuCreate(json); 119 | } 120 | 121 | /** 122 | *
123 |      * 自定义菜单删除接口
124 |      * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN
125 |      * 
126 | */ 127 | @GetMapping("/delete") 128 | public void menuDelete(@PathVariable String appid) throws WxErrorException { 129 | this.wxService.switchoverTo(appid).getMenuService().menuDelete(); 130 | } 131 | 132 | /** 133 | *
134 |      * 删除个性化菜单接口
135 |      * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN
136 |      * 
137 | * 138 | * @param menuId 个性化菜单的menuid 139 | */ 140 | @GetMapping("/delete/{menuId}") 141 | public void menuDelete(@PathVariable String appid, @PathVariable String menuId) throws WxErrorException { 142 | this.wxService.switchoverTo(appid).getMenuService().menuDelete(menuId); 143 | } 144 | 145 | /** 146 | *
147 |      * 自定义菜单查询接口
148 |      * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN
149 |      * 
150 | */ 151 | @GetMapping("/get") 152 | public WxMpMenu menuGet(@PathVariable String appid) throws WxErrorException { 153 | return this.wxService.switchoverTo(appid).getMenuService().menuGet(); 154 | } 155 | 156 | /** 157 | *
158 |      * 测试个性化菜单匹配结果
159 |      * 详情请见: http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html
160 |      * 
161 | * 162 | * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号。 163 | */ 164 | @GetMapping("/menuTryMatch/{userid}") 165 | public WxMenu menuTryMatch(@PathVariable String appid, @PathVariable String userid) throws WxErrorException { 166 | return this.wxService.switchoverTo(appid).getMenuService().menuTryMatch(userid); 167 | } 168 | 169 | /** 170 | *
171 |      * 获取自定义菜单配置接口
172 |      * 本接口将会提供公众号当前使用的自定义菜单的配置,如果公众号是通过API调用设置的菜单,则返回菜单的开发配置,而如果公众号是在公众平台官网通过网站功能发布菜单,则本接口返回运营者设置的菜单配置。
173 |      * 请注意:
174 |      * 1、第三方平台开发者可以通过本接口,在旗下公众号将业务授权给你后,立即通过本接口检测公众号的自定义菜单配置,并通过接口再次给公众号设置好自动回复规则,以提升公众号运营者的业务体验。
175 |      * 2、本接口与自定义菜单查询接口的不同之处在于,本接口无论公众号的接口是如何设置的,都能查询到接口,而自定义菜单查询接口则仅能查询到使用API设置的菜单配置。
176 |      * 3、认证/未认证的服务号/订阅号,以及接口测试号,均拥有该接口权限。
177 |      * 4、从第三方平台的公众号登录授权机制上来说,该接口从属于消息与菜单权限集。
178 |      * 5、本接口中返回的图片/语音/视频为临时素材(临时素材每次获取都不同,3天内有效,通过素材管理-获取临时素材接口来获取这些素材),本接口返回的图文消息为永久素材素材(通过素材管理-获取永久素材接口来获取这些素材)。
179 |      *  接口调用请求说明:
180 |      * http请求方式: GET(请使用https协议)
181 |      * https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN
182 |      * 
183 | */ 184 | @GetMapping("/getSelfMenuInfo") 185 | public WxMpGetSelfMenuInfoResult getSelfMenuInfo(@PathVariable String appid) throws WxErrorException { 186 | return this.wxService.switchoverTo(appid).getMenuService().getSelfMenuInfo(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/controller/WxPortalController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.log4j.Log4j; 5 | import lombok.extern.slf4j.Slf4j; 6 | import me.chanjar.weixin.mp.api.WxMpMessageRouter; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import me.chanjar.weixin.mp.api.WxMpService; 20 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 21 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 22 | 23 | /** 24 | * @author Binary Wang 25 | */ 26 | @Slf4j 27 | @AllArgsConstructor 28 | @RestController 29 | @RequestMapping("/wx/portal/{appid}") 30 | public class WxPortalController { 31 | private final WxMpService wxService; 32 | private final WxMpMessageRouter messageRouter; 33 | 34 | @GetMapping(produces = "text/plain;charset=utf-8") 35 | public String authGet(@PathVariable String appid, 36 | @RequestParam(name = "signature", required = false) String signature, 37 | @RequestParam(name = "timestamp", required = false) String timestamp, 38 | @RequestParam(name = "nonce", required = false) String nonce, 39 | @RequestParam(name = "echostr", required = false) String echostr) { 40 | 41 | log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, 42 | timestamp, nonce, echostr); 43 | if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { 44 | throw new IllegalArgumentException("请求参数非法,请核实!"); 45 | } 46 | 47 | if (!this.wxService.switchover(appid)) { 48 | throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); 49 | } 50 | 51 | if (wxService.checkSignature(timestamp, nonce, signature)) { 52 | return echostr; 53 | } 54 | 55 | return "非法请求"; 56 | } 57 | 58 | @PostMapping(produces = "application/xml; charset=UTF-8") 59 | public String post(@PathVariable String appid, 60 | @RequestBody String requestBody, 61 | @RequestParam("signature") String signature, 62 | @RequestParam("timestamp") String timestamp, 63 | @RequestParam("nonce") String nonce, 64 | @RequestParam("openid") String openid, 65 | @RequestParam(name = "encrypt_type", required = false) String encType, 66 | @RequestParam(name = "msg_signature", required = false) String msgSignature) { 67 | log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," 68 | + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", 69 | openid, signature, encType, msgSignature, timestamp, nonce, requestBody); 70 | 71 | if (!this.wxService.switchover(appid)) { 72 | throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); 73 | } 74 | 75 | if (!wxService.checkSignature(timestamp, nonce, signature)) { 76 | throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); 77 | } 78 | 79 | String out = null; 80 | if (encType == null) { 81 | // 明文传输的消息 82 | WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); 83 | WxMpXmlOutMessage outMessage = this.route(inMessage); 84 | if (outMessage == null) { 85 | return ""; 86 | } 87 | 88 | out = outMessage.toXml(); 89 | } else if ("aes".equalsIgnoreCase(encType)) { 90 | // aes加密的消息 91 | WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), 92 | timestamp, nonce, msgSignature); 93 | log.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); 94 | WxMpXmlOutMessage outMessage = this.route(inMessage); 95 | if (outMessage == null) { 96 | return ""; 97 | } 98 | 99 | out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); 100 | } 101 | 102 | log.debug("\n组装回复信息:{}", out); 103 | return out; 104 | } 105 | 106 | private WxMpXmlOutMessage route(WxMpXmlMessage message) { 107 | try { 108 | return this.messageRouter.route(message); 109 | } catch (Exception e) { 110 | log.error("路由消息时出现异常!", e); 111 | } 112 | 113 | return null; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/controller/WxRedirectController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; 5 | import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; 6 | import me.chanjar.weixin.common.error.WxErrorException; 7 | import me.chanjar.weixin.mp.api.WxMpService; 8 | import me.chanjar.weixin.mp.bean.result.WxMpUser; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.ModelMap; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | 15 | /** 16 | * @author Edward 17 | */ 18 | @AllArgsConstructor 19 | @Controller 20 | @RequestMapping("/wx/redirect/{appid}") 21 | public class WxRedirectController { 22 | private final WxMpService wxService; 23 | 24 | @RequestMapping("/greet") 25 | public String greetUser(@PathVariable String appid, @RequestParam String code, ModelMap map) { 26 | if (!this.wxService.switchover(appid)) { 27 | throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); 28 | } 29 | 30 | try { 31 | WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code); 32 | WxOAuth2UserInfo user = wxService.getOAuth2Service().getUserInfo(accessToken, null); 33 | map.put("user", user); 34 | } catch (WxErrorException e) { 35 | e.printStackTrace(); 36 | } 37 | 38 | return "greet_user"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/error/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.error; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | 7 | /** 8 | *
 9 |  * 出错页面控制器
10 |  * Created by Binary Wang on 2018/8/25.
11 |  * 
12 | * 13 | * @author Binary Wang 14 | */ 15 | @Controller 16 | @RequestMapping("/error") 17 | public class ErrorController { 18 | 19 | @GetMapping(value = "/404") 20 | public String error404() { 21 | return "error"; 22 | } 23 | 24 | @GetMapping(value = "/500") 25 | public String error500() { 26 | return "error"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/error/ErrorPageConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.error; 2 | 3 | import org.springframework.boot.web.server.ErrorPage; 4 | import org.springframework.boot.web.server.ErrorPageRegistrar; 5 | import org.springframework.boot.web.server.ErrorPageRegistry; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | *
11 |  * 配置错误状态与对应访问路径
12 |  * Created by Binary Wang on 2018/8/25.
13 |  * 
14 | * 15 | * @author Binary Wang 16 | */ 17 | @Component 18 | public class ErrorPageConfiguration implements ErrorPageRegistrar { 19 | @Override 20 | public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { 21 | errorPageRegistry.addErrorPages( 22 | new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"), 23 | new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500") 24 | ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.mp.api.WxMpMessageHandler; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * @author Binary Wang 9 | */ 10 | public abstract class AbstractHandler implements WxMpMessageHandler { 11 | protected Logger logger = LoggerFactory.getLogger(getClass()); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/KfSessionHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.common.session.WxSessionManager; 4 | import me.chanjar.weixin.mp.api.WxMpService; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Binary Wang 13 | */ 14 | @Component 15 | public class KfSessionHandler extends AbstractHandler { 16 | 17 | @Override 18 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 19 | Map context, WxMpService wxMpService, 20 | WxSessionManager sessionManager) { 21 | //TODO 对会话做处理 22 | return null; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/LocationHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import com.github.binarywang.demo.wx.mp.builder.TextBuilder; 4 | import me.chanjar.weixin.common.session.WxSessionManager; 5 | import me.chanjar.weixin.mp.api.WxMpService; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 7 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | 12 | import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; 13 | 14 | /** 15 | * @author Binary Wang 16 | */ 17 | @Component 18 | public class LocationHandler extends AbstractHandler { 19 | 20 | @Override 21 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 22 | Map context, WxMpService wxMpService, 23 | WxSessionManager sessionManager) { 24 | if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) { 25 | //TODO 接收处理用户发送的地理位置消息 26 | try { 27 | String content = "感谢反馈,您的的地理位置已收到!"; 28 | return new TextBuilder().build(content, wxMessage, null); 29 | } catch (Exception e) { 30 | this.logger.error("位置消息接收处理失败", e); 31 | return null; 32 | } 33 | } 34 | 35 | //上报地理位置事件 36 | this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}", 37 | wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision())); 38 | 39 | //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 40 | 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/LogHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import com.github.binarywang.demo.wx.mp.utils.JsonUtils; 4 | import me.chanjar.weixin.common.session.WxSessionManager; 5 | import me.chanjar.weixin.mp.api.WxMpService; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 7 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Binary Wang 14 | */ 15 | @Component 16 | public class LogHandler extends AbstractHandler { 17 | @Override 18 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 19 | Map context, WxMpService wxMpService, 20 | WxSessionManager sessionManager) { 21 | this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); 22 | return null; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/MenuHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.common.session.WxSessionManager; 4 | import me.chanjar.weixin.mp.api.WxMpService; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | import static me.chanjar.weixin.common.api.WxConsts.EventType; 12 | 13 | /** 14 | * @author Binary Wang 15 | */ 16 | @Component 17 | public class MenuHandler extends AbstractHandler { 18 | 19 | @Override 20 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 21 | Map context, WxMpService weixinService, 22 | WxSessionManager sessionManager) { 23 | String msg = String.format("type:%s, event:%s, key:%s", 24 | wxMessage.getMsgType(), wxMessage.getEvent(), 25 | wxMessage.getEventKey()); 26 | if (EventType.VIEW.equals(wxMessage.getEvent())) { 27 | return null; 28 | } 29 | 30 | return WxMpXmlOutMessage.TEXT().content(msg) 31 | .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) 32 | .build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/MsgHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import com.github.binarywang.demo.wx.mp.builder.TextBuilder; 4 | import com.github.binarywang.demo.wx.mp.utils.JsonUtils; 5 | import me.chanjar.weixin.common.error.WxErrorException; 6 | import me.chanjar.weixin.common.session.WxSessionManager; 7 | import me.chanjar.weixin.mp.api.WxMpService; 8 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 9 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Map; 14 | 15 | import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; 16 | 17 | /** 18 | * @author Binary Wang 19 | */ 20 | @Component 21 | public class MsgHandler extends AbstractHandler { 22 | 23 | @Override 24 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 25 | Map context, WxMpService weixinService, 26 | WxSessionManager sessionManager) { 27 | 28 | if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) { 29 | //TODO 可以选择将消息保存到本地 30 | } 31 | 32 | //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 33 | try { 34 | if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") 35 | && weixinService.getKefuService().kfOnlineList() 36 | .getKfOnlineList().size() > 0) { 37 | return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() 38 | .fromUser(wxMessage.getToUser()) 39 | .toUser(wxMessage.getFromUser()).build(); 40 | } 41 | } catch (WxErrorException e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | //TODO 组装回复消息 46 | String content = "收到信息内容:" + JsonUtils.toJson(wxMessage); 47 | 48 | return new TextBuilder().build(content, wxMessage, weixinService); 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/NullHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.common.session.WxSessionManager; 4 | import me.chanjar.weixin.mp.api.WxMpService; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Binary Wang 13 | */ 14 | @Component 15 | public class NullHandler extends AbstractHandler { 16 | 17 | @Override 18 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 19 | Map context, WxMpService wxMpService, 20 | WxSessionManager sessionManager) { 21 | return null; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/ScanHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import me.chanjar.weixin.common.error.WxErrorException; 8 | import me.chanjar.weixin.common.session.WxSessionManager; 9 | import me.chanjar.weixin.mp.api.WxMpService; 10 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 11 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 12 | 13 | /** 14 | * @author Binary Wang 15 | */ 16 | @Component 17 | public class ScanHandler extends AbstractHandler { 18 | 19 | @Override 20 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, 21 | WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { 22 | // 扫码事件处理 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/StoreCheckNotifyHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.common.session.WxSessionManager; 4 | import me.chanjar.weixin.mp.api.WxMpService; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * 门店审核事件处理 13 | * 14 | * @author Binary Wang 15 | */ 16 | @Component 17 | public class StoreCheckNotifyHandler extends AbstractHandler { 18 | 19 | @Override 20 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 21 | Map context, WxMpService wxMpService, 22 | WxSessionManager sessionManager) { 23 | // TODO 处理门店审核事件 24 | return null; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/SubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import com.github.binarywang.demo.wx.mp.builder.TextBuilder; 8 | import me.chanjar.weixin.common.error.WxErrorException; 9 | import me.chanjar.weixin.common.session.WxSessionManager; 10 | import me.chanjar.weixin.mp.api.WxMpService; 11 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 12 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 13 | import me.chanjar.weixin.mp.bean.result.WxMpUser; 14 | 15 | /** 16 | * @author Binary Wang 17 | */ 18 | @Component 19 | public class SubscribeHandler extends AbstractHandler { 20 | 21 | @Override 22 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 23 | Map context, WxMpService weixinService, 24 | WxSessionManager sessionManager) throws WxErrorException { 25 | 26 | this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser()); 27 | 28 | // 获取微信用户基本信息 29 | try { 30 | WxMpUser userWxInfo = weixinService.getUserService() 31 | .userInfo(wxMessage.getFromUser(), null); 32 | if (userWxInfo != null) { 33 | // TODO 可以添加关注用户到本地数据库 34 | } 35 | } catch (WxErrorException e) { 36 | if (e.getError().getErrorCode() == 48001) { 37 | this.logger.info("该公众号没有获取用户信息权限!"); 38 | } 39 | } 40 | 41 | 42 | WxMpXmlOutMessage responseResult = null; 43 | try { 44 | responseResult = this.handleSpecial(wxMessage); 45 | } catch (Exception e) { 46 | this.logger.error(e.getMessage(), e); 47 | } 48 | 49 | if (responseResult != null) { 50 | return responseResult; 51 | } 52 | 53 | try { 54 | return new TextBuilder().build("感谢关注", wxMessage, weixinService); 55 | } catch (Exception e) { 56 | this.logger.error(e.getMessage(), e); 57 | } 58 | 59 | return null; 60 | } 61 | 62 | /** 63 | * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 64 | */ 65 | private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage) 66 | throws Exception { 67 | //TODO 68 | return null; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/handler/UnsubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.handler; 2 | 3 | import me.chanjar.weixin.common.session.WxSessionManager; 4 | import me.chanjar.weixin.mp.api.WxMpService; 5 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Binary Wang 13 | */ 14 | @Component 15 | public class UnsubscribeHandler extends AbstractHandler { 16 | 17 | @Override 18 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, 19 | Map context, WxMpService wxMpService, 20 | WxSessionManager sessionManager) { 21 | String openId = wxMessage.getFromUser(); 22 | this.logger.info("取消关注用户 OPENID: " + openId); 23 | // TODO 可以更新本地数据库为取消关注状态 24 | return null; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/mp/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | /** 7 | * @author Binary Wang 8 | */ 9 | public class JsonUtils { 10 | public static String toJson(Object obj) { 11 | Gson gson = new GsonBuilder() 12 | .setPrettyPrinting() 13 | .disableHtmlEscaping() 14 | .create(); 15 | return gson.toJson(obj); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": [ 3 | { 4 | "name": "wx.mp.configs", 5 | "type": "java.util.List", 6 | "description": "Description for wx.mp.configs." 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/application.yml.template: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org.springframework.web: INFO 4 | com.github.binarywang.demo.wx.mp: DEBUG 5 | me.chanjar.weixin: DEBUG 6 | wx: 7 | mp: 8 | useRedis: false 9 | redisConfig: 10 | host: 127.0.0.1 11 | port: 6379 12 | configs: 13 | - appId: 1111 # 第一个公众号的appid 14 | secret: 1111 # 公众号的appsecret 15 | token: 111 # 接口配置里的Token值 16 | aesKey: 111 # 接口配置里的EncodingAESKey值 17 | - appId: 2222 # 第二个公众号的appid,以下同上 18 | secret: 1111 19 | token: 111 20 | aesKey: 111 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 出错啦! 5 | 6 | 7 | 8 |

9 |

10 |

11 |

12 |

13 |

14 |

15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/greet_user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Hello, ! 15 |
16 |
17 |
18 |

19 | Card image cap 20 |
21 |
22 |

性别:

23 |

城市:

24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/java/com/github/binarywang/demo/wx/mp/controller/BaseControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import io.restassured.RestAssured; 4 | import org.testng.annotations.BeforeTest; 5 | 6 | import java.util.Base64; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | /** 11 | * 公共测试方法和参数. 12 | * 13 | * @author Binary Wang 14 | * @date 2019-06-14 15 | */ 16 | public abstract class BaseControllerTest { 17 | private static final String ROOT_URL = "http://127.0.0.1:8080/"; 18 | 19 | @BeforeTest 20 | public void setup() { 21 | RestAssured.baseURI = ROOT_URL; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/github/binarywang/demo/wx/mp/controller/WxJsapiControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import org.testng.annotations.Test; 4 | 5 | import static io.restassured.RestAssured.given; 6 | 7 | /** 8 | * jsapi 测试. 9 | * 10 | * @author Binary Wang 11 | * @date 2020-04-25 12 | */ 13 | @Test 14 | public class WxJsapiControllerTest extends BaseControllerTest { 15 | @Test(invocationCount = 1000, threadPoolSize = 5) 16 | public void testGetJsapiTicket() { 17 | given() 18 | .when().get("/wx/jsapi/xxxx/getJsapiTicket") 19 | .then() 20 | .log().everything(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/github/binarywang/demo/wx/mp/controller/WxMenuControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.mp.controller; 2 | 3 | import io.restassured.http.ContentType; 4 | import me.chanjar.weixin.common.bean.menu.WxMenu; 5 | import org.testng.annotations.Test; 6 | 7 | import static io.restassured.RestAssured.given; 8 | 9 | /** 10 | * 菜单测试. 11 | * 12 | * @author Binary Wang 13 | * @date 2020-08-19 14 | */ 15 | public class WxMenuControllerTest extends BaseControllerTest { 16 | 17 | @Test 18 | public void testMenuCreate() { 19 | given() 20 | .when() 21 | .body(new WxMenu()) 22 | .contentType(ContentType.JSON) 23 | .post("/wx/menu/wxappid/create") 24 | .then() 25 | .log().everything(); 26 | } 27 | } 28 | --------------------------------------------------------------------------------