├── .editorconfig ├── .gitignore ├── .travis.yml ├── README.md ├── commit.sh ├── pom.xml └── src └── main ├── java └── com │ └── github │ └── binarywang │ └── demo │ └── wx │ └── cp │ ├── WxCpDemoApplication.java │ ├── builder │ ├── AbstractBuilder.java │ ├── ImageBuilder.java │ └── TextBuilder.java │ ├── config │ ├── mutil │ │ ├── RedissonConfig.java │ │ ├── WxCpConfiguration.java │ │ └── WxCpProperties.java │ └── single │ │ ├── WxCpConfiguration.java │ │ └── WxCpProperties.java │ ├── controller │ ├── mutil │ │ ├── WxJsController.java │ │ └── WxPortalController.java │ └── single │ │ ├── WxJsController.java │ │ └── WxPortalController.java │ ├── error │ ├── ErrorController.java │ └── ErrorPageConfiguration.java │ ├── handler │ ├── AbstractHandler.java │ ├── ContactChangeHandler.java │ ├── EnterAgentHandler.java │ ├── LocationHandler.java │ ├── LogHandler.java │ ├── MenuHandler.java │ ├── MsgHandler.java │ ├── NullHandler.java │ ├── ScanHandler.java │ ├── SubscribeHandler.java │ └── UnsubscribeHandler.java │ └── utils │ └── JsonUtils.java └── resources ├── META-INF └── MANIFEST.MF ├── application.yml.template └── templates └── error.html /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.project 4 | *.classpath 5 | application.yml 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /.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 | - binaryw@qq.com 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![码云Gitee](https://gitee.com/binary/weixin-java-cp-demo/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-cp-demo) 2 | [![Github](http://github-svg-buttons.herokuapp.com/star.svg?user=binarywang&repo=weixin-java-cp-demo&style=flat&background=1081C1)](https://github.com/binarywang/weixin-java-cp-demo) 3 | [![Build Status](https://travis-ci.com/binarywang/weixin-java-cp-demo.svg?branch=master)](https://travis-ci.com/binarywang/weixin-java-cp-demo) 4 | ----------------------- 5 | 6 | ### 本项目为 `WxJava` 的 Demo 演示程序,基于 `Spring Boot` 构建,实现企业微信后端开发功能。 7 | 更多信息请查阅:https://github.com/Wechat-Group/WxJava 8 | 9 | ## 使用步骤: 10 | 1. 请注意,本demo为简化代码编译时加入了 `lombok` 支持,如果不了解 `lombok` 的话,请先学习下相关知识,比如可以阅读[此文章](https://mp.weixin.qq.com/s/cUc-bUcprycADfNepnSwZQ); 11 | 1. 另外,新手遇到问题,请务必先阅读[【开发文档 Wiki 首页】](https://github.com/Wechat-Group/WxJava/wiki)的常见问题部分,可以少走很多弯路,节省不少时间。 12 | 1. 配置:复制 `/src/main/resources/application.yml.template` 或者修改其扩展名生成 `application.yml` 文件,根据自己需要填写相关配置(需要注意的是:yml文件内的属性冒号后面的文字之前需要加空格,可参考已有配置,否则属性会设置不成功); 13 | 2. 主要配置说明如下:( 注意:如果是要配置通讯录同步的应用,`agentId` 可以随便配置一个,保证跟下面服务器URL地址里的一致即可。) 14 | ``` 15 | wechat: 16 | cp: 17 | corpId: 111 (企业ID 在此页面查看:https://work.weixin.qq.com/wework_admin/frame#profile) 18 | appConfigs: 19 | - agentId: 1000001 (某一具体应用的AgentId,如果是要配置通讯录同步的应用,可以随便配置一个) 20 | secret: 1111(该应用的Secret) 21 | token: 111 (应用中的 “接受消息” 部分的 “接收消息服务器配置” 里的Token值) 22 | aesKey: 111 (应用中的 “接受消息” 部分的 “接收消息服务器配置” 里的EncodingAESKey值) 23 | - agentId: 1000002 (另一个应用,以下同上) 24 | secret: 1111 25 | token: 111 26 | aesKey: 111 27 | ``` 28 | 3. 运行Java程序:`WxCpDemoApplication`; 29 | 4. 配置企业微信对应应用中的 `接受消息` 部分的 `接收消息服务器配置` URL地址:`http://{可外网访问的域名}/wx/cp/portal/{xxxxx}` (`xxxx` 要跟 `AgentId` 保持一致,注意 `my-domain` 要跟上面的一致,需要符合微信官方的要求); 30 | 6. 根据自己需要修改各个 `handler` 的实现,加入自己的业务逻辑。 31 | 32 | -------------------------------------------------------------------------------- /commit.sh: -------------------------------------------------------------------------------- 1 | git commit -a -m "update wxjava sdk to 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-cp-demo 15 | jar 16 | 17 | spring-boot-demo-for-wechat-cp 18 | Spring Boot Demo with wechat CP 19 | 20 | 21 | 4.7.0 22 | 1.8 23 | 1.8 24 | UTF-8 25 | UTF-8 26 | zh_CN 27 | 28 | 2.2.4.RELEASE 29 | 3.22.0 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-thymeleaf 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-configuration-processor 44 | true 45 | 46 | 47 | 48 | com.github.binarywang 49 | weixin-java-cp 50 | ${weixin-java-cp.version} 51 | 52 | 53 | 54 | org.projectlombok 55 | lombok 56 | provided 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-test 63 | test 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-data-redis 70 | ${spring-redis-version} 71 | 72 | 73 | 74 | 75 | org.redisson 76 | redisson 77 | ${redisson-version} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-maven-plugin 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/WxCpDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 在配置目录下,分为single和mutil目录,代表单实例和多实例方式,请自行选择配置方式 8 | * 9 | * @author Binary Wang 10 | */ 11 | @SpringBootApplication 12 | public class WxCpDemoApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(WxCpDemoApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/builder/AbstractBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.builder; 2 | 3 | import me.chanjar.weixin.cp.api.WxCpService; 4 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 5 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 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 WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/builder/ImageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.builder; 2 | 3 | import me.chanjar.weixin.cp.api.WxCpService; 4 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 5 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage; 6 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 7 | 8 | 9 | /** 10 | * @author Binary Wang 11 | */ 12 | public class ImageBuilder extends AbstractBuilder { 13 | 14 | @Override 15 | public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, 16 | WxCpService service) { 17 | 18 | WxCpXmlOutImageMessage m = WxCpXmlOutMessage.IMAGE().mediaId(content) 19 | .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) 20 | .build(); 21 | 22 | return m; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/builder/TextBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.builder; 2 | 3 | import me.chanjar.weixin.cp.api.WxCpService; 4 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 5 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 6 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage; 7 | 8 | /** 9 | * @author Binary Wang 10 | */ 11 | public class TextBuilder extends AbstractBuilder { 12 | 13 | @Override 14 | public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, 15 | WxCpService service) { 16 | WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content(content) 17 | .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) 18 | .build(); 19 | return m; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/config/mutil/RedissonConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.config.mutil; 2 | 3 | import org.redisson.Redisson; 4 | import org.redisson.api.RedissonClient; 5 | import org.redisson.client.codec.StringCodec; 6 | import org.redisson.config.Config; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * @author Wang_Wong 13 | */ 14 | @Configuration 15 | public class RedissonConfig { 16 | 17 | @Value("${spring.redis.database}") 18 | private int database; 19 | 20 | @Value("${spring.redis.host}") 21 | private String host; 22 | 23 | @Value("${spring.redis.port}") 24 | private String port; 25 | 26 | @Value("${spring.redis.password}") 27 | private String password; 28 | 29 | @Bean(value = "redissonClient", destroyMethod = "shutdown") 30 | public RedissonClient redissonClient() throws Exception{ 31 | 32 | Config config = new Config(); 33 | config.useSingleServer().setAddress(String.format("redis://%s:%s", this.host, this.port)); 34 | if (!this.password.isEmpty()) { 35 | config.useSingleServer().setPassword(this.password); 36 | } 37 | config.useSingleServer().setDatabase(this.database); 38 | 39 | StringCodec codec = new StringCodec(); 40 | config.setCodec(codec); 41 | return Redisson.create(config); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/config/mutil/WxCpConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.config.mutil; 2 | 3 | import com.github.binarywang.demo.wx.cp.handler.*; 4 | import com.google.common.collect.Maps; 5 | import lombok.val; 6 | import me.chanjar.weixin.common.api.WxConsts; 7 | import me.chanjar.weixin.common.error.WxRuntimeException; 8 | import me.chanjar.weixin.cp.api.WxCpService; 9 | import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; 10 | import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl; 11 | import me.chanjar.weixin.cp.constant.WxCpConsts; 12 | import me.chanjar.weixin.cp.message.WxCpMessageRouter; 13 | import org.redisson.api.RedissonClient; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.data.redis.core.StringRedisTemplate; 18 | 19 | import javax.annotation.PostConstruct; 20 | import java.util.Map; 21 | import java.util.Optional; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * 多实例配置 26 | * 27 | * @author Wang_Wong 28 | */ 29 | @Configuration 30 | @EnableConfigurationProperties(WxCpProperties.class) 31 | public class WxCpConfiguration { 32 | 33 | private LogHandler logHandler; 34 | private NullHandler nullHandler; 35 | private LocationHandler locationHandler; 36 | private MenuHandler menuHandler; 37 | private MsgHandler msgHandler; 38 | private ContactChangeHandler contactChangeHandler; 39 | private UnsubscribeHandler unsubscribeHandler; 40 | private SubscribeHandler subscribeHandler; 41 | 42 | private WxCpProperties properties; 43 | private RedissonClient redissonClient; 44 | private StringRedisTemplate stringRedisTemplate; 45 | 46 | private static Map routers = Maps.newHashMap(); 47 | private static Map cpServices = Maps.newHashMap(); 48 | 49 | @Autowired 50 | public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler, 51 | MenuHandler menuHandler, MsgHandler msgHandler, ContactChangeHandler contactChangeHandler, UnsubscribeHandler unsubscribeHandler, 52 | SubscribeHandler subscribeHandler, WxCpProperties properties, RedissonClient redissonClient, StringRedisTemplate stringRedisTemplate) { 53 | this.logHandler = logHandler; 54 | this.nullHandler = nullHandler; 55 | this.locationHandler = locationHandler; 56 | this.menuHandler = menuHandler; 57 | this.msgHandler = msgHandler; 58 | this.contactChangeHandler = contactChangeHandler; 59 | this.unsubscribeHandler = unsubscribeHandler; 60 | this.subscribeHandler = subscribeHandler; 61 | 62 | this.properties = properties; 63 | this.redissonClient = redissonClient; 64 | this.stringRedisTemplate = stringRedisTemplate; 65 | } 66 | 67 | 68 | public static Map getRouters() { 69 | return routers; 70 | } 71 | 72 | public static WxCpService getCpService(String corpId, Integer agentId) { 73 | WxCpService cpService = cpServices.get(corpId + agentId); 74 | return Optional.ofNullable(cpService).orElseThrow(() -> new WxRuntimeException("未配置此service")); 75 | } 76 | 77 | @PostConstruct 78 | public void initServices() { 79 | cpServices = this.properties.getAppConfigs().stream().map(a -> { 80 | 81 | /** 82 | * 第二种方式,请参考: todo 83 | * https://github.com/binarywang/weixin-java-mp-demo/blob/master/src/main/java/com/github/binarywang/demo/wx/mp/config/WxMpConfiguration.java 84 | */ 85 | // WxRedisOps redisTemplateOps = new RedisTemplateWxRedisOps(stringRedisTemplate); 86 | // WxCpRedisConfigImpl redisConfig = new WxCpRedisConfigImpl(todo); 87 | 88 | WxCpRedissonConfigImpl config = new WxCpRedissonConfigImpl(redissonClient, "workRedis:"); 89 | config.setCorpId(a.getCorpId()); 90 | config.setAgentId(a.getAgentId()); 91 | config.setCorpSecret(a.getSecret()); 92 | config.setToken(a.getToken()); 93 | config.setAesKey(a.getAesKey()); 94 | 95 | val service = new WxCpServiceImpl(); 96 | service.setWxCpConfigStorage(config); 97 | 98 | routers.put(a.getCorpId() + a.getAgentId(), this.newRouter(service)); 99 | return service; 100 | }).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getCorpId() + service.getWxCpConfigStorage().getAgentId(), a -> a)); 101 | } 102 | 103 | private WxCpMessageRouter newRouter(WxCpService wxCpService) { 104 | final val newRouter = new WxCpMessageRouter(wxCpService); 105 | 106 | // 记录所有事件的日志 (异步执行) 107 | newRouter.rule().handler(this.logHandler).next(); 108 | 109 | // 自定义菜单事件 110 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 111 | .event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end(); 112 | 113 | // 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) 114 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 115 | .event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end(); 116 | 117 | // 关注事件 118 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 119 | .event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler) 120 | .end(); 121 | 122 | // 取消关注事件 123 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 124 | .event(WxConsts.EventType.UNSUBSCRIBE) 125 | .handler(this.unsubscribeHandler).end(); 126 | 127 | // 上报地理位置事件 128 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 129 | .event(WxConsts.EventType.LOCATION).handler(this.locationHandler) 130 | .end(); 131 | 132 | // 接收地理位置消息 133 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) 134 | .handler(this.locationHandler).end(); 135 | 136 | // 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) 137 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 138 | .event(WxConsts.EventType.SCAN).handler(this.nullHandler).end(); 139 | 140 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 141 | .event(WxCpConsts.EventType.CHANGE_CONTACT).handler(this.contactChangeHandler).end(); 142 | 143 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 144 | .event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end(); 145 | 146 | // 默认 147 | newRouter.rule().async(false).handler(this.msgHandler).end(); 148 | 149 | return newRouter; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/config/mutil/WxCpProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.config.mutil; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author Wang_Wong 12 | */ 13 | @Data 14 | @ConfigurationProperties(prefix = "wechat.cp") 15 | public class WxCpProperties { 16 | 17 | private List appConfigs; 18 | 19 | @Getter 20 | @Setter 21 | public static class AppConfig { 22 | 23 | /** 24 | * 设置企业微信的corpId 25 | */ 26 | private String corpId; 27 | 28 | /** 29 | * 设置企业微信应用的AgentId 30 | */ 31 | private Integer agentId; 32 | 33 | /** 34 | * 设置企业微信应用的Secret 35 | */ 36 | private String secret; 37 | 38 | /** 39 | * 设置企业微信应用的token 40 | */ 41 | private String token; 42 | 43 | /** 44 | * 设置企业微信应用的EncodingAESKey 45 | */ 46 | private String aesKey; 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/config/single/WxCpConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.config.single; 2 | 3 | import com.github.binarywang.demo.wx.cp.handler.*; 4 | import com.google.common.collect.Maps; 5 | import lombok.val; 6 | import me.chanjar.weixin.common.api.WxConsts; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; 9 | import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; 10 | import me.chanjar.weixin.cp.constant.WxCpConsts; 11 | import me.chanjar.weixin.cp.message.WxCpMessageRouter; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * 单实例配置 20 | * 21 | * @author Binary Wang 22 | */ 23 | //@Configuration 24 | //@EnableConfigurationProperties(WxCpProperties.class) 25 | public class WxCpConfiguration { 26 | private LogHandler logHandler; 27 | private NullHandler nullHandler; 28 | private LocationHandler locationHandler; 29 | private MenuHandler menuHandler; 30 | private MsgHandler msgHandler; 31 | private UnsubscribeHandler unsubscribeHandler; 32 | private SubscribeHandler subscribeHandler; 33 | 34 | private WxCpProperties properties; 35 | 36 | private static Map routers = Maps.newHashMap(); 37 | private static Map cpServices = Maps.newHashMap(); 38 | 39 | @Autowired 40 | public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler, 41 | MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler, 42 | SubscribeHandler subscribeHandler, WxCpProperties properties) { 43 | this.logHandler = logHandler; 44 | this.nullHandler = nullHandler; 45 | this.locationHandler = locationHandler; 46 | this.menuHandler = menuHandler; 47 | this.msgHandler = msgHandler; 48 | this.unsubscribeHandler = unsubscribeHandler; 49 | this.subscribeHandler = subscribeHandler; 50 | this.properties = properties; 51 | } 52 | 53 | 54 | public static Map getRouters() { 55 | return routers; 56 | } 57 | 58 | public static WxCpService getCpService(Integer agentId) { 59 | return cpServices.get(agentId); 60 | } 61 | 62 | @PostConstruct 63 | public void initServices() { 64 | cpServices = this.properties.getAppConfigs().stream().map(a -> { 65 | val configStorage = new WxCpDefaultConfigImpl(); 66 | configStorage.setCorpId(this.properties.getCorpId()); 67 | configStorage.setAgentId(a.getAgentId()); 68 | configStorage.setCorpSecret(a.getSecret()); 69 | configStorage.setToken(a.getToken()); 70 | configStorage.setAesKey(a.getAesKey()); 71 | val service = new WxCpServiceImpl(); 72 | service.setWxCpConfigStorage(configStorage); 73 | routers.put(a.getAgentId(), this.newRouter(service)); 74 | return service; 75 | }).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a)); 76 | } 77 | 78 | private WxCpMessageRouter newRouter(WxCpService wxCpService) { 79 | final val newRouter = new WxCpMessageRouter(wxCpService); 80 | 81 | // 记录所有事件的日志 (异步执行) 82 | newRouter.rule().handler(this.logHandler).next(); 83 | 84 | // 自定义菜单事件 85 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 86 | .event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end(); 87 | 88 | // 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) 89 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 90 | .event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end(); 91 | 92 | // 关注事件 93 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 94 | .event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler) 95 | .end(); 96 | 97 | // 取消关注事件 98 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 99 | .event(WxConsts.EventType.UNSUBSCRIBE) 100 | .handler(this.unsubscribeHandler).end(); 101 | 102 | // 上报地理位置事件 103 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 104 | .event(WxConsts.EventType.LOCATION).handler(this.locationHandler) 105 | .end(); 106 | 107 | // 接收地理位置消息 108 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) 109 | .handler(this.locationHandler).end(); 110 | 111 | // 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) 112 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 113 | .event(WxConsts.EventType.SCAN).handler(this.nullHandler).end(); 114 | 115 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 116 | .event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end(); 117 | 118 | newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) 119 | .event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end(); 120 | 121 | // 默认 122 | newRouter.rule().async(false).handler(this.msgHandler).end(); 123 | 124 | return newRouter; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/config/single/WxCpProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.config.single; 2 | 3 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author Binary Wang 12 | */ 13 | @Data 14 | //@ConfigurationProperties(prefix = "wechat.cp") 15 | public class WxCpProperties { 16 | /** 17 | * 设置企业微信的corpId 18 | */ 19 | private String corpId; 20 | 21 | private List appConfigs; 22 | 23 | @Getter 24 | @Setter 25 | public static class AppConfig { 26 | /** 27 | * 设置企业微信应用的AgentId 28 | */ 29 | private Integer agentId; 30 | 31 | /** 32 | * 设置企业微信应用的Secret 33 | */ 34 | private String secret; 35 | 36 | /** 37 | * 设置企业微信应用的token 38 | */ 39 | private String token; 40 | 41 | /** 42 | * 设置企业微信应用的EncodingAESKey 43 | */ 44 | private String aesKey; 45 | 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return JsonUtils.toJson(this); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/controller/mutil/WxJsController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.controller.mutil; 2 | 3 | import com.github.binarywang.demo.wx.cp.config.mutil.WxCpConfiguration; 4 | import lombok.RequiredArgsConstructor; 5 | import me.chanjar.weixin.common.bean.WxJsapiSignature; 6 | import me.chanjar.weixin.common.error.WxErrorException; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.data.redis.core.StringRedisTemplate; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Formatter; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * @author Wang_Wong 20 | */ 21 | @RestController 22 | @RequiredArgsConstructor 23 | @RequestMapping("/wx/cp/js/{corpId}/{agentId}") 24 | public class WxJsController { 25 | private final StringRedisTemplate stringRedisTemplate; 26 | 27 | @PostMapping("/getJsConf") 28 | public Map getJsConf( 29 | @PathVariable String corpId, 30 | @PathVariable Integer agentId, 31 | String uri) throws WxErrorException { 32 | 33 | final WxCpService wxCpService = WxCpConfiguration.getCpService(corpId, agentId); 34 | if (wxCpService == null) { 35 | throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); 36 | } 37 | 38 | WxJsapiSignature wxJsapiSignature = wxCpService.createJsapiSignature(uri); 39 | String signature = wxJsapiSignature.getSignature(); 40 | String nonceStr = wxJsapiSignature.getNonceStr(); 41 | long timestamp = wxJsapiSignature.getTimestamp(); 42 | 43 | Map res = new HashMap(); 44 | res.put("appId", corpId); // 必填,企业微信的corpID 45 | res.put("timestamp", timestamp); // 必填,生成签名的时间戳 46 | res.put("nonceStr", nonceStr); // 必填,生成签名的随机串 47 | res.put("signature", signature); // 必填,签名,见 附录-JS-SDK使用权限签名算法 48 | return res; 49 | } 50 | 51 | 52 | public static String genNonce() { 53 | return bytesToHex(Long.toString(System.nanoTime()).getBytes(StandardCharsets.UTF_8)); 54 | } 55 | 56 | public static String bytesToHex(final byte[] hash) { 57 | Formatter formatter = new Formatter(); 58 | for (byte b : hash) { 59 | formatter.format("%02x", b); 60 | } 61 | String result = formatter.toString(); 62 | formatter.close(); 63 | return result; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/controller/mutil/WxPortalController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.controller.mutil; 2 | 3 | import com.github.binarywang.demo.wx.cp.config.mutil.WxCpConfiguration; 4 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import lombok.val; 8 | import me.chanjar.weixin.common.error.WxErrorException; 9 | import me.chanjar.weixin.cp.api.WxCpService; 10 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 11 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 12 | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.springframework.data.redis.core.StringRedisTemplate; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * @author Wang_Wong 21 | */ 22 | @Slf4j 23 | @RestController 24 | @RequiredArgsConstructor 25 | @RequestMapping("/wx/cp/portal/{corpId}/{agentId}") 26 | public class WxPortalController { 27 | private final StringRedisTemplate stringRedisTemplate; 28 | 29 | @PostMapping("/test") 30 | public String test(@PathVariable String corpId, 31 | @RequestParam(name = "userId", required = true) String userId) throws WxErrorException { 32 | 33 | String s = stringRedisTemplate.opsForValue().get("test"); 34 | log.info("data is {}", s); 35 | 36 | final val wxCpService = WxCpConfiguration.getCpService(corpId, 10001); 37 | List list = wxCpService.getExternalContactService().listExternalContacts(userId); 38 | 39 | return list.toString(); 40 | } 41 | 42 | @GetMapping(produces = "text/plain;charset=utf-8") 43 | public String authGet(@PathVariable String corpId, 44 | @PathVariable Integer agentId, 45 | @RequestParam(name = "msg_signature", required = false) String signature, 46 | @RequestParam(name = "timestamp", required = false) String timestamp, 47 | @RequestParam(name = "nonce", required = false) String nonce, 48 | @RequestParam(name = "echostr", required = false) String echostr) { 49 | log.info("\n接收到来自微信服务器的认证消息:signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]", 50 | signature, timestamp, nonce, echostr); 51 | 52 | if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { 53 | throw new IllegalArgumentException("请求参数非法,请核实!"); 54 | } 55 | 56 | final WxCpService wxCpService = WxCpConfiguration.getCpService(corpId, agentId); 57 | if (wxCpService == null) { 58 | throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); 59 | } 60 | 61 | if (wxCpService.checkSignature(signature, timestamp, nonce, echostr)) { 62 | return new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()).decrypt(echostr); 63 | } 64 | 65 | return "非法请求"; 66 | } 67 | 68 | @PostMapping(produces = "application/xml; charset=UTF-8") 69 | public String post(@PathVariable String corpId, 70 | @PathVariable Integer agentId, 71 | @RequestBody String requestBody, 72 | @RequestParam("msg_signature") String signature, 73 | @RequestParam("timestamp") String timestamp, 74 | @RequestParam("nonce") String nonce) { 75 | log.info("\n接收微信请求:[signature=[{}], timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", 76 | signature, timestamp, nonce, requestBody); 77 | 78 | final WxCpService wxCpService = WxCpConfiguration.getCpService(corpId, agentId); 79 | if (wxCpService == null) { 80 | throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); 81 | } 82 | 83 | WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml(requestBody, wxCpService.getWxCpConfigStorage(), 84 | timestamp, nonce, signature); 85 | log.debug("\n消息解密后内容为:\n{} ", JsonUtils.toJson(inMessage)); 86 | WxCpXmlOutMessage outMessage = this.route(corpId, agentId, inMessage); 87 | if (outMessage == null) { 88 | return ""; 89 | } 90 | 91 | String out = outMessage.toEncryptedXml(wxCpService.getWxCpConfigStorage()); 92 | log.debug("\n组装回复信息:{}", out); 93 | return out; 94 | } 95 | 96 | private WxCpXmlOutMessage route(String corpId, Integer agentId, WxCpXmlMessage message) { 97 | try { 98 | return WxCpConfiguration.getRouters().get(corpId + agentId).route(message); 99 | } catch (Exception e) { 100 | log.error(e.getMessage(), e); 101 | } 102 | 103 | return null; 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/controller/single/WxJsController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.controller.single; 2 | 3 | import com.github.binarywang.demo.wx.cp.config.single.WxCpConfiguration; 4 | import me.chanjar.weixin.common.bean.WxJsapiSignature; 5 | import me.chanjar.weixin.common.error.WxErrorException; 6 | import me.chanjar.weixin.cp.api.WxCpService; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Formatter; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author Wang_Wong 18 | */ 19 | //@RestController 20 | //@RequiredArgsConstructor 21 | //@RequestMapping("/wx/cp/js/{corpId}/{agentId}/getJsConf") 22 | 23 | public class WxJsController { 24 | @PostMapping("/getJsConf") 25 | public Map getJsConf( 26 | @PathVariable String corpId, 27 | @PathVariable Integer agentId, 28 | String uri) throws WxErrorException { 29 | 30 | final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId); 31 | if (wxCpService == null) { 32 | throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); 33 | } 34 | 35 | WxJsapiSignature wxJsapiSignature = wxCpService.createJsapiSignature(uri); 36 | String signature = wxJsapiSignature.getSignature(); 37 | String nonceStr = wxJsapiSignature.getNonceStr(); 38 | long timestamp = wxJsapiSignature.getTimestamp(); 39 | 40 | Map res = new HashMap(); 41 | res.put("appId", corpId); // 必填,企业微信的corpID 42 | res.put("timestamp", timestamp); // 必填,生成签名的时间戳 43 | res.put("nonceStr", nonceStr); // 必填,生成签名的随机串 44 | res.put("signature", signature); // 必填,签名,见 附录-JS-SDK使用权限签名算法 45 | return res; 46 | } 47 | 48 | 49 | public static String genNonce() { 50 | return bytesToHex(Long.toString(System.nanoTime()).getBytes(StandardCharsets.UTF_8)); 51 | } 52 | 53 | public static String bytesToHex(final byte[] hash) { 54 | Formatter formatter = new Formatter(); 55 | for (byte b : hash) { 56 | formatter.format("%02x", b); 57 | } 58 | String result = formatter.toString(); 59 | formatter.close(); 60 | return result; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/controller/single/WxPortalController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.controller.single; 2 | 3 | import com.github.binarywang.demo.wx.cp.config.single.WxCpConfiguration; 4 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import me.chanjar.weixin.cp.api.WxCpService; 7 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 8 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 9 | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | /** 16 | * @author Binary Wang 17 | */ 18 | //@RestController 19 | //@RequestMapping("/wx/cp/portal/{agentId}") 20 | @Slf4j 21 | public class WxPortalController { 22 | @GetMapping(produces = "text/plain;charset=utf-8") 23 | public String authGet(@PathVariable Integer agentId, 24 | @RequestParam(name = "msg_signature", required = false) String signature, 25 | @RequestParam(name = "timestamp", required = false) String timestamp, 26 | @RequestParam(name = "nonce", required = false) String nonce, 27 | @RequestParam(name = "echostr", required = false) String echostr) { 28 | log.info("\n接收到来自微信服务器的认证消息:signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]", 29 | signature, timestamp, nonce, echostr); 30 | 31 | if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { 32 | throw new IllegalArgumentException("请求参数非法,请核实!"); 33 | } 34 | 35 | final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId); 36 | if (wxCpService == null) { 37 | throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); 38 | } 39 | 40 | if (wxCpService.checkSignature(signature, timestamp, nonce, echostr)) { 41 | return new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()).decrypt(echostr); 42 | } 43 | 44 | return "非法请求"; 45 | } 46 | 47 | @PostMapping(produces = "application/xml; charset=UTF-8") 48 | public String post(@PathVariable Integer agentId, 49 | @RequestBody String requestBody, 50 | @RequestParam("msg_signature") String signature, 51 | @RequestParam("timestamp") String timestamp, 52 | @RequestParam("nonce") String nonce) { 53 | log.info("\n接收微信请求:[signature=[{}], timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", 54 | signature, timestamp, nonce, requestBody); 55 | 56 | final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId); 57 | WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml(requestBody, wxCpService.getWxCpConfigStorage(), 58 | timestamp, nonce, signature); 59 | log.debug("\n消息解密后内容为:\n{} ", JsonUtils.toJson(inMessage)); 60 | WxCpXmlOutMessage outMessage = this.route(agentId, inMessage); 61 | if (outMessage == null) { 62 | return ""; 63 | } 64 | 65 | String out = outMessage.toEncryptedXml(wxCpService.getWxCpConfigStorage()); 66 | log.debug("\n组装回复信息:{}", out); 67 | return out; 68 | } 69 | 70 | private WxCpXmlOutMessage route(Integer agentId, WxCpXmlMessage message) { 71 | try { 72 | return WxCpConfiguration.getRouters().get(agentId).route(message); 73 | } catch (Exception e) { 74 | log.error(e.getMessage(), e); 75 | } 76 | 77 | return null; 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/error/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.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/cp/error/ErrorPageConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.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/cp/handler/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import me.chanjar.weixin.cp.message.WxCpMessageHandler; 7 | 8 | /** 9 | * @author Binary Wang 10 | */ 11 | public abstract class AbstractHandler implements WxCpMessageHandler { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/ContactChangeHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import com.github.binarywang.demo.wx.cp.builder.TextBuilder; 4 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import me.chanjar.weixin.common.session.WxSessionManager; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * 通讯录变更事件处理器. 16 | * 17 | * @author Binary Wang 18 | */ 19 | @Slf4j 20 | @Component 21 | public class ContactChangeHandler extends AbstractHandler { 22 | 23 | @Override 24 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 25 | WxSessionManager sessionManager) { 26 | String content = "收到通讯录变更事件,内容:" + JsonUtils.toJson(wxMessage); 27 | log.info(content); 28 | 29 | return new TextBuilder().build(content, wxMessage, cpService); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/EnterAgentHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import java.util.Map; 4 | 5 | import lombok.extern.slf4j.Slf4j; 6 | import me.chanjar.weixin.common.error.WxErrorException; 7 | import me.chanjar.weixin.common.session.WxSessionManager; 8 | import me.chanjar.weixin.cp.api.WxCpService; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 10 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 11 | 12 | /** 13 | *
14 |  *
15 |  * Created by Binary Wang on 2018/8/27.
16 |  * 
17 | * 18 | * @author Binary Wang 19 | */ 20 | @Slf4j 21 | public class EnterAgentHandler extends AbstractHandler { 22 | private static final int TEST_AGENT = 1000002; 23 | 24 | @Override 25 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException { 26 | // do something 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/LocationHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import com.github.binarywang.demo.wx.cp.builder.TextBuilder; 4 | import lombok.extern.slf4j.Slf4j; 5 | import me.chanjar.weixin.common.api.WxConsts; 6 | import me.chanjar.weixin.common.session.WxSessionManager; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * @author Binary Wang 16 | */ 17 | @Slf4j 18 | @Component 19 | public class LocationHandler extends AbstractHandler { 20 | 21 | @Override 22 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 23 | WxSessionManager sessionManager) { 24 | if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) { 25 | //TODO 接收处理用户发送的地理位置消息 26 | try { 27 | String content = "感谢反馈,您的的地理位置已收到!"; 28 | return new TextBuilder().build(content, wxMessage, null); 29 | } catch (Exception e) { 30 | log.error("位置消息接收处理失败", e); 31 | return null; 32 | } 33 | } 34 | 35 | //上报地理位置事件 36 | log.info("\n上报地理位置,纬度 : {}\n经度 : {}\n精度 : {}", 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/cp/handler/LogHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 4 | import lombok.extern.slf4j.Slf4j; 5 | import me.chanjar.weixin.common.session.WxSessionManager; 6 | import me.chanjar.weixin.cp.api.WxCpService; 7 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 8 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Binary Wang 15 | */ 16 | @Slf4j 17 | @Component 18 | public class LogHandler extends AbstractHandler { 19 | @Override 20 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 21 | WxSessionManager sessionManager) { 22 | log.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); 23 | return null; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/MenuHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; 8 | import me.chanjar.weixin.common.session.WxSessionManager; 9 | import me.chanjar.weixin.cp.api.WxCpService; 10 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 11 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 12 | 13 | /** 14 | * @author Binary Wang 15 | */ 16 | @Component 17 | public class MenuHandler extends AbstractHandler { 18 | 19 | @Override 20 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 21 | WxSessionManager sessionManager) { 22 | 23 | String msg = String.format("type:%s, event:%s, key:%s", 24 | wxMessage.getMsgType(), wxMessage.getEvent(), 25 | wxMessage.getEventKey()); 26 | if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { 27 | return null; 28 | } 29 | 30 | return WxCpXmlOutMessage.TEXT().content(msg) 31 | .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) 32 | .build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/MsgHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import com.github.binarywang.demo.wx.cp.builder.TextBuilder; 4 | import com.github.binarywang.demo.wx.cp.utils.JsonUtils; 5 | import me.chanjar.weixin.common.api.WxConsts; 6 | import me.chanjar.weixin.common.session.WxSessionManager; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * @author Binary Wang 16 | */ 17 | @Component 18 | public class MsgHandler extends AbstractHandler { 19 | 20 | @Override 21 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 22 | WxSessionManager sessionManager) { 23 | final String msgType = wxMessage.getMsgType(); 24 | if (msgType == null) { 25 | // 如果msgType没有,就自己根据具体报文内容做处理 26 | } 27 | 28 | if (!msgType.equals(WxConsts.XmlMsgType.EVENT)) { 29 | //TODO 可以选择将消息保存到本地 30 | } 31 | 32 | //TODO 组装回复消息 33 | String content = "收到信息内容:" + JsonUtils.toJson(wxMessage); 34 | 35 | return new TextBuilder().build(content, wxMessage, cpService); 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/NullHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import me.chanjar.weixin.common.session.WxSessionManager; 8 | import me.chanjar.weixin.cp.api.WxCpService; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 10 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 11 | 12 | /** 13 | * @author Binary Wang 14 | */ 15 | @Component 16 | public class NullHandler extends AbstractHandler { 17 | 18 | @Override 19 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 20 | WxSessionManager sessionManager) { 21 | return null; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/ScanHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | /** 4 | * @author Binary Wang 5 | */ 6 | public abstract class ScanHandler extends AbstractHandler { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/SubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import com.github.binarywang.demo.wx.cp.builder.TextBuilder; 4 | import lombok.extern.slf4j.Slf4j; 5 | import me.chanjar.weixin.common.error.WxErrorException; 6 | import me.chanjar.weixin.common.session.WxSessionManager; 7 | import me.chanjar.weixin.cp.api.WxCpService; 8 | import me.chanjar.weixin.cp.bean.WxCpUser; 9 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 10 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * @author Binary Wang 17 | */ 18 | @Slf4j 19 | @Component 20 | public class SubscribeHandler extends AbstractHandler { 21 | 22 | @Override 23 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 24 | WxSessionManager sessionManager) throws WxErrorException { 25 | 26 | log.info("新关注用户 OPENID: " + wxMessage.getFromUserName()); 27 | 28 | // 获取微信用户基本信息 29 | WxCpUser userWxInfo = cpService.getUserService().getById(wxMessage.getFromUserName()); 30 | 31 | if (userWxInfo != null) { 32 | // TODO 可以添加关注用户到本地 33 | } 34 | 35 | WxCpXmlOutMessage responseResult = null; 36 | try { 37 | responseResult = handleSpecial(wxMessage); 38 | } catch (Exception e) { 39 | log.error(e.getMessage(), e); 40 | } 41 | 42 | if (responseResult != null) { 43 | return responseResult; 44 | } 45 | 46 | try { 47 | return new TextBuilder().build("感谢关注", wxMessage, cpService); 48 | } catch (Exception e) { 49 | log.error(e.getMessage(), e); 50 | } 51 | 52 | return null; 53 | } 54 | 55 | /** 56 | * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 57 | */ 58 | private WxCpXmlOutMessage handleSpecial(WxCpXmlMessage wxMessage) { 59 | //TODO 60 | return null; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/handler/UnsubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.handler; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import me.chanjar.weixin.common.session.WxSessionManager; 5 | import me.chanjar.weixin.cp.api.WxCpService; 6 | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; 7 | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Binary Wang 14 | */ 15 | @Slf4j 16 | @Component 17 | public class UnsubscribeHandler extends AbstractHandler { 18 | 19 | @Override 20 | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, 21 | WxSessionManager sessionManager) { 22 | String openId = wxMessage.getFromUserName(); 23 | log.info("取消关注用户 OPENID: " + openId); 24 | // TODO 可以更新本地数据库为取消关注状态 25 | return null; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/binarywang/demo/wx/cp/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.binarywang.demo.wx.cp.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @author Binary Wang 11 | */ 12 | @Slf4j 13 | public class JsonUtils { 14 | private static final ObjectMapper JSON = new ObjectMapper(); 15 | 16 | static { 17 | JSON.setSerializationInclusion(Include.NON_NULL); 18 | JSON.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE); 19 | } 20 | 21 | public static String toJson(Object obj) { 22 | try { 23 | return JSON.writeValueAsString(obj); 24 | } catch (JsonProcessingException e) { 25 | log.error(e.getMessage(), e); 26 | } 27 | 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.github.binarywang.demo.wx.cp.WxCpDemoApplication 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/application.yml.template: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | logging: 4 | level: 5 | org.springframework.web: INFO 6 | com.github.binarywang.demo.wx.cp: DEBUG 7 | me.chanjar.weixin: DEBUG 8 | 9 | #多实例配置 10 | spring: 11 | redis: 12 | database: 0 13 | host: xx.xx.xx.xx 14 | password: xxxx 15 | port: 6380 16 | wechat: 17 | cp: 18 | appConfigs: 19 | - agentId: 1000001 20 | corpId: xxxx 21 | secret: 1111 22 | token: 111 23 | aesKey: 111 24 | - agentId: 1000002 25 | corpId: xxxx 26 | secret: 1111 27 | token: 111 28 | aesKey: 111 29 | 30 | #单实例配置 31 | wechat: 32 | cp: 33 | corpId: 111 34 | appConfigs: 35 | - agentId: 1000001 36 | secret: 1111 37 | token: 111 38 | aesKey: 111 39 | - agentId: 1000002 40 | secret: 1111 41 | token: 111 42 | aesKey: 111 43 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 出错啦! 5 | 6 | 7 | 8 |

9 |

10 |

11 |

12 |

13 |

14 |

15 | 16 | --------------------------------------------------------------------------------