package cn.iocoder.yudao.module.mp.service.menu; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper; import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; import cn.iocoder.yudao.module.mp.service.account.MpAccountService; import cn.iocoder.yudao.module.mp.service.message.MpMessageService; import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.menu.WxMenu; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import jakarta.validation.Validator; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL; import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL; /** * 公众号菜单 Service 实现类 * * @author 芋道源码 */ @Service @Validated @Slf4j public class MpMenuServiceImpl implements MpMenuService { @Resource private MpMessageService mpMessageService; @Resource @Lazy // 延迟加载,避免循环引用报错 private MpAccountService mpAccountService; @Resource @Lazy // 延迟加载,避免循环引用报错 private MpServiceFactory mpServiceFactory; @Resource private Validator validator; @Resource private MpMenuMapper mpMenuMapper; @Override @Transactional(rollbackFor = Exception.class) public void saveMenu(MpMenuSaveReqVO createReqVO) { MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); // 参数校验 createReqVO.getMenus().forEach(this::validateMenu); // 第一步,同步公众号 WxMenu wxMenu = new WxMenu(); wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus())); try { mpService.getMenuService().menuCreate(wxMenu); } catch (WxErrorException e) { throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg()); } // 第二步,存储到数据库 mpMenuMapper.deleteByAccountId(createReqVO.getAccountId()); createReqVO.getMenus().forEach(menu -> { // 先保存顶级菜单 MpMenuDO menuDO = createMenu(menu, null, account); // 再保存子菜单 if (CollUtil.isEmpty(menu.getChildren())) { return; } menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account)); }); } /** * 校验菜单的格式是否正确 * * @param menu 菜单 */ private void validateMenu(MpMenuSaveReqVO.Menu menu) { MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu); // 子菜单 if (CollUtil.isEmpty(menu.getChildren())) { return; } menu.getChildren().forEach(this::validateMenu); } /** * 创建菜单,并存储到数据库 * * @param wxMenu 菜单信息 * @param parentMenu 父菜单 * @param account 公众号账号 * @return 创建后的菜单 */ private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) { // 创建菜单 MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren()) ? new MpMenuDO().setName(wxMenu.getName()) : MpMenuConvert.INSTANCE.convert02(wxMenu); // 设置菜单的公众号账号信息 if (account != null) { menu.setAccountId(account.getId()).setAppId(account.getAppId()); } // 设置父编号 if (parentMenu != null) { menu.setParentId(parentMenu.getId()); } else { menu.setParentId(MpMenuDO.ID_ROOT); } // 插入到数据库 mpMenuMapper.insert(menu); return menu; } @Override public void deleteMenuByAccountId(Long accountId) { WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); // 第一步,同步公众号 try { mpService.getMenuService().menuDelete(); } catch (WxErrorException e) { throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg()); } // 第二步,存储到数据库 mpMenuMapper.deleteByAccountId(accountId); } @Override public WxMpXmlOutMessage reply(String appId, String key, String openid) { // 第一步,获得菜单 MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key); if (menu == null) { log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key); return null; } // 按钮必须要有消息类型,不然后续无法回复消息 if (StrUtil.isEmpty(menu.getReplyMessageType())) { log.error("[reply][menu({}) 不存在对应的消息类型]", menu); return null; } // 第二步,回复消息 MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu); return mpMessageService.sendOutMessage(sendReqBO); } @Override public List getMenuListByAccountId(Long accountId) { return mpMenuMapper.selectListByAccountId(accountId); } }