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


    公众号开发】(2)

    在这里插入图片描述

    【公众号开发】(2)

    natapp:NATAPP -

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

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

    重新配置哦:

    在这里插入图片描述
    之后不提醒了

    1. 第三方接口

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

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

    1.1 申请免费接口

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

    在这里插入图片描述

    搜索一下:

    在这里插入图片描述

    申请:

    在这里插入图片描述

    我的API查看:

    在这里插入图片描述

    实名一下:

    在这里插入图片描述

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

    在这里插入图片描述

    错误码之后需要再说:

    在这里插入图片描述

    1.2 解读接口文档

    接口与格式约定
    在这里插入图片描述

    参数约定:

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

    在这里插入图片描述

    在这里插入图片描述

    1.3 postman测试接口

    key输入错误:

    在这里插入图片描述

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

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

    在这里插入图片描述

    在这里插入图片描述

    • 补充:type不传默认为1

    查看请求的统计:

    在这里插入图片描述

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

    在这里插入图片描述

    1.5 访问第三方接口示例

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

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

    在这里插入图片描述

    1.5.1 引入依赖
    <dependency>            
        <groupId>net.sf.json-libgroupId>          
        <artifactId>json-libartifactId>         
        <version>2.2.3version>        
        <classifier>jdk15classifier>     
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    之后就是编写业务代码了

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

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

    这里我做过改良的~

    public class HttpUtils {
    
        public static final String URL = "http://apis.juhe.cn/tyfy/query";
    
        //申请接口的请求key
        // TODO: 您需要改为自己的请求key
        public static final String KEY = "key";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这个方法可以将一个哈希表的所有键值对转化为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
    • 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
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    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
    • 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
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    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
    • 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
    1.5.7 测试
    public static void main(String[] args) {
        getWords(1, "开心");
    }
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    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
    • 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
    1.5.9 修改controller层代码

    在这里插入图片描述

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

    在这里插入图片描述

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

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

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

    补充: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;
        }
    }
    
    • 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
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • item代表一个Article元素(一个文本元素)

    参数描述:

    在这里插入图片描述

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

    在这里插入图片描述

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

    别忘了,这个图文信息被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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    别忘了,最外层标签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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

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

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

    在这里插入图片描述

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

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

    发图文两个字给公众号

    在这里插入图片描述

    点击链接可以跳转:

    在这里插入图片描述

    控制台查看:

    在这里插入图片描述

    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");
        }});
    }});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

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

    在这里插入图片描述

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

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


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

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


  • 相关阅读:
    【ASP.NET Core】MVC模型绑定:非规范正文内容的处理
    Linux学习第二枪(yum,vim,g++/gcc,makefile的使用)
    深度学习之 python pandas
    【信号处理】扩展卡尔曼滤波EKF(Matlab代码实现)
    Java基础——标识符及命名规范
    完美解决docker skywalking报错:no provider found for module storage
    超越 Transformer开启高效开放语言模型的新篇章
    防止按钮点击事件如何多次重复点击
    定时器概述
    App Deploy as Code! SAE & Terraform 实现 IaC 式部署应用
  • 原文地址:https://blog.csdn.net/Carefree_State/article/details/133952346