公众号开发之事件监听、自定义菜单

1、配置服务器配置信息
在这里插入图片描述
2、没有公众号的可以申请微信测试账号
在这里插入图片描述

申请地址:

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

3、校验token 监听事件

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@AllArgsConstructor
@RequestMapping("/weixin/api")
@Api(value = "测试", tags = "测试")
@ApiSort(1)
public class VatifyToken {


	// 这⾥写的token要与测试公众号Token ⼀致
	private static String token = "d2b37b3057e6edcb9a5cebc00f11e027";


	@RequestMapping(value = "/vatToken/{appId}")
	public String toVatifyToken(@PathVariable String appId, HttpServletRequest request, HttpServletResponse response) throws Exception {

		String signature = request.getParameter("signature");
		// 时间戳
		String timestamp = request.getParameter("timestamp");
		// 随机数
		String nonce = request.getParameter("nonce");
		// 随机字符串
		String echostr = request.getParameter("echostr");

		if (signature != null && checkSignature(signature, timestamp, nonce)) {
			//token 校验过后放开以下代码即可
//			response.setHeader("Content-type", "application/xml");
//			Map<String, String> mapInfo = parseXml(request);
//			JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(mapInfo));
//			if (ObjectUtils.isNotEmpty(jsonObject)) {
//				System.out.println("-------------appid-------" + appId);
//				System.out.println("-------------解密信息明文-------" + jsonObject);
//				String msgType = jsonObject.getString("MsgType");
//				String toUserName = jsonObject.getString("ToUserName");
//				String fromUserName = jsonObject.getString("FromUserName");
//				String createTime = jsonObject.getString("CreateTime");
//				String eventKey = jsonObject.getString("EventKey");
//				String event = jsonObject.getString("Event");
//				String ticket = jsonObject.getString("Ticket");
//				System.out.println("-------------openid-------" + fromUserName);
//				System.out.println("-------------msgType-------" + msgType);
//				System.out.println("-------------eventKey-------" + eventKey);
//				System.out.println("-------------event-------" + event);
//				System.out.println("-------------ticket-------" + ticket);
//				//回复,可以回复表情,蓝色可点击链接文字等
//				String content = "你好啊";
//				// 区分消息类型
//				// 普通消息
//				if ("text".equals(msgType)) { // 文本消息
//					// todo 处理文本消息
//				} else if ("image".equals(msgType)) { // 图片消息
//					// todo 处理图片消息
//				} else if ("voice".equals(msgType)) { //语音消息
//					// todo 处理语音消息
//				} else if ("video".equals(msgType)) { // 视频消息
//					// todo 处理视频消息
//				} else if ("shortvideo".equals(msgType)) { // 小视频消息
//					// todo 处理小视频消息
//				} else if ("location".equals(msgType)) { // 地理位置消息
//					// todo 处理地理位置消息
//				} else if ("link".equals(msgType)) { // 链接消息
//					// todo 处理链接消息
//				} else if ("subscribe".equals(event)) {//关注自动回复
//					content = "[Party]终于等到你!欢迎你的关注!\n" +
//						"\n" +
//						"/:rose点击<a href=\"https://www.baidu.com\">【百度】</a>搜索更多优质内容\n";
//				} else if ("unsubscribe".equals(event)) { // 取消订阅事件
//					// todo 处理取消订阅事件
//				} else if ("SCAN1".equals(event)) {
//					content = "<a href='' data-miniprogram-path='/pages/home/index?orderId=WS220908164444014R6XDZF' data-miniprogram-appid='xxx'>滴滴打车丨天天低至8折></a>";
//				} else if ("LOCATION".equals(event)) { // 上报地理位置事件
//					// todo 处理上报地理位置事件
//				} else if ("CLICK".equals(event)) { // 点击菜单拉取消息时的事件推送事件
//					content = "<a href='' data-miniprogram-path='/pages/home/index?orderId=WS220908164444014R6XDZF' data-miniprogram-appid='xxx'>滴滴打车丨天天低至8折></a>";
//				} else if ("VIEW".equals(event)) { // 点击菜单跳转链接时的事件推送
//					// todo 处理点击菜单跳转链接时的事件推送
//				} else {
//
//				}
//				response.setCharacterEncoding("UTF-8");
//				response.getWriter().write(getXmlReturnMsg(fromUserName, toUserName, System.currentTimeMillis() / 1000, content));
//			}
			return echostr;
		}
		return null;

	}

	/**
	 * 构建普通消息
	 *
	 * @param toUser     接收方账号(openId)
	 * @param fromUser   开发者账号
	 * @param createTime 创建时间,整型
	 * @param content    内容
	 * @return 回复消息
	 */
	public String getXmlReturnMsg(String toUser, String fromUser, Long createTime, String content) {
		return "<xml>\n" +
			"  <ToUserName><![CDATA[" + toUser + "]]></ToUserName>\n" +
			"  <FromUserName><![CDATA[" + fromUser + "]]></FromUserName>\n" +
			"  <CreateTime>" + createTime + "</CreateTime>\n" +
			"  <MsgType><![CDATA[text]]></MsgType>\n" +
			"  <Content><![CDATA[" + content + "]]></Content>\n" +
			"</xml>";
	}


	/**
	 * dom4j 解析 xml 转换为 map
	 *
	 * @param request
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
		// 将解析结果存储在HashMap中
		Map<String, String> map = new HashMap<String, String>();
		// 从request中取得输入流
		InputStream inputStream = request.getInputStream();
		// 读取输入流
		SAXReader reader = new SAXReader();
		Document document = reader.read(inputStream);
		// 得到xml根元素
		Element root = document.getRootElement();
		// 得到根元素的所有子节点
		List<Element> elementList = root.elements();

		// 遍历所有子节点
		for (Element e : elementList)
			map.put(e.getName(), e.getText());

		// 释放资源
		inputStream.close();
		inputStream = null;
		return map;
	}


	/**
	 * 验证签名
	 *
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	public static boolean checkSignature(String signature, String timestamp, String nonce) {
		String[] arr = new String[]{token, timestamp, nonce};
		// 将token、timestamp、nonce三个参数进⾏字典序排序
		// Arrays.sort(arr);
		sort(arr);
		sort(arr);
		StringBuilder content = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			content.append(arr[i]);
		}
		MessageDigest md = null;
		String tmpStr = null;
		try {
			md = MessageDigest.getInstance("SHA-1");
			// 将三个参数字符串拼接成⼀个字符串进⾏sha1加密
			byte[] digest = md.digest(content.toString().getBytes());
			tmpStr = byteToStr(digest);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		content = null;
		// 将sha1加密后的字符串可与signature对⽐,标识该请求来源于微信
		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
	}

	/**
	 * 将字节数组转换为⼗六进制字符串
	 *
	 * @param byteArray
	 * @return
	 */
	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);
		}
		return strDigest;
	}

	/**
	 * 将字节转换为⼗六进制字符串
	 *
	 * @param mByte
	 * @return
	 */
	private static String byteToHexStr(byte mByte) {
		char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
		char[] tempArr = new char[2];
		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = Digit[mByte & 0X0F];
		String s = new String(tempArr);
		return s;
	}

	public static void sort(String a[]) {
		for (int i = 0; i < a.length - 1; i++) {
			for (int j = i + 1; j < a.length; j++) {
				if (a[j].compareTo(a[i]) < 0) {
					String temp = a[i];
					a[i] = a[j];
					a[j] = temp;
				}
			}
		}
	}
}

4、自定义菜单
接口地址

https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html

引入依赖

 <weixin-java.version>4.1.0</weixin-java.version>
 <!--weixin-java-common-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-common</artifactId>
            <version>${weixin-java.version}</version>
        </dependency>
        <!--weixin-java-mp-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>${weixin-java.version}</version>
        </dependency>
        <!--weixin-java-open-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-open</artifactId>
            <version>${weixin-java.version}</version>
        </dependency>
        <!--weixin-java-miniapp-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-miniapp</artifactId>
            <version>${weixin-java.version}</version>
        </dependency>

sql:

CREATE TABLE `wx_menu` (
  `id` varchar(32) NOT NULL COMMENT '菜单ID(click、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select:保存key)',
  `sort` int(11) DEFAULT '1' COMMENT '排序值',
  `parent_id` varchar(32) DEFAULT NULL COMMENT '父菜单ID',
  `app_id` varchar(32) DEFAULT NULL COMMENT '应用ID',
  `type` char(20) DEFAULT NULL COMMENT '菜单类型click、view、miniprogram、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select、media_id、view_limited等',
  `name` varchar(20) DEFAULT NULL COMMENT '菜单名',
  `url` varchar(500) DEFAULT NULL COMMENT 'view、miniprogram保存链接',
  `ma_app_id` varchar(32) DEFAULT NULL COMMENT '小程序的appid',
  `ma_page_path` varchar(100) DEFAULT NULL COMMENT '小程序的页面路径',
  `rep_type` char(10) DEFAULT NULL COMMENT '回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文)',
  `rep_content` text COMMENT 'Text:保存文字',
  `rep_media_id` varchar(64) DEFAULT NULL COMMENT 'imge、voice、news、video:mediaID',
  `rep_name` varchar(100) DEFAULT NULL COMMENT '素材名、视频和音乐的标题',
  `rep_desc` varchar(200) DEFAULT NULL COMMENT '视频和音乐的描述',
  `rep_url` varchar(500) DEFAULT NULL COMMENT '链接',
  `rep_hq_url` varchar(500) DEFAULT NULL COMMENT '高质量链接',
  `rep_thumb_media_id` varchar(64) DEFAULT NULL COMMENT '缩略图的媒体id',
  `rep_thumb_url` varchar(500) DEFAULT NULL COMMENT '缩略图url',
  `content` mediumtext COMMENT '图文消息的内容',
  `menu_rule_id` varchar(32) DEFAULT NULL COMMENT '菜单组ID',
  `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
  `create_user` bigint(64) DEFAULT NULL COMMENT '创建人',
  `create_dept` bigint(64) DEFAULT NULL COMMENT '创建部门',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user` bigint(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `status` int(2) DEFAULT NULL COMMENT '状态1初始2已付款3部分退款4全额退款5订单关闭',
  `is_deleted` int(2) DEFAULT '0' COMMENT '是否已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='自定义菜单表';


CREATE TABLE `wx_menu_rule` (
  `id` varchar(32) NOT NULL DEFAULT '' COMMENT 'PK',
  `app_id` varchar(32) DEFAULT NULL COMMENT 'appId',
  `name` varchar(32) DEFAULT NULL COMMENT '名称',
  `menu_type` char(2) CHARACTER SET utf8 DEFAULT NULL COMMENT '菜单类型(1:普通菜单,2:个性化菜单)',
  `menu_id` varchar(32) DEFAULT NULL COMMENT 'menuid',
  `tag_id` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '用户标签的id',
  `sex` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '性别:男(1)女(2)',
  `client_platform_type` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端版本,当前只具体到系统型号:IOS(1), Android(2),Others(3)',
  `country` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '国家信息',
  `province` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '省份信息',
  `city` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '城市信息',
  `language` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '语言信息',
  `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
  `create_user` bigint(64) DEFAULT NULL COMMENT '创建人',
  `create_dept` bigint(64) DEFAULT NULL COMMENT '创建部门',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user` bigint(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `status` int(2) DEFAULT NULL COMMENT '状态1初始2已付款3部分退款4全额退款5订单关闭',
  `is_deleted` int(2) DEFAULT '0' COMMENT '是否已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='微信自定义菜单分组';

需要引入的代码

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springblade.modules.manage.entity.WxConfig;
import org.springblade.modules.manage.service.IWxConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 公众号Configuration
 * @author www.joolun.com
 *
 */
@Slf4j
@Configuration
public class WxMpConfiguration {

	private static RedisTemplate redisTemplate;
	private static IWxConfigService iWxConfigService;

	@Autowired
	public WxMpConfiguration( RedisTemplate redisTemplate, IWxConfigService iWxConfigService) {
		this.redisTemplate = redisTemplate;
		this.iWxConfigService = iWxConfigService;
	}

	/**
	 *  获取WxMpService
	 * @param appId
	 * @return
	 */
	public static WxMpService getMpService(String appId) {
		WxMpService wxMpService = null;
		WxConfig wxApp = iWxConfigService.getOne(Wrappers.<WxConfig>lambdaQuery().eq(WxConfig::getAppId, appId));
		if(wxApp!=null && wxApp.getStatus()==1) {
			if(wxApp.getIsComponent().equals("1")){//第三方授权账号
//				wxMpService = WxOpenConfiguration.getOpenService().getWxOpenComponentService().getWxMpServiceByAppid(appId);
			}else{
				WxMpInRedisConfigStorage configStorage = new WxMpInRedisConfigStorage(redisTemplate);
				configStorage.setAppId(wxApp.getAppId());
				configStorage.setSecret(wxApp.getSecret());
				configStorage.setToken(wxApp.getToken());
				configStorage.setAesKey(wxApp.getAesKey());
				wxMpService = new WxMpServiceImpl();
				wxMpService.setWxMpConfigStorage(configStorage);
			}
		}
		return wxMpService;
    }
}
package org.springblade.modules.api.common.mp;

import me.chanjar.weixin.common.enums.TicketType;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * 基于Redis的微信配置provider.
 *
 * @author www.joolun.com
 */
@SuppressWarnings("serial")
public class WxMpInRedisConfigStorage extends WxMpDefaultConfigImpl {

	public static final String ACCESS_TOKEN_KEY = "wx:mp:access_token:";

	public final static String JSAPI_TICKET_KEY = "wx:mp:jsapi_ticket:";

	public final static String CARDAPI_TICKET_KEY = "wx:mp:cardapi_ticket:";

	private final RedisTemplate<String, String> redisTemplate;

	public WxMpInRedisConfigStorage(RedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	private String accessTokenKey;

	private String jsapiTicketKey;

	private String cardapiTicketKey;

	/**
	 * 每个公众号生成独有的存储key.
	 */
	@Override
	public void setAppId(String appId) {
		super.setAppId(appId);
		this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId);
		this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(appId);
		this.cardapiTicketKey = CARDAPI_TICKET_KEY.concat(appId);
	}

	/**
	 *
	 * @param type
	 * @return
	 */
	private String getTicketRedisKey(TicketType type) {
		return String.format("wx:mp:ticket:key:%s:%s", this.appId, type.getCode());
	}

	/**
	 *
	 * @return
	 */
	@Override
	public String getAccessToken() {
		return redisTemplate.opsForValue().get(this.accessTokenKey);
	}

	/**
	 *
	 * @return
	 */
	@Override
	public boolean isAccessTokenExpired() {
		return redisTemplate.getExpire(accessTokenKey) < 2;
	}

	/**
	 *
	 * @param accessToken
	 * @param expiresInSeconds
	 */
	@Override
	public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
		redisTemplate.opsForValue().set(this.accessTokenKey, accessToken, expiresInSeconds - 200, TimeUnit.SECONDS);
	}

	/**
	 *
	 */
	@Override
	public void expireAccessToken() {
		redisTemplate.expire(this.accessTokenKey, 0, TimeUnit.SECONDS);
	}

	/**
	 *
	 * @param type
	 * @return
	 */
	@Override
	public String getTicket(TicketType type) {
		return redisTemplate.opsForValue().get(this.getTicketRedisKey(type));
	}

	/**
	 *
	 * @param type
	 * @return
	 */
	@Override
	public boolean isTicketExpired(TicketType type) {
		return redisTemplate.getExpire(this.getTicketRedisKey(type)) < 2;
	}

	/**
	 *
	 * @param type
	 * @param jsapiTicket
	 * @param expiresInSeconds
	 */
	@Override
	public synchronized void updateTicket(TicketType type, String jsapiTicket, int expiresInSeconds) {
		redisTemplate.opsForValue().set(this.getTicketRedisKey(type), jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS);
	}

	/**
	 *
	 * @param type
	 */
	@Override
	public void expireTicket(TicketType type) {
		redisTemplate.expire(this.getTicketRedisKey(type), 0, TimeUnit.SECONDS);
	}

	/**
	 *
	 * @return
	 */
	@Override
	public String getJsapiTicket() {
		return redisTemplate.opsForValue().get(this.jsapiTicketKey);
	}

	/**
	 *
	 * @return
	 */
	@Override
	public String getCardApiTicket() {
		return redisTemplate.opsForValue().get(cardapiTicketKey);
	}
}
package org.springblade.modules.manage.controller;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springblade.common.constant.WxReturnCode;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.tool.api.R;
import org.springblade.modules.manage.service.IWxMenuService;
import org.springframework.web.bind.annotation.*;

/**
 * 自定义菜单表 控制器
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("weixin/wxmenu")
@Api(value = "自定义菜单表", tags = "自定义菜单表接口")
public class WxMenuController extends BladeController {

	private final IWxMenuService wxMenuService;


	/**
	 * 通过appId查询自定义菜单
	 *
	 * @param appId
	 * @param menuRuleId
	 * @return R
	 */
	@ApiOperation(value = "通过appId查询自定义菜单")
	@GetMapping("/list")
	public R getWxMenuButton(String appId, String menuRuleId) {
		return R.data(wxMenuService.getWxMenuButton(appId, menuRuleId));
	}

	/**
	 * 保存并发布菜单
	 *
	 * @param
	 * @return R
	 */
	@ApiOperation(value = "保存并发布菜单")
	@ApiLog("保存并发布菜单")
	@PostMapping("/release")
	public R saveAndRelease(@RequestBody String data) {
		JSONObject jSONObject = JSONUtil.parseObj(data);
		String strWxMenu = jSONObject.getStr("strWxMenu");
		String appId = jSONObject.getStr("appId");
		try {
			return R.data(wxMenuService.saveAndRelease(appId, strWxMenu));
		} catch (WxErrorException e) {
			e.printStackTrace();
			log.error("发布自定义菜单失败appID:" + appId + ":" + e.getMessage());
			return WxReturnCode.wxErrorExceptionHandler(e);
		}
	}

	/**
	 * 删除菜单
	 *
	 * @param ruleId
	 * @return R
	 */
	@ApiOperation(value = "删除菜单")
	@ApiLog("删除微信菜单")
	@DeleteMapping("/{ruleId}")
	public R removeByRuleId(@PathVariable String ruleId) {
		try {
			wxMenuService.removeByRuleId(ruleId);
			return R.success("操作成功");
		} catch (WxErrorException e) {
			e.printStackTrace();
			log.error("删除微信菜单失败ruleId:" + ruleId + ":" + e.getMessage());
			return WxReturnCode.wxErrorExceptionHandler(e);
		}
	}
}
package org.springblade.modules.manage.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springblade.modules.manage.entity.WxMenu;
import org.springblade.modules.manage.entity.WxMenuRule;
import org.springblade.modules.manage.vo.WxMenuVO;

import java.io.Serializable;

/**
 * 自定义菜单表 服务类
 *
 * @author BladeX
 * @since 2021-10-25
 */
public interface IWxMenuService extends IService<WxMenu> {

	/**
	 * 自定义分页
	 *
	 * @param page
	 * @param wxMenu
	 * @return
	 */
	IPage<WxMenuVO> selectWxMenuPage(IPage<WxMenuVO> page, WxMenuVO wxMenu);

	/***
	 * 获取WxApp下的菜单
	 * @param appId
	 * @param menuRuleId
	 * @return
	 */
	String getWxMenuButton(String appId, String menuRuleId);

	/**
	 * 保存并发布菜单
	 * @param
	 */
	WxMenuRule saveAndRelease(String appId, String strWxMenu) throws WxErrorException;

	/**
	 * 删除菜单
	 * @param ruleId
	 * @return
	 */
	void removeByRuleId(Serializable ruleId) throws WxErrorException;
}
package org.springblade.modules.manage.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.springblade.common.constant.CommonConstants;
import org.springblade.common.constant.ConfigConstant;
import org.springblade.modules.api.common.mp.WxMpConfiguration;
import org.springblade.modules.manage.entity.*;
import org.springblade.modules.manage.mapper.WxMenuMapper;
import org.springblade.modules.manage.service.IWxConfigService;
import org.springblade.modules.manage.service.IWxMenuRuleService;
import org.springblade.modules.manage.service.IWxMenuService;
import org.springblade.modules.manage.vo.WxMenuVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 自定义菜单表 服务实现类
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Service
@AllArgsConstructor
public class WxMenuServiceImpl extends ServiceImpl<WxMenuMapper, WxMenu> implements IWxMenuService {

	@Override
	public IPage<WxMenuVO> selectWxMenuPage(IPage<WxMenuVO> page, WxMenuVO wxMenu) {
		return page.setRecords(baseMapper.selectWxMenuPage(page, wxMenu));
	}

	private final IWxConfigService iWxConfigService;
	private final IWxMenuRuleService wxMenuRuleService;
	/***
	 * 获取WxApp下的菜单树结构
	 * @param appId
	 * @return
	 */
	@Override
	public String getWxMenuButton(String appId, String menuRuleId) {
		MenuMp menu = new MenuMp();
		//查询菜单组
		WxMenuRule wxMenuRule = wxMenuRuleService.getById(menuRuleId);
		if(wxMenuRule == null){
			return null;
		}
		MenuRule menuRule = new MenuRule();
		BeanUtil.copyProperties(wxMenuRule,menuRule);
		menuRule.setTag_id(wxMenuRule.getTagId());
		menu.setMatchrule(menuRule);
		//查出一级菜单
		List<WxMenu> listWxMenu = baseMapper.selectList(Wrappers
			.<WxMenu>query().lambda()
			.eq(WxMenu::getAppId, appId)
			.eq(WxMenu::getParentId, CommonConstants.PARENT_ID)
			.eq(WxMenu::getMenuRuleId, menuRuleId)
			.orderByAsc(WxMenu::getSort));
		List<MenuButton> listMenuButton = new ArrayList<>();
		MenuButton menuButton;
		List<MenuButton> subButtons;
		MenuButton subButton;
		if(listWxMenu!=null&&listWxMenu.size()>0){
			for(WxMenu wxMenu : listWxMenu){
				menuButton = new MenuButton();
				menuButton.setName(wxMenu.getName());
				String type = wxMenu.getType();
				if(StringUtils.isNotBlank(type)){//无二级菜单
					menuButton.setType(type);
					setButtonValue(menuButton,wxMenu);
				}else{//有二级菜单
					//查出二级菜单
					List<WxMenu> listWxMenu1 = baseMapper.selectList(Wrappers
						.<WxMenu>query().lambda()
						.eq(WxMenu::getAppId, appId)
						.eq(WxMenu::getParentId,wxMenu.getId())
						.eq(WxMenu::getMenuRuleId, menuRuleId)
						.orderByAsc(WxMenu::getSort));
					subButtons = new ArrayList<>();
					for(WxMenu wxMenu1 : listWxMenu1){
						subButton = new MenuButton();
						String type1 = wxMenu1.getType();
						subButton.setName(wxMenu1.getName());
						subButton.setType(type1);
						setButtonValue(subButton,wxMenu1);
						subButtons.add(subButton);
					}
					menuButton.setSub_button(subButtons);
				}
				listMenuButton.add(menuButton);
			}
		}
		menu.setButton(listMenuButton);
		return menu.toString();
	}

	void setButtonValue(MenuButton menuButton,WxMenu wxMenu){
		menuButton.setKey(wxMenu.getId());
		menuButton.setUrl(wxMenu.getUrl());
//		menuButton.setContent(wxMenu.getContent());
		menuButton.setRepContent(wxMenu.getRepContent());
		menuButton.setMedia_id(wxMenu.getRepMediaId());
		menuButton.setRepType(wxMenu.getRepType());
		menuButton.setRepName(wxMenu.getRepName());
		menuButton.setAppid(wxMenu.getMaAppId());
		menuButton.setPagepath(wxMenu.getMaPagePath());
		menuButton.setUrl(wxMenu.getUrl());
		menuButton.setRepUrl(wxMenu.getRepUrl());
		menuButton.setRepHqUrl(wxMenu.getRepHqUrl());
		menuButton.setRepDesc(wxMenu.getRepDesc());
		menuButton.setRepThumbMediaId(wxMenu.getRepThumbMediaId());
		menuButton.setRepThumbUrl(wxMenu.getRepThumbUrl());
	}

	/**
	 * 保存并发布菜单
	 * @param
	 * {
	 *     "appId":"wx54b08bd2fcb3a25b",
	 *     "strWxMenu":{
	 *      "matchrule":{
	 *          "tag_id":1,
	 *          "menuType":"1",
	 *          "name":"测试菜单"
	 *      },
	 *      "button":[
	 *      {
	 *           "type":"click",
	 *           "name":"测试菜单",
	 *           "key":"V1001_TODAY_MUSIC"
	 *       },
	 *       {
	 *            "name":"菜单",
	 *            "sub_button":[
	 *             {
	 *                "type":"click",
	 *                "name":"赞一下我们",
	 *                "key":"V1001_GOOD"
	 *             }]
	 *        }]
	 *  }
	 * }
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public WxMenuRule saveAndRelease(String appId , String strWxMenu) throws WxErrorException {
		MenuMp menu = MenuMp.fromJson(strWxMenu);
		MenuRule menuRule = menu.getMatchrule();
		if(menuRule != null){
			WxMenuRule wxMenuRule = new WxMenuRule();
			BeanUtil.copyProperties(menuRule,wxMenuRule);
			wxMenuRule.setTagId(menuRule.getTag_id());
			//保存或修改菜单分组
			wxMenuRule.setAppId(appId);
			wxMenuRuleService.saveOrUpdate(wxMenuRule);
			List<MenuButton> buttons = menu.getButton();
			if(StrUtil.isNotBlank(menuRule.getId())){
				//先删除菜单
				baseMapper.delete(Wrappers
					.<WxMenu>query().lambda()
					.eq(WxMenu::getAppId, appId)
					.eq(WxMenu::getMenuRuleId,wxMenuRule.getId()));
				if(ConfigConstant.WX_MENU_TYPE_2.equals(wxMenuRule.getMenuType())){
					//删除微信已有个性化菜单
					wxMenuRule = wxMenuRuleService.getById(wxMenuRule.getId());
					WxMpConfiguration.getMpService(wxMenuRule.getAppId()).getMenuService().menuDelete(wxMenuRule.getMenuId());
				}
			}
			WxConfig wxApp = iWxConfigService.getOne(Wrappers.<WxConfig>lambdaQuery().eq(WxConfig::getAppId, appId));
			WxMenu wxMenu = null;
			WxMenu wxMenu1 = null;
			int sort1 = 1;
			int sort2 = 1;
			//入库菜单
			for(MenuButton menuButton : buttons){
				wxMenu = new WxMenu();
				setWxMenuValue(wxMenu,menuButton,wxApp);
				wxMenu.setSort(sort1);
				wxMenu.setParentId(CommonConstants.PARENT_ID);
				wxMenu.setMenuRuleId(wxMenuRule.getId());
				baseMapper.insert(wxMenu);
				menuButton.setKey(wxMenu.getId());
				sort1++;
				for(MenuButton menuButton1 : menuButton.getSub_button()){
					wxMenu1 = new WxMenu();
					setWxMenuValue(wxMenu1,menuButton1,wxApp);
					wxMenu1.setSort(sort2);
					wxMenu1.setParentId(wxMenu.getId());
					wxMenu1.setMenuRuleId(wxMenuRule.getId());
					baseMapper.insert(wxMenu1);
					menuButton1.setKey(wxMenu1.getId());
					sort2++;
				}
			}
			//创建菜单
			if(ConfigConstant.WX_MENU_TYPE_1.equals(wxMenuRule.getMenuType())){
				menu.setMatchrule(null);
			}
			String menuId = WxMpConfiguration.getMpService(appId).getMenuService().menuCreate(menu.toString());
			//个性化菜单保存菜单id
			if(ConfigConstant.WX_MENU_TYPE_2.equals(wxMenuRule.getMenuType())){
				wxMenuRule.setMenuId(menuId);
				wxMenuRuleService.updateById(wxMenuRule);
			}
			return wxMenuRule;
		}
		return null;
	}

	void setWxMenuValue(WxMenu wxMenu,MenuButton menuButton,WxConfig wxConfig){
		wxMenu.setId(menuButton.getKey());
		wxMenu.setAppId(wxConfig.getAppId());
		wxMenu.setType(menuButton.getType());
		wxMenu.setName(menuButton.getName());
		wxMenu.setUrl(menuButton.getUrl());
		wxMenu.setRepMediaId(menuButton.getMedia_id());
		wxMenu.setRepType(menuButton.getRepType());
		wxMenu.setRepName(menuButton.getRepName());
		wxMenu.setMaAppId(menuButton.getAppid());
		wxMenu.setMaPagePath(menuButton.getPagepath());
		wxMenu.setRepContent(menuButton.getRepContent());
//		wxMenu.setContent(menuButton.getContent());
		wxMenu.setRepUrl(menuButton.getRepUrl());
		wxMenu.setRepHqUrl(menuButton.getRepHqUrl());
		wxMenu.setRepDesc(menuButton.getRepDesc());
		wxMenu.setRepThumbMediaId(menuButton.getRepThumbMediaId());
		wxMenu.setRepThumbUrl(menuButton.getRepThumbUrl());
		menuButton.setRepUrl(null);
		menuButton.setRepDesc(null);
		menuButton.setRepHqUrl(null);
		menuButton.setContent(null);
		menuButton.setRepContent(null);
		menuButton.setRepType(null);
		menuButton.setRepName(null);
		menuButton.setRepThumbMediaId(null);
		menuButton.setRepThumbUrl(null);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void removeByRuleId(Serializable ruleId) throws WxErrorException {
		WxMenuRule wxMenuRule = wxMenuRuleService.getById(ruleId);
		if(ConfigConstant.WX_MENU_TYPE_1.equals(wxMenuRule.getMenuType())){//删除普通菜单,要删除所有个性化菜单
			//数据库删除
			wxMenuRuleService.remove(Wrappers
				.<WxMenuRule>query().lambda()
				.eq(WxMenuRule::getAppId, wxMenuRule.getAppId()));
			baseMapper.delete(Wrappers
				.<WxMenu>query().lambda()
				.eq(WxMenu::getAppId, wxMenuRule.getAppId()));
			//微信删除
			WxMpConfiguration.getMpService(wxMenuRule.getAppId()).getMenuService().menuDelete();
		}else{
			//数据库删除
			wxMenuRuleService.removeById(wxMenuRule.getId());
			baseMapper.delete(Wrappers
				.<WxMenu>query().lambda()
				.eq(WxMenu::getAppId, wxMenuRule.getAppId())
				.eq(WxMenu::getMenuRuleId,wxMenuRule.getId()));
			//微信删除
			WxMpConfiguration.getMpService(wxMenuRule.getAppId()).getMenuService().menuDelete(wxMenuRule.getMenuId());
		}
	}
}
/**
 * 自定义菜单表 Mapper 接口
 *
 * @author BladeX
 * @since 2021-10-25
 */
public interface WxMenuMapper extends BaseMapper<WxMenu> {

	/**
	 * 自定义分页
	 *
	 * @param page
	 * @param wxMenu
	 * @return
	 */
	List<WxMenuVO> selectWxMenuPage(IPage page, WxMenuVO wxMenu);

}
<?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.springblade.modules.manage.mapper.WxMenuMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="wxMenuResultMap" type="org.springblade.modules.manage.entity.WxMenu">
        <result column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_dept" property="createDept"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="sort" property="sort"/>
        <result column="parent_id" property="parentId"/>
        <result column="app_id" property="appId"/>
        <result column="type" property="type"/>
        <result column="name" property="name"/>
        <result column="url" property="url"/>
        <result column="ma_app_id" property="maAppId"/>
        <result column="ma_page_path" property="maPagePath"/>
        <result column="rep_type" property="repType"/>
        <result column="rep_content" property="repContent"/>
        <result column="rep_media_id" property="repMediaId"/>
        <result column="rep_name" property="repName"/>
        <result column="rep_desc" property="repDesc"/>
        <result column="rep_url" property="repUrl"/>
        <result column="rep_hq_url" property="repHqUrl"/>
        <result column="rep_thumb_media_id" property="repThumbMediaId"/>
        <result column="rep_thumb_url" property="repThumbUrl"/>
<!--        <result column="content" property="content"/>-->
        <result column="menu_rule_id" property="menuRuleId"/>
    </resultMap>


    <select id="selectWxMenuPage" resultMap="wxMenuResultMap">
        select * from wx_menu where is_deleted = 0
    </select>

</mapper>

用到的实体类vo

package org.springblade.modules.manage.entity;

import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import org.springblade.common.handler.JsonTypeHandler;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
 * 自定义菜单表实体类
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Data
@ApiModel(value = "WxMenu对象", description = "自定义菜单表")
public class WxMenu implements Serializable {

	private static final long serialVersionUID = 1L;

	@ApiModelProperty("主键id")
	@TableId(value = "id",type = IdType.ASSIGN_ID)
	private String id;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("创建人")
	private Long createUser;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("创建部门")
	private Long createDept;

	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@ApiModelProperty("创建时间")
	private Date createTime;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("更新人")
	private Long updateUser;

	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@ApiModelProperty("更新时间")
	private Date updateTime;

	@ApiModelProperty("业务状态")
	private Integer status;

	@ApiModelProperty("是否已删除")
	private Integer isDeleted;

	@ApiModelProperty("租户ID")
	private String tenantId;
	/**
	 * 排序值
	 */
	@ApiModelProperty(value = "排序值")
	private Integer sort;
	/**
	 * 父菜单ID
	 */
	@ApiModelProperty(value = "父菜单ID")
	private String parentId;
	/**
	 * 应用ID
	 */
	@ApiModelProperty(value = "应用ID")
	private String appId;
	/**
	 * 菜单类型click、view、miniprogram、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select、media_id、view_limited等
	 */
	@ApiModelProperty(value = "菜单类型click、view、miniprogram、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、pic_weixin、location_select、media_id、view_limited等")
	private String type;
	/**
	 * 菜单名
	 */
	@ApiModelProperty(value = "菜单名")
	private String name;
	/**
	 * view、miniprogram保存链接
	 */
	@ApiModelProperty(value = "view、miniprogram保存链接")
	private String url;
	/**
	 * 小程序的appid
	 */
	@ApiModelProperty(value = "小程序的appid")
	private String maAppId;
	/**
	 * 小程序的页面路径
	 */
	@ApiModelProperty(value = "小程序的页面路径")
	private String maPagePath;
	/**
	 * 回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文)
	 */
	@ApiModelProperty(value = "回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文)")
	private String repType;
	/**
	 * Text:保存文字
	 */
	@ApiModelProperty(value = "Text:保存文字")
	private String repContent;
	/**
	 * imge、voice、news、video:mediaID
	 */
	@ApiModelProperty(value = "imge、voice、news、video:mediaID")
	private String repMediaId;
	/**
	 * 素材名、视频和音乐的标题
	 */
	@ApiModelProperty(value = "素材名、视频和音乐的标题")
	private String repName;
	/**
	 * 视频和音乐的描述
	 */
	@ApiModelProperty(value = "视频和音乐的描述")
	private String repDesc;
	/**
	 * 链接
	 */
	@ApiModelProperty(value = "链接")
	private String repUrl;
	/**
	 * 高质量链接
	 */
	@ApiModelProperty(value = "高质量链接")
	private String repHqUrl;
	/**
	 * 缩略图的媒体id
	 */
	@ApiModelProperty(value = "缩略图的媒体id")
	private String repThumbMediaId;
	/**
	 * 缩略图url
	 */
	@ApiModelProperty(value = "缩略图url")
	private String repThumbUrl;
	/**
	 * 图文消息的内容
	 */
	@TableField(typeHandler = JsonTypeHandler.class)
//	private JSONObject content;
	/**
	 * 菜单组ID
	 */
	@ApiModelProperty(value = "菜单组ID")
	private String menuRuleId;


}
package org.springblade.modules.manage.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
 * 微信自定义菜单分组实体类
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Data
@ApiModel(value = "WxMenuRule对象", description = "微信自定义菜单分组")
public class WxMenuRule implements Serializable {

	private static final long serialVersionUID = 1L;

	@ApiModelProperty("主键id")
	@TableId(value = "id", type = IdType.ASSIGN_ID)
	private String id;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("创建人")
	private Long createUser;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("创建部门")
	private Long createDept;

	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@ApiModelProperty("创建时间")
	private Date createTime;

	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty("更新人")
	private Long updateUser;

	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	@ApiModelProperty("更新时间")
	private Date updateTime;

	@ApiModelProperty("业务状态")
	private Integer status;

	@TableLogic
	@ApiModelProperty("是否已删除")
	private Integer isDeleted;

	@ApiModelProperty("租户ID")
	private String tenantId;
	/**
	 * appId
	 */
	@ApiModelProperty(value = "appId")
	private String appId;
	/**
	 * 名称
	 */
	@ApiModelProperty(value = "名称")
	private String name;
	/**
	 * 菜单类型(1:普通菜单,2:个性化菜单)
	 */
	@ApiModelProperty(value = "菜单类型(1:普通菜单,2:个性化菜单)")
	private String menuType;
	/**
	 * menuid
	 */
	@ApiModelProperty(value = "menuid")
	private String menuId;
	/**
	 * 用户标签的id
	 */
	@ApiModelProperty(value = "用户标签的id")
	private String tagId;
	/**
	 * 性别:男(1)女(2)
	 */
	@ApiModelProperty(value = "性别:男(1)女(2)")
	private String sex;
	/**
	 * 客户端版本,当前只具体到系统型号:IOS(1), Android(2),Others(3)
	 */
	@ApiModelProperty(value = "客户端版本,当前只具体到系统型号:IOS(1), Android(2),Others(3)")
	private String clientPlatformType;
	/**
	 * 国家信息
	 */
	@ApiModelProperty(value = "国家信息")
	private String country;
	/**
	 * 省份信息
	 */
	@ApiModelProperty(value = "省份信息")
	private String province;
	/**
	 * 城市信息
	 */
	@ApiModelProperty(value = "城市信息")
	private String city;
	/**
	 * 语言信息
	 */
	@ApiModelProperty(value = "语言信息")
	private String language;


}
package org.springblade.modules.manage.entity;

import lombok.Data;

/**
 * 微信自定义菜单分组
 *
 * @author www.joolun.com
 * @date 2020-02-22 19:34:22
 */
@Data
public class MenuRule {
	private static final long serialVersionUID=1L;

	/**
	 * 用户标签的id
	 */
	private String tag_id;

	private String id;

	private String menuType;

	private String name;
}

package org.springblade.modules.manage.entity;


import cn.hutool.json.JSONUtil;
import lombok.Data;

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

/**
 * 自定义菜单模型
 *
 * @author www.joolun.com
 */
@Data
public class MenuMp implements Serializable {
	private static final long serialVersionUID = -7083914585539687746L;

	private List<MenuButton> button = new ArrayList<>();

	private MenuRule matchrule;

	/**
	 * 反序列化
	 */
	public static MenuMp fromJson(String json) {
		return JSONUtil.parseObj(json).toBean(MenuMp.class);
	}

	public String toJson() {
		return JSONUtil.toJsonStr(this);
	}

	@Override
	public String toString() {
		return this.toJson();
	}

}

package org.springblade.modules.manage.entity;

import cn.hutool.json.JSONObject;
import lombok.Data;

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

/**
 * 自定义菜单模型
 *
 * @author www.joolun.com
 */
@Data
public class MenuButton implements Serializable {

	private String type;

	private String name;

	private String key;

	private String url;

	private String media_id;

	private String appid;

	private String pagepath;

	private List<MenuButton> sub_button = new ArrayList();
	/**
	 * content内容
	 */
	private JSONObject content;

	private String repContent;
	/**
	 * 消息类型
	 */
	private String repType;
	/**
	 * 消息名
	 */
	private String repName;
	/**
	 * 视频和音乐的描述
	 */
	private String repDesc;
	/**
	 * 视频和音乐的描述
	 */
	private String repUrl;
	/**
	 * 高质量链接
	 */
	private String repHqUrl;
	/**
	 * 缩略图的媒体id
	 */
	private String repThumbMediaId;
	/**
	 * 缩略图url
	 */
	private String repThumbUrl;
}

package org.springblade.modules.manage.vo;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.modules.manage.entity.WxMenuRule;

/**
 * 微信自定义菜单分组视图实体类
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "WxMenuRuleVO对象", description = "微信自定义菜单分组")
public class WxMenuRuleVO extends WxMenuRule {
	private static final long serialVersionUID = 1L;

}

package org.springblade.modules.manage.vo;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.modules.manage.entity.WxMenu;

/**
 * 自定义菜单表视图实体类
 *
 * @author BladeX
 * @since 2021-10-25
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "WxMenuVO对象", description = "自定义菜单表")
public class WxMenuVO extends WxMenu {
	private static final long serialVersionUID = 1L;

}

保存菜单请求数据格式

{
	      "appId":"wx54b08bd2fcbxxxx",
	      "strWxMenu":{
	       "matchrule":{
	           "id":"1572852254619709441",
	          "tag_id":2,
	          "menuType":"1",
	          "name":"测试"
	       },
	       "button":[
	            {
                "type": "scancode_push", 
                "name": "扫扫", 
                "key": "V1004_TODAY_SCAN", 
                "sub_button": [ ],
                 "url":"http://www.soso.com/"
            }, 
	        {
	             "name":"菜单",
	             "sub_button":[
	              {
	                 "type":"view",
	                 "name":"测试",
	                 "key":"V1001_GOOD",
	                 "url":"http://www.soso.com/"
	              },
	               {
	                 "type":"click",
	                 "name":"点击推送",
	                 "key":"V1002_CLICK",
	                 "url":"http://www.soso.com/"
	              }
	              ]
	         }
	         ]
	   }
	  }

实现效果

在这里插入图片描述

v: OurBestTime

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值