与你共眠 發表於 2021-8-14 22:33:00

微信公众号开发Java版-学习总结

<p>本文基于罗召勇老师的教程加上自己的理解整理</p>
<p>本文源码已上传至我的码云: https://gitee.com/heliufang/wx</p>
<p>微信公众号开发整体不难,主要是熟悉微信公众号常用的一些接口文档,然后会一门后端语言(比如java)即可。</p>
<p>罗召勇老师教程:微信公众号开发-Java版(蓝桥罗召勇)</p>
<p>微信公众号文档:微信公众号官方文档</p>
<h2 id="1-微信公众号介绍">1 微信公众号介绍</h2>
<p>账号分为<code>服务号</code>、<code>订阅号</code>、小程序</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222935821-1773258254.png" alt="image-20210808110213141" loading="lazy"></p>
<p>服务号和订阅号开发类似,但是申请服务号必须是企业,所以学习的话申请一个订阅号+测试账号即可。为啥要申请测试账号呢?因为订阅号的接口功能有限,为了学习开发以及熟悉更多的接口,所以还需要申请一个测试号。</p>
<h2 id="2-注册订阅号">2 注册订阅号</h2>
<p>第一步:访问:https://mp.weixin.qq.com/点击<code>立即注册</code>按钮</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222935459-770419844.png" alt="image-20210808110839008" loading="lazy"></p>
<p>第二步:注册类型页面选择<code>订阅号</code></p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222933949-1055574703.png" alt="image-20210808110933591" loading="lazy"></p>
<p>第三步:填写相关信息,点击注册即可</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222933649-1336399140.png" alt="image-20210808111031011" loading="lazy"></p>
<h2 id="3-注册测试号">3 注册测试号</h2>
<p>因为订阅号的接口权限是有限的,为了熟悉更多的微信公众号接口,所以需要申请一个测试号。</p>
<p>第一步:用注册的订阅号登录</p>
<p>第二步:在目录中【设置与开发】---&gt;【开发者工具】下选择公众平台测试账号,点击进入后申请即可。</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222933344-322593453.png" alt="image-20210808112917414" loading="lazy"></p>
<p>申请成功之后,就可以配置相关信息进行开发了,具体怎么配置后面再解释</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222932525-448725350.png" alt="image-20210808113103181" loading="lazy"></p>
<h2 id="4-程序运行流程">4 程序运行流程</h2>
<p>用户在公众号发送请求到<code>微信服务器</code></p>
<p><code>微信服务器</code>将请求转发到<code>我们自己的服务器</code></p>
<p><code>我们自己的服务器</code>处理完之后再把结果发送到<code>微信服务器</code></p>
<p>最后<code>微信服务器</code>再把结果响应给客户</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222932336-970558709.png" alt="image-20210808114624893" loading="lazy"></p>
<h2 id="5-搭建开发环境">5 搭建开发环境</h2>
<p>罗老师用的是eclipse并且没有用maven环境,我用的是eclipse+maven+jdk7+tomcat8.0。maven的话可以兼容idea,而且下载依赖方便。</p>
<p>新建一个名为<code>wx</code>的maven项目(这个项目名字任意都行),<code>pom.xml</code>的依赖如下:</p>
<pre><code class="language-xml">&lt;dependencies&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;junit&lt;/groupId&gt;
                        &lt;artifactId&gt;junit&lt;/artifactId&gt;
                        &lt;version&gt;4.12&lt;/version&gt;
                        &lt;scope&gt;test&lt;/scope&gt;
                &lt;/dependency&gt;
                &lt;!-- 阿里云小蜜-自动回复机器人 --&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;com.aliyun&lt;/groupId&gt;
                        &lt;artifactId&gt;aliyun-java-sdk-chatbot&lt;/artifactId&gt;
                        &lt;version&gt;1.0.0&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;com.aliyun&lt;/groupId&gt;
                        &lt;artifactId&gt;aliyun-java-sdk-core&lt;/artifactId&gt;
                        &lt;version&gt;4.5.2&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;!-- xml操作相关依赖 --&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;com.thoughtworks.xstream&lt;/groupId&gt;
                        &lt;artifactId&gt;xstream&lt;/artifactId&gt;
                        &lt;version&gt;1.4.11.1&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;org.dom4j&lt;/groupId&gt;
                        &lt;artifactId&gt;dom4j&lt;/artifactId&gt;
                        &lt;version&gt;2.0.0&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;!-- 阿里json解析 --&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
                        &lt;artifactId&gt;fastjson&lt;/artifactId&gt;
                        &lt;version&gt;1.2.28&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;!-- 这个是编码解码的 --&gt;
                &lt;dependency&gt;
                        &lt;groupId&gt;commons-codec&lt;/groupId&gt;
                        &lt;artifactId&gt;commons-codec&lt;/artifactId&gt;
                        &lt;version&gt;1.10&lt;/version&gt;
                &lt;/dependency&gt;
        &lt;/dependencies&gt;
</code></pre>
<p>编写一个测试的servlet</p>
<pre><code class="language-java">import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/test")
public class TestServlet extends HttpServlet{

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                System.out.println("请求到达了");
                resp.getWriter().write("hello weixin");
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
               
        }

}
</code></pre>
<p>启动项目访问:http://localhost:8080/wx/test</p>
<p>浏览器看到如下效果说明搭建成功</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222932155-223165702.png" alt="image-20210808120644851" loading="lazy"></p>
<h2 id="6-内外网穿透">6 内外网穿透</h2>
<p>外网默认是访问不到自己电脑上的项目的,为了让外网能够访问,所以需要做内外网穿透.这个不需要自己实现,可以借助一些工具,如花生壳、ngrok.这里用的是ngrok.</p>
<p>ngrok文档</p>
<p>第一步:访问ngrok官网,注册ngrok账号。</p>
<p>第二步:使用注册的账号登录</p>
<p>第三步:【隧道管理---&gt;开通隧道】立即购买,可以购买最后那个免费的,也可以花10块钱买一个。免费的有时候不稳定,可以买一个10块。</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222931932-775542139.png" alt="image-20210808195028874" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222931643-1134749667.png" alt="image-20210808195438170" loading="lazy"></p>
<p>开通之后在隧道管理下就可以看到刚刚开通的隧道</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222931414-1158782081.png" alt="image-20210808195618721" loading="lazy"></p>
<p>第四步:下载客户端工具,我电脑是windows的所以下载windows版</p>
<p>各版本工具下载地址:https://www.ngrok.cc/download.html</p>
<p>第五步:启动ngrok客户端工具,运行bat,输入隧道id,回车</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222931175-264434399.png" alt="image-20210808195948516" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222930944-1415233256.png" alt="image-20210808200101402" loading="lazy"></p>
<p>看到下面这个状态为【online】表示启动成功<br>
<img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222930663-1749615539.png" alt="image-20210808200116096" loading="lazy"></p>
<p>然后就可以通过http://heliufang.vipgz4.idcfengye.com这个域名访问本地8080端口上的项目了,比如访问之前搭建的wx项目</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222930455-1866678182.png" alt="image-20210808200322739" loading="lazy"></p>
<h2 id="7-开发接入">7 开发接入</h2>
<p>接入之后微信服务器和我们自己的项目就接通了。那么如何接入呢?</p>
<p>接入的官方文档</p>
<ul>
<li>第一步:登录微信公众测试号的管理界面,填写好相关信息</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222930142-860342016.png" alt="image-20210808213637686" loading="lazy"></p>
<p>上图中的url就是自己电脑的项目</p>
<p>点击上图的提交按钮之后,微信会向上图中的url发送一个get请求,请求参数如下:</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">signature</td>
<td style="text-align: left">微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。</td>
</tr>
<tr>
<td style="text-align: left">timestamp</td>
<td style="text-align: left">时间戳</td>
</tr>
<tr>
<td style="text-align: left">nonce</td>
<td style="text-align: left">随机数</td>
</tr>
<tr>
<td style="text-align: left">echostr</td>
<td style="text-align: left">随机字符串</td>
</tr>
</tbody>
</table>
<ul>
<li>第二步:编写代码校验,用代码实现下面的逻辑</li>
</ul>
<p>1)将token、timestamp、nonce三个参数进行字典序排序</p>
<p>2)将三个参数字符串拼接成一个字符串进行sha1加密</p>
<p>3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信,如果比对成功,请原样返回echostr参数内容</p>
<p>在之前搭建的名为<code>wx</code>的项目中新建一个【WxServlet.java】</p>
<pre><code class="language-java">import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.qy.service.WxService;

@WebServlet("/api")
public class WxServlet extends HttpServlet{

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                System.out.println("请求到达了");
                //取出微信服务器传过来的参数
                String signature = req.getParameter("signature");
                String timestamp = req.getParameter("timestamp");
                String nonce = req.getParameter("nonce");
                String echostr = req.getParameter("echostr");
      //自定义一个check方法用来校验接入
                boolean success = WxService.check(timestamp, nonce, signature);
                if(success){
                        System.out.println("接入成功");
                        PrintWriter writer = resp.getWriter();
                        writer.write(echostr);//接入成功需要原样返回echostr
                }else{
                        System.out.println("接入失败");
                }
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
               
        }
}
</code></pre>
<p>新建一个【WxService.java】并添加一个check工具方法</p>
<pre><code class="language-java">import java.util.Arrays;
import org.apache.commons.codec.digest.DigestUtils;

public class WxService {
       
        public static final String TOKEN = "hlf";//在微信配置界面自定义的token
       
        /**
       * 接入校验
       * @param timestamp
       * @param nonce
       * @param signature
       * @return
       */
        public static boolean check(String timestamp, String nonce, String signature) {
                //1.将token、timestamp、nonce三个参数进行字典序排序
                String[] arr = new String[]{TOKEN,timestamp,nonce};
                Arrays.sort(arr);
                //2.将三个参数字符串拼接成一个字符串进行sha1加密https://www.cnblogs.com/2333/p/6405386.html
                String str = arr+arr+arr;
                str = DigestUtils.sha1Hex(str);//sha1加密,这里没有像罗老师那样手写,直接用的commons-codec包的工具类
                System.out.println("str:"+str);
                //3.将加密后的字符串和signature比较
                System.out.println(signature);
                return str.equalsIgnoreCase(signature);
        }
}

</code></pre>
<p>启动项目,点击提交按钮,出现下面这个代表接入成功。</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222929888-1826325339.png" alt="image-20210808220055857" loading="lazy"></p>
<h2 id="8-接收用户消息">8 接收用户消息</h2>
<p>官方文档:接受普通消息</p>
<blockquote>
<p>当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。</p>
</blockquote>
<p>也就是说用户发消息给微信服务器,微信服务器会发送<code>post请求</code>到我们自己的服务器,并且传送一个xml的数据给我们自己的服务器。</p>
<p>例如文本消息是这样的</p>
<pre><code class="language-xml">&lt;xml&gt;
&lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
&lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
&lt;CreateTime&gt;1348831860&lt;/CreateTime&gt;
&lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
&lt;Content&gt;&lt;!]&gt;&lt;/Content&gt;
&lt;MsgId&gt;1234567890123456&lt;/MsgId&gt;
&lt;/xml&gt;
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ToUserName</td>
<td style="text-align: left">开发者微信号</td>
</tr>
<tr>
<td style="text-align: left">FromUserName</td>
<td style="text-align: left">发送方帐号(一个OpenID)</td>
</tr>
<tr>
<td style="text-align: left">CreateTime</td>
<td style="text-align: left">消息创建时间 (整型)</td>
</tr>
<tr>
<td style="text-align: left">MsgType</td>
<td style="text-align: left">消息类型,文本为text</td>
</tr>
<tr>
<td style="text-align: left">Content</td>
<td style="text-align: left">文本消息内容</td>
</tr>
<tr>
<td style="text-align: left">MsgId</td>
<td style="text-align: left">消息id,64位整型</td>
</tr>
</tbody>
</table>
<p>java中这样的数据读取并不方便。可以转换一下,先通过dom4j这个包转成dom对象,再把标签名和对应的标签的值保存到HashMap集合中,这样后面处理数据就很方便了,具体代码实现如下:</p>
<p>在【WxServlet】中编写<code>doPost</code>方法,在测试号管理界面,扫码关注测试公众号</p>
<pre><code class="language-java">@Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                Map&lt;String,String&gt; map = WxService.parseRequest(req.getInputStream());
                System.out.println(map);//关注测试号,给测试公众号发消息,就可以看到打印结果了
        }
</code></pre>
<p>在【WxService】中添加<code>parseRequest</code>方法</p>
<pre><code class="language-java">/**
       * 将接受到的消息转化成map
       * @param req
       * @return
       */
        public staticMap&lt;String, String&gt; parseRequest(InputStream is) {
                Map&lt;String,String&gt; map = new HashMap&lt;String,String&gt;();
                //1.通过io流得到文档对象
                SAXReader saxReader = new SAXReader();
                Document document = null;
                try {
                        document = saxReader.read(is);
                } catch (DocumentException e) {
                        e.printStackTrace();
                }
                //2.通过文档对象得到根节点对象
                Element root = document.getRootElement();
                //3.通过根节点对象获取所有子节点对象
                List&lt;Element&gt; elements = root.elements();
                //4.将所有节点放入map
                for (Element element : elements) {
                        map.put(element.getName(), element.getStringValue());
                }
                return map;
        }
</code></pre>
<h2 id="9-回复用户消息封装">9 回复用户消息封装</h2>
<p>官方文档:被动回复用户消息</p>
<blockquote>
<p>当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。</p>
<p><strong>一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:</strong></p>
<p>1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等</p>
</blockquote>
<p>上面这段文字来自官方,可以看出</p>
<ul>
<li>
<p>回复必须是xml的类型</p>
</li>
<li>
<p>可以回复多种类型的xml(文本、图片、图文、语音、视频、音乐)</p>
</li>
<li>
<p>接收到消息没有做出响应就会抛出:<code>该公众号暂时无法提供服务,请稍后再试</code></p>
</li>
</ul>
<h3 id="91--回复消息入门demo">9.1回复消息入门demo</h3>
<p>这个demo就是给用户回复一个文本消息</p>
<p>回复的xml格式如下:</p>
<pre><code class="language-xml">&lt;xml&gt;
&lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
&lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
&lt;CreateTime&gt;12345678&lt;/CreateTime&gt;
&lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
&lt;Content&gt;&lt;!]&gt;&lt;/Content&gt;
&lt;/xml&gt;
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ToUserName</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接收方帐号(收到的OpenID)</td>
</tr>
<tr>
<td style="text-align: left">FromUserName</td>
<td style="text-align: left">是</td>
<td style="text-align: left">开发者微信号</td>
</tr>
<tr>
<td style="text-align: left">CreateTime</td>
<td style="text-align: left">是</td>
<td style="text-align: left">消息创建时间 (整型)</td>
</tr>
<tr>
<td style="text-align: left">MsgType</td>
<td style="text-align: left">是</td>
<td style="text-align: left">消息类型,文本为text</td>
</tr>
<tr>
<td style="text-align: left">Content</td>
<td style="text-align: left">是</td>
<td style="text-align: left">回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)</td>
</tr>
</tbody>
</table>
<p>在wxservlet中doPost编写如下代码</p>
<pre><code class="language-java">@Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                //设置编码格式,不然中文会乱码
                req.setCharacterEncoding("UTF-8");
                resp.setCharacterEncoding("UTF-8");
      //将请求中的xml参数转成map
                Map&lt;String,String&gt; map = WxService.parseRequest(req.getInputStream());
                System.out.println(map);
                //回复消息
                String textMsg = "&lt;xml&gt;&lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;&lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;&lt;CreateTime&gt;12345678&lt;/CreateTime&gt;&lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;&lt;Content&gt;&lt;!]&gt;&lt;/Content&gt;&lt;/xml&gt;";
                resp.getWriter().print(textMsg);
        }
</code></pre>
<p>然后用测试号发消息,公众号都会回复一个【你好】</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222929492-1242196308.png" alt="image-20210810221633028" loading="lazy"></p>
<p>这样写代码功能是可以实现,但是这样拼接字符串,再回复消息很不方便.然后自然就想到可以用java类来封装消息,响应的时候将java类转成xml(通过<code>xstream</code>这个工具包实现)。下面就以文本消息和图文消息为例进行封装,其它消息类似。</p>
<h3 id="92-基础消息类的封装">9.2 基础消息类的封装</h3>
<p>把公共的属性放到基础消息类中,然后其它消息类继承即可。</p>
<p><code>@XStreamAlias</code> 这个注解配置的就是转成xml时对应的节点名字</p>
<pre><code class="language-java">public class BaseMsg {
        @XStreamAlias("ToUserName")
        private String toUserName;//接收方的账号(收到的openid)
        @XStreamAlias("FromUserName")
        private String fromUserName;//开发者的微信号
        @XStreamAlias("CreateTime")
        private String createTime;//消息创建时间
        @XStreamAlias("MsgType")
        private String msgType;//消息类型

        public BaseMsg(Map&lt;String,String&gt; requestMap) {
                super();
                this.toUserName = requestMap.get("FromUserName");
                this.fromUserName = requestMap.get("ToUserName");
                this.createTime = requestMap.get("CreateTime");
        }
   
    //get and set ...
}
</code></pre>
<h3 id="93-文本消息类封装">9.3 文本消息类封装</h3>
<p>回复的xml的格式说明可以参考9.1入门demo.回复文本的封装类如下:</p>
<pre><code class="language-java">@XStreamAlias("xml") //xml指的就是xml这个根节点名称
public class TextMsg extends BaseMsg {
        @XStreamAlias("Content")
        private String content;//回复的文本内容
       
        public TextMsg(Map&lt;String,String&gt; requestMap,String content) {
                super(requestMap);
                this.setMsgType("text");
                this.content = content;
        }
   
    //get and set ...
}
</code></pre>
<h3 id="94-图文消息封装">9.4 图文消息封装</h3>
<p>图文消息格式说明</p>
<pre><code class="language-xml">&lt;xml&gt;
&lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
&lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
&lt;CreateTime&gt;12345678&lt;/CreateTime&gt;
&lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
&lt;ArticleCount&gt;1&lt;/ArticleCount&gt;
&lt;Articles&gt;
    &lt;item&gt;
      &lt;Title&gt;&lt;!]&gt;&lt;/Title&gt;
      &lt;Description&gt;&lt;!]&gt;&lt;/Description&gt;
      &lt;PicUrl&gt;&lt;!]&gt;&lt;/PicUrl&gt;
      &lt;Url&gt;&lt;!]&gt;&lt;/Url&gt;
    &lt;/item&gt;
&lt;/Articles&gt;
&lt;/xml&gt;
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ToUserName</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接收方帐号(收到的OpenID)</td>
</tr>
<tr>
<td style="text-align: left">FromUserName</td>
<td style="text-align: left">是</td>
<td style="text-align: left">开发者微信号</td>
</tr>
<tr>
<td style="text-align: left">CreateTime</td>
<td style="text-align: left">是</td>
<td style="text-align: left">消息创建时间 (整型)</td>
</tr>
<tr>
<td style="text-align: left">MsgType</td>
<td style="text-align: left">是</td>
<td style="text-align: left">消息类型,图文为news</td>
</tr>
<tr>
<td style="text-align: left">ArticleCount</td>
<td style="text-align: left">是</td>
<td style="text-align: left">图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息</td>
</tr>
<tr>
<td style="text-align: left">Articles</td>
<td style="text-align: left">是</td>
<td style="text-align: left">图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数</td>
</tr>
<tr>
<td style="text-align: left">Title</td>
<td style="text-align: left">是</td>
<td style="text-align: left">图文消息标题</td>
</tr>
<tr>
<td style="text-align: left">Description</td>
<td style="text-align: left">是</td>
<td style="text-align: left">图文消息描述</td>
</tr>
<tr>
<td style="text-align: left">PicUrl</td>
<td style="text-align: left">是</td>
<td style="text-align: left">图片链接,支持JPG、PNG格式,较好的效果为大图360<em>200,小图200</em>200</td>
</tr>
<tr>
<td style="text-align: left">Url</td>
<td style="text-align: left">是</td>
<td style="text-align: left">点击图文消息跳转链接</td>
</tr>
</tbody>
</table>
<p>首先封装一个article类,对应就是xml中的item这个节点</p>
<pre><code class="language-java">@XStreamAlias("item")//映射到xml中的item这个节点
public class Article {
        @XStreamAlias("Title")
        private String title;//图文消息标题
        @XStreamAlias("Description")
        private String description;//图文消息描述
        @XStreamAlias("PicUrl")
        private String picUrl;//图片链接
        @XStreamAlias("Url")
        private String url;//点击图文消息跳转链接
   
    //get and set ...
}
</code></pre>
<p>然后再封装一个图文消息类</p>
<pre><code class="language-java">@XStreamAlias("xml")
public class NewsMsg extends BaseMsg {
       
        @XStreamAlias("ArticleCount")
        private String articleCount;//图文消息个数
        @XStreamAlias("Articles")
        private List&lt;Article&gt; articles;

        public NewsMsg(Map&lt;String, String&gt; requestMap,List&lt;Article&gt; articles) {
                super(requestMap);
                this.setMsgType("news");
                this.articles = articles;
                this.setArticleCount(this.articles.size()+"");
        }
    //get and set ...
}
</code></pre>
<h3 id="95-测试">9.5 测试</h3>
<p>前面已经将基础消息和图文消息封装好了,现在用封装好的消息类来回复</p>
<p>第一步:将<code>wxservlet</code>的<code>doPost</code>方法改成如下</p>
<pre><code class="language-java">@Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                //设置编码格式,不然中文会乱码
                req.setCharacterEncoding("UTF-8");
                resp.setCharacterEncoding("UTF-8");
      //将请求中的xml参数转成map
                Map&lt;String,String&gt; map = WxService.parseRequest(req.getInputStream());
                System.out.println(map);
      //处理完将响应一个xml给微信
                String respXml = WxService.getRespose(map);
                System.out.println(respXml);
                resp.getWriter().print(respXml);
        }
</code></pre>
<p>第二步:WxService添加如下方法:</p>
<pre><code class="language-java">/**
       * 事件消息回复
       */
        public static String getRespose(Map&lt;String, String&gt; requestMap) {
                BaseMsg msg = null;
                // 根据用户发送消息的类型,做不同的处理
                String msgType = requestMap.get("MsgType");
                switch (msgType) {
                case "text":
                        msg = dealTextMsg(requestMap);
                        break;
                case "news":
                        break;
                default:
                        break;
                }
                // System.out.println(msg);
                // 将处理结果转化成xml的字符串返回
                if (null != msg) {
                        return beanToXml(msg);
                }
                return null;
        }

        /**
       * 将回复的消息类转成xml字符串
       *
       * @param msg
       * @return
       */
        public static String beanToXml(BaseMsg msg) {
                XStream stream = new XStream();
                stream.processAnnotations(TextMsg.class);
                stream.processAnnotations(NewsMsg.class);
                String xml = stream.toXML(msg);
                return xml;
        }

        /**
       * 当用户发送是文本消息的处理逻辑
       *
       * @param map
       * @return
       */
        private static BaseMsg dealTextMsg(Map&lt;String, String&gt; requestMap) {
                // 获取用户发送的消息内容
                String msg = requestMap.get("Content");
                // 如果是图文回复一个图文消息
                if (msg.equals("图文")) {
                        List&lt;Article&gt; articles = new ArrayList&lt;Article&gt;();
                        articles.add(new Article("码云博客", "这个是我个人的码云博客,基于hexo搭建,里面的文章都是使用markdown编写",
                                        "https://heliufang.gitee.io/uploads/banner.jpg", "https://heliufang.gitee.io/"));
                        return new NewsMsg(requestMap, articles);
                }
                //否则回复一个文本消息,文本内容为'当前时间+你好'
      //当然这个内容可以自定义,在这里也可以接入自动回复机器人
                TextMsg textMsg = new TextMsg(requestMap, new Date(System.currentTimeMillis()).toLocaleString() + "你好");
                return textMsg;
        }
</code></pre>
<p>然后分别给公众号发一个1和图文</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222928795-2056461867.png" alt="image-20210810231459572" loading="lazy"></p>
<h3 id="96-自动回复机器人">9.6 自动回复机器人</h3>
<p>罗老师教程中的图灵机器人已经要收费.我使用的是阿里云的<code>阿里云小蜜</code>这个机器人来做的回复.</p>
<p>阿里云小蜜机器人可以免费体验三个月。</p>
<p>具体代码可以查看阿里云小蜜的文档:阿里云产品服务协议(云小蜜)</p>
<h2 id="10-access-token的获取">10 ★access token的获取</h2>
<p>access_token是公众号的全局唯一接口调用凭据,<strong>公众号调用各接口时都需使用access_token</strong>。开发者需要进行妥善保存.access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效</p>
<p>access token文档</p>
<p>目前access_token的有效期通过返回的<code>expire_in</code>来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token</p>
<p>总结:调用很多接口需要access_token,获取access_token之后需要保存起来,过期了再重新获取,而不是每次都重新获取。</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=APPID&amp;secret=APPSECRET</p>
</blockquote>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">grant_type</td>
<td style="text-align: left">是</td>
<td style="text-align: left">获取access_token填写client_credential</td>
</tr>
<tr>
<td style="text-align: left">appid</td>
<td style="text-align: left">是</td>
<td style="text-align: left">第三方用户唯一凭证</td>
</tr>
<tr>
<td style="text-align: left">secret</td>
<td style="text-align: left">是</td>
<td style="text-align: left">第三方用户唯一凭证密钥,即appsecret</td>
</tr>
</tbody>
</table>
<p><strong>返回说明</strong></p>
<p>正常情况下,微信会返回下述JSON数据包给公众号:</p>
<pre><code class="language-json">{"access_token":"ACCESS_TOKEN","expires_in":7200}
</code></pre>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">获取到的凭证</td>
</tr>
<tr>
<td style="text-align: left">expires_in</td>
<td style="text-align: left">凭证有效时间,单位:秒</td>
</tr>
</tbody>
</table>
<h3 id="101-封装请求工具类">10.1 ★封装请求工具类</h3>
<p>因为需要发送请求给微信服务器,所以需要有请求的工具类。罗老师用的是java自带的请求类,相对来说比较繁琐。所以我这里采用的是Apache HttpClient,这个用起来更加的简单。</p>
<p>第一步:pom.xml中导入依赖</p>
<pre><code class="language-xml">&lt;!--httpClient需要的依赖--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
    &lt;artifactId&gt;httpclient&lt;/artifactId&gt;
    &lt;version&gt;4.5.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;!--//httpclient缓存--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
    &lt;artifactId&gt;httpclient-cache&lt;/artifactId&gt;
    &lt;version&gt;4.5&lt;/version&gt;
&lt;/dependency&gt;
&lt;!--//http的mime类型都在这里面--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
    &lt;artifactId&gt;httpmime&lt;/artifactId&gt;
    &lt;version&gt;4.3.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>第二步:基于<code>Apache HttpClient</code>封装<code>HttpUtils</code>工具类,我封装了4个方法,可以支持get请求和post请求。后面很多需要用的地方直接调用即可。</p>
<p>可以参考这个博客:HttpClient发送get/post请求</p>
<pre><code class="language-java">public class HttpUtils {

        public static void main(String[] args) {
                // 1.测试get请求
                /*
               String getUrl = "http://localhost:8080/user/searchPage?pageNum=1&amp;pageSize=2";
               System.out.println(sendGet(getUrl));
               */
               
                // 2.测试post请求 携带x-www-form-urlencoded数据格式
                /*String postUrlForm = "http://localhost:8080/user";
                Map paramMap = new HashMap();
                paramMap.put("name", "杰克");
                paramMap.put("age", "20");
                paramMap.put("gender", "1");
                System.out.println(sendPost(postUrlForm, paramMap));*/
               
                //3.测试post请求 携带json数据格式
                /*String postUrlJson = "http://localhost:8080/user";
                String jsonParam = "{\"name\":\"jack\",\"age\":\"18\",\"gender\":\"2\"}";
                System.out.println(sendPost(postUrlJson,jsonParam));*/
               
                //4 测试post 携带文件
                String postUrlFile = "http://localhost:8080/user/upload";
                Map paramMap = new HashMap();
                paramMap.put("name", "tom");
                String localFile = "d:\\logo.png";
                String fileParamName = "file";
                System.out.println(sendPost(postUrlFile, paramMap,localFile,fileParamName));
        }

        // 1.httpClient发送get请求
        public static String sendGet(String url) {
                String result = "";
                CloseableHttpResponse response = null;
                try {
                        // 根据地址获取请求
                        HttpGet request = new HttpGet(url);// 这里发送get请求
                        // 获取当前客户端对象
                        CloseableHttpClient httpClient = HttpClients.createDefault();
                        // 通过请求对象获取响应对象
                        response = httpClient.execute(request);
                        // 判断网络连接状态码是否正常(0--200都数正常)
                        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                                result = EntityUtils.toString(response.getEntity(), "utf-8");
                        }
                } catch (Exception e) {
                        e.printStackTrace();
                } finally {
                        if (null != response) {
                                try {
                                        response.close();
                                } catch (IOException e) {
                                        e.printStackTrace();
                                }
                        }
                }
                return result;
        }

        // 2.httpClient发送post请求 携带x-www-form-urlencoded数据格式
        public static String sendPost(String url, Map&lt;String, String&gt; map) {
                CloseableHttpResponse httpResponse = null;
                String result = "";
                try {
                        // 1、创建一个httpClient客户端对象
                        CloseableHttpClient httpClient = HttpClients.createDefault();
                        // 2、创建一个HttpPost请求
                        HttpPost httpPost = new HttpPost(url);
                        // 设置请求头
                        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); // 设置传输的数据格式
                        // 携带普通的参数params的方式
                        List&lt;BasicNameValuePair&gt; params = new ArrayList&lt;BasicNameValuePair&gt;();
                        Set&lt;String&gt; keys = map.keySet();
                        for (String key : keys) {
                                params.add(new BasicNameValuePair(key, map.get(key)));
                        }
                        String str = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8));
                        // 这里就是:username=kylin&amp;password=123456
                        System.out.println(str);

                        // 放参数进post请求里面 从名字可以知道 这个类是专门处理x-www-form-urlencoded 添加参数的
                        httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

                        // 7、执行post请求操作,并拿到结果
                        httpResponse = httpClient.execute(httpPost);
                        // 获取结果实体
                        HttpEntity entity = httpResponse.getEntity();
                        if (entity != null) {
                                result = EntityUtils.toString(entity, "UTF-8");
                        } else {
                                EntityUtils.consume(entity);//// 如果entity为空,那么直接消化掉即可
                        }
                } catch (Exception e) {
                        e.printStackTrace();
                } finally {
                        if (null != httpResponse) {
                                try {
                                        httpResponse.close();
                                } catch (IOException e) {
                                        e.printStackTrace();
                                }
                        }
                }
                return result;
        }

        // 3.httpClient发送post请求 携带json数据格式
        public static String sendPost(String url, String jsonStr) {
                CloseableHttpResponse httpResponse = null;
                String result = "";
                try {
                        // 1.创建httpClient
                        CloseableHttpClient httpClient = HttpClients.createDefault();
                        // 2.创建post请求方式实例
                        HttpPost httpPost = new HttpPost(url);

                        // 2.1设置请求头 发送的是json数据格式
                        httpPost.setHeader("Content-type", "application/json;charset=utf-8");
                        httpPost.setHeader("Connection", "Close");

                        // 3.设置参数---设置消息实体 也就是携带的数据
                        /*
                       * 比如传递: { "username": "aries", "password": "666666" }
                       */
                        //String jsonStr = " {\"username\":\"aries\",\"password\":\"666666\"}";
                        StringEntity entity = new StringEntity(jsonStr.toString(), Charset.forName("UTF-8"));
                        entity.setContentEncoding("UTF-8"); // 设置编码格式
                        // 发送Json格式的数据请求
                        entity.setContentType("application/json");
                        // 把请求消息实体塞进去
                        httpPost.setEntity(entity);

                        // 4.执行http的post请求
                        // 4.执行post请求操作,并拿到结果
                        httpResponse = httpClient.execute(httpPost);
                        // 获取结果实体
                        HttpEntity httpEntity = httpResponse.getEntity();
                        if (httpEntity != null) {
                                result = EntityUtils.toString(httpEntity, "UTF-8");
                        } else {
                                EntityUtils.consume(httpEntity);//// 如果httpEntity为空,那么直接消化掉即可
                        }
                } catch (Exception e) {
                        e.printStackTrace();
                } finally {
                        if (null != httpResponse) {
                                try {
                                        httpResponse.close();
                                } catch (IOException e) {
                                        e.printStackTrace();
                                }
                        }
                }
                return result;
        }

        // 4.httpClient发送post请求 携带文件
        public static String sendPost(String url, Map&lt;String, String&gt; map,String localFile, String fileParamName) {
                HttpPost httpPost = new HttpPost(url);
                CloseableHttpClient httpClient = HttpClients.createDefault();
      String resultString = "";
      CloseableHttpResponse response = null;
      try {
            // 把文件转换成流对象FileBody
            FileBody bin = new FileBody(new File(localFile));

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();

            // 相当于&lt;input type="file" name="fileParamName"/&gt; 其中fileParamName以传进来的为准
            builder.addPart(fileParamName, bin);
            // 相当于&lt;input type="text" name="userName" value=userName&gt;
            /*builder.addPart("filesFileName",
                  new StringBody(fileParamName, ContentType.create("text/plain", Consts.UTF_8)));*/
            if (map != null) {
                for (String key : map.keySet()) {
                  builder.addPart(key,
                            new StringBody(map.get(key), ContentType.create("text/plain", Consts.UTF_8)));
                }
            }
            HttpEntity reqEntity = builder.build();
            httpPost.setEntity(reqEntity);
            // 发起请求 并返回请求的响应
            response = httpClient.execute(httpPost, HttpClientContext.create());
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
      }catch (Exception e) {
            e.printStackTrace();
      } finally {
            try {
                if (response != null)
                  response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
      }
      return resultString;
        }
}
</code></pre>
<h3 id="102-创建accesstoken类">10.2 创建AccessToken类</h3>
<pre><code class="language-java">public class AccessToken {
        private String token;
        private long expiresTime;//过期时间
       
        public AccessToken(String token, String expiresIn) {
                super();
                this.token = token;
                //当前时间+有效期 = 过期时间
                this.expiresTime = System.currentTimeMillis()+Integer.parseInt(expiresIn);
        }
       
        /**
       * 判断token是否过期
       * @return
       */
        public boolean isExpire() {
                return System.currentTimeMillis() &gt; expiresTime;
        }
    //get and set ...
}
</code></pre>
<h3 id="103-wxservice中添加获取accesstoken的方法">10.3 WxService中添加获取AccessToken的方法</h3>
<pre><code class="language-java">private static AccessToken at;//token获取的次数有限,有效期也有限,所以需要保存起来
private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=APPID&amp;secret=APPSECRET";
       
        //登录测试号管理界面-测试号信息下面可以得到你的APPID和APPSECRET
        private static String APPID = "wx7bf783afc5150a5a";
        private static String APPSECRET = "8d9930d60717c7aaa0620ad993d984d8";
/**
       * 发送get请求获取AccessToken
       */
        private static void getToken() {
                String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
                String tokenStr = HttpUtils.sendGet(url);//调用工具类发get请求
                System.out.println(tokenStr);
                JSONObject jsonObject = JSONObject.parseObject(tokenStr);
                String token = jsonObject.getString("access_token");
                String expiresIn = jsonObject.getString("expires_in");
                at = new AccessToken(token, expiresIn);
        }
       
        /**
       * 获取AccessToken向外提供
       */
        public static String getAccessToken() {
                //过期了或者没有值再去发送请求获取
                if(at == null || at.isExpire()) {
                        getToken();
                }
                return at.getToken();
        }
</code></pre>
<p>编写一个测试类获取AccessToken</p>
<pre><code class="language-java">import org.junit.Test;

import com.qy.service.WxService;

public class TestToken {

        @Test
        public void getAccessToken() {
      //可以看到下面两次获取的值一致
                System.out.println(WxService.getAccessToken());
                System.out.println(WxService.getAccessToken());
        }
}
</code></pre>
<h2 id="11-自定义菜单">11 自定义菜单</h2>
<p>自定义菜单文档</p>
<p>请注意:</p>
<ol>
<li>自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。</li>
<li>一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“...”代替。</li>
<li>创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。</li>
</ol>
<p>自定义菜单接口可实现多种类型按钮,如下:</p>
<ol>
<li>click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;</li>
<li>view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。</li>
<li>scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。</li>
<li>scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。</li>
<li>pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。</li>
<li>pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。</li>
<li>pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。</li>
<li>location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。</li>
<li>media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。</li>
<li>view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。</li>
</ol>
<p><strong>接口调用请求说明</strong></p>
<p>http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN</p>
<p>url中的ACCESS_TOKEN就是之前获取的,调用这个接口需要带上</p>
<p>请求需携带json参数</p>
<pre><code class="language-json">{
"button":[
        {
                  "type":"click",
          "name":"一级点击",
          "key":"1"
        },
        {
                  "type":"view",
          "name":"个人博客",
          "url":"https://heliufang.gitee.io/"
        },
        {
          "name":"有子菜单",
          "sub_button":[
                  {
                  "type":"click",
                  "name":"三一点击",
                  "key":"31"
            },
            {
                  "type":"view",
                  "name":"码云博客",
                  "url":"https://heliufang.gitee.io/"
            },
            {
                    "type":"pic_photo_or_album",
                    "name":"拍照或发图",
                    "key":"33"
            }
          ]
        }
]
}
</code></pre>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">button</td>
<td style="text-align: left">是</td>
<td style="text-align: left">一级菜单数组,个数应为1~3个</td>
</tr>
<tr>
<td style="text-align: left">sub_button</td>
<td style="text-align: left">否</td>
<td style="text-align: left">二级菜单数组,个数应为1~5个</td>
</tr>
<tr>
<td style="text-align: left">type</td>
<td style="text-align: left">是</td>
<td style="text-align: left">菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型</td>
</tr>
<tr>
<td style="text-align: left">name</td>
<td style="text-align: left">是</td>
<td style="text-align: left">菜单标题,不超过16个字节,子菜单不超过60个字节</td>
</tr>
<tr>
<td style="text-align: left">key</td>
<td style="text-align: left">click等点击类型必须</td>
<td style="text-align: left">菜单KEY值,用于消息接口推送,不超过128字节</td>
</tr>
<tr>
<td style="text-align: left">url</td>
<td style="text-align: left">view、miniprogram类型必须</td>
<td style="text-align: left">网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。</td>
</tr>
<tr>
<td style="text-align: left">media_id</td>
<td style="text-align: left">media_id类型和view_limited类型必须</td>
<td style="text-align: left">调用新增永久素材接口返回的合法media_id</td>
</tr>
<tr>
<td style="text-align: left">appid</td>
<td style="text-align: left">miniprogram类型必须</td>
<td style="text-align: left">小程序的appid(仅认证公众号可配置)</td>
</tr>
<tr>
<td style="text-align: left">pagepath</td>
<td style="text-align: left">miniprogram类型必须</td>
<td style="text-align: left">小程序的页面路径</td>
</tr>
</tbody>
</table>
<p><strong>返回结果</strong></p>
<p>正确时的返回JSON数据包如下:</p>
<pre><code class="language-json">{"errcode":0,"errmsg":"ok"}
</code></pre>
<p>错误时的返回JSON数据包如下(示例为无效菜单名长度):</p>
<pre><code class="language-json">{"errcode":40018,"errmsg":"invalid button name size"}
</code></pre>
<p>和前面xml的类似,我们需要对着请求的json数据封装按钮类,这样后面操作起来就比较方便,而且也方便维护。</p>
<h3 id="111-封装菜单类">11.1 封装菜单类</h3>
<p>&lt;1&gt;AbstractButton类</p>
<pre><code class="language-java">//所有菜单(按钮)的父类
public abstract class AbstractButton {
        private String name;//按钮标题

        public String getName() {
                return this.name;
        }

        public void setName(final String name) {
                this.name = name;
        }

        public AbstractButton(final String name) {
                this.name = name;
        }
}
</code></pre>
<p>&lt;2&gt;Button类</p>
<pre><code class="language-java">//一级菜单对象
public class Button {
        private List&lt;AbstractButton&gt; button;

        public Button() {
                this.button = new ArrayList&lt;AbstractButton&gt;();
        }

        public List&lt;AbstractButton&gt; getButton() {
                return this.button;
        }

        public void setButton(final List&lt;AbstractButton&gt; button) {
                this.button = button;
        }
}

</code></pre>
<p>&lt;3&gt;ClickButton类</p>
<pre><code class="language-java">//点击类型的菜单
public class ClickButton extends AbstractButton {
        private String type;
        private String key;

        public String getType() {
                return this.type;
        }

        public void setType(final String type) {
                this.type = type;
        }

        public String getKey() {
                return this.key;
        }

        public void setKey(final String key) {
                this.key = key;
        }

        public ClickButton(final String name, final String key) {
                super(name);
                this.type = "click";//点击类型
                this.key = key;
        }
}
</code></pre>
<p>&lt;4&gt;ViewButton类</p>
<pre><code class="language-java">//网页类型的菜单
public class ViewButton extends AbstractButton {
        private String type;
        private String url;

        public String getType() {
                return this.type;
        }

        public void setType(final String type) {
                this.type = type;
        }

        public String getUrl() {
                return this.url;
        }

        public void setUrl(final String url) {
                this.url = url;
        }

        public ViewButton(final String name, final String url) {
                super(name);
                this.type = "view";//网页类型
                this.url = url;
        }
}
</code></pre>
<p>&lt;5&gt; PhotoOrAlbumButton</p>
<pre><code class="language-java">//拍照或传图菜单
public class PhotoOrAlbumButton extends AbstractButton{
        private String type;
        private String key;

        public PhotoOrAlbumButton(String name,String key) {
                super(name);
                this.type = "pic_photo_or_album";//拍照获取传图
                this.key = key;
        }

        public String getType() {
                return type;
        }

        public void setType(String type) {
                this.type = type;
        }

        public String getKey() {
                return key;
        }

        public void setKey(String key) {
                this.key = key;
        }
}
</code></pre>
<p>&lt;6&gt;SubButton</p>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;

//二级菜单对象
public class SubButton extends AbstractButton {
        private List&lt;AbstractButton&gt; sub_button;

        public List&lt;AbstractButton&gt; getSub_button() {
                return this.sub_button;
        }

        public void setSub_button(final List&lt;AbstractButton&gt; sub_button) {
                this.sub_button = sub_button;
        }

        public SubButton(final String name) {
                super(name);
                this.sub_button = new ArrayList&lt;AbstractButton&gt;();
        }
}
</code></pre>
<h3 id="112-测试">11.2 测试</h3>
<p>新增一个Test方法</p>
<pre><code class="language-java">package com.qy.test;

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.alibaba.fastjson.JSONObject;
import com.qy.entity.button.AbstractButton;
import com.qy.entity.button.Button;
import com.qy.entity.button.ClickButton;
import com.qy.entity.button.PhotoOrAlbumButton;
import com.qy.entity.button.SubButton;
import com.qy.entity.button.ViewButton;
import com.qy.service.WxService;
import com.qy.utils.HttpUtils;

public class TestButton {

        @Test
        public void setButton() {
                //创建一级菜单
                Button button = new Button();
                //在第三个菜单中创建二级菜单
                SubButton subButton = new SubButton("有子菜单");
                List&lt;AbstractButton&gt; list2 = new ArrayList();
                list2.add(new ClickButton("三一点击", "31"));
                list2.add(new ViewButton("码云博客", "https://heliufang.gitee.io/"));
                list2.add(new PhotoOrAlbumButton("拍照或发图","33"));
                subButton.setSub_button(list2);
                //在一级菜单中添加三个按钮,
                List&lt;AbstractButton&gt; list = new ArrayList();
                list.add(new ClickButton("一级点击", "1"));
                list.add(new ViewButton("个人博客", "https://heliufang.gitee.io/"));
                list.add(subButton);
                button.setButton(list);
                //转成json格式字符串
                String jsonString = JSONObject.toJSONString(button);
                //System.out.println(jsonString);
                //发送请求
                String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());//把token带上
                String result = HttpUtils.sendPost(url, jsonString);
                System.out.println(result);
        }
}
</code></pre>
<p>运行效果如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222928481-1237851377.png" alt="image-20210813000312449" loading="lazy"></p>
<h2 id="12-设置和获取行业信息">12 设置和获取行业信息</h2>
<h3 id="121-设置行业信息">12.1 设置行业信息</h3>
<p>如果要发送模板消息,那么首先就得设置行业信息,如何设置和获取可以看下面接口。</p>
<p>模板消息文档</p>
<p>设置行业可在微信公众平台后台完成,每月可修改行业1次,帐号仅可使用所属行业中相关的模板,为方便第三方开发者,提供通过接口调用的方式来修改账号所属行业,具体如下:</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>http请求方式: POST https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN</p>
</blockquote>
<p><strong>POST数据说明</strong></p>
<p>POST数据示例如下:</p>
<pre><code class="language-json">{
    "industry_id1":"1",
    "industry_id2":"4"
}
</code></pre>
<p>参数说明</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接口调用凭证</td>
</tr>
<tr>
<td style="text-align: left">industry_id1</td>
<td style="text-align: left">是</td>
<td style="text-align: left">公众号模板消息所属行业编号-主行业</td>
</tr>
<tr>
<td style="text-align: left">industry_id2</td>
<td style="text-align: left">是</td>
<td style="text-align: left">公众号模板消息所属行业编号-副行业</td>
</tr>
</tbody>
</table>
<p><strong>行业代码查询</strong>,更多代码可以查询文档</p>
<table>
<thead>
<tr>
<th style="text-align: left">主行业</th>
<th style="text-align: left">副行业</th>
<th style="text-align: left">代码</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">IT科技</td>
<td style="text-align: left">互联网/电子商务</td>
<td style="text-align: left">1</td>
</tr>
<tr>
<td style="text-align: left">IT科技</td>
<td style="text-align: left">IT软件与服务</td>
<td style="text-align: left">2</td>
</tr>
<tr>
<td style="text-align: left">IT科技</td>
<td style="text-align: left">IT硬件与设备</td>
<td style="text-align: left">3</td>
</tr>
<tr>
<td style="text-align: left">...</td>
<td style="text-align: left">...</td>
<td style="text-align: left">...</td>
</tr>
</tbody>
</table>
<p>编写测试代码</p>
<pre><code class="language-java">@Test
        public void setIndustry() {
                String url = "https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
                String jsonStr = "{\"industry_id1\":\"1\",\"industry_id2\":\"4\"}";
                String rString = HttpUtils.sendPost(url, jsonStr);
                System.out.println(rString);
        }
</code></pre>
<h3 id="122-获取行业信息">12.2 获取行业信息</h3>
<p>获取帐号设置的行业信息。可登录微信公众平台,在公众号后台中查看行业信息。为方便第三方开发者,提供通过接口调用的方式来获取帐号所设置的行业信息,具体如下:</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>http请求方式:GET https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN</p>
</blockquote>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接口调用凭证</td>
</tr>
</tbody>
</table>
<p><strong>返回说明</strong></p>
<p>正确调用后的返回示例:</p>
<pre><code class="language-json">{
    "primary_industry":{"first_class":"运输与仓储","second_class":"快递"},
    "secondary_industry":{"first_class":"IT科技","second_class":"互联网|电子商务"}
}
</code></pre>
<p>返回参数说明</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必填</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接口调用凭证</td>
</tr>
<tr>
<td style="text-align: left">primary_industry</td>
<td style="text-align: left">是</td>
<td style="text-align: left">帐号设置的主营行业</td>
</tr>
<tr>
<td style="text-align: left">secondary_industry</td>
<td style="text-align: left">是</td>
<td style="text-align: left">帐号设置的副营行业</td>
</tr>
</tbody>
</table>
<p>编写测试代码</p>
<pre><code class="language-java">@Test
        public void getIndustry() {
                String url = "https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
                String string = HttpUtils.sendGet(url);
                System.out.println(string);
        }
</code></pre>
<h2 id="13-发送模板消息">13 发送模板消息</h2>
<p>模板消息接口</p>
<p>就是微信主动给用户推送消息,不需要像之前那样被动(用户发送之后再回复).</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>http请求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN</p>
</blockquote>
<p>POST数据如下:</p>
<pre><code class="language-json">{
        "touser": "oQxvI51GI5t9wBaBjmBXgJZZVM3A",
        "template_id": "tQ0G9Pmd_n_ylmplYsEnexgabkJXH1S3J7BXahK454g",
        "url": "https://heliufang.gitee.io/",
        "data": {
                "first": {
                        "value": "您好!您投递的简历有新的反馈",
                        "color": "#173177"
                },
                "company": {
                        "value": "广州壹新网络科技有限公司",
                        "color": "#173177"
                },
                "time": {
                        "value": "2021-8-5 23:31:23",
                        "color": "#173177"
                },
                "result": {
                        "value": "已通过",
                        "color": "#ff0000"
                },
                "remark": {
                        "value": "带身份证",
                        "color": "#173177"
                }
        }
}
</code></pre>
<p>参数说明</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必填</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">touser</td>
<td style="text-align: left">是</td>
<td style="text-align: left">接收者openid</td>
</tr>
<tr>
<td style="text-align: left">template_id</td>
<td style="text-align: left">是</td>
<td style="text-align: left">模板ID,这个需要在管理界面配置</td>
</tr>
<tr>
<td style="text-align: left">url</td>
<td style="text-align: left">否</td>
<td style="text-align: left">模板跳转链接(海外帐号没有跳转能力)</td>
</tr>
<tr>
<td style="text-align: left">data</td>
<td style="text-align: left">是</td>
<td style="text-align: left">模板数据</td>
</tr>
<tr>
<td style="text-align: left">color</td>
<td style="text-align: left">否</td>
<td style="text-align: left">模板内容字体颜色,不填默认为黑色</td>
</tr>
</tbody>
</table>
<p><strong>返回码说明</strong></p>
<p>在调用模板消息接口后,会返回JSON数据包。正常时的返回JSON数据包示例:</p>
<pre><code class="language-json">{"errcode":0,"errmsg":"ok","msgid":200228332}
</code></pre>
<p>★第一步:在微信测试号管理后台配置模板:</p>
<ul>
<li>
<p>模板标题: 简历反馈提醒</p>
</li>
<li>
<p>模板内容:</p>
</li>
</ul>
<pre><code>{{first.DATA}}
公司名:{{company.DATA}}
投递时间:{{time.DATA}}
反馈结果:{{result.DATA}} {{remark.DATA}}
</code></pre>
<p>创建好之后是下面这个样子</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222928295-2011390578.png" alt="image-20210814170124702" loading="lazy"></p>
<p>第二步:编写代码</p>
<pre><code class="language-java">@Test
        public void sendTemplateMsg() {
                String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
                //实际开发中应封装成java类,再把java对象转成类似下面的jsonstr
                String jsonStr = "{\r\n" +
                                "        \"touser\": \"oQxvI51GI5t9wBaBjmBXgJZZVM3A\",\r\n" +
                                "        \"template_id\": \"tQ0G9Pmd_n_ylmplYsEnexgabkJXH1S3J7BXahK454g\",\r\n" +
                                "        \"url\": \"https://heliufang.gitee.io/\",\r\n" +
                                "        \"data\": {\r\n" +
                                "                \"first\": {\r\n" +
                                "                        \"value\": \"您好!您投递的简历有新的反馈\",\r\n" +
                                "                        \"color\": \"#173177\"\r\n" +
                                "                },\r\n" +
                                "                \"company\": {\r\n" +
                                "                        \"value\": \"广州壹新网络科技有限公司\",\r\n" +
                                "                        \"color\": \"#173177\"\r\n" +
                                "                },\r\n" +
                                "                \"time\": {\r\n" +
                                "                        \"value\": \"2021-8-5 23:31:23\",\r\n" +
                                "                        \"color\": \"#173177\"\r\n" +
                                "                },\r\n" +
                                "                \"result\": {\r\n" +
                                "                        \"value\": \"已通过\",\r\n" +
                                "                        \"color\": \"#ff0000\"\r\n" +
                                "                },\r\n" +
                                "                \"remark\": {\r\n" +
                                "                        \"value\": \"带身份证\",\r\n" +
                                "                        \"color\": \"#173177\"\r\n" +
                                "                }\r\n" +
                                "        }\r\n" +
                                "}";
                String rString = HttpUtils.sendPost(url, jsonStr);
                System.out.println(rString);
        }
</code></pre>
<p>测试结果如下</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222928071-220181022.png" alt="image-20210814171316722" loading="lazy"></p>
<h2 id="14-新增和获取临时素材">14 新增和获取临时素材</h2>
<p>公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操作,是通过media_id来进行的。素材管理接口对所有认证的订阅号和服务号开放。</p>
<p>注意点:</p>
<p>1、临时素材media_id是可复用的。</p>
<p>2、<strong>媒体文件在微信后台保存时间为3天,即3天后media_id失效。</strong></p>
<p>3、上传临时素材的格式、大小限制与公众平台官网一致。</p>
<p>图片(image): 10M,支持PNG\JPEG\JPG\GIF格式</p>
<p>语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式</p>
<p>视频(video):10MB,支持MP4格式</p>
<p>缩略图(thumb):64KB,支持JPG格式</p>
<h3 id="141-新增临时素材">14.1 新增临时素材</h3>
<p>新增临时素材文档</p>
<p>罗老师用的是java自带的文件类上传,代码比较繁琐。而我使用HttpClient封装的HttpUtils上传就很简单了。</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>http请求方式: POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&amp;type=TYPE</p>
</blockquote>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">调用接口凭证</td>
</tr>
<tr>
<td style="text-align: left">type</td>
<td style="text-align: left">是</td>
<td style="text-align: left">媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)</td>
</tr>
<tr>
<td style="text-align: left">media</td>
<td style="text-align: left">是</td>
<td style="text-align: left">form-data中媒体文件标识,有filename、filelength、content-type等信息</td>
</tr>
</tbody>
</table>
<p><strong>返回说明</strong></p>
<p>正确情况下的返回JSON数据包结果如下:</p>
<pre><code class="language-json">{"type":"image","media_id":"atL80WWRNpMWhivoIGf9KTUUUO5pm6RxML8OPEUd7cbfb1Rs0kl2Yv0319KMQI-0","created_at":1628933345,"item":[]}
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">type</td>
<td style="text-align: left">媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb,主要用于视频与音乐格式的缩略图)</td>
</tr>
<tr>
<td style="text-align: left">media_id</td>
<td style="text-align: left">媒体文件上传后,获取标识</td>
</tr>
<tr>
<td style="text-align: left">created_at</td>
<td style="text-align: left">媒体文件上传时间戳</td>
</tr>
</tbody>
</table>
<p>编写测试代码</p>
<pre><code class="language-java">//上传图片
        @Test
        public void uploadMedia() {
                String url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&amp;type=TYPE";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
                url = url.replace("TYPE", "image");
                String string = HttpUtils.sendPost(url, null, "C:\\Users\\Administrator\\Desktop\\2.jpg", "");
                System.out.println(string);
        }
</code></pre>
<h3 id="142-获取临时素材">14.2 获取临时素材</h3>
<p>获取临时素材文档</p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
<p>http请求方式: GET,https调用 https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&amp;media_id=MEDIA_ID 请求示例(示例为通过curl命令获取多媒体文件) curl -I -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&amp;media_id=MEDIA_ID"</p>
</blockquote>
<p>把ACCESS_TOKEN和MEDIA_ID替换到url的位置,然后浏览器打开就可以下载了</p>
<p><strong>参数说明</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">调用接口凭证</td>
</tr>
<tr>
<td style="text-align: left">media_id</td>
<td style="text-align: left">是</td>
<td style="text-align: left">媒体文件ID</td>
</tr>
</tbody>
</table>
<p><strong>返回说明</strong></p>
<p>正确情况下的返回HTTP头如下:</p>
<pre><code class="language-text">HTTP/1.1 200 OK
Connection: close
Content-Type: image/jpeg
Content-disposition: attachment; filename="MEDIA_ID.jpg"
Date: Sun, 06 Jan 2013 10:20:18 GMT
Cache-Control: no-cache, must-revalidate
Content-Length: 339721
curl -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&amp;media_id=MEDIA_ID"
</code></pre>
<h2 id="15-二维码生成和扫描">15 二维码生成和扫描</h2>
<h3 id="151-生成带参数的临时二维码">15.1 生成带参数的临时二维码</h3>
<p>为了满足用户渠道推广分析和用户帐号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。</p>
<p>目前有2种类型的二维码:</p>
<p>1、<code>临时二维码</code>,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景 2、<code>永久二维码</code>,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。</p>
<p><strong>获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。</strong></p>
<p>生成带参数的二维码文档</p>
<p>测试代码将实现下面这样一个功能,点击页面上的生成按钮,在页面展示生成好的二维码</p>
<p>【index.jsp】</p>
<pre><code class="language-jsp">&lt;%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;二维码测试页面&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
   &lt;button type="button"&gt;生成二维码&lt;/button&gt;&lt;br&gt;
   &lt;img alt="暂无图片" src=""&gt;
&lt;/body&gt;
&lt;script src="jquery.js"&gt;&lt;/script&gt;
&lt;script&gt;
   $("button").click(function(){
           $.ajax({
                   url: "/wx/getQrCode",
                   type: "get",
                   dataType: "json",
                   success: function(resp){
                           console.log(resp);
               //通过ticket获取图片
                           var src = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+resp.ticket;
                           $("img").attr("src",src)
                   }
           })
   })
&lt;/script&gt;
&lt;/html&gt;
</code></pre>
<p>【后端servlet】</p>
<pre><code class="language-java">@WebServlet("/getQrCode")
public class QrCodeServlet extends HttpServlet {

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                //设置编码格式,不然中文会乱码
                req.setCharacterEncoding("UTF-8");
                resp.setCharacterEncoding("UTF-8");
                //发送post请求获取ticket,页面通过ticket就可以展示二维码图片了
                String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
                url = url.replace("TOKEN", WxService.getAccessToken());
                /*600表示10分钟有效scene_str是一个唯一标识,类似点击事件的key, QR_STR_SCENE表示临时二维码
               * {
                                "expire_seconds": 600,
                                "action_name": "QR_STR_SCENE",
                                "action_info": {
                                        "scene": {
                                                "scene_str": "test"
                                               
                                        }
                                }
                        }
               */
                String jsonStr = "{\r\n" +
                                "        \"expire_seconds\": 600, \r\n" +
                                "        \"action_name\": \"QR_STR_SCENE\", \r\n" +
                                "        \"action_info\": {\r\n" +
                                "                \"scene\": {\r\n" +
                                "                        \"scene_str\": \"test\"\r\n" +
                                "                        \r\n" +
                                "                }\r\n" +
                                "        }\r\n" +
                                "}";
                String string = HttpUtils.sendPost(url, jsonStr);
                JSONObject object = JSONObject.parseObject(string);
                //将响应结果返回页面,用于显示二维码
                resp.getWriter().write(string);
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        }
}

</code></pre>
<p>访问页面,点击按钮就可以看到如下效果</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222927849-1087482256.png" alt="image-20210814182247464" loading="lazy"></p>
<h3 id="152-扫描二维码">15.2 扫描二维码</h3>
<p>用户扫描带场景值二维码时,可能推送以下两种事件:</p>
<p>如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。</p>
<p>如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者</p>
<p>扫描临时二维码之后,会向服务器推送一个xml数据包,解析之后打印效果如下:</p>
<pre><code>{
Ticket=gQFr8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAya1JKeDQ2M3JmOEQxOGlybk54Y08AAgS6mBdhAwRYAgAA,
FromUserName=oQxvI51GI5t9wBaBjmBXgJZZVM3A,
EventKey=test,
Event=SCAN,
CreateTime=1628936703,
ToUserName=gh_c8af0521f09a,
MsgType=event
}
</code></pre>
<p>实现扫码之后给用户回复一个[你扫码了]</p>
<p>修改【WxService】的代码,修改getRespose方法,新增dealEvent和dealScanEvent方法</p>
<pre><code class="language-java">/**
       * 事件消息回复
       */
        public static String getRespose(Map&lt;String, String&gt; requestMap) {
                BaseMsg msg = null;
                // 根据用户发送消息的类型,做不同的处理
                String msgType = requestMap.get("MsgType");
                switch (msgType) {
                case "text":
                        msg = dealTextMsg(requestMap);
                        break;
                case "news":
                        break;
                case "event":
                  //新增处理事件的方法
                        msg = dealEvent(requestMap);
                        break;
                default:
                        break;
                }
                // System.out.println(msg);
                // 将处理结果转化成xml的字符串返回
                if (null != msg) {
                        return beanToXml(msg);
                }
                return null;
        }

        //处理事件
        private static BaseMsg dealEvent(Map&lt;String, String&gt; requestMap) {
                String event = requestMap.get("Event");
                BaseMsg msg = null;
      //switch分发到具体事件
                switch (event) {
            case "SCAN":
                msg = dealScanEvent(requestMap);
                break;
            default:
                break;
                }
                return msg;
        }

        //处理SCAN事件
        private static BaseMsg dealScanEvent(Map&lt;String, String&gt; requestMap) {
                String eventKey = requestMap.get("EventKey");
                if("test".equals(eventKey)) {
                        return new TextMsg(requestMap, "你扫码了");
                }
                return new TextMsg(requestMap, requestMap.toString());
        }
</code></pre>
<p>扫码之后效果如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222927368-82234857.png" alt="image-20210814184046627" loading="lazy"></p>
<h2 id="16-获取用户信息">16 获取用户信息</h2>
<p>一般在做网页授权的时候,会用到这个功能。</p>
<h3 id="161-获取已关注的用户信息">16.1 获取已关注的用户信息</h3>
<p>获取用户基本信息(UnionID机制)</p>
<p>在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。</p>
<p><strong>获取用户基本信息(包括UnionID机制)</strong></p>
<p>开发者可通过OpenID来获取用户基本信息。请使用https协议。</p>
<blockquote>
<p>接口调用请求说明 http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&amp;openid=OPENID&amp;lang=zh_CN</p>
</blockquote>
<p>参数说明</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">是否必须</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">access_token</td>
<td style="text-align: left">是</td>
<td style="text-align: left">调用接口凭证</td>
</tr>
<tr>
<td style="text-align: left">openid</td>
<td style="text-align: left">是</td>
<td style="text-align: left">普通用户的标识,对当前公众号唯一</td>
</tr>
<tr>
<td style="text-align: left">lang</td>
<td style="text-align: left">否</td>
<td style="text-align: left">返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语</td>
</tr>
</tbody>
</table>
<p>openid可以登录测试号管理界面获取,对应关注者的微信号</p>
<p><img src="https://img2020.cnblogs.com/blog/1419795/202108/1419795-20210814222926986-409685167.png" alt="image-20210814185850181" loading="lazy"></p>
<p>测试代码</p>
<pre><code class="language-java">@Test
        public void getUserInfo() {
                String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&amp;openid=OPENID&amp;lang=zh_CN";
                url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
                url = url.replace("OPENID", "oQxvI51GI5t9wBaBjmBXgJZZVM3A");
                String string = HttpUtils.sendGet(url);
                System.out.println(string);//这里就可以看到打印的用户信息了
        }
</code></pre>
<h3 id="162-网页授权">16.2 网页授权</h3>
<p>可以获取未关注的用户信息,这部分需要有域名才能测试,后面再完善.先把文档放上</p>
<p>网页授权</p>
<h2 id="17-微信公众号开发框架">17 微信公众号开发框架</h2>
<p>前面的开发都是原生的写法,github上有很多现成的公众号开发框架。</p>
<p>比如这个基于springboot的公众号开发框架:</p>
<p>仓库:https://github.com/binarywang/weixin-java-mp-demo</p>
<p>文档:https://github.com/Wechat-Group/WxJava/wiki/公众号开发文档</p>
<p>最后多说一句只有把原生的基础打好了,才能更好的理解和使用框架,所以建议先学原生的公众号开发,再上手框架。</p><br><br>
来源:https://www.cnblogs.com/helf/p/15142117.html
頁: [1]
查看完整版本: 微信公众号开发Java版-学习总结