自定义菜单接口可实现多种类型按钮,如下:
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
click和view的请求示例
{
“button”:[
{
“type”:“click”,
“name”:“今日歌曲”,
“key”:“V1001_TODAY_MUSIC”
},
{
“name”:“菜单”,
“sub_button”:[
{
“type”:“view”,
“name”:“搜索”,
“url”:“http://www.soso.com/”
},
{
“type”:“miniprogram”,
“name”:“wxa”,
“url”:“http://mp.weixin.qq.com”,
“appid”:“wx286b93c14bbf93aa”,
“pagepath”:“pages/lunar/index”
},
{
“type”:“click”,
“name”:“赞一下我们”,
“key”:“V1001_GOOD”
}]
}]
}
使用对象封装,通过JSONObject.fromObject(button)我们直接将对象转化成josn。
@Data
public class Button {
private List<AbstractButton> button = new ArrayList<>();
}
@Data
public class AbstractButton {
private String name;
public AbstractButton(String name){
this.name = name;
}
}
注意
使用JSONObject jsonObject = JSONObject.fromObject(button);
方法需要导入依赖,并不是阿里巴巴的fastjson的方法。
<!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
将AbstractButton类修改为抽象类,其他按钮继承它即可。
@Data
public abstract class AbstractButton {
private String name;
public AbstractButton(String name){
this.name = name;
}
}
新建按钮对象
@Data
public class ClickButten extends AbstractButton {
private String type = "click";
private String key;
public ClickButten(String name,String key){
super(name);
this.key = key;
}
}
@Data
public class ViewButton extends AbstractButton {
private String type = "view";
private String url;
public ViewButton(String name,String url){
super(name);
this.url=url;
}
}
package com.wx.model.button;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author :LiuShihao
* @date :Created in 2020/10/10 10:52 上午
* @desc :
* {
* "name": "发图",
* "sub_button": [
* {
* "type": "pic_sysphoto",
* "name": "系统拍照发图",
* "key": "rselfmenu_1_0",
* "sub_button": [ ]
* },
* {
* "type": "pic_photo_or_album",
* "name": "拍照或者相册发图",
* "key": "rselfmenu_1_1",
* "sub_button": [ ]
* },
* {
* "type": "pic_weixin",
* "name": "微信相册发图",
* "key": "rselfmenu_1_2",
* "sub_button": [ ]
* }
* ]
* }
*/
@Data
public class PhotoAlbumButton extends AbstractButton {
private String type;
private String key;
private List<AbstractButton> sub_button = new ArrayList<>();
public PhotoAlbumButton(String name,String type,String key){
super(name);
this.key = key;
this.type = type;
}
}
@Data
public class SubButton extends AbstractButton {
private List<AbstractButton> sub_button = new ArrayList<>();
public SubButton(String name ){
super(name);
}
}
将对象解析成Json
@Test
public void test2(){
Button button = new Button();
button.getButton().add(new ClickButten("一级菜单_点击","1"));
button.getButton().add(new ViewButton("一级跳转","http://baidu.com"));
SubButton subButton = new SubButton("子菜单");
subButton.getSub_button().add(new PhotoAlbumButton("系统拍照发图","pic_sysphoto","rselfmenu_1_0"));
subButton.getSub_button().add(new ClickButten("二级菜单_点击","2"));
subButton.getSub_button().add(new ViewButton("二级跳转","http://news.163.com"));
button.getButton().add(subButton);
JSONObject jsonObject = JSONObject.fromObject(button);
System.out.println(jsonObject);
}
{"button":[{"key":"1","name":"一级菜单_点击","type":"click"},{"name":"一级跳转","type":"view","url":"http://baidu.com"},{"name":"子菜单","sub_button":[{"key":"rselfmenu_1_0","name":"系统拍照发图","sub_button":[],"type":"pic_sysphoto"},{"key":"2","name":"二级菜单_点击","type":"click"},{"name":"二级跳转","type":"view","url":"http://news.163.com"}]}]}
{
"button":[
{
"key":"1",
"name":"一级菜单_点击",
"type":"click"
},
{
"name":"一级跳转",
"type":"view",
"url":"http://baidu.com"
},
{
"name":"子菜单",
"sub_button":[
{
"key":"rselfmenu_1_0",
"name":"系统拍照发图",
"sub_button":[
],
"type":"pic_sysphoto"
},
{
"key":"2",
"name":"二级菜单_点击",
"type":"click"
},
{
"name":"二级跳转",
"type":"view",
"url":"http://news.163.com"
}
]
}
]
}
二、创建自定义菜单
package com.wx.util;
import com.wx.model.button.*;
import com.wx.service.Impl.WXServiceImpl;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author :LiuShihao
* @date :Created in 2020/10/10 12:32 下午
* @desc :
*/
public class CreateMenu {
@Autowired
WXServiceImpl wxService;
public static void main(String[] args) {
Button button = new Button();
button.getButton().add(new ClickButten("一级菜单_点击","1"));
button.getButton().add(new ViewButton("一级跳转","http://baidu.com"));
SubButton subButton = new SubButton("子菜单");
subButton.getSub_button().add(new PhotoAlbumButton("系统拍照发图","pic_sysphoto","rselfmenu_1_0"));
subButton.getSub_button().add(new ClickButten("二级菜单_点击","2"));
subButton.getSub_button().add(new ViewButton("二级跳转","http://news.163.com"));
button.getButton().add(subButton);
JSONObject jsonObject = JSONObject.fromObject(button);
System.out.println(jsonObject);
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
String token = WXServiceImpl.getToken();
url = url.replace("ACCESS_TOKEN",token);
com.alibaba.fastjson.JSONObject post = CommonUtil.httpsRequest(url, "POST", jsonObject.toString());
System.out.println("post:"+post);
}
}
处理图片信息(识别图片文字)调用百度AI开放平台的API
当用户发送一个图片时,会发送到微信服务器,我们服务器接收的微信服务器发送过来的xml数据包,进行解析
/**
* 处理消息和事件推送
* @param request
* @throws IOException
*/
@PostMapping
public String method2(HttpServletRequest request) throws IOException {
Map<String,String> map = wxService.parseRequest(request.getInputStream());
System.out.println("处理消息和事件推送:"+map);
return wxService.getRespose(map);
}
将xml数据包解析成map形式
/**
* 使用dom4j包 解析xml数据包 成Map形式
* @param inputStream
* @return
*/
@Override
public Map<String, String> parseRequest(ServletInputStream inputStream) {
HashMap<String, String> hashMap = new HashMap<>();
SAXReader reader = new SAXReader();
try {
//读取输入流 回去文档对象
Document document = reader.read(inputStream);
//根据文档对象 回去根节点
Element rootElement = document.getRootElement();
//根据根节点获取所有的子节点
List<Element> elements = rootElement.elements();
for (Element element : elements) {
hashMap.put(element.getName(), element.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return hashMap;
}
然后对不同的消息类型进行相应的处理:
/**
* 用于处理所有的事件和消息de 回复
* 将Map解析成xml数据包 回复消息
* @param map
* @return 返回的是xml数据包
*/
@Override
public String getRespose(Map<String, String> map) {
String msgType = map.get("MsgType");
BaseMessage msg = null;
switch (msgType){
case "text":
msg = dealText(map);
break;
case "image":
msg = dealImage(map);
break;
case "voice":
msg = dealVoice(map);
break;
case "video":
break;
case "shortvideo":
break;
case "location":
break;
case "link":
break;
case "event":
msg = dealEvent(map);
break;
default:
break;
}
if (msg != null){
String xml = beanToXml(msg);
System.out.println(xml);
log.info(xml);
return xml;
}else {
return "success";
}
}
msgType
类型如果为image,就调用百度API;
/**
* 处理图片消息
* @param map
* @return
*/
private BaseMessage dealImage(Map<String, String> map) {
String url = map.get("PicUrl");
String toText = bdUtil.imageToText(url);
return new TextMessage(map,toText);
}
package com.wx.util;
import com.baidu.aip.ocr.AipOcr;
import com.wx.model.weather.*;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.json.JSONException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* @author :LiuShihao
* @date :Created in 2020/10/10 3:24 下午
* @desc :
*/
@Slf4j
@Component
public class BDUtil {
//你的对应信息
public static final String APP_ID = "228****23";
public static final String API_KEY = "2D0gCVQP***********gGdjgUCs";
public static final String SECRET_KEY = "UXL0pNYD***********ZO357kmXBa";
public static AipOcr client;
static {
// 初始化一个AipOcr
client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);
}
/**
* 识别 图片 文字
* @param picUrl
* @return
* @throws JSONException
*/
public String imageToText(String picUrl) throws JSONException {
String totext = null;
// 传入可选参数调用接口
HashMap<String, String> options = new HashMap<String, String>();
options.put("language_type", "CHN_ENG");
options.put("detect_direction", "true");
options.put("detect_language", "true");
options.put("probability", "true");
// // 参数为本地路径
// String image = "test.jpg";
// JSONObject res = client.basicGeneral(image, options);
// System.out.println(res.toString(2));
//
// // 参数为二进制数组
// byte[] file = readFile("test.jpg");
// res = client.basicGeneral(file, options);
// System.out.println(res.toString(2));
// 通用文字识别, 图片参数为远程url图片
org.json.JSONObject res = client.basicGeneralUrl(picUrl, options);
/**
* 返回成功:
* {
* "words_result": [{
* "probability": {
* "average": 0.950565,
* "min": 0.74773,
* "variance": 0.005733
* },
* "words": "该公众号提供的服务出现故障,请稍后再试"
* }],
* "log_id": 1314882863493021696,
* "words_result_num": 1,
* "language": 3,
* "direction": 0
* }
*
* 返回失败:
*
*/
System.out.println(res.toString(2));
String s = res.toString(2);
//将 字符创 转化成 JSONObject package net.sf.json;
JSONObject jsonObject = JSONObject.fromObject(s);
System.out.println("jsonObject:"+jsonObject);
try{
String error_code = jsonObject.getString("error_code");
if(!StringUtils.isEmpty(error_code)){
if ("216202".equals(error_code)){
totext = "上传的图片大小错误,现阶段我们支持的图片大小为:base64编码后小于4M,分辨率不高于4096*4096,请重新上传图片";
}
if ("216201".equals(error_code)){
totext = "上传的图片格式错误,现阶段我们支持的图片格式为:PNG、JPG、JPEG、BMP,请进行转码或更换图片";
}
}
}catch (net.sf.json.JSONException e){
e.printStackTrace();
JSONArray words_result = jsonObject.getJSONArray("words_result");
StringBuilder stringBuilder = new StringBuilder();
Iterator it = words_result.iterator();
while (it.hasNext()){
JSONObject next = (JSONObject)it.next();
stringBuilder.append(next.getString("words"));
}
log.info("imageToText:"+stringBuilder);
totext= stringBuilder.toString();
}
return totext;
}
}
打开百度AI开放平台网站,选择文字识别服务,如果没有应用,创建一个应用
删除菜单
使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。另请注意,在个性化菜单时,调用此接口会删除默认菜单及全部个性化菜单。
请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
返回说明
对应创建接口,正确的Json返回结果:
{“errcode”:0,“errmsg”:“ok”}
package com.wx.util;
import com.wx.model.button.*;
import com.wx.service.Impl.WXServiceImpl;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author :LiuShihao
* @date :Created in 2020/10/10 12:32 下午
* @desc :
*/
@Component
public class CreateMenu {
@Autowired
WXServiceImpl wxService;
public static void main(String[] args) {
}
public void createMenu(){
Button button = new Button();
button.getButton().add(new ClickButten("一级菜单_点击","1"));
button.getButton().add(new ViewButton("一级跳转","http://baidu.com"));
SubButton subButton = new SubButton("子菜单");
subButton.getSub_button().add(new PhotoAlbumButton("拍照或者相册发图","pic_photo_or_album","rselfmenu_1_0"));
subButton.getSub_button().add(new ClickButten("二级菜单_点击","2"));
subButton.getSub_button().add(new ViewButton("二级跳转","http://news.163.com"));
subButton.getSub_button().add(new ViewButton("我的博客","http://116.62.13.104:8090/"));
subButton.getSub_button().add(new ViewButton("我的网盘","http://116.62.13.104:8081/"));
button.getButton().add(subButton);
JSONObject jsonObject = JSONObject.fromObject(button);
System.out.println(jsonObject);
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
String token = WXServiceImpl.getToken();
url = url.replace("ACCESS_TOKEN",token);
com.alibaba.fastjson.JSONObject post = CommonUtil.httpsRequest(url, "POST", jsonObject.toString());
System.out.println("post:"+post);
}
/**
* http请求方式:GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
*/
public void delMenu(){
String url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
String token = wxService.getRedisToken();
url = url.replace("ACCESS_TOKEN",token);
com.alibaba.fastjson.JSONObject post = CommonUtil.httpsRequest(url, "GET", null);
System.out.println("post:"+post);
}
/**
* http请求方式: GET(请使用https协议)https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN
*/
public void findMenu(){
String url = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN";
String token = wxService.getRedisToken();
url = url.replace("ACCESS_TOKEN",token);
com.alibaba.fastjson.JSONObject post = CommonUtil.httpsRequest(url, "GET", null);
System.out.println("post:"+post);
}
}
@Test
public void test3(){
// menu.delMenu();
menu.createMenu();
menu.findMenu();
}