赞
踩
前些日子在抖音上看到一个写给女朋友的微信公众号突然心血来潮自己也想写一个,随后就开始在下面的踩坑填坑的阶段了,因为也是第一次写微信公众号再次也是记录一下自己的开发过程。
如果你也是刚刚接触的公众号的我认为我们可以慢慢聊聊,再者我是一个喜欢化繁为简的人,不喜欢将问题搞的很复杂,所以我会尽可能的将问题描述的简单、清楚。
注册好之后
在开发者工具里面你能看到这些工具,我一般用的就是开发者文档、在线接口调试工具和公众平台测试账号。
这是就是测试号,往下滑就有关注的二维码,一般测试都是在这里测试开发,这里权限几乎全开,所以很方便。
我其实一开始用的服务器,但是后来觉得这样调试特别麻烦,不过有兴趣的可以去试试微信云托管,这里就不说了(因为我也不会用),之后我就用花生壳内网穿透,映射出了一个外网ip。
需要注意的就是需要选择https这个,会出现什么不支持web访问。
最后就是将这个外网ip填写在下图,就是测试公众号的那个页面
到这里你可能有疑问:
1、接口配置信息是干嘛的?
简单画了一个示意图,因为你需要将自己的服务器接入公众号,那必然需要配置你的服务器地址,这个接口配置信息就是用来验证你的服务器是否有用和这个验证消息来自微信服务器。
2、URL是指什么地址?
这个就是花生壳映射出来的外网IP,也就是用来验证你的服务器是否有用的。
3、Token是什么?
这个就是用来验证消息来自微信服务器。
这个值是可以任意填写的。在微信开发文档中也有介绍到如何验证服务器。
那就直接看代码
这里可以简单的看一下目录结构,就是Controller层和Service
@GetMapping()
@ResponseBody
public String doGet(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
return weChatService.checkSignature(signature, timestamp, nonce,echostr);
}
下面代码所做的就是:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与 signature 对比,标识该请求来源于微信
token就是你自己填写的值。
@Override public String checkSignature(String signature, String timestamp, String nonce, String echostr) { log.info("接收来自微信服务器的认证消息:[{},{},{},{}]",signature,timestamp,nonce,echostr); //1)将token、timestamp、nonce三个参数进行字典序排序 String[] str = new String[]{token, timestamp, nonce}; Arrays.sort(str); //2)将三个参数字符串拼接成一个字符串进行sha1加密 String str2 = str[0] + str[1] + str[2]; String sha1Str = SecureUtil.sha1(str2); //3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 System.out.println("结果:" + sha1Str.equals(signature)); log.info("结果:{}", sha1Str.equals(signature)); if (sha1Str.equals(signature)){ return echostr; }else { return "请求非法"; } }
代码编写好这些之后,你就可以在接口配置信息里填写好你的url和你自己的token,点击提交,会提示提交成功。如果显示提交失败,需要注意一下代码是否报错和你的花生壳内网穿透是否成功,比如你yml文件里的端口号是否和花生壳内网端口相同。
这里需要提醒一下微信公众号使用的xml数据格式。
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
<MsgDataId>xxxx</MsgDataId>
<Idx>xxxx</Idx>
</xml>
而且这里需要特别注意一个点
我们之前开发都是在通过请求路径去区分调用方法,例如
@RestController
@RequestMapping("/wechat")
@Slf4j
public class WeChatController {
@Autowired
private WeChatService weChatService;
@GetMapping("/getToken")
public ApiResponse doGet() {
String token = weChatService.getToken();
return ApiResponse.ok(token);
}
}
请求就是ip:端口/wechat/getToken来达到请求的目的
但是微信这个是自动回复的微信服务器只是单纯的给我们服务器发送一个POST请求然后带上XML数据,所以我们只需要接收一个POST请求,读取XML格式返回;
Controller
@PostMapping(produces = "application/xml; charset=UTF-8")
public String doPost(HttpServletRequest request) {
return weChatService.dealMessage(request);
}
Service
@Override public String dealMessage(HttpServletRequest request) { Map<String, String> map = MessageUtil.xmlToMap(request); String ToUserName = map.get("ToUserName"); String FromUserName = map.get("FromUserName"); String MsgType = map.get("MsgType"); String CreateTime = map.get("CreateTime"); String MsgId = map.get("MsgId"); log.info("ToUserName:{},FromUserName:{},MsgType:{},CreateTime:{},MsgId:{}",ToUserName,FromUserName,MsgType,CreateTime,MsgId); if(MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(MsgType)){ //文字 String Content = map.get("Content"); log.info("Content:{}",Content); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(FromUserName) .setFromUserName(ToUserName) .setMsgType(MsgType) .setCreateTime(System.currentTimeMillis()); textMessage.setContent(textReplyTemplateMapper.getReply(Content)); return MessageUtil.messageToXml(textMessage); } return null; }
Util
public class MessageUtil { /** * 返回消息类型:文本 */ public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /** * 返回消息类型:音乐 */ public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /** * 返回消息类型:图文 */ public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /** * 请求消息类型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 请求消息类型:图片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 请求消息类型:链接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 请求消息类型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 请求消息类型:音频 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 请求消息类型:推送 */ public static final String REQ_MESSAGE_TYPE_EVENT = "event"; /** * 事件类型:subscribe(订阅) */ public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /** * 事件类型:unsubscribe(取消订阅) */ public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /** * 事件类型:CLICK(自定义菜单点击事件) */ public static final String EVENT_TYPE_CLICK = "CLICK"; public static Map<String, String> xmlToMap(HttpServletRequest request) { Map<String, String> map = new HashMap<>(); SAXReader reader = new SAXReader(); InputStream in = null; try { in = request.getInputStream(); Document doc = reader.read(in); Element root = doc.getRootElement(); List<Element> list = root.elements(); for (Element element : list) { map.put(element.getName(), element.getText()); } } catch (IOException | DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return map; } /** * 消息转XML * @param message * @param <T> * @return */ public static <T> String messageToXml(T message) { XStream xstream = new XStream(); xstream.alias("xml", message.getClass()); return xstream.toXML(message); } }
以上就是消息回复的全部代码,回复的文本我保存在自己的数据库里,到时也可以自己配置回复对应的文本信息。
目前刚学到这也是即使的分享出来学习过程。。。也是诚恳的希望和大佬们多多交流交流。。。未完待续
=============================
女朋友分了,不搞了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。