【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复

【公众号开发】(1)

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第1张图片

文章目录

  • 【公众号开发】(1)
    • 1. 获得一个测试号
    • 2. 公众号开发原理
    • 3. 创建开发者服务器
    • 4. 内网穿透
    • 5. 验证开发者服务器的url
      • 5.1 公众号服务器对url发送get请求
      • 5.2 消息的验证逻辑
      • 5.3 代码实现
    • 6. 接受并解析用户发来的消息
      • 6.1 查看post请求的数据格式
      • 6.2 解析xml字符串
      • 6.3 将xml字符串解析结构构造回复消息
        • 6.3.1 消息对象
        • 6.3.2 将map封装成消息对象
        • 6.3.3 将消息对象进行序列化为xml
        • 6.3.4 在controller实现(调用方法)

【公众号开发】(1)

公众号的使用和作用不必多说,很多企业都会选择公众号这种与客户粘性大的方式去推广自己或者其他的动作…

而本专栏主要讲解的就是公众号的基本开发,不囊括所有,但是可以从0到1,之后的1生万物由你完成!

我这里不列举公众号开发能咋开发,你可以查看开发者文档:开发前必读 / 首页 (qq.com)

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第2张图片

这些是微信公众号自带的功能,但我们要满足我们自己的需求,当然是需要进行开发的

这些功能就是字面意思~

本专栏已开发为主,很少有理论的讲解,直接进入实战吧,公众号有啥功能我们可以开发,进入实战即可见分晓~

1. 获得一个测试号

正式的公众号的申请需要门槛,所以为了方便我们开发者上手,专门有一个“接口测试公众号”用来练习,只不过没有一些高级的接口,这些无伤大雅,有了基础就可以去学习高级接口了

开始开发 / 接口测试号申请 (qq.com)

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第3张图片

按照指引注册即可,如果已经注册,登录微信即可

  • 由于是测试号,还不能登录公众号后台管理平台~
    • 所以没有那些现成的公众号提供的一些固定的操作~
  • 这个之后申请正式的公众号去登录用一用就会了~

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第4张图片

这些消息可能你会有一些没有,不过没关系,之后慢慢展开

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第5张图片

  • 用你的手机扫码关注后,你将在右侧看到你的关注用户

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第6张图片

  • 这个之后再说

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第7张图片

  • 这些就是测试号的体验的接口总览咯,使用上限,点击链接跳转到对于的开发手册页面
    • 需要就学即可
  • 我不会全讲也不会按顺序讲~

2. 公众号开发原理

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第8张图片

大概就是这样的,微信公众号并不是什么功能都能实现,只能实现一些公众号提供的接口(就是刚才那些)

因为用户并不是直接访问开发者,开发者也不是直接响应给用户,微信公众号提供较为统一的页面给用户,和较为统一的接口给开发者

这样的介入,提高公众号的统一性,用户可以依照经验使用,又有公众号的特性,提供的接口给开发者开发自定义!

  • 功能的限制,对用户的请求和返回的响应需要经过公众号的检验,也提高微信的安全性和健康性

3. 创建开发者服务器

我们先创建一个项目(SpringBoot),作为开发者服务器

  • 由于涉及Spring web开发,如果没有此基础学不了,建议先去学!
  • 推荐资料:【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解-CSDN博客

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第9张图片

这里就是创建好的了

  1. 我导入了lombok依赖(在一开始选上就好)

  2. 我的配置文件为application.yml:

    spring:
      application:
        name: 微信公众号测试 # 项目的名称
      output:
        ansi:
          enabled: ALWAYS # 配置控制台输出的效果,彩色
      # JSON序列化配置
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss # ⽇期格式
        default-property-inclusion: NON_NULL # 不为null序列化
    server:
      port: 8080
    
    logging:
      pattern:
        dateformat: MM-dd HH:mm:ss # 日期显示格式
      level:
        root: info # 日志默认级别
      file:
        path: D:/log/project/wx
    

4. 内网穿透

由于微信公众号服务器在千里之外,要想访问我们的开发者服务器,必然是要通过外网去访问的,所以我们需要一个外网ip!

  1. 云服务器
  2. 内网穿透工具

首先,云服务器是必然的,最终一定是要部署到云服务器去维持服务,但是我们开发阶段,频繁的修改和测验,云服务器的方式太麻烦了,所以我们要用内网穿透工具,将我们的内网映射成外网,让外接可以访问!

官网:NATAPP-内网穿透 基于ngrok的国内高速内网映射工具

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第10张图片

安装到本地,并解压缩,到达natapp.exe的所在目录下:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第11张图片

右键在命令行打开:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第12张图片

回到官网注册/登录

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第13张图片

这两项去做一下:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第14张图片

购买免费隧道:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第15张图片

进入配置页:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第16张图片

可以看到刚才的信息了,复制authtoken(等一下要用):

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第17张图片

回到命令行输入(authtoken输入自己的):

./natapp -authtoken=72810d6d1ab6e1fe

这个就是映射的地址(综合了ip与端口的字符串):

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第18张图片

不过注意的是,按ctrl c或者直接关闭或者电脑关机(甚至你没有关闭,过一段时间就变了),这个字符串的映射就失效了,并且下次生成的也不一样哦!

  • 所以,有时候公众号访问不了,那就是这个字符串变了;之后正式公众号和部署到云服务就不会出现这种情况了

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第19张图片

我们写一个接口,检测一下这个字符串是否有效:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第20张图片

浏览器访问:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第21张图片

命令行有请求的记录:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第22张图片

5. 验证开发者服务器的url

其实就是这个地方的接口配置设置:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第23张图片

点击修改:

  • URL为刚才的内网穿透的字符串
  • Token为自定义令牌字符串

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第24张图片

但是点击提交后,并不会显示成功:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第25张图片

这是因为微信公众号服务器要确保这个url是开发者的url,有这些好处(我认为):

  1. 这个可以先将地址访问问题单独拎出来,如果通过,那么以后发生的问题就不会是地址的问题(除非你的外网ip变了)
  2. 这个令牌Token只有公众号和开发者知道,开发者可以通过这个验证这个请求来自微信公众号服务器,保证信息安全

具体是啥原因,太底层不需要了解,我们“坐享其成,遵循规矩”

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第26张图片

点击提交:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第27张图片

但是你现在纳闷了,“凭什么我不行?”,这是因为你的服务器并没有进行相关操作~

5.1 公众号服务器对url发送get请求

公众号会对我们填写的url的根目录(就是后面不带路由,也就是/)发送一个GET请求,这个请求的目的就是验证

但是浏览器访问是这样的:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第28张图片

原因是缺少参数,我们可以在开发者文档查看:开始开发 / 接入指南 (qq.com)

5.2 消息的验证逻辑

重点看这里:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第29张图片

他的意思就是,他的请求携带了这四个参数,而我们开发者如果返回echostr这个回显字符串的话,那么就是验证成功,公众号也就确认你是,并且授权了,所以就可以匹配成功

那么我们要咋样才可以返回echostr呢?

原理就是:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第30张图片

这里 Token是只有公众号和开发者知道

所以以此与timestamp和nonce拼接之后的字符串, 加密之后的字符串作为建议身份的标志 非常合理科学

一般加密是不可逆的,所以我们并没有办法将signature解密出Token,而是将 加密出一个字符串与之对比!

加密算法:

  1. timestamp、nonce、Token按字典序排列
  2. 将排列后的字符串进行拼接
  3. 将拼接字符串进行加密(加密结果为字节数组
  4. 字节数组每个字节按照十六进制进行拼接

最后,加密拼接字符串与signature进行比较

5.3 代码实现

所需依赖

<dependency>
   <groupId>org.dom4jgroupId>
   <artifactId>dom4jartifactId>
   <version>2.1.3version>
dependency>
@GetMapping("/")
public String check(@RequestParam("signature") String signature,
                    @RequestParam("timestamp") String timestamp,
                    @RequestParam("nonce") String nonce,
                    @RequestParam("echostr") String echostr) {
    if(!StringUtils.hasLength(signature) || !StringUtils.hasLength(timestamp)
            || !StringUtils.hasLength(nonce) || !StringUtils.hasLength(echostr)) {
        return "";
    }
    // 1) 将token 、 timestamp 、 nonce 三个参数进行字典序排序
    String token = "maras103";
    String[] list = {token, timestamp, nonce};
    Arrays.sort(list);
    // 2) 将三个字符串以此顺序进行拼接
    StringBuilder builder = new StringBuilder();
    for(String s : list) {
        builder.append(s);
    }
    // 2.1) 加密
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("sha1");
        byte[] digest = messageDigest.digest(builder.toString().getBytes(StandardCharsets.UTF_8));
        // 2.2) 将加密后的byte数组转换为signature一样的格式(每个字节都转换为十六进制进行拼接)
        builder = new StringBuilder();
        for(byte b : digest) {
            // builder.append(Integer.toHexString(b));不能这么弄因为这样弄b如果是负,那么就凉凉
            // 这样写保证两位十六进制都存在并且正确
            builder.append(Integer.toHexString((b >> 4) & 15));//前四个字节转换为十六进制
            builder.append(Integer.toHexString(b & 15));//后四个字节转换为十六进制
        }
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    }
    // 3) 核对请求是否来自于微信(加密解析后的字符串与signature比较)
    if(builder.toString().equals(signature)) {
        // 相同才返回回显字符串
        // 并且返回echostr代表开发者确认了这是公众号服务器发来的请求,公众号就进行配置
        return echostr;
    } else {
        // 否则返回null
        return null;
    }
}

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第31张图片

6. 接受并解析用户发来的消息

用户有时候会发一些消息给公众号,那我们的服务器怎么针对这些消息做出一些自定义的动作呢?

原理是这样的

  • 具体参考开发者文档,我这里直接说了

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第32张图片

当用户发消息给公众号:

在这里插入图片描述

就相当于发请求给微服务公众号服务器,访问了它的一个接口,微信公众号就会打包我们的数据构造post请求访问我们的开发者服务器(根路径/

并且是以xml的格式!

6.1 查看post请求的数据格式

获取请求的输入流对象,读取数据

@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException {
    ServletInputStream inputStream = request.getInputStream();
    byte[] b = new byte[1024];
    int len = 0;
    while((len = inputStream.read(b)) != -1) {
        System.out.println((new String(b, 0, len)));
    }
    return "";
}

注意:返回空字符串,代表什么都不回~

我们向公众号发一条消息:

在这里插入图片描述

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第33张图片

6.2 解析xml字符串

@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException {
    Map<String, String> map = new HashMap<>();
    SAXReader reader = new SAXReader();
    // xml字符串解析方法
    try {
        //通过请求的输入流,获取Document对象
        Document document = reader.read(request.getInputStream());
        // 获取root节点
        Element root = document.getRootElement();
        // 获取所有子节点
        List<Element> elements = root.elements();
        // 遍历集合
        for(Element e : elements) {
            map.put(e.getName(), e.getStringValue());
        }
        log.info(map.toString());
    } catch (DocumentException e) {
        throw new RuntimeException(e);
    }
    return "";
}

在这里插入图片描述

查看日志:
在这里插入图片描述

解析成功~

6.3 将xml字符串解析结构构造回复消息

注意:回复消息字符串必须是xml字符串格式,否则微信公众号不做任何动作

所需依赖

<dependency>
   <groupId>com.thoughtworks.xstreamgroupId>
   <artifactId>xstreamartifactId>
   <version>1.4.11.1version>
dependency>
6.3.1 消息对象
@Data
@XStreamAlias("xml")
public class TextMessage {
    @XStreamAlias("ToUserName")
    private String toUserName;

    @XStreamAlias("FromUserName")
    private String fromUserName;

    @XStreamAlias("CreateTime")
    private long createTime;

    @XStreamAlias("MsgType")
    private String msgType;

    @XStreamAlias("Content")
    private String content;
}

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第34张图片

这个对象进行序列化一个对象为xml字符串的时候,默认以类名为父标签,各个属性的属性名为子标签,如果一个属性为自定义类型,则这个对象也是树形结构xml标签嵌套于此xml标签(套娃套娃套娃…)

6.3.2 将map封装成消息对象
public static TextMessage getReplyTextMessage(Map<String, String> map) {
    TextMessage message = new TextMessage();
    message.setToUserName(map.get("FromUserName"));
    message.setFromUserName(map.get("ToUserName"));
    message.setCreateTime(System.currentTimeMillis() / 1000);
    message.setMsgType("text");
    message.setContent("回复" + map.get("Content") + ":就不告诉你");
    return message;
}

这里其实回复的消息可以是很多种(文本、图片、音频等等等…)

  • 复杂的回复体系这里暂时不讲,之后业务上做好分支即可~

(这里简单的回复个文本消息)

(发起者和接收者的username进行调换)

(时间戳是秒级别为当前时间)

6.3.3 将消息对象进行序列化为xml
/**
 * 将TextMessage对象序列化为xml字符串返回
 * @param message
 * @return
 */
public static String getXML(TextMessage message) {
    //获取序列化工具XStream对象
    XStream xStream = new XStream();
    //指定类型
    xStream.processAnnotations(TextMessage.class);
    //转化为xml字符串
    String xml = xStream.toXML(message);
    //返回
    return xml;
}

无非就是调用一些库方法

6.3.4 在controller实现(调用方法)
@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException {
    //        ServletInputStream inputStream = request.getInputStream();
    //        byte[] b = new byte[1024];
    //        int len = 0;
    //        while((len = inputStream.read(b)) != -1) {
    //            System.out.println((new String(b, 0, len)));
    //        }
    Map<String, String> map = new HashMap<>();
    SAXReader reader = new SAXReader();
    // xml字符串解析方法
    try {
        //通过请求的输入流,获取Document对象
        Document document = reader.read(request.getInputStream());
        // 获取root节点
        Element root = document.getRootElement();
        // 获取所有子节点
        List<Element> elements = root.elements();
        // 遍历集合
        for(Element e : elements) {
            map.put(e.getName(), e.getStringValue());
        }
        log.info(map.toString());
    } catch (DocumentException e) {
        throw new RuntimeException(e);
    }
    // 回复消息
    // 1. 封装对象
    TextMessage textMessage = TextMessage.getReplyTextMessage(map);
    // 2. 序列化对象
    String message = TextMessage.getXML(textMessage);
    System.out.println(message);
    return message;
}

公众号发消息测试:

【公众号开发】如何写出第一个公众号开发程序 · 动态自定义自动回复_第35张图片

成功啦!

我想这么一个用户与公众号交互的程序,让大家对公众号开发有了初步的认知!


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭

代码:wx-demo · 游离态/马拉圈2023年10月 - 码云 - 开源中国 (gitee.com)


你可能感兴趣的:(公众号开发,JavaEE,java,微信公众平台,springboot,springmvc,spring)