|
@@ -1,5 +1,6 @@
|
|
|
package cn.iocoder.yudao.module.mp.service.account;
|
|
|
|
|
|
+import cn.hutool.core.util.ObjUtil;
|
|
|
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;
|
|
@@ -12,24 +13,27 @@ 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 cn.iocoder.yudao.module.mp.mq.producer.MpAccountProducer;
|
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
|
import lombok.Getter;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import me.chanjar.weixin.common.error.WxErrorException;
|
|
|
+import me.chanjar.weixin.mp.api.WxMpService;
|
|
|
+import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
|
|
|
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;
|
|
|
import java.util.Map;
|
|
|
|
|
|
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS;
|
|
|
|
|
|
/**
|
|
|
- * 公众号账户 Service 实现类
|
|
|
+ * 公众号账号 Service 实现类
|
|
|
*
|
|
|
* @author fengdan
|
|
|
*/
|
|
@@ -38,12 +42,6 @@ import java.util.Map;
|
|
|
@Validated
|
|
|
public class MpAccountServiceImpl implements MpAccountService {
|
|
|
|
|
|
- /**
|
|
|
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
|
|
|
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
|
|
|
- */
|
|
|
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
|
|
|
-
|
|
|
/**
|
|
|
* 账号缓存
|
|
|
* key:账号编号 {@link MpAccountDO#getAppId()}
|
|
@@ -52,11 +50,6 @@ public class MpAccountServiceImpl implements MpAccountService {
|
|
|
*/
|
|
|
@Getter
|
|
|
private volatile Map<String, MpAccountDO> accountCache;
|
|
|
- /**
|
|
|
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
|
|
|
- */
|
|
|
- @Getter
|
|
|
- private volatile LocalDateTime maxUpdateTime;
|
|
|
|
|
|
@Resource
|
|
|
private MpAccountMapper mpAccountMapper;
|
|
@@ -66,89 +59,85 @@ public class MpAccountServiceImpl implements MpAccountService {
|
|
|
private MpServiceFactory mpServiceFactory;
|
|
|
|
|
|
@Resource
|
|
|
- private MpConfigProducer mpConfigDataProducer;
|
|
|
+ private MpAccountProducer mpAccountProducer;
|
|
|
|
|
|
@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);
|
|
|
accountCache = CollectionUtils.convertMap(accounts, MpAccountDO::getAppId);
|
|
|
-
|
|
|
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
|
|
- this.maxUpdateTime = CollectionUtils.getMaxValue(accounts, MpAccountDO::getUpdateTime);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Long createAccount(MpAccountCreateReqVO createReqVO) {
|
|
|
- // TODO 芋艿:校验唯一性
|
|
|
+ // 校验 appId 唯一
|
|
|
+ validateAppIdUnique(null, createReqVO.getAppId());
|
|
|
+
|
|
|
// 插入
|
|
|
- MpAccountDO wxAccount = MpAccountConvert.INSTANCE.convert(createReqVO);
|
|
|
- mpAccountMapper.insert(wxAccount);
|
|
|
+ MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO);
|
|
|
+ mpAccountMapper.insert(account);
|
|
|
|
|
|
- // TODO 芋艿:刷新的方式
|
|
|
- mpConfigDataProducer.sendDictDataRefreshMessage();
|
|
|
- // 返回
|
|
|
- return wxAccount.getId();
|
|
|
+ // 发送刷新消息
|
|
|
+ mpAccountProducer.sendAccountRefreshMessage();
|
|
|
+ return account.getId();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void updateAccount(MpAccountUpdateReqVO updateReqVO) {
|
|
|
- // TODO 芋艿:校验唯一性
|
|
|
// 校验存在
|
|
|
- validateWxAccountExists(updateReqVO.getId());
|
|
|
+ validateAccountExists(updateReqVO.getId());
|
|
|
+ // 校验 appId 唯一
|
|
|
+ validateAppIdUnique(updateReqVO.getId(), updateReqVO.getAppId());
|
|
|
+
|
|
|
// 更新
|
|
|
MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);
|
|
|
mpAccountMapper.updateById(updateObj);
|
|
|
|
|
|
- // TODO 芋艿:刷新的方式
|
|
|
- mpConfigDataProducer.sendDictDataRefreshMessage();
|
|
|
+ // 发送刷新消息
|
|
|
+ mpAccountProducer.sendAccountRefreshMessage();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void deleteAccount(Long id) {
|
|
|
// 校验存在
|
|
|
- validateWxAccountExists(id);
|
|
|
+ validateAccountExists(id);
|
|
|
// 删除
|
|
|
mpAccountMapper.deleteById(id);
|
|
|
|
|
|
- // TODO 芋艿:刷新的方式
|
|
|
- mpConfigDataProducer.sendDictDataRefreshMessage();
|
|
|
+ // 发送刷新消息
|
|
|
+ mpAccountProducer.sendAccountRefreshMessage();
|
|
|
}
|
|
|
|
|
|
- private void validateWxAccountExists(Long id) {
|
|
|
- if (mpAccountMapper.selectById(id) == null) {
|
|
|
- throw ServiceExceptionUtil.exception(ErrorCodeConstants.WX_ACCOUNT_NOT_EXISTS);
|
|
|
+ private MpAccountDO validateAccountExists(Long id) {
|
|
|
+ MpAccountDO account = mpAccountMapper.selectById(id);
|
|
|
+ if (account == null) {
|
|
|
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS);
|
|
|
}
|
|
|
+ return account;
|
|
|
+ }
|
|
|
+
|
|
|
+ @VisibleForTesting
|
|
|
+ public void validateAppIdUnique(Long id, String appId) {
|
|
|
+ // 多个租户,appId 是不能重复,否则公众号回调会无法识别
|
|
|
+ TenantUtils.executeIgnore(() -> {
|
|
|
+ MpAccountDO account = mpAccountMapper.selectByAppId(appId);
|
|
|
+ if (account == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 存在 account 记录的情况下
|
|
|
+ if (id == null // 新增时,说明重复
|
|
|
+ || ObjUtil.notEqual(id, account.getId())) { // 更新时,如果 id 不一致,说明重复
|
|
|
+ throw exception(USER_USERNAME_EXISTS);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -167,8 +156,36 @@ public class MpAccountServiceImpl implements MpAccountService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val) {
|
|
|
- return mpAccountMapper.selectOne(field, val);
|
|
|
+ public void generateAccountQrCode(Long id) {
|
|
|
+ // 校验存在
|
|
|
+ MpAccountDO account = validateAccountExists(id);
|
|
|
+
|
|
|
+ // 生成二维码
|
|
|
+ WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());
|
|
|
+ String qrCodeUrl;
|
|
|
+ try {
|
|
|
+ WxMpQrCodeTicket qrCodeTicket = mpService.getQrcodeService().qrCodeCreateLastTicket("default");
|
|
|
+ qrCodeUrl = mpService.getQrcodeService().qrCodePictureUrl(qrCodeTicket.getTicket());
|
|
|
+ } catch (WxErrorException e) {
|
|
|
+ throw exception(ErrorCodeConstants.ACCOUNT_GENERATE_QR_CODE_FAIL, e.getError().getErrorMsg());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存二维码
|
|
|
+ mpAccountMapper.updateById(new MpAccountDO().setId(id).setQrCodeUrl(qrCodeUrl));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clearAccountQuota(Long id) {
|
|
|
+ // 校验存在
|
|
|
+ MpAccountDO account = validateAccountExists(id);
|
|
|
+
|
|
|
+ // 生成二维码
|
|
|
+ WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());
|
|
|
+ try {
|
|
|
+ mpService.clearQuota(account.getAppId());
|
|
|
+ } catch (WxErrorException e) {
|
|
|
+ throw exception(ErrorCodeConstants.ACCOUNT_CLEAR_QUOTA_FAIL, e.getError().getErrorMsg());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
}
|