1.前言
经过前面的配置,基本完成了一些基础配置。后面接下来就是一些开发流程了。
2.配置pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/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 "! 欢迎使用杰理故事机/:heart 如果是第一次使用,请点击下方按钮,按提示步骤进行操作。"; 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();
公众号后台-菜单分析