【公众号开发】Access Token的获取 · 请求公众号服务器创建自定义菜单 · 处理自定义菜单按钮事件

编程入门 行业动态 更新时间:2024-10-24 22:23:53

【公众号开发】Access Token的获取 · 请求公众号服务器创建<a href=https://www.elefans.com/category/jswz/34/1771438.html style=自定义菜单 · 处理自定义菜单按钮事件"/>

【公众号开发】Access Token的获取 · 请求公众号服务器创建自定义菜单 · 处理自定义菜单按钮事件

【公众号开发】(3)

文章目录

  • 【公众号开发】(3)
    • 1. 获取Access token
      • 1.1 确定参数
      • 1.2 补全URL(添加query string)
      • 1.3 测试
    • 2. 封装AccessToken以便保存与后期使用
      • 2.1 TokenUtils做出一些调整
      • 2.2 单例模式的AccessToken
      • 2.3 TokenUtils获取全局唯一的token字符串的方法
    • 3. 自定义菜单
      • 3.1 菜单显示的原理
      • 3.2 封装菜单类
      • 3.3 构造一个菜单对象
      • 3.4 发送post请求
      • 3.5 启动main方法查看效果
    • 4. 处理自定义菜单事件
      • 4.1 了解公众号发过来的post请求机制
      • 4.2 了解公众号发过来的post请求格式
      • 4.3 分支处理请求
      • 4.4 测试

【公众号开发】(3)

开始开发 / 获取 Access token (qq)

access_token是公众号的全局唯一的接口调用凭据,公众号调用各接口时都需使用access_token

开发者需要进行妥善保存

  1. access_token的存储至少要保留512个字符空间
  2. access_token的有效期目前为2个小时(7200s),需定时刷新,重复获取将导致上次获取的access_token失效

获取到Access token,我们才能够去调用微信公众号给我们提供的一些接口(Access token就类似于第三方接口的key,验证凭据后才可以去实现一些功能)

1. 获取Access token

这里是几张来自文档的重点截图:

1.1 确定参数

public class TokenUtils {private static final String APP_ID = "wxdadd0122365919e8";private static final String APP_SECRET = "69fd4a3ad04167f288e49bea9dce3e45";public static String getAccessToken() {// 获取token的urlfinal String URL = "";// 获取token的grant_typefinal String GRANT_TYPE = "client_credential";}}

1.2 补全URL(添加query string)

public static String getAccessToken() {// 获取token的urlfinal String URL = "";// 获取token的grant_typefinal String GRANT_TYPE = "client_credential";// 构造参数表Map<String, Object> param = new HashMap<String, Object>(){{this.put("grant_type", GRANT_TYPE);this.put("appid", APP_ID);this.put("secret", APP_SECRET);}};// 发起get请求String response = HttpUtils.doGet(URL, param);// 解析jsonMap<String, Object> result = JsonUtils.jsonToMap(response);System.out.println(result);// 返回tokenreturn (String) result.get("access_token");
}

1.3 测试

public static void main(String[] args) {System.out.println(getAccessToken());
}

2. 封装AccessToken以便保存与后期使用

这里我们全局的AccessToken唯一一份,我们希望其未过期就无需刷新,这里用的是**单例模式**!

2.1 TokenUtils做出一些调整

为了实现这个初心,在TokenUtils做出一些调整

  • 改为获取map

2.2 单例模式的AccessToken

  • 单例模式参考文章:【JavaEE】线程案例-单例模式 and 阻塞队列_s:103的博客-CSDN博客
@Data
public class AccessToken {private String token;private long expireTime;//有效期限volatile private static AccessToken accessToken = null;public void setExpireTime(long expireIn) {// 设置有效期限的时候的时间戳this.expireTime = System.currentTimeMillis() + expireIn * 1000;}public boolean isExpired() {return System.currentTimeMillis() > this.getExpireTime();}private static void setAccessToken() {if(accessToken == null) {accessToken = new AccessToken();}Map<String, Object> map = TokenUtils.getAccessTokenMap();accessToken.setToken((String) map.get("access_token"));accessToken.setExpireTime((Integer) map.get("expires_in"));}public static AccessToken getAccessToken() {if(accessToken == null || accessToken.isExpired()) {synchronized (AccessToken.class) {if(accessToken == null || accessToken.isExpired()) {setAccessToken();}}}return accessToken;}}

2.3 TokenUtils获取全局唯一的token字符串的方法

public static String getToken() {return AccessToken.getAccessToken().getToken();
}

测试

public static void main(String[] args) {System.out.println(getToken());System.out.println(getToken());System.out.println(getToken());
}

  • 三个一样,代表我们的AccessToken单例第一次被实例和设置并且因为没有过期而没有被更新~

有了凭据之后,我们就可以去调用微信公众号给我们提供的一些接口了,实现一些功能~

3. 自定义菜单

你会发现,我们的测试公众号现在还没有菜单的选项

而我们的常识也知道,公众号的菜单是必不可少的,接下来我们来完成一下自定义菜单吧

开发手册:自定义菜单 / 创建接口 (qq)

抓重点:

  • 按字数截取…
  • 刷新策略我们创建后再讲

自定义菜单接口,就相当于触发各种各样事件

3.1 菜单显示的原理


我们提交的信息会给公众号服务器保存起来,构造成菜单显示给用户~

这里我们来看个post请求body的例子:

  • (要求是json,这也合理,因为我们要传递的信息就是多个菜单,多级菜单,这个可是对象~)

我们需要什么功能,我们就查看与学习对应的按钮类型和其他参数就行了

  • 自定义菜单 / 创建接口 (qq)

3.2 封装菜单类

对于这个post请求的body,也就是这个json字符串的构造,是最大的问题,我们首先要封装菜单类

@Data
public class Button {private List<AbstractButton> button;}

这是构造最外层的button属性:

{"button": [...]
}

AbstractButton是我们抽象出来的按钮类(可以是一些按钮/二级菜单)

@Data
public abstract class AbstractButton {private String name;public AbstractButton(String name) {this.name = name;}public AbstractButton() {}
}

这个name属性是按钮/二级菜单的共性(二级菜单没有type,所以这里不应该写type)

根据刚才的json字符串,里面提到的属性就是对应类型按钮的属性~

以这几个为示例(其他根据实际举一反三就行):

@Data
public class ViewButton extends AbstractButton {private final String type = "view";private String url;public ViewButton(String name) {super(name);}
}

@Data
public class ClickButton extends AbstractButton {private final String type = "click";private String key;public ClickButton(String name) {super(name);}}

@Data
public class PicPhotoOrAlbumButton extends AbstractButton {private final String type = "pic_photo_or_album";private String key;public PicPhotoOrAlbumButton(String name) {super(name);}
}

@Data
public class SubButton extends AbstractButton {private List<AbstractButton> sub_button;public SubButton(String name) {super(name);}
}

3.3 构造一个菜单对象

预计菜单效果如下:

public class ButtonUtils {public static Button createButton() {Button button = new Button();button.setButton(new ArrayList<>());return button;}public static ClickButton createClickButton(String name, String key) {ClickButton clickButton = new ClickButton(name);clickButton.setKey(key);return clickButton;}public static ViewButton createViewButton(String name, String url) {ViewButton viewButton = new ViewButton(name);viewButton.setUrl(url);return viewButton;}public static SubButton createSubButton(String name) {SubButton subButton = new SubButton(name);subButton.setSub_button(new ArrayList<>());return subButton;}public static PicPhotoOrAlbumButton createPicPhotoOrAlbumButton(String name, String key) {PicPhotoOrAlbumButton picPhotoOrAlbumButton = new PicPhotoOrAlbumButton(name);picPhotoOrAlbumButton.setKey(key);return picPhotoOrAlbumButton;}public static void main(String[] args) {Button button = createButton();// 一级菜单的两个按钮button.getButton().add(createClickButton("mara\uD83D\uDE00😀😀😀", "1"));button.getButton().add(createViewButton("baidu\uD83D\uDE00", ""));// 二级菜单SubButton subButton = createSubButton("更多\uD83D\uDE00");subButton.getSub_button().add(createClickButton("mason\uD83D\uDE00", "2"));subButton.getSub_button().add(createViewButton("blog\uD83D\uDE00", "=blog"));subButton.getSub_button().add(createPicPhotoOrAlbumButton("上传图片\uD83D\uDE00", "3"));// 二级菜单加入到一级菜单中button.getButton().add(subButton);
//        System.out.println(button);String json = JsonUtils.objectToJson(button);System.out.println(json);}
}

emoji可以直接复制或者用unicode码,本质没啥区别,跟普通字符差不多:

  • 之前我做的网站,文本中有emoji是不行的,因为我的服务器并不支持emoji存到数据库
  • 可以这样:👨‍💻你知道如何使用MySQL存储Emoji表情吗?明白MySQL中UTF-8和UTF-8MB4字符编码有何区别吗? - 知乎 (zhihu)

Unicode 11.0版本的emoji表情 - emoji大全,emoji百科 (emojidaquan)

打印json后查看效果:

在线 JSON 解析 | 菜鸟工具 (runoob)

😊符合预期😊

对于json集合属性的序列,各个元素json字符串都不一样或者有联系,可以试试抽象成一个类,具体类继承这个抽象类,序列化的时候序列的是具体的实例

或者,你干脆写成List<Object>也行,序列化的时候自然知道这个Object是谁向上转型来的,序列化也能正确,不过每个按钮都有name的~

3.4 发送post请求

url的创建:

  1. ,访问的接口~
  2. queryString:携带我们的access_token,调用方法获取即可~
// 构造url
String url = " " + HttpUtils.getQueryString(new HashMap<String, Object>() {{this.put("access_token", TokenUtils.getToken());
}});
// 发送post请求
String response = HttpUtils.doPost(url, json);
System.out.println(response);

这里,doPost是区别于提交form格式的doPost的一个重载方法,作用就是根据url,提交json字符串:

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

3.5 启动main方法查看效果

微信公众号查看:

点击baidu😀(view按钮)跳转:

点击更多😀上拉菜单:

点击上传按钮(photo按钮)😀:

对于click按钮,点击了似乎没什么作用,接下来俺们来研究研究这个!

4. 处理自定义菜单事件

其实,用户每点击一次按钮,就相当于与公众号交互,对于这个“按钮事件”,消息类型为Event

也就是这里的其他消息类型:

而只要是用户发来的消息,都会触发公众号服务器发送post请求到我们的服务器的根路径

  • 也就是之前写的那个接口一致

开发文档:基础消息能力 / 接收事件推送 (qq)

以自定义菜单事件为例,其他的举一反三、自行学习😊

这里以click按钮为例子!

4.1 了解公众号发过来的post请求机制

如果是上拉菜单的按钮,则不会上报,也就是不会发post请求

  • 或者是弹出“拍照/上传图片”,这也算是子菜单吧,等等类似的~

4.2 了解公众号发过来的post请求格式

这个key,就是我们之前的按钮属性里的key:

这个key对于公众号服务器而已没啥作用,但是对于开发者而言很重要,因为post请求访问的是同一个接口,并且,请求并没有发送按钮名参数,并且按钮名也不一定唯一,开发者用按钮名来区分每个按钮不合理!

所以有公众号辅助我们,以key为按钮的标识,作为参数传递给开发者

开发者以key作为区分按钮触发事件的手段,不同的key执行不同的业务~

4.3 分支处理请求

对于不同的消息类型、不同的事件类型、不同的key,你可以用哈希表记录“键与业务方法”,这里的业务方法可以是一个接口,用普通类去实现接口,最后结合多态实现,输入键执行对于业务

这里我为了方便,易懂,任意演示/调试,用的是swtich分支处理

@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException {String body = HttpUtils.getBody(request);Map<String, Object> map = XmlUtils.xmlToMap(body);System.out.println(map);// 回复消息String message = "";String MsgType = (String) map.get("MsgType");switch (MsgType) {case "event":message = handleEvent(map);//处理事件break;case "text":message = handleText(map);//处理文本break;default:System.out.println("其他消息类型");break;}return message;
}
  • message返回空字符串才是正常的不回复,其他都是因为错误而不回复的

handleText:

private String handleText(Map<String, Object> map) {String message = "";if("图文".equals(map.get("Content"))) {NewsMessage newsMessage = NewsMessage.getReplyNewsMessage(map);message = XmlUtils.objectToXml(newsMessage);System.out.println(message);}else {// 1. 封装对象TextMessage textMessage = TextMessage.getAntonym(map);// 2. 序列化对象message = XmlUtils.objectToXml(textMessage);}return message;
}

handleEvent:

  • 通过事件类型分支
private String handleEvent(Map<String, Object> map) {String message = "";// 获取event值String event = (String) map.get("Event");// 事件分支switch (event) {case "CLICK":message = EventUtils.handleClick(map);break;case "VIEW":System.out.println("view");break;default:break;}return message;
}

EventUtils.handleClick:

public class EventUtils {public static String handleClick(Map<String, Object> map) {String message = "";String key = (String) map.get("EventKey");switch (key) {case "1":map.put("Content","\"触发了点击事件,key = 1\"");break;case "2":map.put("Content","\"触发了点击事件,key = 2\"");break;case "3":map.put("Content","\"触发了点击事件,key = 3\"");break;default:break;}TextMessage textMessage = TextMessage.getReplyTextMessage(map);message = XmlUtils.objectToXml(textMessage);return message;}
}

这个只是示例,至于你要执行什么业务,是你的事咯

4.4 测试

点击view类型按钮:

查看控制台:

还是那句话,这个只是示例,至于你要执行什么业务,是你的事咯

举一反三,由你发挥,一生万物!


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

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


更多推荐

【公众号开发】Access Token的获取 · 请求公众号服务器创建自定义菜单 · 处理自定义菜单按钮事件

本文发布于:2023-12-06 03:38:32,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1666330.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自定义   公众   菜单   按钮   事件

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!