-
微信公众号开发Java版-学习总结
本文基于罗召勇老师的教程加上自己的理解整理
本文源码已上传至我的码云: https://gitee.com/heliufang/wx
微信公众号开发整体不难,主要是熟悉微信公众号常用的一些接口文档,然后会一门后端语言(比如java)即可。
罗召勇老师教程:微信公众号开发-Java版(蓝桥罗召勇)
微信公众号文档:微信公众号官方文档
1 微信公众号介绍
账号分为服务号
、订阅号
、小程序
服务号和订阅号开发类似,但是申请服务号必须是企业,所以学习的话申请一个订阅号+测试账号即可。为啥要申请测试账号呢?因为订阅号的接口功能有限,为了学习开发以及熟悉更多的接口,所以还需要申请一个测试号。
2 注册订阅号
第一步:访问:https://mp.weixin.qq.com/ 点击立即注册
按钮
第二步:注册类型页面选择订阅号
第三步:填写相关信息,点击注册即可
3 注册测试号
因为订阅号的接口权限是有限的,为了熟悉更多的微信公众号接口,所以需要申请一个测试号。
第一步:用注册的订阅号登录
第二步:在目录中【设置与开发】--->【开发者工具】下选择公众平台测试账号,点击进入后申请即可。
申请成功之后,就可以配置相关信息进行开发了,具体怎么配置后面再解释
4 程序运行流程
用户在公众号发送请求到微信服务器
微信服务器
将请求转发到我们自己的服务器
我们自己的服务器
处理完之后再把结果发送到微信服务器
最后微信服务器
再把结果响应给客户
5 搭建开发环境
罗老师用的是eclipse并且没有用maven环境,我用的是eclipse+maven+jdk7+tomcat8.0。maven的话可以兼容idea,而且下载依赖方便。
新建一个名为wx
的maven项目(这个项目名字任意都行),pom.xml
的依赖如下:
- 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
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 阿里云小蜜-自动回复机器人 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-chatbot</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.2</version> </dependency> <!-- xml操作相关依赖 --> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.11.1</version> </dependency> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.0.0</version> </dependency> <!-- 阿里json解析 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.28</version> </dependency> <!-- 这个是编码解码的 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> </dependencies>
编写一个测试的servlet
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
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; public class TestServlet extends HttpServlet{ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("请求到达了"); resp.getWriter().write("hello weixin"); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
启动项目访问:http://localhost:8080/wx/test
浏览器看到如下效果说明搭建成功
6 内外网穿透
外网默认是访问不到自己电脑上的项目的,为了让外网能够访问,所以需要做内外网穿透.这个不需要自己实现,可以借助一些工具,如花生壳、ngrok.这里用的是ngrok.
ngrok文档
第一步:访问ngrok官网,注册ngrok账号。
第二步:使用注册的账号登录
第三步:【隧道管理--->开通隧道】立即购买,可以购买最后那个免费的,也可以花10块钱买一个。免费的有时候不稳定,可以买一个10块。
开通之后在隧道管理下就可以看到刚刚开通的隧道
第四步:下载客户端工具,我电脑是windows的所以下载windows版
各版本工具下载地址:https://www.ngrok.cc/download.html
第五步:启动ngrok客户端工具,运行bat,输入隧道id,回车
看到下面这个状态为【online】表示启动成功
然后就可以通过http://heliufang.vipgz4.idcfengye.com这个域名访问本地8080端口上的项目了,比如访问之前搭建的wx项目
7 开发接入
接入之后微信服务器和我们自己的项目就接通了。那么如何接入呢?
接入的官方文档
- 第一步:登录微信公众测试号的管理界面,填写好相关信息
上图中的url就是自己电脑的项目
点击上图的提交按钮之后,微信会向上图中的url发送一个get请求,请求参数如下:
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
- 第二步:编写代码校验,用代码实现下面的逻辑
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信,如果比对成功,请原样返回echostr参数内容
在之前搭建的名为wx
的项目中新建一个【WxServlet.java】
- 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
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; public class WxServlet extends HttpServlet{ 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("接入失败"); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
新建一个【WxService.java】并添加一个check工具方法
- 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
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[0]+arr[1]+arr[2]; str = DigestUtils.sha1Hex(str);//sha1加密,这里没有像罗老师那样手写,直接用的commons-codec包的工具类 System.out.println("str:"+str); //3.将加密后的字符串和signature比较 System.out.println(signature); return str.equalsIgnoreCase(signature); } }
启动项目,点击提交按钮,出现下面这个代表接入成功。
8 接收用户消息
官方文档:接受普通消息
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
也就是说用户发消息给微信服务器,微信服务器会发送post请求
到我们自己的服务器,并且传送一个xml的数据给我们自己的服务器。
例如文本消息是这样的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
java中这样的数据读取并不方便。可以转换一下,先通过dom4j这个包转成dom对象,再把标签名和对应的标签的值保存到HashMap集合中,这样后面处理数据就很方便了,具体代码实现如下:
在【WxServlet】中编写doPost
方法,在测试号管理界面,扫码关注测试公众号
- 1
- 2
- 3
- 4
- 5
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Map<String,String> map = WxService.parseRequest(req.getInputStream()); System.out.println(map);//关注测试号,给测试公众号发消息,就可以看到打印结果了 }
在【WxService】中添加parseRequest
方法
- 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
/** * 将接受到的消息转化成map * @param req * @return */ public static Map<String, String> parseRequest(InputStream is) { Map<String,String> map = new HashMap<String,String>(); //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<Element> elements = root.elements(); //4.将所有节点放入map for (Element element : elements) { map.put(element.getName(), element.getStringValue()); } return map; }
9 回复用户消息封装
官方文档:被动回复用户消息
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
上面这段文字来自官方,可以看出
-
回复必须是xml的类型
-
可以回复多种类型的xml(文本、图片、图文、语音、视频、音乐)
-
接收到消息没有做出响应就会抛出:
该公众号暂时无法提供服务,请稍后再试
9.1 回复消息入门demo
这个demo就是给用户回复一个文本消息
回复的xml格式如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> </xml>
参数 | 是否必须 | 描述 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,文本为text |
Content | 是 | 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |
在wxservlet中doPost编写如下代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置编码格式,不然中文会乱码 req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); //将请求中的xml参数转成map Map<String,String> map = WxService.parseRequest(req.getInputStream()); System.out.println(map); //回复消息 String textMsg = "<xml><ToUserName><![CDATA["+map.get("FromUserName")+"]]></ToUserName><FromUserName><![CDATA["+map.get("ToUserName")+"]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[你好]]></Content></xml>"; resp.getWriter().print(textMsg); }
然后用测试号发消息,公众号都会回复一个 【你好】
这样写代码功能是可以实现,但是这样拼接字符串,再回复消息很不方便.然后自然就想到可以用java类来封装消息,响应的时候将java类转成xml(通过xstream
这个工具包实现)。下面就以文本消息和图文消息为例进行封装,其它消息类似。
9.2 基础消息类的封装
把公共的属性放到基础消息类中,然后其它消息类继承即可。
@XStreamAlias
这个注解配置的就是转成xml时对应的节点名字
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
public class BaseMsg { private String toUserName;//接收方的账号(收到的openid) private String fromUserName;//开发者的微信号 private String createTime;//消息创建时间 private String msgType;//消息类型 public BaseMsg(Map<String,String> requestMap) { super(); this.toUserName = requestMap.get("FromUserName"); this.fromUserName = requestMap.get("ToUserName"); this.createTime = requestMap.get("CreateTime"); } //get and set ... }
9.3 文本消息类封装
回复的xml的格式说明可以参考9.1入门demo.回复文本的封装类如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
//xml指的就是xml这个根节点名称 public class TextMsg extends BaseMsg { private String content;//回复的文本内容 public TextMsg(Map<String,String> requestMap,String content) { super(requestMap); this.setMsgType("text"); this.content = content; } //get and set ... }
9.4 图文消息封装
图文消息格式说明
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>1</ArticleCount> <Articles> <item> <Title><![CDATA[title1]]></Title> <Description><![CDATA[description1]]></Description> <PicUrl><![CDATA[picurl]]></PicUrl> <Url><![CDATA[url]]></Url> </item> </Articles> </xml>
参数 | 是否必须 | 说明 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,图文为news |
ArticleCount | 是 | 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息 |
Articles | 是 | 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数 |
Title | 是 | 图文消息标题 |
Description | 是 | 图文消息描述 |
PicUrl | 是 | 图片链接,支持JPG、PNG格式,较好的效果为大图360200,小图200200 |
Url | 是 | 点击图文消息跳转链接 |
首先封装一个article类,对应就是xml中的item这个节点
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
//映射到xml中的item这个节点 public class Article { private String title;//图文消息标题 private String description;//图文消息描述 private String picUrl;//图片链接 private String url;//点击图文消息跳转链接 //get and set ... }
然后再封装一个图文消息类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
public class NewsMsg extends BaseMsg { private String articleCount;//图文消息个数 private List<Article> articles; public NewsMsg(Map<String, String> requestMap,List<Article> articles) { super(requestMap); this.setMsgType("news"); this.articles = articles; this.setArticleCount(this.articles.size()+""); } //get and set ... }
9.5 测试
前面已经将基础消息和图文消息封装好了,现在用封装好的消息类来回复
第一步:将wxservlet
的doPost
方法改成如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置编码格式,不然中文会乱码 req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); //将请求中的xml参数转成map Map<String,String> map = WxService.parseRequest(req.getInputStream()); System.out.println(map); //处理完将响应一个xml给微信 String respXml = WxService.getRespose(map); System.out.println(respXml); resp.getWriter().print(respXml); }
第二步:WxService添加如下方法:
- 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
/** * 事件消息回复 */ public static String getRespose(Map<String, String> 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<String, String> requestMap) { // 获取用户发送的消息内容 String msg = requestMap.get("Content"); // 如果是图文回复一个图文消息 if (msg.equals("图文")) { List<Article> articles = new ArrayList<Article>(); 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; }
然后分别给公众号发一个1和图文
9.6 自动回复机器人
罗老师教程中的图灵机器人已经要收费.我使用的是阿里云的阿里云小蜜
这个机器人来做的回复.
阿里云小蜜机器人可以免费体验三个月。
具体代码可以查看阿里云小蜜的文档:阿里云产品服务协议(云小蜜)
10 ★access token的获取
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存.access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效
access token文档
目前access_token的有效期通过返回的expire_in
来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token
总结:调用很多接口需要access_token,获取access_token之后需要保存起来,过期了再重新获取,而不是每次都重新获取。
接口调用请求说明
https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,即appsecret |
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
- 1
{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
参数 | 说明 |
---|---|
access_token | 获取到的凭证 |
expires_in | 凭证有效时间,单位:秒 |
10.1 ★封装请求工具类
因为需要发送请求给微信服务器,所以需要有请求的工具类。罗老师用的是java自带的请求类,相对来说比较繁琐。所以我这里采用的是Apache HttpClient,这个用起来更加的简单。
第一步:pom.xml中导入依赖
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
<!--httpClient需要的依赖--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <!--//httpclient缓存--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient-cache</artifactId> <version>4.5</version> </dependency> <!--//http的mime类型都在这里面--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.3.2</version> </dependency>
第二步:基于Apache HttpClient
封装HttpUtils
工具类,我封装了4个方法,可以支持get请求和post请求。后面很多需要用的地方直接调用即可。
可以参考这个博客:HttpClient发送get/post请求
- 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
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
public class HttpUtils { public static void main(String[] args) { // 1.测试get请求 /* String getUrl = "http://localhost:8080/user/searchPage?pageNum=1&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<String, String> 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<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(); Set<String> 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&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<String, String> 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(); // 相当于<input type="file" name="fileParamName"/> 其中fileParamName以传进来的为准 builder.addPart(fileParamName, bin); // 相当于<input type="text" name="userName" value=userName> /*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; } }
10.2 创建AccessToken类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
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() > expiresTime; } //get and set ... }
10.3 WxService中添加获取AccessToken的方法
- 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
private static AccessToken at;//token获取的次数有限,有效期也有限,所以需要保存起来 private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&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(); }
编写一个测试类获取AccessToken
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
import org.junit.Test; import com.qy.service.WxService; public class TestToken { public void getAccessToken() { //可以看到下面两次获取的值一致 System.out.println(WxService.getAccessToken()); System.out.println(WxService.getAccessToken()); } }
11 自定义菜单
自定义菜单文档
请注意:
- 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
- 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“...”代替。
- 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
自定义菜单接口可实现多种类型按钮,如下:
- click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
- view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
- scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
- scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
- pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
- pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
- pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
- location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
- media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
- view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
接口调用请求说明
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
url中的ACCESS_TOKEN就是之前获取的,调用这个接口需要带上
请求需携带json参数
- 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
{ "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" } ] } ] }
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
button | 是 | 一级菜单数组,个数应为1~3个 |
sub_button | 否 | 二级菜单数组,个数应为1~5个 |
type | 是 | 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型 |
name | 是 | 菜单标题,不超过16个字节,子菜单不超过60个字节 |
key | click等点击类型必须 | 菜单KEY值,用于消息接口推送,不超过128字节 |
url | view、miniprogram类型必须 | 网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。 |
media_id | media_id类型和view_limited类型必须 | 调用新增永久素材接口返回的合法media_id |
appid | miniprogram类型必须 | 小程序的appid(仅认证公众号可配置) |
pagepath | miniprogram类型必须 | 小程序的页面路径 |
返回结果
正确时的返回JSON数据包如下:
- 1
{"errcode":0,"errmsg":"ok"}
错误时的返回JSON数据包如下(示例为无效菜单名长度):
- 1
{"errcode":40018,"errmsg":"invalid button name size"}
和前面xml的类似,我们需要对着请求的json数据封装按钮类,这样后面操作起来就比较方便,而且也方便维护。
11.1 封装菜单类
<1>AbstractButton类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
//所有菜单(按钮)的父类 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; } }
<2>Button类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
//一级菜单对象 public class Button { private List<AbstractButton> button; public Button() { this.button = new ArrayList<AbstractButton>(); } public List<AbstractButton> getButton() { return this.button; } public void setButton(final List<AbstractButton> button) { this.button = button; } }
<3>ClickButton类
- 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
//点击类型的菜单 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; } }
<4>ViewButton类
- 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
//网页类型的菜单 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; } }
<5> PhotoOrAlbumButton
- 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
//拍照或传图菜单 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; } }
<6>SubButton
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
import java.util.ArrayList; import java.util.List; //二级菜单对象 public class SubButton extends AbstractButton { private List<AbstractButton> sub_button; public List<AbstractButton> getSub_button() { return this.sub_button; } public void setSub_button(final List<AbstractButton> sub_button) { this.sub_button = sub_button; } public SubButton(final String name) { super(name); this.sub_button = new ArrayList<AbstractButton>(); } }
11.2 测试
新增一个Test方法
- 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
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 { public void setButton() { //创建一级菜单 Button button = new Button(); //在第三个菜单中创建二级菜单 SubButton subButton = new SubButton("有子菜单"); List<AbstractButton> 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<AbstractButton> 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); } }
运行效果如下:
12 设置和获取行业信息
12.1 设置行业信息
如果要发送模板消息,那么首先就得设置行业信息,如何设置和获取可以看下面接口。
模板消息文档
设置行业可在微信公众平台后台完成,每月可修改行业1次,帐号仅可使用所属行业中相关的模板,为方便第三方开发者,提供通过接口调用的方式来修改账号所属行业,具体如下:
接口调用请求说明
http请求方式: POST https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN
POST数据说明
POST数据示例如下:
- 1
- 2
- 3
- 4
{ "industry_id1":"1", "industry_id2":"4" }
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 接口调用凭证 |
industry_id1 | 是 | 公众号模板消息所属行业编号-主行业 |
industry_id2 | 是 | 公众号模板消息所属行业编号-副行业 |
行业代码查询,更多代码可以查询文档
主行业 | 副行业 | 代码 |
---|---|---|
IT科技 | 互联网/电子商务 | 1 |
IT科技 | IT软件与服务 | 2 |
IT科技 | IT硬件与设备 | 3 |
... | ... | ... |
编写测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
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); }
12.2 获取行业信息
获取帐号设置的行业信息。可登录微信公众平台,在公众号后台中查看行业信息。为方便第三方开发者,提供通过接口调用的方式来获取帐号所设置的行业信息,具体如下:
接口调用请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 接口调用凭证 |
返回说明
正确调用后的返回示例:
- 1
- 2
- 3
- 4
{ "primary_industry":{"first_class":"运输与仓储","second_class":"快递"}, "secondary_industry":{"first_class":"IT科技","second_class":"互联网|电子商务"} }
返回参数说明
参数 | 是否必填 | 说明 |
---|---|---|
access_token | 是 | 接口调用凭证 |
primary_industry | 是 | 帐号设置的主营行业 |
secondary_industry | 是 | 帐号设置的副营行业 |
编写测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
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); }
13 发送模板消息
模板消息接口
就是微信主动给用户推送消息,不需要像之前那样被动(用户发送之后再回复).
接口调用请求说明
http请求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
POST数据如下:
- 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
{ "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" } } }
参数说明
参数 | 是否必填 | 说明 |
---|---|---|
touser | 是 | 接收者openid |
template_id | 是 | 模板ID,这个需要在管理界面配置 |
url | 否 | 模板跳转链接(海外帐号没有跳转能力) |
data | 是 | 模板数据 |
color | 否 | 模板内容字体颜色,不填默认为黑色 |
返回码说明
在调用模板消息接口后,会返回JSON数据包。正常时的返回JSON数据包示例:
- 1
{"errcode":0,"errmsg":"ok","msgid":200228332}
★第一步:在微信测试号管理后台配置模板:
-
模板标题: 简历反馈提醒
-
模板内容:
- 1
- 2
- 3
- 4
{{first
}} 公司名:{{company }} 投递时间:{{time }} 反馈结果:{{result }} {{remark }} 创建好之后是下面这个样子
第二步:编写代码
- 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
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); }
测试结果如下
14 新增和获取临时素材
公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操作,是通过media_id来进行的。素材管理接口对所有认证的订阅号和服务号开放。
注意点:
1、临时素材media_id是可复用的。
2、媒体文件在微信后台保存时间为3天,即3天后media_id失效。
3、上传临时素材的格式、大小限制与公众平台官网一致。
图片(image): 10M,支持PNG\JPEG\JPG\GIF格式
语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
14.1 新增临时素材
新增临时素材文档
罗老师用的是java自带的文件类上传,代码比较繁琐。而我使用HttpClient封装的HttpUtils上传就很简单了。
接口调用请求说明
http请求方式: POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证 |
type | 是 | 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) |
media | 是 | form-data中媒体文件标识,有filename、filelength、content-type等信息 |
返回说明
正确情况下的返回JSON数据包结果如下:
- 1
{"type":"image","media_id":"atL80WWRNpMWhivoIGf9KTUUUO5pm6RxML8OPEUd7cbfb1Rs0kl2Yv0319KMQI-0","created_at":1628933345,"item":[]}
参数 | 描述 |
---|---|
type | 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb,主要用于视频与音乐格式的缩略图) |
media_id | 媒体文件上传后,获取标识 |
created_at | 媒体文件上传时间戳 |
编写测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
//上传图片 public void uploadMedia() { String url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&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); }
14.2 获取临时素材
获取临时素材文档
接口调用请求说明
http请求方式: GET,https调用 https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID 请求示例(示例为通过curl命令获取多媒体文件) curl -I -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"
把ACCESS_TOKEN和MEDIA_ID替换到url的位置,然后浏览器打开就可以下载了
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证 |
media_id | 是 | 媒体文件ID |
返回说明
正确情况下的返回HTTP头如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
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&media_id=MEDIA_ID"
15 二维码生成和扫描
15.1 生成带参数的临时二维码
为了满足用户渠道推广分析和用户帐号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。
目前有2种类型的二维码:
1、临时二维码
,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景 2、永久二维码
,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。
获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。
生成带参数的二维码文档
测试代码将实现下面这样一个功能,点击页面上的生成按钮,在页面展示生成好的二维码
【index.jsp】
- 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
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>二维码测试页面</title> </head> <body> <button type="button">生成二维码</button><br> <img alt="暂无图片" src=""> </body> <script src="jquery.js"></script> <script> $("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) } }) }) </script> </html>
【后端servlet】
- 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
public class QrCodeServlet extends HttpServlet { 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); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
访问页面,点击按钮就可以看到如下效果
15.2 扫描二维码
用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者
扫描临时二维码之后,会向服务器推送一个xml数据包,解析之后打印效果如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
{ Ticket=gQFr8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAya1JKeDQ2M3JmOEQxOGlybk54Y08AAgS6mBdhAwRYAgAA, FromUserName=oQxvI51GI5t9wBaBjmBXgJZZVM3A, EventKey=test, Event=SCAN, CreateTime=1628936703, ToUserName=gh_c8af0521f09a, MsgType=event }
实现扫码之后给用户回复一个[你扫码了]
修改【WxService】的代码,修改getRespose方法,新增dealEvent和dealScanEvent方法
- 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
/** * 事件消息回复 */ public static String getRespose(Map<String, String> 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<String, String> 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<String, String> requestMap) { String eventKey = requestMap.get("EventKey"); if("test".equals(eventKey)) { return new TextMsg(requestMap, "你扫码了"); } return new TextMsg(requestMap, requestMap.toString()); }
扫码之后效果如下:
16 获取用户信息
一般在做网页授权的时候,会用到这个功能。
16.1 获取已关注的用户信息
获取用户基本信息(UnionID机制)
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
获取用户基本信息(包括UnionID机制)
开发者可通过OpenID来获取用户基本信息。请使用https协议。
接口调用请求说明 http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证 |
openid | 是 | 普通用户的标识,对当前公众号唯一 |
lang | 否 | 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 |
openid可以登录测试号管理界面获取,对应关注者的微信号
测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
public void getUserInfo() { String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; url = url.replace("ACCESS_TOKEN", WxService.getAccessToken()); url = url.replace("OPENID", "oQxvI51GI5t9wBaBjmBXgJZZVM3A"); String string = HttpUtils.sendGet(url); System.out.println(string);//这里就可以看到打印的用户信息了 }
16.2 网页授权
可以获取未关注的用户信息,这部分需要有域名才能测试,后面再完善.先把文档放上
网页授权
17 微信公众号开发框架
前面的开发都是原生的写法,github上有很多现成的公众号开发框架。
比如这个基于springboot的公众号开发框架:
仓库:https://github.com/binarywang/weixin-java-mp-demo
文档:https://github.com/Wechat-Group/WxJava/wiki/公众号开发文档
最后多说一句只有把原生的基础打好了,才能更好的理解和使用框架,所以建议先学原生的公众号开发,再上手框架。