【公众号开发】访问第三方接口应用于开发 · 回复图文消息

【公众号开发】(2)

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第1张图片

文章目录

  • 【公众号开发】(2)
    • 1. 第三方接口
      • 1.1 申请免费接口
      • 1.2 解读接口文档
      • 1.3 postman测试接口
      • 1.4 公众号开发访问第三方接口原理
      • 1.5 访问第三方接口示例
        • 1.5.1 引入依赖
        • 1.5.2 获取form格式的body字符串的方法
        • 1.5.3 发送get请求
        • 1.5.4 发送post请求
        • 1.5.5 json序列化与反序列化相关方法
        • 1.5.6 获取单词方法
        • 1.5.7 测试
        • 1.5.8 TextMessage的包装方法
        • 1.5.9 修改controller层代码
        • 1.5.10 重启并给测试公众号发消息测试
    • 2. 回复图文消息
      • 2.1 封装类
        • 2.1.1 Article对象
        • 2.1.2 NewsMessage对象
      • 2.2 编写回复图文消息的方法
        • 2.2.1 封装一个NewsMessage对象并返回
        • 2.2.2 在controller约定一个分支回复图文消息
        • 2.2.3 发“图文”这个文本消息给公众号进行测试
        • 2.2.4 装杯带来的小坑

【公众号开发】(2)

natapp:NATAPP -

开发手册:开发前必读 / 首页 (qq.com)

微信测试公众号:微信公众平台 (qq.com)

重新配置哦:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第2张图片
之后不提醒了

1. 第三方接口

聚合数据 - API接口开放平台_API接口大全_免费API数据接口服务 (juhe.cn)

在这个网站中注册一个账号后登录:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第3张图片

在这里,有超级多的现成的api,可以实现对应的功能:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第4张图片

而我们要的就是里面免费的api去实现我们需要的功能(不过白嫖每天访问有次数限制)

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第5张图片

而我们要学习的就是 申请免费接口,用于我们的开发,实现我们的自定义功能!

接下来,我们用一个可以免费实现的功能 “同义词接口” 为示例,其他免费接口根据实际情况举一反三即可!

1.1 申请免费接口

进入个人中心,数据中心,我的api, 申请新数据:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第6张图片

搜索一下:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第7张图片

申请:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第8张图片

我的API查看:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第9张图片

实名一下:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第10张图片

点进去查看接口文档(功能与使用):

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第11张图片

错误码之后需要再说:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第12张图片

1.2 解读接口文档

接口与格式约定
【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第13张图片

参数约定:

  • 意味着我们请求的时候,要给定什么参数,接受响应时,可以获得什么数据

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第14张图片

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第15张图片

1.3 postman测试接口

key输入错误:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第16张图片

key输入要输入你的接口所描述的请求key:

  • 这个key可以说是身份标识吧

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第17张图片

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第18张图片

  • 补充:type不传默认为1

查看请求的统计:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第19张图片

1.4 公众号开发访问第三方接口原理

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第20张图片

1.5 访问第三方接口示例

有些接口是有示例代码的,可以去看看,这里不带着大家看了

例如笑话大全接口的api页面:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第21张图片

1.5.1 引入依赖
<dependency>            
    <groupId>net.sf.json-libgroupId>          
    <artifactId>json-libartifactId>         
    <version>2.2.3version>        
    <classifier>jdk15classifier>     
dependency>

之后就是编写业务代码了

我们直接抄一些示例代码发送请求的工具类和方法即可:

  • 感兴趣可以去阅读一下代码~

这里我做过改良的~

public class HttpUtils {

    public static final String URL = "http://apis.juhe.cn/tyfy/query";

    //申请接口的请求key
    // TODO: 您需要改为自己的请求key
    public static final String KEY = "key";
}
1.5.2 获取form格式的body字符串的方法
public static String getFormBody(Map<String, Object> map) {
    Set<Map.Entry<String, Object>> entrySet = map.entrySet();
    StringBuilder builder = new StringBuilder();
    for(Map.Entry<String, Object> entry : entrySet) {
        builder.append(entry.toString());
        builder.append("&");
    }
    String formBody = builder.toString();
    if(StringUtils.hasLength(formBody)) {
        formBody = formBody.substring(0, formBody.length() - 1);
    }
    System.out.println(formBody);
    return formBody;
}

这个方法可以将一个哈希表的所有键值对转化为form格式:key1=val1&key2=val2

1.5.3 发送get请求
public static String doGet(String httpUrl, Map<String, Object> map) {
    // 有queryString的就加
    String formBody = HttpUtils.getFormBody(map);
    if(StringUtils.hasLength(formBody)) {
        httpUrl += "?" + formBody;
    }
    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;
}
1.5.4 发送post请求
public static String doPost(String httpUrl, Map<String, Object> map) {
    HttpURLConnection connection = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;
    BufferedReader bufferedReader = null;
    String result = null;
    try {
        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(getFormBody(map).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;
}
1.5.5 json序列化与反序列化相关方法
public class JsonUtils {
    public static ObjectMapper objectMapper = new ObjectMapper();

    public static Map<String, Object> jsonToMap(String jsonString) {
        Map map = null;
        try {
            map = objectMapper.readValue(jsonString, Map.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return map;
    }

    public static String objectToJson(Object object) {
        String jsonString = null;
        try {
            jsonString = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return jsonString;
    }
}
1.5.6 获取单词方法
  1. 构造参数表
  2. 发起请求
  3. 处理响应
public static List<String> getWords(Integer type, String word) {
    // 构造参数表
    Map<String, Object> params = new HashMap<>();
    params.put("key", KEY);
    params.put("type", type);
    params.put("word", word);
    //发起请求接受响应
    final String response = doPost(URL, params);
    System.out.println("接口返回:" + response);
    try {
        Map<String, Object> ret = JsonUtils.jsonToMap(response);
        int error_code = (Integer) ret.get("error_code");
        String reason = (String) ret.get("reason");
        if (error_code == 0) {
            System.out.println("调用接口成功");
            Map<String, Object> result = (Map<String, Object>) ret.get("result");
            String t = (String) result.get("type");//返回的type
            List<String> words = (List<String>) result.get("words");
            System.out.println(t);
            System.out.println(words);
            return words;
        } else {
            System.out.println("调用接口失败:" + reason);
            return null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
1.5.7 测试
public static void main(String[] args) {
    getWords(1, "开心");
}

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第22张图片

1.5.8 TextMessage的包装方法
/**
 * 获取同义词
 * @param map
 * @return
 */
public static TextMessage getSynonym(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("回复同义词:  " + HttpUtils.getWords(1, map.get("Content")));
    return message;
}

/**
 * 获取反义词
 * @param map
 * @return
 */
public static TextMessage getAntonym(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("回复反义词:  " + HttpUtils.getWords(2, map.get("Content")));
    return message;
}
1.5.9 修改controller层代码

1.5.10 重启并给测试公众号发消息测试

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第23张图片

第一次访问要加载很多东西,响应时间需要较长,这很正常哦~

到这里,相信你已经了解了访问第三方接口的大致流程,结合实际需求,业务逻辑,开始举一反三吧!

  • 无非就是申请接口,查看功能介绍,接口文档,构造请求,发送请求,处理响应~

补充:xml序列化和反序列化的方法相关方法,可能需要用到

public class XmlUtils {
    public static String objectToXml(Object o) {
        //获取序列化工具XStream对象
        XStream xStream = new XStream();
        //指定类型
        xStream.processAnnotations(o.getClass());
        //转化为xml字符串
        String xml = xStream.toXML(o);
        //返回
        return xml;
    }

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

    public static Map<String, Object> xmlToMap(String xml) {
        Map<String, Object> map = new HashMap<>();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            org.w3c.dom.Document document = builder.parse(new InputSource(new StringReader(xml)));
            org.w3c.dom.Element root = document.getDocumentElement();
            map = parseElement(root);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
    private static Map<String, Object> parseElement(org.w3c.dom.Element element) {
        Map<String, Object> map = new HashMap<>();
        NodeList nodeList = element.getChildNodes();

        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                org.w3c.dom.Element childElement = (org.w3c.dom.Element) node;
                if (childElement.getChildNodes().getLength() > 1) {
                    map.put(childElement.getTagName(), parseElement(childElement));
                } else {
                    map.put(childElement.getTagName(), childElement.getTextContent());
                }
            }
        }
        return map;
    }
}

2. 回复图文消息

基础消息能力 / 被动回复用户消息 (qq.com)

  • 可能有时候,我不会发开发者文档的具体位置,只会发重点截图(感兴趣的可以去文档查一查,学一学)

这里以图文消息为例,其他消息按照模板返回即可,这个最具有代表性,情况覆盖面比较大~

开发者需要回复的消息模板:

<xml>
  <ToUserName>ToUserName>
  <FromUserName>FromUserName>
  <CreateTime>12345678CreateTime>
  <MsgType>MsgType>
  <ArticleCount>1ArticleCount>
  <Articles>
    <item>
      <Title>Title>
      <Description>Description>
      <PicUrl>PicUrl>
      <Url>Url>
    item>
  Articles>
xml>
  • item代表一个Article元素(一个文本元素)

参数描述:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第24张图片

我们回复的图文消息就差不多这种:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第25张图片

  1. 有标题
  2. 描述(简介)
  3. 图片(封面)
  4. 点击就可以跳转链接(微信站内页面、网页…)

2.1 封装类

2.1.1 Article对象
@Data
@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;
}

别忘了,这个图文信息被item标签包裹~

2.1.2 NewsMessage对象
@XStreamAlias("xml")
@Data
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 Integer articleCount;

    @XStreamAlias("Articles")
    private List<Article> articles;
}

别忘了,最外层标签xml~

List对象xml序列化规则:

  1. 这个属性的标签包裹其值
    • 这里就是Articles标签包裹
  2. 其值的序列化为集合遍历每个元素,每个元素的序列化依次排列
    • 这里就是item标签包裹的文本消息

2.2 编写回复图文消息的方法

2.2.1 封装一个NewsMessage对象并返回
public static NewsMessage getReplyNewsMessage(Map<String, Object> map) {
    NewsMessage newsMessage = new NewsMessage();
    newsMessage.setToUserName((String) map.get("FromUserName"));
    newsMessage.setFromUserName((String) map.get("ToUserName"));
    newsMessage.setCreateTime(System.currentTimeMillis() / 1000);
    newsMessage.setMsgType("news");
    newsMessage.setArticleCount(1);
    Article article = new Article();
    article.setTitle("文1");
    article.setDescription("描述1");
    article     .setPicUrl("http://mmbiz.qpic.cn/sz_mmbiz_jpg/M6lc7Lf7u4jOOQRVia3zia334d7FIXC4DoJ984J6kCqicfIaCMsXrqvjJ9ylmNOq25vHPOgv9t0lUva50iapUd5Cpg/0");
    article.setUrl("https://blog.csdn.net/Carefree_State?type=blog");
    List<Article> articleList = new ArrayList<>();
    articleList.add(article);
    newsMessage.setArticles(articleList);
    System.out.println(newsMessage);
    return newsMessage;
}

这里的PicUrl是用户发送过来,缓存在微信公众号服务器的图片链接,或者你也可以填写其他网络图片~

2.2.2 在controller约定一个分支回复图文消息

这里我的代码做出一些跳转,具体参见:wx-demo · 游离态/马拉圈2023年10月 - 码云 - 开源中国 (gitee.com)

调用xml转化工具后返回回去即可~

2.2.3 发“图文”这个文本消息给公众号进行测试

发图文两个字给公众号

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第26张图片

点击链接可以跳转:

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第27张图片

控制台查看:

2.2.4 装杯带来的小坑
newsMessage.setArticles(new ArrayList<Article>() {{
    this.add(new Article(){{
        this.setTitle("文1");
        this.setDescription("描述1");
        this.setUrl("https://blog.csdn.net/Carefree_State?type=blog");
        this.setPicUrl("http://mmbiz.qpic.cn/sz_mmbiz_jpg/M6lc7Lf7u4jOOQRVia3zia334d7FIXC4DoJ984J6kCqicfIaCMsXrqvjJ9ylmNOq25vHPOgv9t0lUva50iapUd5Cpg/0");
    }});
}});

如果你是通过匿名内部类的方式去构造对象并设置,xml序列化的时候会被序列化的乱七八糟,血的教训啊

具体为什么,我也不知道,可能是匿名内部类实例代码块的机制有关吧,有一些细节出现偏差,瑕疵导致的吧~

不要装杯!写正常易懂的代码就好了!

【公众号开发】访问第三方接口应用于开发 · 回复图文消息_第28张图片

看到没有,这个List对象被序列化得乱七八糟的~

ok,就这样了,其他消息格式大家自己去研究,举一反三~


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

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


你可能感兴趣的:(公众号开发,微信公众平台,公众号开发,java,java-ee,spring,spring,boot,mvc)