diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java index d41ac356c..fe39c15a4 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java @@ -9,9 +9,12 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; */ public interface ErrorCodeConstants { - // ========== 用户相关 1004001000============ - ErrorCode WX_ACCOUNT_NOT_EXISTS = new ErrorCode(1006001000, "公众号账户不存在"); - ErrorCode WX_ACCOUNT_FANS_NOT_EXISTS = new ErrorCode(1006001001, "粉丝账号不存在"); + // ========== 公众号账号 1006000000============ + ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1006000000, "公众号账号不存在"); + ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}"); + ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}"); + + // TODO 要处理下 ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在"); } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java index 34ff08de8..460b9f5ff 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.account; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*; +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.MpAccountRespVO; +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.service.account.MpAccountService; @@ -18,54 +21,72 @@ import javax.validation.Valid; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -@Api(tags = "管理后台 - 公众号账户") +@Api(tags = "管理后台 - 公众号账号") @RestController @RequestMapping("/mp/account") @Validated public class MpAccountController { @Resource - private MpAccountService wxAccountService; + private MpAccountService mpAccountService; @PostMapping("/create") - @ApiOperation("创建公众号账户") + @ApiOperation("创建公众号账号") @PreAuthorize("@ss.hasPermission('mp:account:create')") public CommonResult createWxAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { - return success(wxAccountService.createAccount(createReqVO)); + return success(mpAccountService.createAccount(createReqVO)); } @PutMapping("/update") - @ApiOperation("更新公众号账户") + @ApiOperation("更新公众号账号") @PreAuthorize("@ss.hasPermission('mp:account:update')") public CommonResult updateWxAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { - wxAccountService.updateAccount(updateReqVO); + mpAccountService.updateAccount(updateReqVO); return success(true); } @DeleteMapping("/delete") - @ApiOperation("删除公众号账户") + @ApiOperation("删除公众号账号") @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) @PreAuthorize("@ss.hasPermission('mp:account:delete')") public CommonResult deleteWxAccount(@RequestParam("id") Long id) { - wxAccountService.deleteAccount(id); + mpAccountService.deleteAccount(id); return success(true); } @GetMapping("/get") - @ApiOperation("获得公众号账户") + @ApiOperation("获得公众号账号") @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) @PreAuthorize("@ss.hasPermission('mp:account:query')") public CommonResult getWxAccount(@RequestParam("id") Long id) { - MpAccountDO wxAccount = wxAccountService.getAccount(id); + MpAccountDO wxAccount = mpAccountService.getAccount(id); return success(MpAccountConvert.INSTANCE.convert(wxAccount)); } @GetMapping("/page") - @ApiOperation("获得公众号账户分页") + @ApiOperation("获得公众号账号分页") @PreAuthorize("@ss.hasPermission('mp:account:query')") public CommonResult> getWxAccountPage(@Valid MpAccountPageReqVO pageVO) { - PageResult pageResult = wxAccountService.getAccountPage(pageVO); + PageResult pageResult = mpAccountService.getAccountPage(pageVO); return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); } + @PutMapping("/generate-qr-code") + @ApiOperation("生成公众号二维码") + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('mp:account:qr-code')") + public CommonResult generateAccountQrCode(@RequestParam("id") Long id) { + mpAccountService.generateAccountQrCode(id); + return success(true); + } + + @PutMapping("/clear-quota") + @ApiOperation("清空公众号 API 配额") + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('mp:account:clear-quota')") + public CommonResult clearAccountQuota(@RequestParam("id") Long id) { + mpAccountService.clearAccountQuota(id); + return success(true); + } + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java index cc1b158a7..34176f9b5 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.account.vo; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotEmpty; /** - * 公众号账户 Base VO,提供给添加、修改、详细的子 VO 使用 + * 公众号账号 Base VO,提供给添加、修改、详细的子 VO 使用 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 * * @author fengdan @@ -14,29 +14,30 @@ import javax.validation.constraints.NotNull; @Data public class MpAccountBaseVO { - @ApiModelProperty(value = "公众号名称", required = true) - @NotNull(message = "公众号名称不能为空") + @ApiModelProperty(value = "公众号名称", required = true, example = "芋道源码") + @NotEmpty(message = "公众号名称不能为空") private String name; - @ApiModelProperty(value = "公众号账户", required = true) - @NotNull(message = "公众号账户不能为空") + @ApiModelProperty(value = "公众号微信号", required = true, example = "yudaoyuanma") + @NotEmpty(message = "公众号微信号不能为空") private String account; @ApiModelProperty(value = "公众号 appId", required = true, example = "wx5b23ba7a5589ecbb") - @NotNull(message = "公众号 appId 不能为空") + @NotEmpty(message = "公众号 appId 不能为空") private String appId; - @ApiModelProperty(value = "公众号密钥", required = true) - @NotNull(message = "公众号密钥不能为空") + @ApiModelProperty(value = "公众号密钥", required = true, example = "3a7b3b20c537e52e74afd395eb85f61f") + @NotEmpty(message = "公众号密钥不能为空") private String appSecret; - @ApiModelProperty(value = "公众号 token", required = true) + @ApiModelProperty(value = "公众号 token", required = true, example = "kangdayuzhen") + @NotEmpty(message = "公众号 token 不能为空") private String token; - @ApiModelProperty(value = "加密密钥") + @ApiModelProperty(value = "加密密钥", example = "gjN+Ksei") private String aesKey; - @ApiModelProperty(value = "备注") + @ApiModelProperty(value = "备注", example = "请关注芋道源码,学习技术") private String remark; } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java index 571664317..57b329b92 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java @@ -5,7 +5,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -@ApiModel("管理后台 - 公众号账户创建 Request VO") +@ApiModel("管理后台 - 公众号账号创建 Request VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java index 7446fb3f2..23f2c2f0e 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java @@ -4,7 +4,7 @@ import lombok.*; import io.swagger.annotations.*; import cn.iocoder.yudao.framework.common.pojo.PageParam; -@ApiModel("管理后台 - 公众号账户分页 Request VO") +@ApiModel("管理后台 - 公众号账号分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @@ -13,7 +13,7 @@ public class MpAccountPageReqVO extends PageParam { @ApiModelProperty(value = "公众号名称", notes = "模糊匹配") private String name; - @ApiModelProperty(value = "公众号账户", notes = "模糊匹配") + @ApiModelProperty(value = "公众号账号", notes = "模糊匹配") private String account; @ApiModelProperty(value = "公众号 appid", notes = "模糊匹配") diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java index 0a2dcdc82..8cda94d9f 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java @@ -8,25 +8,19 @@ import lombok.ToString; import java.util.Date; -/** - * @author fengdan - */ -@ApiModel("管理后台 - 公众号账户 Response VO") +@ApiModel("管理后台 - 公众号账号 Response VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class MpAccountRespVO extends MpAccountBaseVO { - @ApiModelProperty(value = "编号", required = true) + @ApiModelProperty(value = "编号", required = true, example = "1024") private Long id; - @ApiModelProperty(value = "二维码图片URL") + @ApiModelProperty(value = "二维码图片URL", example = "https://www.iocoder.cn/1024.png") private String qrCodeUrl; @ApiModelProperty(value = "创建时间", required = true) private Date createTime; - @ApiModelProperty(value = "公众号密钥", required = true) - private String appSecret; - } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java index 33cc57e01..047a26ecd 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java @@ -7,7 +7,7 @@ import javax.validation.constraints.*; /** * @author fengdan */ -@ApiModel("管理后台 - 公众号账户更新 Request VO") +@ApiModel("管理后台 - 公众号账号更新 Request VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java index 593befdd4..eddf87ba0 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java @@ -1,18 +1,13 @@ package cn.iocoder.yudao.module.mp.dal.dataobject.account; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; import lombok.*; -import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; -import me.chanjar.weixin.mp.config.WxMpConfigStorage; -import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; /** - * 公众号账户 DO + * 公众号账号 DO * * @author 芋道源码 */ @@ -36,7 +31,7 @@ public class MpAccountDO extends TenantBaseDO { */ private String name; /** - * 公众号账户 + * 公众号账号 */ private String account; /** @@ -52,7 +47,7 @@ public class MpAccountDO extends TenantBaseDO { */ private String token; /** - * 加密密钥 + * 消息加解密密钥 */ private String aesKey; /** diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java index 268bacc3b..8050aea34 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java @@ -6,9 +6,6 @@ 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 { @@ -21,7 +18,8 @@ public interface MpAccountMapper extends BaseMapperX { .orderByDesc(MpAccountDO::getId)); } - @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}") - Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); + default MpAccountDO selectByAppId(String appId) { + return selectOne(MpAccountDO::getAppId, appId); + } } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpAccountRefreshConsumer.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpAccountRefreshConsumer.java new file mode 100644 index 000000000..89b2b8f6d --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpAccountRefreshConsumer.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.mp.mq.consumer; + +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; +import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 针对 {@link MpAccountRefreshMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class MpAccountRefreshConsumer extends AbstractChannelMessageListener { + + @Resource + private MpAccountService mpAccountService; + + @Override + public void onMessage(MpAccountRefreshMessage message) { + log.info("[onMessage][收到 Account 刷新消息]"); + mpAccountService.initLocalCache(); + } + +} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpConfigRefreshConsumer.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpConfigRefreshConsumer.java deleted file mode 100644 index 853dd1479..000000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpConfigRefreshConsumer.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.mp.mq.consumer; - -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 { - - @Resource - private MpAccountService wxAccountService; - - @Override - public void onMessage(MpConfigRefreshMessage message) { - log.info("[onMessage][收到 MpConfig 刷新消息]"); - wxAccountService.initLocalCache(); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpConfigRefreshMessage.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpAccountRefreshMessage.java similarity index 62% rename from yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpConfigRefreshMessage.java rename to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpAccountRefreshMessage.java index 13bcc7335..3088f6bbc 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpConfigRefreshMessage.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpAccountRefreshMessage.java @@ -5,15 +5,17 @@ import lombok.Data; import lombok.EqualsAndHashCode; /** - * 微信配置数据刷新 Message + * 公众号账号刷新 Message + * + * @author 芋道源码 */ @Data @EqualsAndHashCode(callSuper = true) -public class MpConfigRefreshMessage extends AbstractChannelMessage { +public class MpAccountRefreshMessage extends AbstractChannelMessage { @Override public String getChannel() { - return "mp.config-data.refresh"; + return "mp.account.refresh"; } } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpConfigProducer.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpAccountProducer.java similarity index 50% rename from yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpConfigProducer.java rename to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpAccountProducer.java index f64d33cd4..47d8a1b52 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpConfigProducer.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpAccountProducer.java @@ -1,25 +1,27 @@ package cn.iocoder.yudao.module.mp.mq.producer; import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; -import cn.iocoder.yudao.module.mp.mq.message.MpConfigRefreshMessage; +import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** - * 微信配置数据相关消息的 Producer + * 公众号账号 Producer + * + * @author 芋道源码 */ @Component -public class MpConfigProducer { +public class MpAccountProducer { @Resource private RedisMQTemplate redisMQTemplate; /** - * 发送 {@link MpConfigRefreshMessage} 消息 + * 发送 {@link MpAccountRefreshMessage} 消息 */ - public void sendDictDataRefreshMessage() { - MpConfigRefreshMessage message = new MpConfigRefreshMessage(); + public void sendAccountRefreshMessage() { + MpAccountRefreshMessage message = new MpAccountRefreshMessage(); redisMQTemplate.send(message); } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java index 657fc7cbb..05248c4cd 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java @@ -5,12 +5,11 @@ import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReq 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.dal.dataobject.account.MpAccountDO; -import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import javax.validation.Valid; /** - * 公众号账户 Service 接口 + * 公众号账号 Service 接口 * * @author 芋道源码 */ @@ -22,7 +21,7 @@ public interface MpAccountService { void initLocalCache(); /** - * 创建公众号账户 + * 创建公众号账号 * * @param createReqVO 创建信息 * @return 编号 @@ -30,51 +29,57 @@ public interface MpAccountService { Long createAccount(@Valid MpAccountCreateReqVO createReqVO); /** - * 更新公众号账户 + * 更新公众号账号 * * @param updateReqVO 更新信息 */ void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO); /** - * 删除公众号账户 + * 删除公众号账号 * * @param id 编号 */ void deleteAccount(Long id); /** - * 获得公众号账户 + * 获得公众号账号 * * @param id 编号 - * @return 公众号账户 + * @return 公众号账号 */ MpAccountDO getAccount(Long id); /** - * 从缓存中,获得公众号账户 + * 从缓存中,获得公众号账号 * * @param appId 微信公众号 appId - * @return 公众号账户 + * @return 公众号账号 */ MpAccountDO getAccountFromCache(String appId); /** - * 获得公众号账户分页 + * 获得公众号账号分页 * * @param pageReqVO 分页查询 - * @return 公众号账户分页 + * @return 公众号账号分页 */ PageResult getAccountPage(MpAccountPageReqVO pageReqVO); - // TODO 芋艿:去除这样的方法 /** - * 查询账户 + * 生成公众号账号的二维码 * - * @param field - * @param val - * @return + * @param id 编号 */ - MpAccountDO findBy(SFunction field, Object val); + void generateAccountQrCode(Long id); + + /** + * 清空公众号账号的 API 配额 + * + * 参考文档:接口调用频次限制说明 + * + * @param id 编号 + */ + void clearAccountQuota(Long id); } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java index adbe28ac2..4da23dd4d 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java @@ -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 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 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 芋艿:校验唯一性 - // 插入 - MpAccountDO wxAccount = MpAccountConvert.INSTANCE.convert(createReqVO); - mpAccountMapper.insert(wxAccount); + // 校验 appId 唯一 + validateAppIdUnique(null, createReqVO.getAppId()); - // TODO 芋艿:刷新的方式 - mpConfigDataProducer.sendDictDataRefreshMessage(); - // 返回 - return wxAccount.getId(); + // 插入 + MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO); + mpAccountMapper.insert(account); + + // 发送刷新消息 + 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 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()); + } } } diff --git a/yudao-ui-admin/src/api/mp/account.js b/yudao-ui-admin/src/api/mp/account.js index 4ff18ffac..48ee61f35 100644 --- a/yudao-ui-admin/src/api/mp/account.js +++ b/yudao-ui-admin/src/api/mp/account.js @@ -1,6 +1,6 @@ import request from '@/utils/request' -// 创建公众号账户 +// 创建公众号账号 export function createAccount(data) { return request({ url: '/mp/account/create', @@ -9,7 +9,7 @@ export function createAccount(data) { }) } -// 更新公众号账户 +// 更新公众号账号 export function updateAccount(data) { return request({ url: '/mp/account/update', @@ -18,7 +18,7 @@ export function updateAccount(data) { }) } -// 删除公众号账户 +// 删除公众号账号 export function deleteAccount(id) { return request({ url: '/mp/account/delete?id=' + id, @@ -26,7 +26,7 @@ export function deleteAccount(id) { }) } -// 获得公众号账户 +// 获得公众号账号 export function getAccount(id) { return request({ url: '/mp/account/get?id=' + id, @@ -34,7 +34,7 @@ export function getAccount(id) { }) } -// 获得公众号账户分页 +// 获得公众号账号分页 export function getAccountPage(query) { return request({ url: '/mp/account/page', @@ -43,12 +43,18 @@ export function getAccountPage(query) { }) } -// 导出公众号账户 Excel -export function exportAccountExcel(query) { +// 生成公众号二维码 +export function generateAccountQrCode(id) { return request({ - url: '/mp/account/export-excel', - method: 'get', - params: query, - responseType: 'blob' + url: '/mp/account/generate-qr-code?id=' + id, + method: 'put' + }) +} + +// 清空公众号 API 配额 +export function clearAccountQuota(id) { + return request({ + url: '/mp/account/clear-quota?id=' + id, + method: 'put' }) } diff --git a/yudao-ui-admin/src/views/mp/account/index.vue b/yudao-ui-admin/src/views/mp/account/index.vue index c81977a86..97d6ff514 100644 --- a/yudao-ui-admin/src/views/mp/account/index.vue +++ b/yudao-ui-admin/src/views/mp/account/index.vue @@ -28,16 +28,25 @@ - - - - - - -