公众号开发:获取用户消息和回复消息

您所在的位置:网站首页 如何登录自己的微信公众号发布消息记录 公众号开发:获取用户消息和回复消息

公众号开发:获取用户消息和回复消息

2024-07-15 01:36| 来源: 网络整理| 查看: 265

最近在看微信公众号的开发文档,觉得很有意思,可以自定义开发一些功能,比如有人关注了公众号之后,你可以做出稍微复杂点的回复(简单的回复在公众号后台配置就好啦);比如关注者发送了「学习」消息,你可以给他推送一些文章,发送「天气」的消息,你可以回复当前的天气状况;还可以进行素材的管理,用户的管理等等。

今天先来实现下最简单的获取关注者发送的消息,并给他回复同样的消息,支持文本消息,图片和语音。后续再解锁其他的姿势。

先来看看最终效果:

接下来开始操作,主要分为以下几个步骤:

申请测试公众号配置服务器地址验证服务器地址的有效性接收消息,回复消息申请测试公众号

第一步首先要申请一个测试的微信公众号,地址:

代码语言:javascript复制https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 配置服务器地址

在测试号页面上有接口配置信息选项,在这个选项下面进行配置:

有两个配置项:服务器地址(URL),Token,在正式的公众号上还有一个选项EncodingAESKey

URL:是开发者用来接收微信消息和事件的接口URL。Token可可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

当输入这个 URL 和 Token 点击保存的时候,需要后台启动并且验证 Token 通过之后才能保存,不然会保存失败,所以先把后台代码启动起来。

验证 Token

当填写 URL, Token,点击保存时,微信会通过 GET 的方式把微信加密签名(signature),时间戳(timestamp),随机数(nonce)和随机字符串(echostr)传到后台,我们根据这四个参数来验证 Token。

验证步骤如下:

将token、timestamp、nonce三个参数进行字典序排序。将三个参数字符串拼接成一个字符串进行sha1加密.获得加密后的字符串与signature对比,如果相等,返回echostr,表示配置成功,否则返回null,配置失败。代码如下

首先在 application.properties 配置文件中配置项目启动端口,Token,appId和appsecret,其中,Token随便写,只要和页面配置一致即可,appId和appsecret 在测试公众号页面可以看到,如下所示:

代码语言:javascript复制server.port=80 wechat.interface.config.token=wechattoken wechat.appid=wxfe39186d102xxxxx wechat.appsecret=cac2d5d68f0270ea37bd6110b26xxxx

然后把这些配置信息映射到类属性上去:

代码语言:javascript复制@Component public class WechatUtils { @Value("${wechat.interface.config.token}") private String token; @Value("${wechat.appid}") private String appid; @Value("${wechat.appsecret}") private String appsecret; public String getToken() { return token; } public String getAppid() {return appid;} public String getAppsecret() {return appsecret;} }

然后,需要有个方法,用来接收微信传过来的四个参数,请求的 URL 和页面配置的需要一样,如下:

代码语言:javascript复制@RestController public class WechatController { /** 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** 工具类 */ @Autowired private WechatUtils wechatUtils; /** * 微信公众号接口配置验证 * @return */ @RequestMapping(value = "/wechat", method = RequestMethod.GET) public String checkSignature(String signature, String timestamp, String nonce, String echostr) { logger.info("signature = {}", signature); logger.info("timestamp = {}", timestamp); logger.info("nonce = {}", nonce); logger.info("echostr = {}", echostr); // 第一步:自然排序 String[] tmp = {wechatUtils.getToken(), timestamp, nonce}; Arrays.sort(tmp); // 第二步:sha1 加密 String sourceStr = StringUtils.join(tmp); String localSignature = DigestUtils.sha1Hex(sourceStr); // 第三步:验证签名 if (signature.equals(localSignature)) { return echostr; } return null; } }

这里需要注意的是请求方式一定是 GET 的方式,POST方式是用户每次向公众号发送消息、或者产生自定义菜单、或产生微信支付订单等情况时,微信还是会根据这个URL来发送消息或事件。

启动项目,这时在测试公众号配置 URL 和 Token:

你会发现保存失败,后台也没有接收到消息,日志都没有打印;这是因为是在本地启动的项目,访问地址为127.0.0.1,而在点击保存的时候,是由腾讯服务器发送过来的,人家的服务器自然是访问不到你本地啦,所以还需要一款内网穿透工具,把我们本机地址映射到互联网上去,这样腾讯服务器就可以把消息发送过来啦。

我使用的是 natapp 这款工具,运行之后,如下:

http://pquigs.natappfree.cc 这个互联网地址会映射到我们的本机127.0.0.1的地址,页面上就配置这个地址:

此时可以保存成功,日志打印如下:

到这里,我们就算配置服务器成功了,接下来就可以调用公众号接口开发了。

access_token

什么是 access_token? 官网的介绍如下:

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

获取 access_token 的接口每日调用是有限制的,所以不是每次调用接口都重新获取access_token,而是获取到之后缓存起来,缓存失效之后再去重新获取即刷新。

❝官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html ❞

由于调用公众号开发接口基本都是XML格式的报文,所以为了开发简单,我们可以使用开源工具 weixin-java-mp 开发,pom.xml 文件添加相关依赖如下:

代码语言:javascript复制 me.chanjar weixin-java-mp 1.3.3 me.chanjar weixin-java-common 1.3.3

在 weixin-java-mp 中有两个重要的类,一个是配置相关的WxMpInMemoryConfigStorage,一个是调用微信接口相关的WxMpServiceImpl.

获取access_token

现在来获取下access_token:

接口地址为:代码语言:javascript复制https请求方式: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 参数说明

参数

是否必须

说明

grant_type

获取access_token填写client_credential

appid

第三方用户唯一凭证

secret

第三方用户唯一凭证密钥,即appsecret

返回的是JSON格式的报文:代码语言:javascript复制{"access_token":"ACCESS_TOKEN","expires_in":7200} 代码如下

首先在文章开头的工具类 WechatUtils 中进行初始化 WxMpInMemoryConfigStorage 和 WxMpServiceImpl,然后调用 WxMpServiceImpl 的 getAccessToken() 方法来获取 access_token:

代码语言:javascript复制/** * 微信工具类 */ @Component public class WechatUtils { @Value("${wechat.interface.config.token}") private String token; @Value("${wechat.appid}") private String appid; @Value("${wechat.appsecret}") private String appsecret; public String getToken() {return token;} public String getAppid() {return appid;} public String getAppsecret() {return appsecret;} /** * 调用微信接口 */ private WxMpService wxMpService; /** * 初始化 */ @PostConstruct private void init() { WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage(); wxMpConfigStorage.setAppId(appid); wxMpConfigStorage.setSecret(appsecret); wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage); } /** * 获取 access_token 不刷新 * @return access_token * @throws WxErrorException */ public String getAccessToken() throws WxErrorException { return wxMpService.getAccessToken(); }

在 WxMpServiceImpl 的 getAccessToken 方法中,会自动的在url中拼接 appId 和 appsecret,然后发送请求获取access_token,源码如下:

代码语言:javascript复制public String getAccessToken(boolean forceRefresh) throws WxErrorException { ..... String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + "&appid=" + wxMpConfigStorage.getAppId() + "&secret=" + wxMpConfigStorage.getSecret(); HttpGet httpGet = new HttpGet(url); if (httpProxy != null) { RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); httpGet.setConfig(config); } CloseableHttpResponse response = getHttpclient().execute(httpGet); String resultContent = new BasicResponseHandler().handleResponse(response); WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); ..... }

启动项目,浏览器输入http://localhost/getAccessToken,测试如下:

代码语言:javascript复制@RestController public class WechatController { /** 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** 工具类 */ @Autowired private WechatUtils wechatUtils; @RequestMapping("/getAccessToken") public void getAccessToken() { try { String accessToken = wechatUtils.getAccessToken(); logger.info("access_token = {}", accessToken); } catch (WxErrorException e) { logger.error("获取access_token失败。", e); } } }

除此之外,还可以获取关注者的列表,关注者的信息等。

获取用户的信息:代码语言:javascript复制@RestController public class WechatController { /** 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** 工具类 */ @Autowired private WechatUtils wechatUtils; @RequestMapping("getUserInfo") public void getUserInfo() { try { WxMpUserList userList = wechatUtils.getUserList(); if (userList == null || userList.getOpenIds().isEmpty()) { logger.warn("关注者openId列表为空"); return; } List openIds = userList.getOpenIds(); logger.info("关注者openId列表 = {}", openIds.toString()); String openId = openIds.get(0); logger.info("开始获取 {} 的基本信息", openId); WxMpUser userInfo = wechatUtils.getUserInfo(openId); if (userInfo == null) { logger.warn("获取 {} 的基本信息为空", openId); return; } String city = userInfo.getCity(); String nickname = userInfo.getNickname(); logger.info("{} 的昵称为:{}, 城市为:{}", openId, nickname, city); } catch (WxErrorException e) { logger.error("获取用户消息失败", e); } } }接收用户发送的消息

当微信用户向公众号发送消息时,微信服务器会通过公众号后台配置的URL把信息发送到我们后台的接口上,注意此时的请求格式为 POST请求,发送过来的消息报文格式是XML格式的,每种消息类型的XML格式不一样。

文本消息

当用户发送的是文本消息,接收到报文格式如下:

代码语言:javascript复制 1348831860 1234567890123456

参数

描述

ToUserName

开发者微信号

FromUserName

用户的openId

CreateTime

消息创建时间 (整型)

MsgType

消息类型,文本为text

Content

文本消息内容

MsgId

消息id,64位整型

由于接收到的消息是XML格式的,所以我们要解析,但是可以使用javax.xml.bind.annotation对应的注解直接映射到实体类的属性上,现在创建一个实体类:

代码语言:javascript复制/** * 接收消息实体 */ @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class ReceiveMsgBody { /**开发者微信号*/ private String ToUserName; /** 发送消息用户的openId */ private String FromUserName; /** 消息创建时间 */ private long CreateTime; /**消息类型*/ private String MsgType; /** 消息ID,根据该字段来判重处理 */ private long MsgId; /** 文本消息的消息体 */ private String Content; // setter/getter }

接收消息的方法如下,参数为 ReceiveMsgBody:

代码语言:javascript复制@RestController public class WechatController { /** 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** 工具类 */ @Autowired private WechatUtils wechatUtils; /** * 微信公众号接口配置验证 * @return */ @RequestMapping(value = "/wechat", method = RequestMethod.GET) public String checkSignature(String signature, String timestamp, String nonce, String echostr) { // 第一步:自然排序 String[] tmp = {wechatUtils.getToken(), timestamp, nonce}; Arrays.sort(tmp); // 第二步:sha1 加密 String sourceStr = StringUtils.join(tmp); String localSignature = DigestUtils.sha1Hex(sourceStr); // 第三步:验证签名 if (signature.equals(localSignature)) { return echostr; } return null; } /** * 接收用户消息 * @param receiveMsgBody 消息 * @return */ @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; "}) @ResponseBody public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) { logger.info("接收到的消息:{}", receiveMsgBody); } }

注意这里请求方式为 POST,报文格式为 xml。

启动项目,给测试号发送消息「哈哈」,接收到的消息如下:

图片消息和语音消息也是一样的获取。

图片消息

报文格式:

代码语言:javascript复制 1348831860 1234567890123456

参数

描述

MsgType

消息类型,image

PicUrl

图片链接(由系统生成)

MediaId

图片消息媒体id,可以调用获取临时素材接口拉取数据

语音消息

报文格式:

代码语言:javascript复制 1357290913 1234567890123456

参数

描述

MsgType

消息类型,voice

Format

语音格式,如amr,speex等

MediaId

语音消息媒体id,可以调用获取临时素材接口拉取数据

回复用户消息

当用户发送消息给公众号时,会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。

也就是说收到消息后,需要返回一个XML格式的报文回去,微信会解析这个报文,然后把消息给用户推送过去。

回复文本消息

需要返回的报文格式如下:

代码语言:javascript复制 12345678

参数

描述

ToUserName

用户的openId

FromUserName

开发者微信号

CreateTime

消息创建时间 (整型)

MsgType

消息类型,文本为text

Content

文本消息内容

这是同样创建一个响应消息的实体类,用xml注解标注:

代码语言:javascript复制/** * 响应消息体 */ @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class ResponseMsgBody { /**接收方帐号(收到的OpenID)*/ private String ToUserName; /** 开发者微信号 */ private String FromUserName; /** 消息创建时间 */ private long CreateTime; /** 消息类型*/ private String MsgType; /** 文本消息的消息体 */ private String Content; // setter/getter }

响应消息:

代码语言:javascript复制 /** * 接收用户下消息 * @param receiveMsgBody 消息 * @return */ @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"}) @ResponseBody public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) { logger.info("接收到的消息:{}", receiveMsgBody); // 响应的消息 ResponseMsgBody textMsg = new ResponseMsgBody(); textMsg.setToUserName(receiveMsgBody.getFromUserName()); textMsg.setFromUserName(receiveMsgBody.getToUserName()); textMsg.setCreateTime(new Date().getTime()); textMsg.setMsgType(receiveMsgBody.getMsgType()); textMsg.setContent(receiveMsgBody.getContent()); return textMsg; }

这里的 ToUserName 和 FromUserName 和接收的消息中反过来即可,内容也原样返回。

这样,用户发送什么,就会收到什么。

回复图片消息

需要返回的报文格式如下:

代码语言:javascript复制 12345678

需要注意的是 MediaId标签外面还有一个标签Image,如果直接映射为实体类的属性,则返回的消息用户是接收不到的,此时需要使用 XmlElementWrapper注解来标识上级标签的名称,且只能作用在数组上:

代码语言:javascript复制/** * 图片消息响应实体类 */ @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class ResponseImageMsg extends ResponseMsgBody { /** 图片媒体ID */ @XmlElementWrapper(name = "Image") private String[] MediaId; public String[] getMediaId() {return MediaId;} public void setMediaId(String[] mediaId) {MediaId = mediaId;} }

设置响应消息:

代码语言:javascript复制ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg(); voiceMsg.setToUserName(receiveMsgBody.getFromUserName()); voiceMsg.setFromUserName(receiveMsgBody.getToUserName()); voiceMsg.setCreateTime(new Date().getTime()); voiceMsg.setMsgType("voice"); voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()}); 回复语音消息:

需要返回的报文格式如下:

代码语言:javascript复制 12345678

MediaId标签的上层标签是Voice,响应实体类为:

代码语言:javascript复制/** * 语音消息响应实体类 */ @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class ResponseVoiceMsg extends ResponseMsgBody{ /** 图片媒体ID */ @XmlElementWrapper(name = "Voice") private String[] MediaId; public String[] getMediaId() { return MediaId; } public void setMediaId(String[] mediaId) {MediaId = mediaId; } }

设置响应消息:

代码语言:javascript复制ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg(); voiceMsg.setToUserName(receiveMsgBody.getFromUserName()); voiceMsg.setFromUserName(receiveMsgBody.getToUserName()); voiceMsg.setCreateTime(new Date().getTime()); voiceMsg.setMsgType(MsgType.voice.getMsgType()); voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()});

到这里,接收文本消息,图片消息,语音消息,回复文本消息,图片消息,语音消息基本完毕了,接下来整合一下实现文章开头的效果。

整合

因为消息由很多类型,所以定义一个枚举类:

代码语言:javascript复制/** * 消息类型枚举 */ public enum MsgType { text("text", "文本消息"), image("image", "图片消息"), voice("voice", "语音消息"), video("video", "视频消息"), shortvideo("shortvideo", "小视频消息"), location("location", "地理位置消息"), link("link", "链接消息"), music("music", "音乐消息"), news("news", "图文消息"), ; MsgType(String msgType, String msgTypeDesc) { this.msgType = msgType; this.msgTypeDesc = msgTypeDesc; } private String msgType; private String msgTypeDesc; //setter/getter /** * 获取对应的消息类型 * @param msgType * @return */ public static MsgType getMsgType(String msgType) { switch (msgType) { case "text": return text; case "image": return image; case "voice": return voice; case "video": return video; case "shortvideo": return shortvideo; case "location": return location; case "link": return link; case "music": return music; case "news": return news; default: return null; } } }

然后接收到消息之后,判断类型,根据类型不同,回复不同类型的消息就可以了:

代码语言:javascript复制@RestController public class WechatController { /** 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** 工具类 */ @Autowired private WechatUtils wechatUtils; /** * 接收用户消息 * @param receiveMsgBody 消息 * @return */ @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"}) @ResponseBody public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) { logger.info("接收到的消息:{}", receiveMsgBody); MsgType msgType = MsgType.getMsgType(receiveMsgBody.getMsgType()); switch (msgType) { case text: logger.info("接收到的消息类型为{}", MsgType.text.getMsgTypeDesc()); ResponseMsgBody textMsg = new ResponseMsgBody(); textMsg.setToUserName(receiveMsgBody.getFromUserName()); textMsg.setFromUserName(receiveMsgBody.getToUserName()); textMsg.setCreateTime(new Date().getTime()); textMsg.setMsgType(MsgType.text.getMsgType()); textMsg.setContent(receiveMsgBody.getContent()); return textMsg; case image: logger.info("接收到的消息类型为{}", MsgType.image.getMsgTypeDesc()); ResponseImageMsg imageMsg = new ResponseImageMsg(); imageMsg.setToUserName(receiveMsgBody.getFromUserName()); imageMsg.setFromUserName(receiveMsgBody.getToUserName()); imageMsg.setCreateTime(new Date().getTime()); imageMsg.setMsgType(MsgType.image.getMsgType()); imageMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()}); return imageMsg; case voice: logger.info("接收到的消息类型为{}", MsgType.voice.getMsgTypeDesc()); ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg(); voiceMsg.setToUserName(receiveMsgBody.getFromUserName()); voiceMsg.setFromUserName(receiveMsgBody.getToUserName()); voiceMsg.setCreateTime(new Date().getTime()); voiceMsg.setMsgType(MsgType.voice.getMsgType()); voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()}); return voiceMsg; default: // 其他类型 break; } return null; } }

这就是实现接收用户消息,回复用户消息的全部实现了,还是比较简单的;除了这个,还可以自定义菜单,关注/取消事件的监听处理,用户管理等其他操作,后面有时间了再慢慢研究吧。

❝代码已经上传到github上,有兴趣的试一下吧。 https://github.com/tsmyk0715/wechatproject ❞



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3