mp:增加 MpServiceFactory 管理创建的 MpService~

This commit is contained in:
YunaiV 2023-01-01 21:58:26 +08:00
parent 627cb39517
commit 275a1b10f6
25 changed files with 539 additions and 357 deletions

View File

@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*;
import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@ -25,7 +25,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
public class MpAccountController {
@Resource
private WxAccountService wxAccountService;
private MpAccountService wxAccountService;
@PostMapping("/create")
@ApiOperation("创建公众号账户")

View File

@ -63,13 +63,4 @@ public class MpAccountDO extends BaseDO {
*/
private String remark;
// TODO 芋艿需要迁移走
public WxMpConfigStorage toWxMpConfigStorage(RedisTemplateWxRedisOps redisTemplateWxRedisOps, WxMpProperties wxMpProperties) {
WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisTemplateWxRedisOps, wxMpProperties.getConfigStorage().getKeyPrefix());
wxMpRedisConfig.setAppId(appId);
wxMpRedisConfig.setSecret(appSecret);
wxMpRedisConfig.setToken(token);
wxMpRedisConfig.setAesKey(aesKey);
return wxMpRedisConfig;
}
}

View File

@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
@Mapper
public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
@ -18,4 +21,7 @@ public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
.orderByDesc(MpAccountDO::getId));
}
@Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mp.framework.mp.config;
import cn.iocoder.yudao.module.mp.framework.mp.core.DefaultMpServiceFactory;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.fansmsgres.NullHandler;
import cn.iocoder.yudao.module.mp.service.handler.*;
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* 微信公众号的配置类
*
* @author 芋道源码
*/
@Configuration
public class MpConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public RedisTemplateWxRedisOps redisTemplateWxRedisOps(StringRedisTemplate stringRedisTemplate) {
return new RedisTemplateWxRedisOps(stringRedisTemplate);
}
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps,
WxMpProperties wxMpProperties,
LogHandler logHandler,
KfSessionHandler kfSessionHandler,
StoreCheckNotifyHandler storeCheckNotifyHandler,
MenuHandler menuHandler,
NullHandler nullHandler,
SubscribeHandler subscribeHandler,
UnsubscribeHandler unsubscribeHandler,
LocationHandler locationHandler,
ScanHandler scanHandler,
DefaultMessageHandler msgHandler) {
return new DefaultMpServiceFactory(redisTemplateWxRedisOps, wxMpProperties,
logHandler, kfSessionHandler, storeCheckNotifyHandler, menuHandler,
nullHandler, subscribeHandler, unsubscribeHandler, locationHandler, scanHandler, msgHandler);
}
}

View File

@ -0,0 +1,158 @@
package cn.iocoder.yudao.module.mp.framework.mp.core;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.service.fansmsgres.NullHandler;
import cn.iocoder.yudao.module.mp.service.handler.*;
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import me.chanjar.weixin.mp.constant.WxMpEventConstants;
import java.util.List;
import java.util.Map;
/**
* 默认的 {@link MpServiceFactory} 实现类
*
* @author 芋道源码
*/
@Slf4j
@RequiredArgsConstructor
public class DefaultMpServiceFactory implements MpServiceFactory {
/**
* 微信 appId WxMpService 的映射
*/
private volatile Map<String, WxMpService> mpServices;
/**
* 微信 appId WxMpMessageRouter 的映射
*/
private volatile Map<String, WxMpMessageRouter> mpMessageRouters;
private final RedisTemplateWxRedisOps redisTemplateWxRedisOps;
private final WxMpProperties mpProperties;
// ========== 各种 Handler ==========
private final LogHandler logHandler;
private final KfSessionHandler kfSessionHandler;
private final StoreCheckNotifyHandler storeCheckNotifyHandler;
private final MenuHandler menuHandler;
private final NullHandler nullHandler;
private final SubscribeHandler subscribeHandler;
private final UnsubscribeHandler unsubscribeHandler;
private final LocationHandler locationHandler;
private final ScanHandler scanHandler;
private final DefaultMessageHandler msgHandler;
@Override
public void init(List<MpAccountDO> list) {
Map<String, WxMpService> mpServices = Maps.newHashMap();
Map<String, WxMpMessageRouter> mpMessageRouters = Maps.newHashMap();
// 处理 list
list.forEach(account -> {
// 构建 WxMpService 对象
WxMpService mpService = buildMpService(account);
mpServices.put(account.getAppId(), mpService);
// 构建 WxMpMessageRouter 对象
WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService);
mpMessageRouters.put(account.getAppId(), mpMessageRouter);
});
// 设置到缓存
this.mpServices = mpServices;
this.mpMessageRouters = mpMessageRouters;
}
@Override
public WxMpService getMpService(String appId) {
return mpServices.get(appId);
}
@Override
public WxMpMessageRouter getMpMessageRouter(String appId) {
return mpMessageRouters.get(appId);
}
private WxMpService buildMpService(MpAccountDO account) {
// 第一步创建 WxMpRedisConfigImpl 对象
// TODO 芋艿需要确认下redis key 的存储结构
WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix());
configStorage.setAppId(account.getAppId());
configStorage.setSecret(account.getAppSecret());
configStorage.setToken(account.getToken());
configStorage.setAesKey(account.getAesKey());
// 第二步创建 WxMpService 对象
WxMpService service = new WxMpServiceImpl();
service.setWxMpConfigStorage(configStorage);
return null;
}
private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(mpService);
// 记录所有事件的日志异步执行
newRouter.rule().handler(logHandler).next();
// 接收客服会话管理事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
.handler(kfSessionHandler).end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
.handler(kfSessionHandler)
.end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
.handler(kfSessionHandler).end();
// 门店审核事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.POI_CHECK_NOTIFY)
.handler(storeCheckNotifyHandler).end();
// 自定义菜单事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end();
// 点击菜单连接事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler)
.end();
// 取消关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.UNSUBSCRIBE)
.handler(unsubscribeHandler).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.LOCATION).handler(locationHandler)
.end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
.handler(locationHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SCAN).handler(scanHandler).end();
// 默认
newRouter.rule().async(false).handler(msgHandler).end();
return newRouter;
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mp.framework.mp.core;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import java.util.List;
/**
* {@link WxMpService} 工厂接口
*
* @author 芋道源码
*/
public interface MpServiceFactory {
/**
* 基于微信公众号的账号初始化对应的 WxMpService WxMpMessageRouter 实例
*
* @param list 公众号的账号列表
*/
void init(List<MpAccountDO> list);
/**
* 获得 appId 对应的 WxMpService 实例
*
* @param appId 微信公众号 appId
* @return WxMpService 实例
*/
WxMpService getMpService(String appId);
/**
* 获得 appId 对应的 WxMpMessageRouter 实例
*
* @param appId 微信公众号 appId
* @return WxMpMessageRouter 实例
*/
WxMpMessageRouter getMpMessageRouter(String appId);
}

View File

@ -1,6 +1,6 @@
/**
* @date 2022/6/20
* @author feng-dan
* @version 1.0
* 属于 mp 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.mp.framework;

View File

@ -1,106 +0,0 @@
package cn.iocoder.yudao.module.mp.framework.weixin;
import cn.iocoder.yudao.module.mp.handler.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.constant.WxMpEventConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author fengdan
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class WxMpMessageRouterConfiguration {
@Resource
private WxMpService wxMpService;
@Resource
private LogHandler logHandler;
@Resource
private KfSessionHandler kfSessionHandler;
@Resource
private StoreCheckNotifyHandler storeCheckNotifyHandler;
@Resource
private MenuHandler menuHandler;
@Resource
private NullHandler nullHandler;
@Resource
private SubscribeHandler subscribeHandler;
@Resource
private UnsubscribeHandler unsubscribeHandler;
@Resource
private LocationHandler locationHandler;
@Resource
private ScanHandler scanHandler;
@Resource
private MsgHandler msgHandler;
@Bean
public WxMpMessageRouter messageRouter() {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 异步执行
newRouter.rule().handler(logHandler).next();
// 接收客服会话管理事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
.handler(kfSessionHandler).end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
.handler(kfSessionHandler)
.end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
.handler(kfSessionHandler).end();
// 门店审核事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.POI_CHECK_NOTIFY)
.handler(storeCheckNotifyHandler).end();
// 自定义菜单事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end();
// 点击菜单连接事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler)
.end();
// 取消关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.UNSUBSCRIBE)
.handler(unsubscribeHandler).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.LOCATION).handler(locationHandler)
.end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
.handler(locationHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SCAN).handler(scanHandler).end();
// 默认
newRouter.rule().async(false).handler(msgHandler).end();
return newRouter;
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mp.mq.costomer;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
import cn.iocoder.yudao.module.mp.mq.message.MpConfigRefreshMessage;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link MpConfigRefreshMessage} 的消费者
*
* @author lyz
*/
@Component
@Slf4j
public class MpConfigRefreshConsumer extends AbstractChannelMessageListener<MpConfigRefreshMessage> {
@Resource
private MpAccountService wxAccountService;
@Override
public void onMessage(MpConfigRefreshMessage message) {
log.info("[onMessage][收到 MpConfig 刷新消息]");
wxAccountService.initLocalCache();
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.mp.mq.costomer;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
import cn.iocoder.yudao.module.mp.mq.message.WxConfigDataRefreshMessage;
import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link WxConfigDataRefreshMessage} 的消费者
*
* @author lyz
*/
@Component
@Slf4j
public class WxConfigDataRefreshConsumer extends AbstractChannelMessageListener<WxConfigDataRefreshMessage> {
@Resource
private WxAccountService wxAccountService;
@Override
public void onMessage(WxConfigDataRefreshMessage message) {
log.info("[onMessage][收到 WxConfigData 刷新消息]");
wxAccountService.initLoadWxMpConfigStorages();
}
}

View File

@ -9,11 +9,11 @@ import lombok.EqualsAndHashCode;
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WxConfigDataRefreshMessage extends AbstractChannelMessage {
public class MpConfigRefreshMessage extends AbstractChannelMessage {
@Override
public String getChannel() {
return "wechat-mp.wx-config-data.refresh";
return "mp.config-data.refresh";
}
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.mp.mq.producer;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.module.mp.mq.message.WxConfigDataRefreshMessage;
import cn.iocoder.yudao.module.mp.mq.message.MpConfigRefreshMessage;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@ -10,16 +10,16 @@ import javax.annotation.Resource;
* 微信配置数据相关消息的 Producer
*/
@Component
public class WxMpConfigDataProducer {
public class MpConfigProducer {
@Resource
private RedisMQTemplate redisMQTemplate;
/**
* 发送 {@link WxConfigDataRefreshMessage} 消息
* 发送 {@link MpConfigRefreshMessage} 消息
*/
public void sendDictDataRefreshMessage() {
WxConfigDataRefreshMessage message = new WxConfigDataRefreshMessage();
MpConfigRefreshMessage message = new MpConfigRefreshMessage();
redisMQTemplate.send(message);
}

View File

@ -14,7 +14,12 @@ import javax.validation.Valid;
*
* @author 芋道源码
*/
public interface WxAccountService {
public interface MpAccountService {
/**
* 初始化缓存
*/
void initLocalCache();
/**
* 创建公众号账户
@ -64,8 +69,4 @@ public interface WxAccountService {
*/
MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val);
/**
* 初始化
*/
void initLoadWxMpConfigStorages();
}

View File

@ -0,0 +1,159 @@
package cn.iocoder.yudao.module.mp.service.account;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;
import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper;
import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.mq.producer.MpConfigProducer;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 公众号账户 Service 实现类
*
* @author fengdan
*/
@Slf4j
@Service
@Validated
public class MpAccountServiceImpl implements MpAccountService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 缓存菜单的最大更新时间用于后续的增量轮询判断是否有更新
*/
@Getter
private volatile LocalDateTime maxUpdateTime;
@Resource
private MpAccountMapper mpAccountMapper;
@Resource
@Lazy // 延迟加载解决循环依赖的问题
private MpServiceFactory mpServiceFactory;
@Resource
private MpConfigProducer mpConfigDataProducer;
@Override
@PostConstruct
public void initLocalCache() {
initLocalCacheIfUpdate(null);
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCacheIfUpdate(this.maxUpdateTime);
}
/**
* 刷新本地缓存
*
* @param maxUpdateTime 最大更新时间
* 1. 如果 maxUpdateTime null强制刷新缓存
* 2. 如果 maxUpdateTime 不为 null判断自 maxUpdateTime 是否有数据发生变化有的情况下才刷新缓存
*/
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
// 注意忽略自动多租户因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步基于 maxUpdateTime 判断缓存是否刷新
// 如果没有增量的数据变化则不进行本地缓存的刷新
if (maxUpdateTime != null
&& mpAccountMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
return;
}
List<MpAccountDO> accounts = mpAccountMapper.selectList();
log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size());
// 第二步构建缓存创建或更新支付 Client
mpServiceFactory.init(accounts);
// 第三步设置最新的 maxUpdateTime用于下次的增量判断
this.maxUpdateTime = CollectionUtils.getMaxValue(accounts, MpAccountDO::getUpdateTime);
});
}
@Override
public Long createAccount(MpAccountCreateReqVO createReqVO) {
// TODO 芋艿校验唯一性
// 插入
MpAccountDO wxAccount = MpAccountConvert.INSTANCE.convert(createReqVO);
mpAccountMapper.insert(wxAccount);
// TODO 芋艿刷新的方式
mpConfigDataProducer.sendDictDataRefreshMessage();
// 返回
return wxAccount.getId();
}
@Override
public void updateAccount(MpAccountUpdateReqVO updateReqVO) {
// TODO 芋艿校验唯一性
// 校验存在
validateWxAccountExists(updateReqVO.getId());
// 更新
MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);
mpAccountMapper.updateById(updateObj);
// TODO 芋艿刷新的方式
mpConfigDataProducer.sendDictDataRefreshMessage();
}
@Override
public void deleteAccount(Long id) {
// 校验存在
validateWxAccountExists(id);
// 删除
mpAccountMapper.deleteById(id);
// TODO 芋艿刷新的方式
mpConfigDataProducer.sendDictDataRefreshMessage();
}
private void validateWxAccountExists(Long id) {
if (mpAccountMapper.selectById(id) == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.WX_ACCOUNT_NOT_EXISTS);
}
}
@Override
public MpAccountDO getAccount(Long id) {
return mpAccountMapper.selectById(id);
}
@Override
public PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO) {
return mpAccountMapper.selectPage(pageReqVO);
}
@Override
public MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val) {
return mpAccountMapper.selectOne(field, val);
}
}

View File

@ -1,157 +0,0 @@
package cn.iocoder.yudao.module.mp.service.account;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;
import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper;
import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.mp.mq.producer.WxMpConfigDataProducer;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 公众号账户 Service 实现类
*
* @author fengdan
*/
@Slf4j
@Service
@Validated
public class WxAccountServiceImpl implements WxAccountService {
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
@Resource
private MpAccountMapper wxAccountMapper;
@Resource
private WxMpConfigDataProducer wxMpConfigDataProducer;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private WxMpService wxMpService;
@Resource
private WxMpProperties wxMpProperties;
@Resource
@Lazy // 注入自己所以延迟加载
private WxAccountService self;
@Override
public Long createAccount(MpAccountCreateReqVO createReqVO) {
// TODO 芋艿校验唯一性
// 插入
MpAccountDO wxAccount = MpAccountConvert.INSTANCE.convert(createReqVO);
wxAccountMapper.insert(wxAccount);
// TODO 芋艿刷新的方式
wxMpConfigDataProducer.sendDictDataRefreshMessage();
// 返回
return wxAccount.getId();
}
@Override
public void updateAccount(MpAccountUpdateReqVO updateReqVO) {
// TODO 芋艿校验唯一性
// 校验存在
validateWxAccountExists(updateReqVO.getId());
// 更新
MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);
wxAccountMapper.updateById(updateObj);
// TODO 芋艿刷新的方式
wxMpConfigDataProducer.sendDictDataRefreshMessage();
}
@Override
public void deleteAccount(Long id) {
// 校验存在
validateWxAccountExists(id);
// 删除
wxAccountMapper.deleteById(id);
// TODO 芋艿刷新的方式
wxMpConfigDataProducer.sendDictDataRefreshMessage();
}
private void validateWxAccountExists(Long id) {
if (wxAccountMapper.selectById(id) == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.WX_ACCOUNT_NOT_EXISTS);
}
}
@Override
public MpAccountDO getAccount(Long id) {
return wxAccountMapper.selectById(id);
}
@Override
public PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO) {
return wxAccountMapper.selectPage(pageReqVO);
}
@Override
public MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val) {
return wxAccountMapper.selectOne(field, val);
}
@PostConstruct
@TenantIgnore
@Override
public void initLoadWxMpConfigStorages() {
// TODO 芋艿刷新的方式
List<MpAccountDO> wxAccountList = this.wxAccountMapper.selectList();
if (CollectionUtils.isEmpty(wxAccountList)) {
log.info("未读取到公众号配置,请在管理后台添加");
return;
}
log.info("加载到{}条公众号配置", wxAccountList.size());
wxAccountList.forEach(account -> addAccountToRuntime(account, new RedisTemplateWxRedisOps(stringRedisTemplate)));
log.info("公众号配置加载完成");
}
/**
* 添加账号到当前程序如首次添加需初始化configStorageMap
*
* @param account 公众号
*/
private synchronized void addAccountToRuntime(MpAccountDO account, RedisTemplateWxRedisOps redisOps) {
String appId = account.getAppId();
WxMpConfigStorage wxMpRedisConfig = account.toWxMpConfigStorage(redisOps, wxMpProperties);
try {
wxMpService.addConfigStorage(appId, wxMpRedisConfig);
} catch (NullPointerException e) {
log.info("需初始化configStorageMap...");
Map<String, WxMpConfigStorage> configStorages = new HashMap<>(4);
configStorages.put(appId, wxMpRedisConfig);
wxMpService.setMultiConfigStorages(configStorages, appId);
}
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
self.initLoadWxMpConfigStorages();
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.fansmsgres;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
@ -9,13 +9,15 @@ import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 点击菜单连接的事件处理器
*/
@Component
public class NullHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
return null;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HtmlUtil;
@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.mp.builder.TextBuilder;
import cn.iocoder.yudao.module.mp.controller.admin.fansmsg.vo.WxFansMsgCreateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.fansmsg.WxFansMsgService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
@ -21,6 +21,7 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@ -28,11 +29,18 @@ import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Map;
/**
* 默认所有消息的事件处理器
*
* // TODO 芋艿待实现
*/
@Component
@Slf4j
public class MsgHandler implements WxMpMessageHandler {
@Autowired
private WxAccountService wxAccountService;
public class DefaultMessageHandler implements WxMpMessageHandler {
@Resource
@Lazy // 延迟加载解决循环依赖的问题
private MpAccountService mpAccountService;
@Autowired
private WxFansMsgService wxFansMsgService;
@ -55,7 +63,7 @@ public class MsgHandler implements WxMpMessageHandler {
WxMpUser wxmpUser = weixinService.getUserService()
.userInfo(wxMessage.getFromUser(), null);
if (wxmpUser != null) {
MpAccountDO wxAccount = wxAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
if (wxAccount != null) {
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
@ -9,13 +9,17 @@ import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 接收客服会话管理的事件处理器
*
* @author 芋道源码
*/
@Component
public class KfSessionHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
// TODO 对会话做处理
return null;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import cn.iocoder.yudao.module.mp.builder.TextBuilder;
import lombok.extern.slf4j.Slf4j;
@ -13,14 +13,18 @@ import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
/**
* 上报地理位置的事件处理器
*
* // TODO @芋艿需要实现一下~
*/
@Component
@Slf4j
public class LocationHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) {
//TODO 接收处理用户发送的地理位置消息
try {

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
@ -11,13 +11,18 @@ import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 保存微信消息的事件处理器
*
* // TODO 芋艿实现一下
*/
@Component
@Slf4j
public class LogHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
log.info("接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage));
return null;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
@ -11,6 +11,11 @@ import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
/**
* 自定义菜单的事件处理器
*
* // TODO 芋艿待实现
*/
@Component
public class MenuHandler implements WxMpMessageHandler {

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
@ -10,6 +10,9 @@ import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 扫码的事件处理器
*/
@Component
public class ScanHandler implements WxMpMessageHandler {

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
@ -10,8 +10,7 @@ import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 门店审核事件处理
*
* 门店审核事件的事件处理器
*/
@Component
public class StoreCheckNotifyHandler implements WxMpMessageHandler {

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.accountfans.WxAccountFansDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.subscribetext.WxSubscribeTextDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.texttemplate.WxTextTemplateDO;
import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.accountfans.WxAccountFansService;
import cn.iocoder.yudao.module.mp.service.accountfanstag.WxAccountFansTagService;
import cn.iocoder.yudao.module.mp.service.subscribetext.WxSubscribeTextService;
@ -23,11 +23,18 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.Map;
/**
* 关注的事件处理器
*
* // TODO 芋艿待实现
*/
@Component
@Slf4j
public class SubscribeHandler implements WxMpMessageHandler {
@ -35,8 +42,9 @@ public class SubscribeHandler implements WxMpMessageHandler {
@Autowired
private WxTextTemplateService wxTextTemplateService;
@Autowired
private WxAccountService wxAccountService;
@Resource
@Lazy // 延迟加载解决循环依赖的问题
private MpAccountService mpAccountService;
@Autowired
private WxSubscribeTextService wxSubscribeTextService;
@ -60,7 +68,7 @@ public class SubscribeHandler implements WxMpMessageHandler {
.userInfo(wxMessage.getFromUser(), null);
if (wxmpUser != null) {
// 可以添加关注用户到本地数据库
MpAccountDO wxAccount = wxAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
if (wxAccount != null) {
WxAccountFansDO wxAccountFans = wxAccountFansService.findBy(WxAccountFansDO::getOpenid, wxmpUser.getOpenId());
if (wxAccountFans == null) {//insert
@ -128,7 +136,7 @@ public class SubscribeHandler implements WxMpMessageHandler {
try {
String content = "感谢关注!";//默认
MpAccountDO wxAccount = wxAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
if (wxAccount != null) {
WxSubscribeTextDO wxSubscribeText = wxSubscribeTextService.findBy(WxSubscribeTextDO::getWxAccountId, String.valueOf(wxAccount.getId()));
if (wxSubscribeText != null) {

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.mp.handler;
package cn.iocoder.yudao.module.mp.service.handler;
import cn.iocoder.yudao.module.mp.controller.admin.accountfans.vo.WxAccountFansUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.accountfans.WxAccountFansDO;
import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.accountfans.WxAccountFansService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.session.WxSessionManager;
@ -12,16 +12,24 @@ import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 取消关注的事件处理器
*
* // TODO 芋艿待实现
*/
@Component
@Slf4j
public class UnsubscribeHandler implements WxMpMessageHandler {
@Autowired
private WxAccountService wxAccountService;
@Resource
@Lazy // 延迟加载解决循环依赖的问题
private MpAccountService mpAccountService;
@Autowired
private WxAccountFansService wxAccountFansService;
@ -34,7 +42,7 @@ public class UnsubscribeHandler implements WxMpMessageHandler {
log.info("取消关注用户 OPENID: " + openId);
// TODO 可以更新本地数据库为取消关注状态
MpAccountDO wxAccountDO = wxAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
MpAccountDO wxAccountDO = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
if (wxAccountDO != null) {
WxAccountFansDO wxAccountFansDO = wxAccountFansService.findBy(WxAccountFansDO::getOpenid, openId);
if (wxAccountFansDO != null) {