关于java对接微信公众号的简单功能实现

前言:

  •  本文只是自己平时的学习记录,所以有点不规范,请见谅 
  • 本文通过简单的demo练习,带你了解公众号的对接流程
  • 简单的功能主要有:(1)个性化菜单:对接聚合数据,完成笑话大全、谜语大全、心灵鸡汤、成语接龙等有趣功能(2)测试模板消息相关(3)对接百度AI开放平台实现网络图片的文字识别功能。
  •  源码地址:https://gitee.com/ZhaoShuHao0413/wx_account1.git

一、关于内网穿透

微信公众号服务器与自己的服务器连接,一般有俩种方式:

(1)将自己的服务器部署到云端上,微信公众号服务器直接与自己的云服务器交互。

(2)微信公众号服务器与本地服务器怎么交互?

可以通过内网穿透的方式,将本地服务器映射到云端(内网穿透就相当于一个代理),微信公众号服务器通过内网穿透映射的地址,访问到本地。

二、关于验证消息是否来自微信服务器

开始开发 / 接入指南 

注:下方的这个url取决于你的内网穿透地址 +接口访问地址

  • 点击提交:微信会将(signature、timestamp、nonce、echostr)四个参数发送到接口配置中的URL接口中
  • 开发者通过检验 signature 对请求进⾏校验(下⾯有校验⽅式)。若确认此次 GET请求来⾃微信服务器,请原样返回 echostr 参数内容,则接⼊⽣效,成为开发者成功,否则接⼊失败。加密/校验流程如下:
    • (1)将token、timestamp、nonce三个参数进⾏字典序排序
    • (2)将三个参数字符串拼接成⼀个字符串进⾏sha1加密
    • (3)开发者获得加密后的字符串可与 signature 对⽐,
    • (4)如果加密后的字符串=signature ,标识该请求来源于微信,返回echostr参数内容即可。
  • 有个问题:进行sha1加密后,得到的是一个byte数组,那么怎么将该数组转化成一个字符串呢?
    • byte[] digest;     使用new String(digest)转化?不行,这种方式会按照string的内置的编码器对byte数组解码,处理过后的数据肯定不会等于signature 字符串,可能还会乱码。
    • 使用digest.toString()?也不行,原因如上。
  • 解决问题?
  • 由数据可知signature字符串,每一个元素都是一个16进制的元素(16进制:0~f)
  • 而byte数组中,每一个元素都是一个字节,而一个字节是8位二进制的数。(byte[]数组是二进制的)
  • 注:在byte[]数组中,每个元素都占据一个字节的空间,并用8个二进制位来表示其值。
  • 那么假设byte[]数组中的第一个元素为 1011 0001 (8位二进制数表示)。那么byte数组转化成16进制的字符串就相当于,二进制—》16进制的转化。即4位一体,1011转化为16进制的值为:11—》B  ,0001转化为16进制为:1 ,则(二进制)10110001—》(16进制)b1。
  • 由上述可知,byte[]数组中一个元素,就是一个字节,就是8位2进制数,就相当于16进制字符串signature中的2个元素。

三、接收普通消息

基础消息能力:接收普通消息:文本消息 | 微信开放文档

1、练习一:接受消息,在控制台输出,查看消息结构

  • 由官方文档可知,发送的是Post请求,格式是xml的格式
  • 代码:
 @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        byte[] bytes = new byte[1024];
        System.out.println("inputStream:"+inputStream);
        int len = 0;
        while ((len=inputStream.read(bytes))!=-1){
            System.out.println(new String(bytes, 0, len));
        }
        return "hello";
    }

  • 使用微信扫描测试公众号,进行测试
  • 向测试号发送消息(后端服务已经启动)
  • 在后端idea控制台中查看结果
    • 发送的文本
    • 发送的图片

2、练习二,解析用户消息,封装成map结构

  • 代码
   <!-- DOM4J是 dom4j.org 出品的一个开源XML解析包-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

 //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });
  
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        return "";
    }

  • 结果:封装成map结构

四、接收消息后,做出回复文本消息

  • 由官方文档可知,消息回复的格式必须是xml结构。格式如下
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>

1、练习一:简单的回复

  • 代码:
 //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });
  
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        String message ="<xml> " +
                "<ToUserName><![CDATA[okuVs5iLGbRWwjjotu8EsKVURyDM]]></ToUserName>" +
                " <FromUserName><![CDATA[gh_54b74a688c3b]]></FromUserName>" +
                " <CreateTime>1692685746</CreateTime> <MsgType><![CDATA[text]]>" +
                "</MsgType> <Content><![CDATA[你好]]></Content> " +
                "</xml>";
        return message;
    }

  • 结果
  • 需要注意的地方:接收消息与发送消息的ToUserName和FromUserName的值是相反的
    • 这是接收的消息
{Content=哈哈哈哈哈, CreateTime=1692686018, ToUserName=gh_54b74a688c3b, FromUserName=okuVs5iLGbRWwjjotu8EsKVURyDM, MsgType=text, MsgId=24233435728488437}

    • 这是发送的消息
"<xml> 
   <ToUserName><![CDATA[okuVs5iLGbRWwjjotu8EsKVURyDM]]></ToUserName>
   <FromUserName><![CDATA[gh_54b74a688c3b]]></FromUserName>
   <CreateTime>1692685746</CreateTime> <MsgType><![CDATA[text]]>
   </MsgType> <Content><![CDATA[你好]]></Content> 
 </xml>"

2、练习二:封装消息

将回复的消息XML内容封装为Java对象进⾏操作,更有利于项⽬开发

  • 代码:创建text实体
package com.zsh.wx_account1.entity;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 封装text结构的数据
 * @Author ZhaoShuHao
 * @Date 2023/8/22 14:50
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@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;
}

  • 创建方法,用来封装结构
 //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        //回复消息的格式必须为xml的格式
        //封装消息回复
        String message = getReplyMessageByWord(map);
        return message;
    }
    //封装消息格式
    private String getReplyMessageByWord(Map<String, String> map) {
        TextMessage textMessage = new TextMessage();
        textMessage.setToUserName(map.get("FromUserName"));
        textMessage.setFromUserName(map.get("ToUserName"));
        textMessage.setMsgType("text");
        textMessage.setContent("哈哈哈哈哈哈哈哈哈");
        textMessage.setCreateTime(System.currentTimeMillis()/1000);
        //XStream将Java对象转换成xml字符串
        XStream xStream = new XStream();
        xStream.processAnnotations(TextMessage.class);
        String xml = xStream.toXML(textMessage);
        return xml;
    }

  • 结果:

3、练习三:消息处理案例-接⼊第三⽅服务接⼝

我们以同义词为例

  • 在聚合数据网址里,找到同义词与反义词
    • 这个没有示例代码,我们可以从其他api进行跳转,例如笑话大全
  • 开始代码编写
    • 引入依赖
 <!-- 和fastjson作用一样,对json进行解析与构建-->
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.2.3</version>
            <classifier>jdk15</classifier>
        </dependency>

    • 编写util工具类
package com.zsh.wx_account1.util;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
 * @Author ZhaoShuHao
 * @Date 2023/8/22 16:36
 */
public class WordUtil {
    //聚合数据网站的api接口(同义词/反义词查询)
    public static final String WORD_URL = "http://apis.juhe.cn/tyfy/query?key=%s";
    //申请接⼝的请求key
    // TODO: 您需要改为⾃⼰的请求key
    public static final String KEY = "fe4c67727c5cc2cbc12094a563c44305";
    public static String getWords(String word) {
        //发送http请求的url
        String url = String.format(WORD_URL, KEY);
        final String response = doPost(url,"word="+word);
        System.out.println("接⼝返回:" + response);
        try {
            JSONObject jsonObject =
                    JSONObject.fromObject(response);
            int error_code = jsonObject.getInt("error_code");
            if (error_code == 0) {
                System.out.println("调⽤接⼝成功");
                JSONObject result =
                        jsonObject.getJSONObject("result");
                JSONArray words = result.getJSONArray("words");
                StringBuilder stringBuilder = new
                        StringBuilder();
                words.stream().forEach(w->stringBuilder.append(w+" "));
                System.out.println(stringBuilder);
                return stringBuilder.toString();
            } else {
                System.out.println("调⽤接⼝失败:" +
                        jsonObject.getString("reason"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * get⽅式的http请求
     *
     * @param httpUrl 请求地址
     * @return 返回结果
     */
    public static String doGet(String httpUrl) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;// 返回结果字符串
        try {
            // 创建远程url连接对象
            URL url = new URL(httpUrl);
            // 通过远程url连接对象打开⼀个连接,强转成httpURLConnection类
            connection = (HttpURLConnection)
                    url.openConnection();
            // 设置连接⽅式:get
            connection.setRequestMethod("GET");
            // 设置连接主机服务器的超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取远程返回的数据时间:60000毫秒
            connection.setReadTimeout(60000);
            // 发送请求
            connection.connect();
            // 通过connection连接,获取输⼊流
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 封装输⼊流,并指定字符集
                bufferedReader = new BufferedReader(new
                        InputStreamReader(inputStream, StandardCharsets.UTF_8));
                // 存放数据
                StringBuilder sbf = new StringBuilder();
                String temp;
                while ((temp = bufferedReader.readLine()) !=
                        null) {
                    sbf.append(temp);

                    sbf.append(System.getProperty("line.separator"));
                }
                result = sbf.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();// 关闭远程连接
            }
        }
        return result;
    }
    /**
     * post⽅式的http请求
     *
     * @param httpUrl 请求地址
     * @param param 请求参数
     * @return 返回结果
     */
    public static String doPost(String httpUrl, String param) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;
        try {
            java.net.URL url = new URL(httpUrl);
            // 通过远程url连接对象打开连接
            connection = (HttpURLConnection)
                    url.openConnection();
            // 设置连接请求⽅式
            connection.setRequestMethod("POST");
            // 设置连接主机服务器超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取主机服务器返回数据超时时间:60000毫秒
            connection.setReadTimeout(60000);
            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
            connection.setDoOutput(true);
            // 设置传⼊参数的格式:请求参数应该是name1=value1&name2=value2 的形式。
            connection.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded");
            // 通过连接对象获取⼀个输出流
            outputStream = connection.getOutputStream();
            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
            outputStream.write(param.getBytes());
            // 通过连接对象获取⼀个输⼊流,向远程读取
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 对输⼊流对象进⾏包装:charset根据⼯作项⽬组的要求来设置
                        bufferedReader = new BufferedReader(new
                        InputStreamReader(inputStream, StandardCharsets.UTF_8));
                StringBuilder sbf = new StringBuilder();
                String temp;
                // 循环遍历⼀⾏⼀⾏读取数据
                while ((temp = bufferedReader.readLine()) !=
                        null) {
                    sbf.append(temp);

                    sbf.append(System.getProperty("line.separator"));
                }
                result = sbf.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
        return result;
    }
}

    • 编写同义词实现
  //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });

        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        //回复消息的格式必须为xml的格式
        //3、消息处理案例-接⼊第三⽅服务接⼝
        String message = getReplyMessageByWord(map);
        return message;
    }
    //3.消息处理案例-接⼊第三⽅服务接⼝
    private String getReplyMessageByWord(Map<String, String> map)
    {
        TextMessage textMessage = new TextMessage();
        textMessage.setToUserName(map.get("FromUserName"));
        textMessage.setFromUserName(map.get("ToUserName"));
        textMessage.setMsgType("text");
        String content = WordUtil.getWords(map.get("Content"));
        textMessage.setContent(content);
        textMessage.setCreateTime(System.currentTimeMillis()/1000);
        //XStream将Java对象转换成xml字符串
        XStream xStream = new XStream();
        xStream.processAnnotations(TextMessage.class);
        String xml = xStream.toXML(textMessage);
        return xml;
    }

  • 结果展示

五、接收消息后,回复图文消息

关于图文消息:回复文本消息 | 微信开放文档

根据官方文档,创建对应的实体,用来组装结构

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[news]]></MsgType>
  <ArticleCount>1</ArticleCount>
  <Articles>
    <item>
      <Title><![CDATA[title1]]></Title>
      <Description><![CDATA[description1]]></Description>
      <PicUrl><![CDATA[picurl]]></PicUrl>
      <Url><![CDATA[url]]></Url>
    </item>
  </Articles>
</xml>

1、新建图文消息实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@XStreamAlias("xml")
public class NewsMessage {
    @XStreamAlias("ToUserName")
    private String toUserName;
    @XStreamAlias("FromUserName")
    private String fromUserName;
    @XStreamAlias("CreateTime")
    private long createTime;
    @XStreamAlias("MsgType")
    private String msgType;
    @XStreamAlias("ArticleCount")
    private int articleCount;
    @XStreamAlias("Articles")
    private List<Article> articles;
}

/**图文信息的Article
 * @Author ZhaoShuHao
 * @Date 2023/8/22 17:27
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@XStreamAlias("item")
public class Article {
    @XStreamAlias("Title")
    private String title;
    @XStreamAlias("Description")
    private String description;
    @XStreamAlias("PicUrl")
    private String picUrl;
    @XStreamAlias("Url")
    private String url;
}

2、创建组装图文消息的方法

 //获取图文消息
    private String getReplyNewsMessage(Map<String, String> map) {
        NewsMessage newsMessage = new NewsMessage();
        newsMessage.setToUserName(map.get("FromUserName"));
        newsMessage.setFromUserName(map.get("ToUserName"));
        newsMessage.setMsgType("news");

        newsMessage.setCreateTime(System.currentTimeMillis()/1000);
        newsMessage.setArticleCount(1);
        List<Article> articles = new ArrayList<>();
        Article article = new Article();
        article.setTitle("要开心呀的csdn个人中心");
        article.setDescription("要开心呀的csdn个人中心,个人笔记、学习记录");
                article.setUrl("https://blog.csdn.net/ZhShH0413?spm=1000.2115.3001.5343");
  article.setPicUrl("http://mmbiz.qpic.cn/sz_mmbiz_jpg/IPwvTbhAHQL1rPJgNdCuD2udSmC7icF7bFtqdOejlAIFQo9HibTFLggqtbRfObia9XP90f9gSQ2mYWApBkSjcl1lA/0");
                articles.add(article);
        newsMessage.setArticles(articles);
        //XStream将Java对象转换成xml字符串
        XStream xStream = new XStream();
        xStream.processAnnotations(NewsMessage.class);
        String xml = xStream.toXML(newsMessage);
        return xml;
    }

3、返回消息

   //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });

        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        //回复消息的格式必须为xml的格式
        //2、封装消息回复
        String message = null;
        if(map.get("Content").equals("图文")){
            message = getReplyNewsMessage(map);
        }else {
            //3、消息处理案例-接⼊第三⽅服务接⼝(获取同义词)
            message = getReplyMessageByWord(map);
        }
        return message;
    }

六、获得Access_token

官方文档:微信开放文档

1、创建HttpUtil工具类,将wordUtil中的doGet()和doPost()请求拿出来,封装到HttpUtil工具类中

package com.zsh.wx_account1.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

/**
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:35
 */
public class HttpUtil {
    /**
     * get⽅式的http请求
     *
     * @param httpUrl 请求地址
     * @return 返回结果
     */
    public static String doGet(String httpUrl) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;// 返回结果字符串
        try {
            // 创建远程url连接对象
            URL url = new URL(httpUrl);
            // 通过远程url连接对象打开⼀个连接,强转成httpURLConnection类
            connection = (HttpURLConnection)
                    url.openConnection();
            // 设置连接⽅式:get
            connection.setRequestMethod("GET");
            // 设置连接主机服务器的超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取远程返回的数据时间:60000毫秒
            connection.setReadTimeout(60000);
            // 发送请求
            connection.connect();
            // 通过connection连接,获取输⼊流
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 封装输⼊流,并指定字符集
                bufferedReader = new BufferedReader(new
                        InputStreamReader(inputStream, StandardCharsets.UTF_8));
                // 存放数据
                StringBuilder sbf = new StringBuilder();
                String temp;
                while ((temp = bufferedReader.readLine()) !=
                        null) {
                    sbf.append(temp);

                    sbf.append(System.getProperty("line.separator"));
                }
                result = sbf.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();// 关闭远程连接
            }
        }
        return result;
    }
    /**
     * post⽅式的http请求
     *
     * @param httpUrl 请求地址
     * @param param 请求参数
     * @return 返回结果
     */
    public static String doPost(String httpUrl, String param) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;
        try {
            java.net.URL url = new URL(httpUrl);
            // 通过远程url连接对象打开连接
            connection = (HttpURLConnection)
                    url.openConnection();
            // 设置连接请求⽅式
            connection.setRequestMethod("POST");
            // 设置连接主机服务器超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取主机服务器返回数据超时时间:60000毫秒
            connection.setReadTimeout(60000);
            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
            connection.setDoOutput(true);
            // 设置传⼊参数的格式:请求参数应该是name1=value1&name2=value2 的形式。
            connection.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded");
            // 通过连接对象获取⼀个输出流
            outputStream = connection.getOutputStream();
            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
            outputStream.write(param.getBytes());
            // 通过连接对象获取⼀个输⼊流,向远程读取
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 对输⼊流对象进⾏包装:charset根据⼯作项⽬组的要求来设置
                bufferedReader = new BufferedReader(new
                        InputStreamReader(inputStream, StandardCharsets.UTF_8));
                StringBuilder sbf = new StringBuilder();
                String temp;
                // 循环遍历⼀⾏⼀⾏读取数据
                while ((temp = bufferedReader.readLine()) !=
                        null) {
                    sbf.append(temp);

                    sbf.append(System.getProperty("line.separator"));
                }
                result = sbf.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
        return result;
    }
}

2、创建AccessToken实体,用来接accessToken,并判断是否超时

package com.zsh.wx_account1.entity;

/**
 *  Access token实体
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class AccessToken {
    //token的值
    private String token;
    //超时时间
    private long expireTime;
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
    public long getExpireTime() {
        return expireTime;
    }
    /**
     * 官方文档规定access_token的有效期目前为2个小时
     * System.currentTimeMillis()单位是毫秒,当前系统时间
     * expireIn单位是秒,*1000转化为毫秒
     * 当前系统时间往后推2小时就是过期时间
     * @param expireIn
     */
    public void setExpireTime(long expireIn) {
        this.expireTime = System.currentTimeMillis() + expireIn * 1000;
    }
    /**
     * 判断是否超时
     * 当前系统时间>过期时间,就代表超时
     * @return
     */
    public boolean isExpired(){
        return System.currentTimeMillis()>this.expireTime;
    }
}

3、创建TokenUtil工具类,获取accessToken

package com.zsh.wx_account1.util;

import com.zsh.wx_account1.entity.AccessToken;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.ObjectUtils;
/**
 * 获取 Access token的工具类
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:44
 */
public class TokenUtil {
    //自己的公众号信息
    private static final String APP_ID = "wx43f47bd394a23feb";
    private static final String APP_SECRET = "1a29d7b96b7403c99b20d563057a10c8";
    private static AccessToken accessToken = new AccessToken();
    public static void main(String[] args) {
    //{"access_token":"63_8R2EcPuM3dz_D81Q2FBiSfgrlwokafQloAU33iFhHIbjabRFtC_thRqk7VOkMbarQ8lA9yyq2pgwh4pc6P-5qQutc6WWMLwFafIR6ZaLkB299OJU78npFt--I0ACXCiACAHCH","expires_in":7200}
        System.out.println(getAccessToken());
    }
    private static void getToken(){
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
                APP_ID,
                APP_SECRET);
        String result = HttpUtil.doGet(url);
        if(StringUtils.isNotBlank(result)){
            JSONObject jsonObject = JSONObject.fromObject(result);
            String token = jsonObject.getString("access_token");
            long expiresIn = jsonObject.getLong("expires_in");
            accessToken.setToken(token);
            accessToken.setExpireTime(expiresIn);
        }
    }
    /**
     * 获取AccessToken
     * @return
     */
    public static String getAccessToken(){
        //如果accessToken为空或者超时,都重新获取token
        if(ObjectUtils.isEmpty(accessToken) || accessToken.isExpired()){
            getToken();
        }
        return accessToken.getToken();
    }
}

七、自定义菜单

官方文档

 {
     "button":[
     {	
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {	
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
                 "type":"miniprogram",
                 "name":"wxa",
                 "url":"http://mp.weixin.qq.com",
                 "appid":"wx286b93c14bbf93aa",
                 "pagepath":"pages/lunar/index"
             },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }

1、生成简单的菜单

1.1 根据官方文档以及按钮的结构,组装按钮实体

/**
 *  button按钮的抽象类,抽出共同的属性
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class AbstractButton {
    private String name;
}

/**
 *  一级菜单
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class Button {
    private List<AbstractButton> button;

    public List<AbstractButton> getButton() {
        return button;
    }

    public void setButton(List<AbstractButton> button) {
        this.button = button;
    }
}
/**
 *  二级菜单
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class SubButton extends AbstractButton{
    public SubButton(String name) {
        super(name);
    }

    private List<AbstractButton> sub_button;

    public List<AbstractButton> getSub_button() {
        return sub_button;
    }
    public void setSub_button(List<AbstractButton> sub_button) {
        this.sub_button = sub_button;
    }
}
/**
 *  点击按钮
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class ClickButton extends AbstractButton{
    public ClickButton(String name) {
        super(name);
        this.type = "click";
        this.key  = String.valueOf(UUID.randomUUID());
    }
    private String type;
    private String key;

    public String getType() {
        return type;
    }
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
}
/**
 *  弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class PhotoOrAlbumButton extends AbstractButton{
    public PhotoOrAlbumButton(String name) {
        super(name);
        this.type = "pic_photo_or_album";
        this.key = String.valueOf(UUID.randomUUID());

    }
    private String type;
    private String key;

    public String getType() {
        return type;
    }
    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}
/**
 *  跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,
 *  可与网页授权获取用户基本信息接口结合,获得用户基本信息。
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class ViewButton extends AbstractButton{

    public ViewButton(String name,String url) {
        super(name);
        this.type = "view";
        this.url = url;
    }
    private String type;
    private String url;
    public String getType() {
        return type;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
}

1.2 编写测试类,生成简单的菜单


/**
 *  二级菜单
 * @Author ZhaoShuHao
 * @Date 2023/8/23 9:37
 */
public class TestButton {

    public static void main(String[] args) {
        //创建一级菜单
        Button button = new Button();
        List<AbstractButton> buttons = new ArrayList<>();
        //一级菜单中的第一个按钮
        ClickButton clickButton = new ClickButton("csdn");
        //一级菜单中的第二个按钮
        ViewButton viewButton = new ViewButton("百度","http://www.baidu.com");
        //一级菜单中的第三个按钮(二级菜单)
        SubButton subButton = new SubButton("更多");
        List<AbstractButton> subButtons = new ArrayList<>();
        //二级菜单的第一个按钮
        subButtons.add(new ViewButton("csdn个人中心","https://blog.csdn.net/ZhShH0413?spm=1010.2135.3001.5343"));
        //二级菜单的第二个按钮
        subButtons.add(new PhotoOrAlbumButton("上传图片"));
        subButton.setSub_button(subButtons);
        //把一级菜单中的三个按钮添加进集合
        buttons.add(clickButton);
        buttons.add(viewButton);
        buttons.add(subButton);
        //把集合添加到一级菜单中
        button.setButton(buttons);
        //转换成json字符串
        JSONObject jsonObject = JSONObject.fromObject(button);
        System.out.println(jsonObject);
        String json = jsonObject.toString();
        String url = String.format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s", TokenUtil.getAccessToken());
        //发送请求
        String result = HttpUtil.doPost(url, json,"application/json;charset=utf-8");
        System.out.println(result);

    }

}

1.3 结果(需取消关注公众号,再次关注即可看到最新)

2、处理自定义菜单事件

2.1 添加处理事件方法

 /**
     * 处理事件推送
     * @param map
     * @return
     */
    private String handleEvent(Map<String, String> map) {
        String message = null;
        String event = map.get("Event");
        //XStream将Java对象转换成xml字符串
        XStream xStream = new XStream();
        switch (event){
            case "CLICK":
                TextMessage textMessage = new TextMessage();
                textMessage.setToUserName(map.get("FromUserName"));
                textMessage.setFromUserName(map.get("ToUserName"));
                textMessage.setMsgType("text");
                textMessage.setContent("你点击了event key是"+map.get("EventKey")+"的按钮");
                textMessage.setCreateTime(System.currentTimeMillis()/1000);
                xStream.processAnnotations(TextMessage.class);
                message = xStream.toXML(textMessage);
                break;
            case "VIEW":
                System.out.println("view");
                break;
            case "pic_photo_or_album":
                System.out.println("pic_photo_or_album");
            default:
                break;
        }
        return message;
    }

2.2 测试事件处理

 //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });

        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        //回复消息的格式必须为xml的格式
        String message = null;
        String msgType = map.get("MsgType");
        switch (msgType){
            case "text":
                if(map.get("Content").equals("图文")){
                    //回复图文信息
                    message = getReplyNewsMessage(map);
                }else {
                    //3、消息处理案例-接⼊第三⽅服务接⼝(获取同义词)
                    message = getReplyMessageByWord(map);
                }
                break;
            case "event":
                //处理事件
                message = handleEvent(map);
                break;
            case "image":
                System.out.println("您发了一张图片");
                break;
            default:
                break;
        }

        return message;
    }

八、文字识别功能

借用百度AI开放平台实现网络图片的文字识别功能。

百度AI地址:

网络图片文字识别

操作流程如下:

1、先在百度AI控制台中,添加应用

可以在应用列表中查看创建的应用服务:

2、查看api教程,进入文档中心,选择文字识别,网络文字识别

网络文字识别

3、引入依赖

<!-- 引入百度ai的依赖 -->
        <dependency>
            <groupId>com.baidu.aip</groupId>
            <artifactId>java-sdk</artifactId>
            <version>4.16.12</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-simple</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

4、实现图片识别

  //百度AI的个人相关信息
    public static final String APP_ID = "38135852";
    public static final String API_KEY = "GimuujuwA63R1xqYtonSC3Fw";
    public static final String SECRET_KEY = "0K2bfiGowe4cEG9yvRoAr35GUG9dC4YV";
 private String handleImage(Map<String, String> map) {
        // 初始化⼀个AipOcr
        AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);
        // ⽹络图⽚⽂字识别, 图⽚参数为远程url图⽚
        String url = map.get("PicUrl");
        JSONObject res = client.webImageUrl(url, new HashMap<String,String>());
        System.out.println(res.toString(2));
        JSONArray wordsResult = res.getJSONArray("words_result");
        StringBuilder stringBuilder = new StringBuilder();
        Iterator<Object> iterator = wordsResult.iterator();
        while(iterator.hasNext()){
            JSONObject next = (JSONObject) iterator.next();
            stringBuilder.append(next.getString("words"));
        }
        return  assembleTextXMLFormat(stringBuilder.toString(),map);
    }

 //获取用户向测试号发送的消息,并解析,封装成map结构
    @PostMapping("/")
    public String receiveMessage(HttpServletRequest request) throws IOException{
        ServletInputStream inputStream = request.getInputStream();
        Map<String, String> map = new HashMap<>();
        //获取xml解析工具类
        SAXReader saxReader = new SAXReader();
        try {
            //读取request输⼊流,获得Document对象
            Document document = saxReader.read(inputStream);
            //获得root节点
            Element rootElement = document.getRootElement();
            //获取所有子节点
            List<Element> elements = rootElement.elements();
            elements.stream().forEach(new Consumer<Element>() {
                @Override
                public void accept(Element element) {
                    map.put(element.getName(),element.getStringValue());
                }
            });

        } catch (DocumentException e) {
            e.printStackTrace();
        }
        System.out.println("消息为:"+map);
        //回复消息的格式必须为xml的格式
        String message = null;
        String msgType = map.get("MsgType");
        switch (msgType){
            case "text":
                if(map.get("Content").equals("图文")){
                    //回复图文信息
                    message = getReplyNewsMessage(map);
                }else {
                    //3、消息处理案例-接⼊第三⽅服务接⼝(获取同义词)
                    message = getReplyMessageByWord(map);
                }
                break;
            case "event":
                //处理事件
                message = handleEvent(map);
                break;
            case "image":
                message=handleImage(map);
                break;
            default:
                break;
        }
        System.out.println(message);
        return message;
    }

5、结果展示

九、模版消息

注:在设置模版消息之前,要先设置所属行业信息

模版消息官方文档

1、设置所属行业

  • 编码:

 //测试设置所属行业(模版消息的前置条件)
    @Test
    public void testSetTrade(){
        String url = String.format("https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=%s", TokenUtil.getAccessToken());
        String data = "{\n" +
                "    \"industry_id1\":\"1\",\n" +
                "    \"industry_id2\":\"4\"\n" +
                "}";
        String s = HttpUtil.doPost(url, data, "application/x-www-form-urlencoded");
        System.out.println(s);
    }

2、查询所属行业

  • 编码
  //测试查询所属行业
    @Test
    public void testGetTrade(){
        String url = String.format("https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=%s",TokenUtil.getAccessToken());
        System.out.println(HttpUtil.doGet(url));
    }

  • 结果

3、获取模版列表

  //获取模版列表
    @Test
    public void testGetModelMessage(){
        String url = String.format("https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=%s", TokenUtil.getAccessToken());
        String s = HttpUtil.doGet(url);
        System.out.println(s);
    }

4、测试发送模版消息

  • 注:最新的模版已经取消了颜色、标题、描述信息
    • 旧模版:
{{first.DATA}}
参赛队伍:{{keyword1.DATA}}
比赛时间:{{keyword2.DATA}}
比赛地点:{{keyword3.DATA}}
已报名人数:{{keyword4.DATA}}
{{remark.DATA}}

    • 新模版:
参赛队伍:{{keyword1.DATA}}
比赛时间:{{keyword2.DATA}}
比赛地点:{{keyword3.DATA}}
已报名人数:{{keyword4.DATA}}

  • 编码:
   //测试发送模版消息
    @Test
    public void testModelmessage(){
        String url = String.format("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s", TokenUtil.getAccessToken());
        String data = "{\n" +
                "           \"touser\":\"okuVs5iLGbRWwjjotu8EsKVURyDM\",\n" +
                "           \"template_id\":\"EOlNyURgdR1rnZx3_x_k9iHb4j5CZ3dshCZxZbCsU1w\",\n" +
                "           \"url\":\"https://blog.csdn.net/ZhShH0413?spm=1010.2135.3001.5343\",\n" +
                "           \"data\":{\n" +
                "                   \"keyword1\": {\n" +
                "                       \"value\":\"张三,恭喜您报名比赛成功\"\n" +
                "                   },\n" +
                "                   \"keyword2\":{\n" +
                "                       \"value\":\"A队\"\n" +
                "                   },\n" +
                "                   \"keyword3\": {\n" +
                "                       \"value\":\"2023年8月26日 6:00\"\n" +
                "                   },\n" +
                "                   \"keyword4\": {\n" +
                "                       \"value\":\"XX集团东大门\"\n" +
                "                   },\n" +
                "                   \"keyword5\": {\n" +
                "                       \"value\":\"A队1人,B队66人\"\n" +
                "                   },\n" +
                "                   \"keyword6\":{\n" +
                "                       \"value\":\"点击链接有惊喜,双击关注666\"\n" +
                "                   }\n" +
                "           }\n" +
                "       }";
        System.out.println(HttpUtil.doPost(url, data,"application/x-www-form-urlencoded"));
    }

  • 结果:

十、临时素材管理

素材管理官方文档

1、新增临时素材

  • 准备素材
    • 开始上传素材
 //上传临时素材
    @Test
    public void testImage(){
        //这里上传了一个图片
        String url = String.format("https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s",
                TokenUtil.getAccessToken(),
                "image");
        String result = HttpUtil.doPostByFile(url, null, "src/main/resources/static/image/image1.jpg", "");
        System.out.println(result);
//{"type":"image","media_id":"gw-BxwWm3mP6T2M_OBR1IzPwl6YcFFncr4fx3CrS-wtJOd78l7evdmUm5e3b2BZP","created_at":1692935476,"item":[]}
    }

2、获取临时素材

  • 获取临时素材
  //获得临时素材(mediaId就是上传图片,返回的图片id)
    @Test
    public void testGetImage(){
        String mediaId = "gw-BxwWm3mP6T2M_OBR1IzPwl6YcFFncr4fx3CrS-wtJOd78l7evdmUm5e3b2BZP";
        String url = String.format("https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s",
                TokenUtil.getAccessToken(),
                mediaId);
        String result = HttpUtil.doGet(url);
        System.out.println(result);

    }

  • 图片在idea中不能看,拼接个地址,在浏览器查看
/*
图片地址:https://api.weixin.qq.com/cgi-bin/media/get?access_token=token的值&media_id=图片id

https://api.weixin.qq.com/cgi-bin/media/get?access_token=71_3PiSMSASfd1s9s5CFK30XX3CaG3IamKRjT25tmfShuk4i2I3y3Ze0ebR0SSES-hro5T7zO5YVEo0pWiaedG3Yv5y4qRcoETPNRbqJJKOYxpH5ac_rD3CPtfCuZQTGGdAIAYLL&media_id=gw-BxwWm3mP6T2M_OBR1IzPwl6YcFFncr4fx3CrS-wtJOd78l7evdmUm5e3b2BZP
 */

十一、二维码的生成与获取信息

官方文档

为了满足用户渠道推广分析和用户账号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。使用接口过程中有任何问题,可以前往微信开放社区 #公众号 专区发帖交流。

目前有2种类型的二维码:

1、临时二维码,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于账号绑定等不要求二维码永久保存的业务场景 2、永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。

2、永久二维码主要用于适用于账号绑定、用户来源统计等场景。

用户扫描带场景值二维码时,可能推送以下两种事件:

如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。

如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。

获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。

1、生成带参数的二维码

  • 编码
 //生成带参数的二维码
    @Test
    public void testQRcode(){
        String url = String.format(" https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s",
                TokenUtil.getAccessToken());
        String data = "{\"expire_seconds\": 604800, \"action_name\": \"QR_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": 123}}}";
        String s = HttpUtil.doPost(url, data, "application/x-www-form-urlencoded");
        System.out.println(s);
    }

  • 结果:
{"ticket":"gQE18DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyOHREMVZraUJkSUMxVFRUTk5BMXkAAgR3PehkAwSAOgkA","expire_seconds":604800,"url":"http:\/\/weixin.qq.com\/q\/028tD1VkiBdIC1TTTNNA1y"}

2、通过ticket换取二维码

  • 编码
 /*
    https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
    * https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQEA8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyeW1TVFVPaUJkSUMxVFhTTk5BMVkAAgR7POhkAwSAOgkA
     * */
    //通过ticket换取二维码
    @Test
    public void testGetQRcode(){
        String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQEA8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyeW1TVFVPaUJkSUMxVFhTTk5BMVkAAgR7POhkAwSAOgkA";
        String s = HttpUtil.doGet(url);
        System.out.println(s);
    }

  • 通过该url,在浏览器查看二维码

3、扫描二维码,取消关注和关注会有一定的事件,可以配合使用

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值