java微信公众号菜单api动态创建

最近做了一个公众号菜单管理,需求是后台添加菜单后公众号同步更新,菜单分为一级菜单,二级菜单,二级菜单挂在一级菜单上。效果图如下

菜单sql

CREATE TABLE `wechat_client_bottom_menu` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `company_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '油企名称',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
  `menu_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单编码',
  `func_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '类型',
  `menu_level` int NOT NULL COMMENT '菜单等级',
  `order_num` int DEFAULT NULL COMMENT '排序',
  `status` int DEFAULT '0' COMMENT '状态 0未发布 1发布',
  `created_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',
  `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` int DEFAULT '0' COMMENT '删除标识符0正常 1删除',
  `parent_id` bigint DEFAULT NULL COMMENT '父菜单id',
  `parent_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '菜单路径',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='微信公众号底部菜单';

微信公众号菜单对象


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.knight.common.core.annotation.AddGroup;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * <p>
 * 微信公众号底部菜单
 * </p>
 *
 * @author ws
 * @since 2022-02-28
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="WechatClientBottomMenu对象", description="微信公众号底部菜单")
public class WechatClientBottomMenu implements Serializable {

    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "油企名称")
    @TableField(exist = false)
    private String companyName;

    @ApiModelProperty(value = "上级菜单名称")
    @TableField(exist = false)
    private String parentName;

    @ApiModelProperty(value = "油企编码")
    private String companyNo;

    @ApiModelProperty(value = "菜单名称")
    @NotBlank(groups = {AddGroup.class}, message = "菜单名称必填")
    private String name;

    @ApiModelProperty(value = "菜单编码")
    @NotBlank(groups = {AddGroup.class}, message = "菜单编码必填")
    private String menuValue;

    @ApiModelProperty(value = "类型 text、 view")
    @NotBlank(groups = {AddGroup.class}, message = "类型 text、 view必填")
    private String funcType;

    @ApiModelProperty(value = "菜单等级")
    @NotBlank(groups = {AddGroup.class}, message = "菜单等级必填")
    private Integer menuLevel;

    @ApiModelProperty(value = "排序")
    @NotBlank(groups = {AddGroup.class}, message = "排序必填")
    private Integer orderNum;

    @ApiModelProperty("菜单全路径")
    private String parentPath;

    @ApiModelProperty(value = "状态 0未发布 1发布")
    private Integer status;

    @ApiModelProperty(value = "父菜单Id")
    private Long parentId;


    @ApiModelProperty(value = "创建人")
    private String createdBy;

    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createdTime;

    @ApiModelProperty(value = "更新人")
    private String updatedBy;

    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updatedTime;

    @ApiModelProperty(value = "删除标识符0正常 1删除")
    private Integer deleted;


}

创建按钮对象,这里只列举了view类型所需的button,其他类型稍微修改一下就可以了

@Data
public class BasicButton {
    private String name;
    private String url;
}
@Data
public class ComplexButtons extends  BasicButton {
    private ViewButton[] sub_button;
}
@Data
public class ViewButton extends BasicButton {
    private String type;
    private String name;
    private String url;
}
@Data
public class MainButton {
    private String name;
    private String type;
    private String key;
    private BasicButton[] sub_button;
}

创建树形菜单对象,用于处理层级关系


import java.util.List;

/**
 * 树节点父类,所有需要使用{@linkplain BaseTreeHelper}工具类形成树形结构等操作的节点都需要实现该接口
 *
 * @param <T> 节点id类型
 * @author huangke
 */
public interface TreeNode<T, E> {
    /**
     * 获取节点id
     *
     * @return 树节点id
     */
    T getId();

    /**
     * 获取该节点的父节点id
     *
     * @return 父节点id
     */
    T getPid();

    /**
     * 是否是根节点
     *
     * @return true:根节点
     */
    Boolean root();

    /**
     * 设置节点的子节点列表
     *
     * @param children 子节点
     */
    void setChildren(List<E> children);

    /**
     * 获取所有子节点
     *
     * @return 子节点列表
     */
    List<E> getChildren();
}


import com.knight.common.core.utils.tree.TreeNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ButtomTreeVo implements TreeNode<Long, ButtomTreeVo> {
    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @ApiModelProperty("菜单id")
    private Long id;

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

    /**
     * 菜单名称
     */
    @ApiModelProperty("菜单名称")
    private String name;

    /**
     * 菜单值(前端路由)
     */
    @ApiModelProperty("菜单值")
    private String menuValue;

    /**
     * 上级菜单id
     */
    @ApiModelProperty("上级菜单")
    private Long pid;

    /**
     * 菜单全路径(用/隔开)
     */
    @ApiModelProperty("菜单全路径")
    private String parentPath;


    /**
     * 排序
     */
    @ApiModelProperty("排序")
    private Integer orderNum;



    @ApiModelProperty("子菜单")
    private List<ButtomTreeVo> children;


    @Override
    @ApiModelProperty("父节点")
    public Boolean root() {
        return pid == null;
    }


}

树形菜单工具类


import cn.hutool.core.collection.CollUtil;
import org.apache.commons.compress.utils.Lists;

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

/**
 * @author ws
 * @date 2021/8/29
 */
public class BaseTreeHelper {

    /**
     * @param <T> 节点类型
     * @param <E> 节点id的类型
     * @return 树形结构列表
     * @author huangke
     * @date 2021/2/1
     * 根据所有树节点列表,生成含有所有树形结构的列表
     **/
    public static <T extends TreeNode<E, T>, E> List<T> generateTrees(List<T> data) {
        if (CollUtil.isEmpty(data)) {
            return Lists.newArrayList();
        }
        //将集合中所有数据按照父Id进行分组,放入Map中,Map<parntId, List<T>>
        Map<String, List<T>> groupByParentIdMap = data.stream().collect(Collectors.groupingBy(item -> item.root() ? "" : Objects.toString(item.getPid())));
        //将集合中所有数据以数据Id为key,放入Map中,Map<id,T>
        Map<String, T> dataMap = data.stream().collect(Collectors.toMap(item -> item.getId().toString(), t -> t));
        List<T> resp = Lists.newArrayList();
        //遍历数据,将子节点放入对应父节点Children属性中
        groupByParentIdMap.forEach((parentId, values) -> {
            if (dataMap.containsKey(parentId)) {
                List<T> child = dataMap.get(parentId).getChildren();
                if (CollUtil.isEmpty(child)) {
                    child = Lists.newArrayList();
                }
                child.addAll(values);
                dataMap.get(parentId).setChildren(child);
            } else {
                resp.addAll(values);
            }
        });
        return resp;
    }


    /**
     * @param parent 父节点
     * @param <T>    实际节点类型
     * @return 叶子节点
     * @author huangke
     * @date 2021/2/1
     * 获取指定树节点下的所有叶子节点
     **/
    public static <T extends TreeNode<E, T>, E> List<T> getLeafs(T parent) {
        List<T> leafs = new ArrayList<>();
        fillLeaf(parent, leafs);
        return leafs;
    }

    /**
     * @param parent 父节点
     * @param leafs  叶子节点列表
     * @param <T>    实际节点类型
     * @author huangke
     * @date 2021/2/1
     * 将parent的所有叶子节点填充至leafs列表中
     **/
    private static <T extends TreeNode<E, T>, E> void fillLeaf(T parent, List<T> leafs) {
        List<T> children = parent.getChildren();
        // 如果节点没有子节点则说明为叶子节点
        if (CollUtil.isEmpty(children)) {
            leafs.add(parent);
            return;
        }
        // 递归调用子节点,查找叶子节点
        for (T child : children) {
            fillLeaf(child, leafs);
        }
    }
}

 创建公众号菜单

 //1.菜单创建(POST) 限100(次/天)  
    public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    

    @ApiOperation("createMenu:创建公众号底部菜单")
    @PostMapping("createMenu")
    public Result createMenu() throws Exception {
        //查询所有菜单
        List<WechatClientBottomMenu> menus = baseService.list(new LambdaQueryWrapper<WechatClientBottomMenu>()
                .eq(WechatClientBottomMenu::getDeleted, 0)
                .eq(WechatClientBottomMenu::getCompanyNo, SecurityUtils.getCompanyNo())
                .orderByAsc(WechatClientBottomMenu::getOrderNum));

        List<ButtomTreeVo> wechatMenus = new ArrayList<>();
        menus.forEach(menu -> {
            ButtomTreeVo mm = ButtomTreeVo.builder().build();
            BeanUtil.copyProperties(menu, mm);
            mm.setName(menu.getName());
            mm.setPid(menu.getParentId());
            wechatMenus.add(mm);
        });
     //生成树形菜单,子节点在children里面
        List<ButtomTreeVo> list = BaseTreeHelper.generateTrees(wechatMenus);

        List<Object> objects = new ArrayList<>();
        for (ButtomTreeVo vo : list) {
//如果有子节点进行处理
            if (vo.getChildren() != null) {
                ComplexButtons mainBtn1 = new ComplexButtons();
                mainBtn1.setName(vo.getName());
                List<ViewButton> buttomTreeVos = new ArrayList<>();
                for (ButtomTreeVo vv : vo.getChildren()) {
                    ViewButton viewButton = new ViewButton();
                    viewButton.setName(vv.getName());
                    viewButton.setType(vv.getFuncType());
                    viewButton.setUrl(vv.getParentPath());
                    buttomTreeVos.add(viewButton);
                }
                 //放入数组中
                ViewButton[] array = new ViewButton[buttomTreeVos.size()];
                for (int i = 0; i < buttomTreeVos.size(); i++) {
                    array[i] = buttomTreeVos.get(i);
                }
                mainBtn1.setSub_button(array);
                objects.add(mainBtn1);
            } else {
              //没有子节点的处理方式
                MainButton mainBtn2 = new MainButton();
                mainBtn2.setKey(vo.getParentPath());
                mainBtn2.setType(vo.getFuncType());
                mainBtn2.setName(vo.getName());
                objects.add(mainBtn2);
            }
        }
        Map map = new HashMap();
        map.put("button", objects);
        //生成的map转字符串json发送微信请求
        String sb = createMenu(JSON.toJSONString(map), 
            SecurityUtils.getCompanyNo());
        Map mm = JSON.parseObject(sb);
        Boolean cc = false;
        if (mm.get("errcode").equals(0)) {
            cc = true;
        }
        return cc ? Result.ok(sb) : Result.fail(sb);
    }





   /**
     * @desc :创建菜单
     */
    public String createMenu(String menuJson, String companyNo) throws Exception {
//公众号秘钥做了管理的,用companyNo去查询公众号秘钥和appid
        WechatClient wechatClient = weChatPublicNoAccountInfoService.getOne(new LambdaQueryWrapper<WechatClient>()
                .eq(WechatClient::getCompanyNo, companyNo));
        if (wechatClient == null) {
            return null;
        }
        
     //通过appid秘钥获取accesstoken
        String accessToken = wxCommonService.getAccessToken(wechatClient.getAppid(), 
      wechatClient.getAppSecret());

        //2.拼装创建菜单的url  
        String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", accessToken);
        StringBuilder requestUrl = new StringBuilder(url);

         //发送pos请求
        String result = httpUtils.postRequestJson(requestUrl.toString(), menuJson);
        return result;

    }

效果如下

                                             

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今朝花落悲颜色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值