1.前言
经过前面的配置,基本完成了一些基础配置。后面接下来就是一些开发流程了。
2.配置pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http:///POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http:///POM/4.0.0 http:///xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5
6 <groupId>com.wunaozai.wechat</groupId>
7 <artifactId>WeChat</artifactId>
8 <version>0.0.1</version>
9 <packaging>jar</packaging>
10
11 <name>WeChat</name>
12 <description>微信公众号服务器</description>
13
14 <parent>
15 <groupId>org.springframework.boot</groupId>
16 <artifactId>spring-boot-starter-parent</artifactId>
17 <version>2.0.4.RELEASE</version>
18 <relativePath/> <!-- lookup parent from repository -->
19 </parent>
20
21 <properties>
22 <weixin-java-mp.version>3.1.0</weixin-java-mp.version>
23 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
24 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
25 <java.version>1.8</java.version>
26 </properties>
27
28 <dependencies>
29 <dependency>
30 <groupId>org.springframework.boot</groupId>
31 <artifactId>spring-boot-starter-web</artifactId>
32 </dependency>
33
34 <dependency>
35 <groupId>org.springframework.boot</groupId>
36 <artifactId>spring-boot-starter-thymeleaf</artifactId>
37 </dependency>
38 <dependency>
39 <groupId>com.github.binarywang</groupId>
40 <artifactId>weixin-java-mp</artifactId>
41 <version>${weixin-java-mp.version}</version>
42 </dependency>
43
44 <dependency>
45 <groupId>org.springframework.boot</groupId>
46 <artifactId>spring-boot-starter-data-redis</artifactId>
47 </dependency>
48 <dependency>
49 <groupId>org.springframework.boot</groupId>
50 <artifactId>spring-boot-starter-data-mongodb</artifactId>
51 </dependency>
52 <dependency>
53 <groupId>org.postgresql</groupId>
54 <artifactId>postgresql</artifactId>
55 </dependency>
56 <dependency>
57 <groupId>org.mybatis.spring.boot</groupId>
58 <artifactId>mybatis-spring-boot-starter</artifactId>
59 <version>1.3.2</version>
60 </dependency>
61 <dependency>
62 <groupId>org.springframework.boot</groupId>
63 <artifactId>spring-boot-starter-security</artifactId>
64 </dependency>
65 <dependency>
66 <groupId>org.springframework.security.oauth</groupId>
67 <artifactId>spring-security-oauth2</artifactId>
68 <version>2.3.3.RELEASE</version>
69 </dependency>
70 <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
71 <dependency>
72 <groupId>com.github.pagehelper</groupId>
73 <artifactId>pagehelper-spring-boot-starter</artifactId>
74 <version>1.2.5</version>
75 </dependency>
76 <!-- MQTT -->
77 <dependency>
78 <groupId>org.springframework.boot</groupId>
79 <artifactId>spring-boot-starter-integration</artifactId>
80 </dependency>
81 <dependency>
82 <groupId>org.springframework.integration</groupId>
83 <artifactId>spring-integration-stream</artifactId>
84 </dependency>
85 <dependency>
86 <groupId>org.springframework.integration</groupId>
87 <artifactId>spring-integration-mqtt</artifactId>
88 </dependency>
89
90 <dependency>
91 <groupId>org.springframework.boot</groupId>
92 <artifactId>spring-boot-devtools</artifactId>
93 <scope>runtime</scope>
94 </dependency>
95 <dependency>
96 <groupId>org.springframework.boot</groupId>
97 <artifactId>spring-boot-starter-test</artifactId>
98 <scope>test</scope>
99 </dependency>
100 </dependencies>
101
102 <build>
103 <plugins>
104 <plugin>
105 <groupId>org.springframework.boot</groupId>
106 <artifactId>spring-boot-maven-plugin</artifactId>
107 </plugin>
108 </plugins>
109 </build>
110
111
112 </project>
Thymeleaf 作为公众号页面前端
weixin-java-mp 是微信公众号开发工具包
Redis 主要用来集成OAuth2.0认证
MongoDB 存一些JSON数据
PostgreSQL 关系型数据,保存业务数据
security-OAuth2 认证、权限控制
spring-integration-mqtt MQTT客户端,用来与设备通讯
3.配置文件application.properties
一些项目配置
1 spring.application.name=wechat
2
3 server.port=8001
4
5 logging.path=log
6 logging.level.org.springframework.web=INFO
7 logging.level.com.github.binarywang.demo.wx.mp=DEBUG
8 logging.level.com.wunaozai.wechat=DEBUG
9
10 wechat.mp.appId=wxc60******
11 wechat.mp.secret=cfe2******
12 wechat.mp.token=we****
13 wechat.mp.aesKey=2Bq*******
14 #微信号
15 #wechat.mp.accountId=gh_8*****
16 #上传临时目录
17 wechat.mp.tmpDir=C:/tmp
18 #系统语音存放目录
19 project.voice.dir=D:/tmp
20 #微信语音资源下载链接前缀
21 project.resource.url=http://wechat.wunaozai.com/wx/v1/voice/recv
22
23 #接口配置
24 wechat.mp.url=http://wechat.wunaozai.com/wx/wechat
25 #JS接口安全域名配置
26 wechat.mp.js=wechat.wunaozai.com
27 #网页授权域名配置
28 wechat.mp.web.oauth=wechat.wunaozai.com
29 wechat.mp.protocol=http
30
31 spring.thymeleaf.prefix=classpath:/templates/
32 spring.thymeleaf.suffix=.html
33 spring.thymeleaf.mode=HTML5
34
35
36 #postgres
37 spring.datasource.url=jdbc:postgresql://172.16.23.202:5432/wechat
38 spring.datasource.username=postgres
39 spring.datasource.password=
40 spring.datasource.driver-class-name=org.postgresql.Driver
41
42 #mybatis
43 mybatis.type-aliases-package=com.wunaozai.wechat.model
44 mybatis.mapper-locations=classpath:com/wunaozai/wechat/mapper/*.xml
45
46 #pagehelper
47 pagehelper.helper-dialect=postgresql
48 pagehelper.reasonable=true
49 pagehelper.support-methods-arguments=true
50 pagehelper.page-size-zero=true
51
52 #redis
53 spring.redis.database=2
54 spring.redis.host=172.16.23.203
55 spring.redis.port=6379
56 spring.redis.password=f4e4********
57 spring.redis.jedis.pool.max-active=8
58 spring.redis.jedis.pool.max-wait=60
59 spring.redis.jedis.pool.max-idle=8
60 spring.redis.jedis.pool.min-idle=0
61 spring.redis.timeout=10000
62
63 #mongoDB
64 spring.data.mongodb.uri=mongodb://www.wunaozai.com:27777/wechat
65
66 #MQTT Client
67 mqtt.client.host=tcp://mqtt.wunaozai.com:1883
68 mqtt.client.clientid=*****admin
69 mqtt.client.username=*****admin
70 mqtt.client.password=*******
71 mqtt.client.timeout=10
72 mqtt.client.keepalive=20
73
74 #tuling123
75 api.tuling.url=http://openapi.tuling123.com/openapi/api/v2
76 api.tuling.apiKey=******
77 api.tuling.userId=******
4.配置wechat-java-mp 工具包
这个配置基本参考官网的Demo就可以了。我这里截图我自己的目录结构。

从目录结构可以看出,一个 WechatMpConfiguration.java 作为全局配置及微信开发工具包入口。而下方的handler是对所有微信的事件进行封装。
例如菜单事件、关注事件、取消关注事件、扫描事件、语音事件、对话事件等。每个事件都对应一个Handler。
1 /**
2 * 微信公众号开发 全局配置区
3 * @author wunaozai
4 * @date 2018-08-15
5 */
6 @Configuration
7 @ConditionalOnClass(value=WxMpService.class)
8 @EnableConfigurationProperties(WechatMpProperties.class)
9 public class WechatMpConfiguration {
10
11 @Autowired
12 private WechatMpProperties properties;
13 @Autowired
14 private WechatMpLogHandler logHandler;
15 @Autowired
16 private WechatMpNullHandler nullHandler;
17 @Autowired
18 private WechatMpMenuHandler menuHandler;
19 @Autowired
20 private WechatMpMsgHandler msgHandler;
21 @Autowired
22 private WechatMpMsgVoiceHandler msgvoiceHandler;
23 @Autowired
24 private WechatMpUnsubscribeHandler unsubscribeHandler;
25 @Autowired
26 private WechatMpSubscribeHandler subscribeHandler;
27 @Autowired
28 private WechatMpScanHandler scanHandler;
29
30 @Bean
31 @ConditionalOnMissingBean
32 public WxMpConfigStorage configStorage() {
33 WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
34 config.setAppId(properties.getAppId());
35 config.setSecret(properties.getSecret());
36 config.setToken(properties.getToken());
37 config.setAesKey(properties.getAesKey());
38 config.setTmpDirFile(new File(properties.getTmpDir()));
39 return config;
40 }
41
42 @Bean
43 @ConditionalOnMissingBean
44 public WxMpService wxmpService(WxMpConfigStorage config) {
45 WxMpService service = new WxMpServiceImpl();
46 service.setWxMpConfigStorage(config);
47 return service;
48 }
49 /*
50 @Bean
51 @ConditionalOnMissingBean
52 public WxMpDeviceService wxMpDeviceService(WxMpService wxmpService) {
53 WxMpDeviceService service = new WxMpDeviceServiceImpl(wxmpService);
54 return service;
55 }
56 */
57
58 @Bean
59 public WxMpMessageRouter router(WxMpService wxmpService) {
60
61 final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxmpService);
62
63 //记录所有事件日志(异步执行)
64 newRouter.rule().async(false).handler(this.logHandler).next();
65
66 //当前项目Wifi故事机 只处理自定义菜单事件、关注/取关事件、文本和语音信息
67 //自定义菜单事件
68 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
69 .event(MenuButtonType.CLICK).handler(menuHandler).end();
70 //关注事件
71 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
72 .event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
73 //取消关注事件
74 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
75 .event(EventType.UNSUBSCRIBE).handler(unsubscribeHandler).end();
76 //处理扫描事件
77 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
78 .event(EventType.SCANCODE_WAITMSG).handler(scanHandler).end();
79
80 //这里过滤掉所有事件
81 newRouter.rule().async(false).msgType(XmlMsgType.EVENT).handler(nullHandler).end();
82
83 //处理语音信息
84 newRouter.rule().async(false).msgType(XmlMsgType.VOICE).handler(msgvoiceHandler).end();
85
86 //这里过滤掉所有输入
87 newRouter.rule().async(false).handler(msgHandler).end();
88
89 /*
90 //接收客服会话管理事件
91 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
92 .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
93 .handler(kfsessionHandler).end();
94 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
95 .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
96 .handler(kfsessionHandler).end();
97 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
98 .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
99 .handler(kfsessionHandler).end();
100
101 //门店审核事件
102 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
103 .event(MenuButtonType.CLICK).handler(nullHandler).end();
104
105 //自定义菜单事件
106 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
107 .event(MenuButtonType.CLICK).handler(menuHandler).end();
108
109 //点击菜单链接事件
110 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
111 .event(MenuButtonType.VIEW).handler(nullHandler).end();
112
113 //关注事件
114 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
115 .event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
116
117 //取消关注事件
118 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
119 .event(EventType.UNSUBSCRIBE).handler(unsubscribeHandler).end();
120
121 //上报地理位置事件
122 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
123 .event(EventType.LOCATION).handler(locationHandler).end();
124
125 //接收地理位置消息
126 newRouter.rule().async(false).msgType(XmlMsgType.LOCATION)
127 .handler(locationHandler).end();
128
129 //扫码事件
130 newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
131 .event(EventType.SCAN).handler(nullHandler).end();
132
133 //默认
134 newRouter.rule().async(false).handler(msgHandler).end();
135 */
136 return newRouter;
137 }
138 }
5. 关注事件举例
我们在WechatMpConfiguration.java中注册了一个关注事件处理Handler。
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
1 /**
2 * 用户关注事件
3 * @author wunaozai
4 * @date 2018-08-15
5 */
6 @Component
7 public class WechatMpSubscribeHandler extends AbstractHandler {
8
9 @Autowired
10 private WechatUserService wechatuserService;
11
12 @Override
13 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
14 WxSessionManager sessionManager) throws WxErrorException {
15 //log.info("新关注用户 openid: " + wxMessage.getFromUser());
16 //获取微信用户基本信息
17 WxMpUser user = wxmpService.getUserService().userInfo(wxMessage.getFromUser(), null);
18 if(user != null) {
19 //可以添加关注用户到本地数据库
20 WechatUserPoModel po = new WechatUserPoModel();
21 po.setOpenid(user.getOpenId());
22 po.setStatus(true);
23 wechatuserService.selectOneAndInsertIt(po);
24
25 po.setNickname(user.getNickname());
26 po.setSex_desc(user.getSexDesc());
27 po.setSex(user.getSex());
28 po.setCity(user.getCity());
29 po.setProvince(user.getProvince());
30 po.setCountry(user.getCountry());
31 po.setHead_img_url(user.getHeadImgUrl());
32 wechatuserService.updateOneByOpenid(po);
33
34 }
35 try {
36 String msg = "您好," + user.getNickname() +
37 "!\n欢迎使用杰理故事机/:heart\n如果是第一次使用,请点击下方按钮,按提示步骤进行操作。";
38 return new WechatMpTextBuilder().build(msg, wxMessage, wxmpService);
39 } catch (Exception e) {
40 log.error(e.getMessage());
41 }
42 return null;
43 }
44 }
6. 扫码事件举例
类似关注事件,扫码事件,也是需要在配置类,注册一个Handler。
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SCANCODE_WAITMSG).handler(scanHandler).end();
1 @Component
2 public class WechatMpScanHandler extends AbstractHandler {
3
4 @Autowired
5 private WechatUserCtrlService wechatuserctrlService;
6
7 @Override
8 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
9 WxSessionManager sessionManager) throws WxErrorException {
10 String scan_type = wxMessage.getScanCodeInfo().getScanType();
11 String scan_result = wxMessage.getScanCodeInfo().getScanResult();
12 String info = "非本公众号内二维码";
13 try {
14 if(scan_type.equalsIgnoreCase("qrcode")) {
15 ScanResult result = TranUtils.tranJSONObejct(scan_result, ScanResult.class);
16 String openid = wxMessage.getFromUser();
17 if(result.getK().equalsIgnoreCase("share")) {
18 //设备分享功能
19 try {
20 boolean flag = wechatuserctrlService.bindDeviceByShareKey(openid, result.getV());
21 if(flag == true) {
22 info = "绑定成功.";
23 }else {
24 info = "绑定失败.";
25 }
26 }catch (Exception e) {
27 info = e.getMessage();
28 }
29 }
30 }
31 }catch (Exception e) {
32 e.printStackTrace();
33 }
34 return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
35 }
36 }
7. 自定义菜单
这个就分为创建自定义菜单和自定义菜单事件。创建自定义菜单,这个需要先构造自定义菜单格式,然后POST到微信。
1 @RestController
2 @RequestMapping("/wx/wechat")
3 public class WechatMenuController implements WxMpMenuService {
4
5 @Autowired
6 private WxMpService wxmpService;
7 @Value(value="${wechat.mp.url}")
8 private String url;
9
10 @GetMapping("/menu")
11 public String menuCreateSample() throws WxErrorException {
12 WxMenu menu = new WxMenu();
13 WxMenuButton story = new WxMenuButton();
14 story.setType(MenuButtonType.VIEW);
15 story.setName("听故事");
16 story.setKey(WechatMpMenuConfig.M_STORY);
17 story.setUrl(url + "/story/index");
18
19 WxMenuButton device_1 = new WxMenuButton();
20 device_1.setType(MenuButtonType.VIEW);
21 device_1.setName("联网配置");
22 device_1.setKey(WechatMpMenuConfig.M_DEVICE_1);
23 device_1.setUrl(url + "/airkiss");
24 WxMenuButton device_2 = new WxMenuButton();
25 device_2.setType(MenuButtonType.VIEW);
26 device_2.setName("设备绑定");
27 device_2.setKey(WechatMpMenuConfig.M_DEVICE_2);
28 device_2.setUrl(url + "/scan_bind");
29 WxMenuButton device_3 = new WxMenuButton();
30 device_3.setType(MenuButtonType.SCANCODE_WAITMSG);
31 device_3.setName("二维码扫描");
32 device_3.setKey(WechatMpMenuConfig.M_DEVICE_3);
33 WxMenuButton device_4 = new WxMenuButton();
34 device_4.setType(MenuButtonType.VIEW);
35 device_4.setName("我的设备");
36 device_4.setKey(WechatMpMenuConfig.M_DEVICE_4);
37 device_4.setUrl(url + "/story/index#/user");
38 WxMenuButton device = new WxMenuButton();
39 device.setType(MenuButtonType.CLICK);
40 device.setName("设备功能");
41 device.setKey(WechatMpMenuConfig.M_DEVICE);
42 device.getSubButtons().add(device_1);
43 device.getSubButtons().add(device_2);
44 device.getSubButtons().add(device_3);
45 device.getSubButtons().add(device_4);
46
47
48 WxMenuButton other_1 = new WxMenuButton();
49 other_1.setType(MenuButtonType.VIEW);
50 other_1.setName("常见问题");
51 other_1.setKey(WechatMpMenuConfig.M_OTHER_1);
52 other_1.setUrl("https://mp.weixin.qq.com/s/YTGis2OK2Jtx-4NnnRqukw");
53 WxMenuButton other_2 = new WxMenuButton();
54 other_2.setType(MenuButtonType.VIEW);
55 other_2.setName("意见反馈");
56 other_2.setKey(WechatMpMenuConfig.M_OTHER_2);
57 other_2.setUrl(url + "/feedback");
58 WxMenuButton other_3 = new WxMenuButton();
59 other_3.setType(MenuButtonType.VIEW);
60 other_3.setName("官网");
61 other_3.setKey(WechatMpMenuConfig.M_OTHER_3);
62 other_3.setUrl("http://www.wunaozai.com/");
63 WxMenuButton other_4 = new WxMenuButton();
64 other_4.setType(MenuButtonType.CLICK);
65 other_4.setName("接收新信息");
66 other_4.setKey(WechatMpMenuConfig.M_OTHER_NEW_MSG);
67 WxMenuButton other = new WxMenuButton();
68 other.setType(MenuButtonType.CLICK);
69 other.setName("更多");
70 other.setKey(WechatMpMenuConfig.M_OTHER);
71 other.getSubButtons().add(other_1);
72 other.getSubButtons().add(other_2);
73 other.getSubButtons().add(other_3);
74 other.getSubButtons().add(other_4);
75
76
77 menu.getButtons().add(story);
78 menu.getButtons().add(device);
79 menu.getButtons().add(other);
80
81 wxmpService.getMenuService().menuCreate(menu);
82 return "ok";
83 }
84 //...
85 }
自定义菜单事件Handler
1 /**
2 * 菜单功能
3 * @author wunaozai
4 * @date 2018-08-15
5 */
6 @Component
7 public class WechatMpMenuHandler extends AbstractHandler {
8
9 @Override
10 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
11 WxSessionManager sessionManager) throws WxErrorException {
12 log.info("微信公众号 菜单功能");
13 String msg = String.format("Type: %s, Event: %s, Key: %s",
14 wxMessage.getMsgType(), wxMessage.getEvent(), wxMessage.getEventKey());
15 if(MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
16 //如果是跳转类按钮的就不进行处理
17 return null;
18 }
19 if(wxMessage.getEventKey().equalsIgnoreCase(WechatMpMenuConfig.M_OTHER_NEW_MSG)) {
20 //TODO: 读取数据库,下发信息,并删除
21 //如果没有信息的,提示没有未读信息
22 String info = "暂无新消息.";
23 return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
24 }
25 String info = "按下“"+wxMessage.getEventKey()+"”按钮,将跳转到指定的公众号页面.";
26 return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
27 }
28
29 }
自定义菜单注册
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(MenuButtonType.CLICK).handler(menuHandler).end();
公众号后台-菜单分析

















