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


    公众号开发】(1)

    在这里插入图片描述

    【公众号开发】(1)

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

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

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

    在这里插入图片描述

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

    这些功能就是字面意思~

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

    1. 获得一个测试号

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

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

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    • 这个之后再说

    在这里插入图片描述

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

    2. 公众号开发原理

    在这里插入图片描述

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

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

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

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

    3. 创建开发者服务器

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

    在这里插入图片描述

    这里就是创建好的了

    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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    4. 内网穿透

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

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

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

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

    在这里插入图片描述

    在这里插入图片描述

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

    在这里插入图片描述

    右键在命令行打开:

    在这里插入图片描述

    回到官网注册/登录

    在这里插入图片描述

    这两项去做一下:

    在这里插入图片描述

    购买免费隧道:

    在这里插入图片描述

    进入配置页:

    在这里插入图片描述

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

    在这里插入图片描述

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

    ./natapp -authtoken=72810d6d1ab6e1fe
    
    • 1

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

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    在这里插入图片描述

    浏览器访问:

    在这里插入图片描述

    命令行有请求的记录:

    在这里插入图片描述

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

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

    在这里插入图片描述

    点击修改:

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

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

    在这里插入图片描述

    点击提交:

    在这里插入图片描述

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

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

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

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

    在这里插入图片描述

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

    5.2 消息的验证逻辑

    重点看这里:

    在这里插入图片描述

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

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

    原理就是:

    在这里插入图片描述

    这里 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    在这里插入图片描述

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

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

    原理是这样的

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

    在这里插入图片描述

    当用户发消息给公众号:

    在这里插入图片描述

    就相当于发请求给微服务公众号服务器,访问了它的一个接口,微信公众号就会打包我们的数据构造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 "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

    在这里插入图片描述

    在这里插入图片描述

    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 "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

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

    解析成功~

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

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

    所需依赖

    <dependency>
       <groupId>com.thoughtworks.xstreamgroupId>
       <artifactId>xstreamartifactId>
       <version>1.4.11.1version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    这个对象进行序列化一个对象为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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

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

    (发起者和接收者的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    无非就是调用一些库方法

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    公众号发消息测试:

    在这里插入图片描述

    成功啦!

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


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

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


  • 相关阅读:
    Ylearn因果推断入门实践——Kaggle银行客户流失
    3、Kafka进阶提升-消费者
    客户端远程连接mysql服务问题记录
    Photoshop 2024 mac/win版:探索图像处理的全新境界
    HTML篇八——(2)
    动态网格理论基础(一)
    深度解析:为什么跨链桥又双叒出事了?
    Leetcode 第394场周赛 问题和解法
    C. Make Good
    Centos 7 - uWSGI服务器 虚拟环境配置详解及 .ini 文件配置模板 - No module named ‘encodings - uWSGI服务器无法识别虚拟环境内的python解释器
  • 原文地址:https://blog.csdn.net/Carefree_State/article/details/133937641