学习笔记(十二)微信公众号-自定义菜单

前言

开发前,根据需求,阅读微信官方文档:

1、获取accesstoken:微信开发文档-获取access_token

2、自定义菜单:微信开放文档-自定义菜单 

一、需求

通过代码实现微信公众号的自定义菜单,包括删除、新增、同步菜单。

二、思路

1、获取微信公众号access_token

2、根据access_token可以查看公众号菜单

3、通过代码接口实现自定义菜单

三、数据库设计

1、表设计

 2、参考数据

一级菜单:热门功能、我的服务、关于我们

二级菜单:企业诉求直通车、自助开锁、一张图V2.0、自助移车

菜单结构:热门功能(企业诉求直通车、自助开锁、一张图V2.0、自助移车)、我的服务、关于我们

▲注意点:type为view时,url不可为空,其余type情况可参考微信开发者文档-自定义菜单

参数说明

参数是否必须说明
button一级菜单数组,个数应为1~3个
sub_button二级菜单数组,个数应为1~5个
type菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
name菜单标题,不超过16个字节,子菜单不超过60个字节
keyclick等点击类型必须菜单KEY值,用于消息接口推送,不超过128字节
urlview、miniprogram类型必须网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。
media_idmedia_id类型和view_limited类型必须调用新增永久素材接口返回的合法media_id
appidminiprogram类型必须小程序的appid(仅认证公众号可配置)
pagepathminiprogram类型必须小程序的页面路径
article_idarticle_id类型和article_view_limited类型必须发布后获得的合法 article_id

 微信公众号平台开发错误返回码说明

返回码错误码描述说明
40001invalid credential不合法的调用凭证
40002invalid grant_type不合法的grant_type
40003invalid openid不合法的OpenID
40004invalid media type不合法的媒体文件类型
40007invalid media_id不合法的media_id
40008invalid message type不合法的message_type
40009invalid image size不合法的图片大小
40010invalid voice size不合法的语音大小
40011invalid video size不合法的视频大小
40012invalid thumb size不合法的缩略图大小
40013invalid appid不合法的AppID
40014invalid access_token不合法的access_token
40015invalid menu type不合法的菜单类型
40016invalid button size不合法的菜单按钮个数
40017invalid button type不合法的按钮类型
40018invalid button name size不合法的按钮名称长度
40019invalid button key size不合法的按钮KEY长度
40020invalid button url size不合法的url长度
40023invalid sub button size不合法的子菜单按钮个数
40024invalid sub button type不合法的子菜单类型
40025invalid sub button name size不合法的子菜单按钮名称长度
40026invalid sub button key size不合法的子菜单按钮KEY长度
40027invalid sub button url size不合法的子菜单按钮url长度
40029invalid code不合法或已过期的code
40030invalid refresh_token不合法的refresh_token
40036invalid template_id size不合法的template_id长度
40037invalid template_id不合法的template_id
40039invalid url size不合法的url长度
40048invalid url domain不合法的url域名
40054invalid sub button url domain不合法的子菜单按钮url域名
40055invalid button url domain不合法的菜单按钮url域名
40066invalid url不合法的url
41001access_token missing缺失access_token参数
41002appid missing缺失appid参数
41003refresh_token missing缺失refresh_token参数
41004appsecret missing缺失secret参数
41005media data missing缺失二进制媒体文件
41006media_id missing缺失media_id参数
41007sub_menu data missing缺失子菜单数据
41008missing code缺失code参数
41009missing openid缺失openid参数
41010missing url缺失url参数
42001access_token expiredaccess_token超时
42002refresh_token expiredrefresh_token超时
42003code expiredcode超时
43001require GET method需要使用GET方法请求
43002require POST method需要使用POST方法请求
43003require https需要使用HTTPS
43004require subscribe需要订阅关系
44001empty media data空白的二进制数据
44002empty post data空白的POST数据
44003empty news data空白的news数据
44004empty content空白的内容
44005empty list size空白的列表
45001media size out of limit二进制文件超过限制
45002content size out of limitcontent参数超过限制
45003title size out of limittitle参数超过限制
45004description size out of limitdescription参数超过限制
45005url size out of limiturl参数长度超过限制
45006picurl size out of limitpicurl参数超过限制
45007playtime out of limit播放时间超过限制(语音为60s最大)
45008article size out of limitarticle参数超过限制
45009api freq out of limit接口调动频率超过限制
45010create menu limit建立菜单被限制
45011api limit频率限制
45012template size out of limit模板大小超过限制
45016can't modify sys group不能修改默认组
45017can't set group name too long sys group修改组名过长
45018too many group now, no need to add new组数量过多
50001api unauthorized接口未授权

四、实现

首先声明,以下代码均在jeecg-boot框架基础之上编写实现。

1、yml配置

#公众号
wechat:
  #公众号appid
  mpAppId: 公众号appid
  #公众号appsecret
  mpAppSecret: 公众号appsecret

2、pom导入依赖

我在jeecg-system-biz和jeecg-boot-parent中均导入了以下依赖

<dependency>
	<groupId>com.github.binarywang</groupId>
	<artifactId>weixin-java-mp</artifactId>
	<version>4.1.0</version>
</dependency>

3、config类

import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.jeecg.modules.menu.utils.ConstantPropertiesUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WeChatMpConfig {

    @Autowired
    private ConstantPropertiesUtil constantPropertiesUtil;

    @Bean
    public WxMpService wxMpService(){
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
    }
    @Bean
    public WxMpConfigStorage wxMpConfigStorage(){
        WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
        wxMpConfigStorage.setAppId(ConstantPropertiesUtil.ACCESS_KEY_ID);
        wxMpConfigStorage.setSecret(ConstantPropertiesUtil.ACCESS_KEY_SECRET);
        return wxMpConfigStorage;
    }
}

4、utils类

(1)ConstantPropertiesUtil

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ConstantPropertiesUtil implements InitializingBean {

    @Value("${wechat.mpAppId}")
    private String appid;

    @Value("${wechat.mpAppSecret}")
    private String appsecret;

    public static String ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;

    @Override
    public void afterPropertiesSet() throws Exception {
        ACCESS_KEY_ID = appid;
        ACCESS_KEY_SECRET = appsecret;
    }
}

(2)HttpClientUtil

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class HttpClientUtil {

    // utf-8字符编码
    public static final String CHARSET_UTF_8 = "utf-8";

    // HTTP内容类型。
    public static final String CONTENT_TYPE_TEXT_HTML = "text/xml";

    // HTTP内容类型。相当于form表单的形式,提交数据
    public static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";

    // HTTP内容类型。相当于form表单的形式,提交数据
    public static final String CONTENT_TYPE_JSON_URL = "application/json;charset=utf-8";


    // 连接管理器
    private static PoolingHttpClientConnectionManager pool;

    // 请求配置
    private static RequestConfig requestConfig;

    static {

        try {
            //System.out.println("初始化HttpClientTest~~~开始");
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    builder.build());
            // 配置同时支持 HTTP 和 HTPPS
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register(
                    "http", PlainConnectionSocketFactory.getSocketFactory()).register(
                    "https", sslsf).build();
            // 初始化连接管理器
            pool = new PoolingHttpClientConnectionManager(
                    socketFactoryRegistry);
            // 将最大连接数增加到200,实际项目最好从配置文件中读取这个值
            pool.setMaxTotal(200);
            // 设置最大路由
            pool.setDefaultMaxPerRoute(2);
            // 根据默认超时限制初始化requestConfig
            int socketTimeout = 10000;
            int connectTimeout = 10000;
            int connectionRequestTimeout = 10000;
            requestConfig = RequestConfig.custom().setConnectionRequestTimeout(
                    connectionRequestTimeout).setSocketTimeout(socketTimeout).setConnectTimeout(
                    connectTimeout).build();

            //System.out.println("初始化HttpClientTest~~~结束");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }


        // 设置请求超时时间
        requestConfig = RequestConfig.custom().setSocketTimeout(50000).setConnectTimeout(50000)
                .setConnectionRequestTimeout(50000).build();
    }

    /**
     * 发送Post请求
     *
     * @param httpPost
     * @return
     */
    private static String sendHttpPost(HttpPost httpPost) {

        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        // 响应内容
        String responseContent = null;
        try {
            // 创建默认的httpClient实例.
            httpClient = getHttpClient();
            // 配置请求信息
            httpPost.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpPost);
            // 得到响应实例
            HttpEntity entity = response.getEntity();

            // 可以获得响应头
            // Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
            // for (Header header : headers) {
            // System.out.println(header.getName());
            // }

            // 得到响应类型
            // System.out.println(ContentType.getOrDefault(response.getEntity()).getMimeType());

            // 判断响应状态
            if (response.getStatusLine().getStatusCode() >= 300) {
                throw new Exception(
                        "HTTP Request is not success, Response code is " + response.getStatusLine().getStatusCode());
            }

            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                responseContent = EntityUtils.toString(entity, CHARSET_UTF_8);
                EntityUtils.consume(entity);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 释放资源
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    public static CloseableHttpClient getHttpClient() {

        CloseableHttpClient httpClient = HttpClients.custom()
                // 设置连接池管理
                .setConnectionManager(pool)
                // 设置请求配置
                .setDefaultRequestConfig(requestConfig)
                // 设置重试次数
                .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
                .build();

        return httpClient;
    }

    /**
     * 发送Get请求
     *
     * @param httpGet
     * @return
     */
    private static String sendHttpGet(HttpGet httpGet) {

        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        // 响应内容
        String responseContent = null;
        try {
            // 创建默认的httpClient实例.
            httpClient = getHttpClient();
            // 配置请求信息
            httpGet.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpGet);
            // 得到响应实例
            HttpEntity entity = response.getEntity();

            // 可以获得响应头
            // Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
            // for (Header header : headers) {
            // System.out.println(header.getName());
            // }

            // 得到响应类型
            // System.out.println(ContentType.getOrDefault(response.getEntity()).getMimeType());

            // 判断响应状态
            if (response.getStatusLine().getStatusCode() >= 300) {
                throw new Exception(
                        "HTTP Request is not success, Response code is " + response.getStatusLine().getStatusCode());
            }

            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                responseContent = EntityUtils.toString(entity, CHARSET_UTF_8);
                EntityUtils.consume(entity);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 释放资源
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }


    /**
     * 发送 post请求
     *
     * @param httpUrl 地址
     */
    public static String sendHttpPost(String httpUrl) {
        // 创建httpPost
        HttpPost httpPost = new HttpPost(httpUrl);
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 get请求
     *
     * @param httpUrl
     */
    public static String sendHttpGet(String httpUrl) {
        // 创建get请求
        HttpGet httpGet = new HttpGet(httpUrl);
        httpGet.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
        httpGet.addHeader("Accept-Encoding", "gzip, deflate, br");
        httpGet.addHeader("Accept-Language", "zh-CN,zh;q=0.9");
        httpGet.addHeader("Cache-Control", "max-age=0");
        httpGet.addHeader("Connection", "keep-alive");
        httpGet.addHeader("Referer", httpUrl);
        httpGet.addHeader("Sec-Fetch-Mode", "navigate");
        httpGet.addHeader("Sec-Fetch-Site", "same-origin");
        httpGet.addHeader("Sec-Fetch-User", "?1");
        httpGet.addHeader("Upgrade-Insecure-Requests", "1");
        httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36");
        return sendHttpGet(httpGet);
    }


    /**
     * 发送 post请求(带文件)
     *
     * @param httpUrl   地址
     * @param maps      参数
     * @param fileLists 附件
     */
    public static String sendHttpPost(String httpUrl, Map<String, String> maps, List<File> fileLists) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        MultipartEntityBuilder meBuilder = MultipartEntityBuilder.create();
        if (maps != null) {
            for (String key : maps.keySet()) {
                meBuilder.addPart(key, new StringBody(maps.get(key), ContentType.TEXT_PLAIN));
            }
        }
        if (fileLists != null) {
            for (File file : fileLists) {
                FileBody fileBody = new FileBody(file);
                meBuilder.addPart("files", fileBody);
            }
        }
        HttpEntity reqEntity = meBuilder.build();
        httpPost.setEntity(reqEntity);
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 post请求
     *
     * @param httpUrl 地址
     * @param params  参数(格式:key1=value1&key2=value2)
     */
    public static String sendHttpPost(String httpUrl, String params) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        try {
            // 设置参数
            if (params != null && params.trim().length() > 0) {
                StringEntity stringEntity = new StringEntity(params, "UTF-8");
                stringEntity.setContentType(CONTENT_TYPE_FORM_URL);
                httpPost.setEntity(stringEntity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 post请求
     *
     * @param maps 参数
     */
    public static String sendHttpPost(String httpUrl, Map<String, String> maps) {
        String parem = convertStringParamter(maps);
        return sendHttpPost(httpUrl, parem);
    }


    /**
     * 发送 post请求 发送json数据
     *
     * @param httpUrl    地址
     * @param paramsJson 参数(格式 json)
     */
    public static String sendHttpPostJson(String httpUrl, String paramsJson) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        try {
            // 设置参数
            if (paramsJson != null && paramsJson.trim().length() > 0) {
                StringEntity stringEntity = new StringEntity(paramsJson, "UTF-8");
                stringEntity.setContentType(CONTENT_TYPE_JSON_URL);
                httpPost.setEntity(stringEntity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 post请求 发送xml数据
     *
     * @param httpUrl   地址
     * @param paramsXml 参数(格式 Xml)
     */
    public static String sendHttpPostXml(String httpUrl, String paramsXml) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        try {
            // 设置参数
            if (paramsXml != null && paramsXml.trim().length() > 0) {
                StringEntity stringEntity = new StringEntity(paramsXml, "UTF-8");
                stringEntity.setContentType(CONTENT_TYPE_TEXT_HTML);
                httpPost.setEntity(stringEntity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 将map集合的键值对转化成:key1=value1&key2=value2 的形式
     *
     * @param parameterMap 需要转化的键值对集合
     * @return 字符串
     */
    public static String convertStringParamter(Map parameterMap) {
        StringBuffer parameterBuffer = new StringBuffer();
        if (parameterMap != null) {
            Iterator iterator = parameterMap.keySet().iterator();
            String key = null;
            String value = null;
            while (iterator.hasNext()) {
                key = (String) iterator.next();
                if (parameterMap.get(key) != null) {
                    value = (String) parameterMap.get(key);
                } else {
                    value = "";
                }
                parameterBuffer.append(key).append("=").append(value);
                if (iterator.hasNext()) {
                    parameterBuffer.append("&");
                }
            }
        }
        return parameterBuffer.toString();
    }
   
}

5、entity及封装类

(1)entity

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.models.auth.In;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;

@Data
@TableName("wechat_menu")
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class WechatMenu implements Serializable {

    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "编号")
    private Integer id;

    @ApiModelProperty(value = "上级id")
    private Integer parentId;

    @ApiModelProperty(value = "菜单名称")
    private String name;

    @ApiModelProperty(value = "类型")
    private String type;

    @ApiModelProperty(value = "网页链接,用户点击菜单可打开链接")
    private String url;

    @ApiModelProperty(value = "菜单key值,用于消息接口推送")
    private String menuKey;

    @ApiModelProperty(value = "排序")
    private Integer sort;

    @ApiModelProperty(value = "删除标志")
    private Integer isDelete;

    /**创建时间*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "创建时间")
    private java.util.Date createTime;
    /**更新时间*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "更新时间")
    private java.util.Date updateTime;

}

(2)vo封装

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class WechatMenuVo implements Serializable {

    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "编号")
    private String id;

    @ApiModelProperty(value = "上级id")
    private String parentId;

    @ApiModelProperty(value = "菜单名称")
    private String name;

    @ApiModelProperty(value = "类型")
    private String type;

    @ApiModelProperty(value = "网页链接,用户点击菜单可打开链接")
    private String url;

    @ApiModelProperty(value = "菜单key值,用于消息接口推送")
    private String menuKey;

    @ApiModelProperty(value = "排序")
    private Integer sort;

    @ApiModelProperty(value = "删除标志")
    private Integer isDelete;

    /**创建时间*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "创建时间")
    private java.util.Date createTime;
    /**更新时间*/
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "更新时间")
    private java.util.Date updateTime;

    private List<WechatMenuVo> children;

}

6、mapper类

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.jeecg.modules.menu.entity.WechatMenu;

@Mapper
public interface WechatMenuMapper extends BaseMapper<WechatMenu> {
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.jeecg.modules.wechatMenu.mapper.WechatMenuMapper">

</mapper>

 7、service及serviceImpl类

(1)service

import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.menu.entity.WechatMenu;
import org.jeecg.modules.menu.vo.WechatMenuVo;

import java.util.List;

public interface IWechatMenuService extends IService<WechatMenu> {
    //获取全部菜单
    List<WechatMenuVo> findMenuInfo();
    //获取一级菜单
    List<WechatMenu> findMenuOneInfo();
    //同步公众号菜单
    void syncMenu();
    //删除公众号菜单
    void removeMenu();
}

(2)serviceImpl

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.menu.entity.WechatMenu;
import org.jeecg.modules.menu.mapper.WechatMenuMapper;
import org.jeecg.modules.menu.service.IWechatMenuService;
import org.jeecg.modules.menu.vo.WechatMenuVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WechatWechatMenuServiceImpl extends ServiceImpl<WechatMenuMapper, WechatMenu> implements IWechatMenuService {

    @Autowired
    private WxMpService wxMpService;

    //获取所有菜单,按照一级和二级菜单封装
    @Override
    public List<WechatMenuVo> findMenuInfo() {
        //1、创建List集合,用于最终数据封装
        List<WechatMenuVo> finalMenuList=new ArrayList<>();

        //2、查询出所有菜单数据(包含一级和二级)
        List<WechatMenu> menuList = baseMapper.selectList(null);

        //3、从所有菜单数据中获取所有一级菜单数据(parent_id=0)
        List<WechatMenu> oneMenuList = menuList.stream()
                .filter(menu -> menu.getParentId() == 0)
                .collect(Collectors.toList());

        //4、封装一级菜单数据,封装到最终数据list集合
        //遍历一级菜单list集合
        oneMenuList.forEach(oneMenu->{
            //Menu -->MenuVo
            WechatMenuVo oneMenuVo = new WechatMenuVo();
            BeanUtils.copyProperties(oneMenu,oneMenuVo);

            //5、封装二级菜单数据(判断一级菜单id和二级菜单的parent_id是否相同)
            //如果相同,把二级菜单数据放到一级菜单里面
            List<WechatMenu> twoMenuList = menuList.stream()
                    .filter(menu -> menu.getParentId().equals(oneMenu.getId()))
                    .collect(Collectors.toList());
            //List<Menu>->List<MenuVo>
            List<WechatMenuVo> children=new ArrayList<>();
            twoMenuList.forEach(twoMenu -> {
                WechatMenuVo twoMenuVo = new WechatMenuVo();
                BeanUtils.copyProperties(twoMenu,twoMenuVo);
                children.add(twoMenuVo);
            });
            //把二级菜单数据放到一级菜单里面
            oneMenuVo.setChildren(children);
            //把oneMenuVo放到最终list集合
            finalMenuList.add(oneMenuVo);
        });
        //返回最终数据
        return finalMenuList;
    }

    //获取所有一级菜单
    @Override
    public List<WechatMenu> findMenuOneInfo() {
        QueryWrapper<WechatMenu> wrapper=new QueryWrapper<>();
        wrapper.eq("parent_id",0);
        List<WechatMenu> list = baseMapper.selectList(wrapper);
        return list;
    }

    //同步公众号菜单方法
    //https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
    //https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
    @Override
    public void syncMenu() {

        //获取所有菜单数据
        List<WechatMenuVo> menuVoList = this.findMenuInfo();
        //封装button里面的结构,数组格式
        JSONArray buttonList=new JSONArray();
        menuVoList.forEach(oneMenuVo -> {
            //json对象  一级菜单
            JSONObject one=new JSONObject();
            one.put("name",oneMenuVo.getName());
            one.put("type",oneMenuVo.getType());
            one.put("key",oneMenuVo.getMenuKey());
            one.put("url",oneMenuVo.getUrl());
            //json数组   二级菜单
            JSONArray subButton=new JSONArray();
            oneMenuVo.getChildren().forEach(twoMenuVo->{
                JSONObject view = new JSONObject();
                view.put("type", twoMenuVo.getType());
                if(twoMenuVo.getType().equals("view")) {
                    view.put("name", twoMenuVo.getName());
                    view.put("url", twoMenuVo.getUrl());
                } else {
                    view.put("name", twoMenuVo.getName());
                    view.put("key", twoMenuVo.getMenuKey());
                }
                subButton.add(view);
            });
            one.put("sub_button",subButton);
            buttonList.add(one);
        });
        //封装最外层的button部分
        JSONObject button=new JSONObject();
        button.put("button",buttonList);

        try {
            String menuId =
                    this.wxMpService.getMenuService().menuCreate(button.toJSONString());
            log.info("menuId:{}",menuId);
        } catch (WxErrorException e) {
            e.printStackTrace();
            Result.error(20001,"公众号菜单同步失败");
        }
    }

    //公众号菜单删除
    @Override
    public void removeMenu() {
        try {
            wxMpService.getMenuService().menuDelete();
        } catch (WxErrorException e) {
            e.printStackTrace();
            Result.error(20001,"公众号菜单删除失败");
        }
    }
}

8、controller类

import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.menu.entity.WechatMenu;
import org.jeecg.modules.menu.service.IWechatMenuService;
import org.jeecg.modules.menu.utils.ConstantPropertiesUtil;
import org.jeecg.modules.menu.utils.HttpClientUtil;
import org.jeecg.modules.menu.vo.WechatMenuVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/admin/wechat/menu")
public class WechatMenuController {

    @Autowired
    private IWechatMenuService menuService;

    //公众号菜单删除
    @DeleteMapping("/removeMenu")
    public Result<?> removeMenu(){
        menuService.removeMenu();
        return Result.ok(null);
    }

    //同步菜单方法
    @GetMapping("/syncMenu")
    public Result<?> createMenu(){
        menuService.syncMenu();
        WechatMenu wechatMenu = new WechatMenu();
        return Result.OK(wechatMenu.getName());
    }

    //获取access_token
    @GetMapping("/getAccessToken")
    public Result<?> getAccessToken(){
        //拼接请求地址
        StringBuffer buffer = new StringBuffer();
        buffer.append("https://api.weixin.qq.com/cgi-bin/token");
        buffer.append("?grant_type=client_credential");
        buffer.append("&appid=%s");
        buffer.append("&secret=%s");
        //设置路径中的参数
        String url = String.format(buffer.toString(),
                ConstantPropertiesUtil.ACCESS_KEY_ID,
                ConstantPropertiesUtil.ACCESS_KEY_SECRET);
        try {
            //发送http请求
            String tokenString = HttpClientUtil.sendHttpGet(url);
            //获取access_token
            JSONObject jsonObject = JSONObject.parseObject(tokenString);
            String access_token = jsonObject.getString("access_token");
            //返回
            return Result.OK(access_token);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(20001,"获取access_token失败");
            //throw new GgktException(20001,"获取access_token失败");
        }
    }

    //获取所有菜单,按照一级和二级菜单封装
    @GetMapping("/findMenuInfo")
    public Result<?> findMenuInfo(){
        List<WechatMenuVo> list=menuService.findMenuInfo();
        return Result.ok(list);
    }

    //获取所有一级菜单
    @GetMapping("/findOneMenuInfo")
    public Result<?> findOneMenuInfo(){
        List<WechatMenu> list=menuService.findMenuOneInfo();
        return Result.ok(list);
    }

    @ApiOperation(value = "获取")
    @GetMapping("/get/{id}")
    public Result<?> get(@PathVariable Long id) {
        WechatMenu wechatMenu = menuService.getById(id);
        return Result.ok(wechatMenu);
    }

    @ApiOperation(value = "新增")
    @PostMapping("/save")
    public Result<?> save(@RequestBody WechatMenu wechatMenu) {
        menuService.save(wechatMenu);
        return Result.ok(null);
    }

    @ApiOperation(value = "修改")
    @PutMapping("/update")
    public Result<?> updateById(@RequestBody WechatMenu wechatMenu) {
        menuService.updateById(wechatMenu);
        return Result.OK(wechatMenu);
    }

    @ApiOperation(value = "删除")
    @DeleteMapping("/remove/{id}")
    public Result<?> remove(@PathVariable Long id) {
        menuService.removeById(id);
        return Result.ok(null);
    }

    @ApiOperation(value = "根据id列表删除")
    @DeleteMapping("/batchRemove")
    public Result<?> batchRemove(@RequestBody List<Long> idList) {
        menuService.removeByIds(idList);
        return Result.ok(null);
    }

}

总结 

1、微信公众号对自定义菜单完成创建、修改等操作的学习过程中参考了以下链接,使用postman测试均无误,在此表示感谢!

链接:微信公众号菜单管理接口开发-阿里云开发者社区 (aliyun.com)

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值