Commit 39b2afd506f177c7410e63f0af3df4980d22340c

Authored by YunaiV
1 parent 71beeabe

mp:完成的公众号的管理界面

Showing 16 changed files with 272 additions and 184 deletions
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; @@ -9,9 +9,12 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 */ 9 */
10 public interface ErrorCodeConstants { 10 public interface ErrorCodeConstants {
11 11
12 - // ========== 用户相关 1004001000============  
13 - ErrorCode WX_ACCOUNT_NOT_EXISTS = new ErrorCode(1006001000, "公众号账户不存在");  
14 - ErrorCode WX_ACCOUNT_FANS_NOT_EXISTS = new ErrorCode(1006001001, "粉丝账号不存在"); 12 + // ========== 公众号账号 1006000000============
  13 + ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1006000000, "公众号账号不存在");
  14 + ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}");
  15 + ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}");
  16 +
  17 + // TODO 要处理下
15 ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在"); 18 ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在");
16 19
17 } 20 }
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; @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.account;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*; 5 +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
  6 +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
  7 +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountRespVO;
  8 +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;
6 import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert; 9 import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert;
7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 10 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
8 import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 11 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
@@ -18,54 +21,72 @@ import javax.validation.Valid; @@ -18,54 +21,72 @@ import javax.validation.Valid;
18 21
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 22 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 23
21 -@Api(tags = "管理后台 - 公众号账户") 24 +@Api(tags = "管理后台 - 公众号账号")
22 @RestController 25 @RestController
23 @RequestMapping("/mp/account") 26 @RequestMapping("/mp/account")
24 @Validated 27 @Validated
25 public class MpAccountController { 28 public class MpAccountController {
26 29
27 @Resource 30 @Resource
28 - private MpAccountService wxAccountService; 31 + private MpAccountService mpAccountService;
29 32
30 @PostMapping("/create") 33 @PostMapping("/create")
31 - @ApiOperation("创建公众号账户") 34 + @ApiOperation("创建公众号账号")
32 @PreAuthorize("@ss.hasPermission('mp:account:create')") 35 @PreAuthorize("@ss.hasPermission('mp:account:create')")
33 public CommonResult<Long> createWxAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { 36 public CommonResult<Long> createWxAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) {
34 - return success(wxAccountService.createAccount(createReqVO)); 37 + return success(mpAccountService.createAccount(createReqVO));
35 } 38 }
36 39
37 @PutMapping("/update") 40 @PutMapping("/update")
38 - @ApiOperation("更新公众号账户") 41 + @ApiOperation("更新公众号账号")
39 @PreAuthorize("@ss.hasPermission('mp:account:update')") 42 @PreAuthorize("@ss.hasPermission('mp:account:update')")
40 public CommonResult<Boolean> updateWxAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { 43 public CommonResult<Boolean> updateWxAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) {
41 - wxAccountService.updateAccount(updateReqVO); 44 + mpAccountService.updateAccount(updateReqVO);
42 return success(true); 45 return success(true);
43 } 46 }
44 47
45 @DeleteMapping("/delete") 48 @DeleteMapping("/delete")
46 - @ApiOperation("删除公众号账户") 49 + @ApiOperation("删除公众号账号")
47 @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) 50 @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
48 @PreAuthorize("@ss.hasPermission('mp:account:delete')") 51 @PreAuthorize("@ss.hasPermission('mp:account:delete')")
49 public CommonResult<Boolean> deleteWxAccount(@RequestParam("id") Long id) { 52 public CommonResult<Boolean> deleteWxAccount(@RequestParam("id") Long id) {
50 - wxAccountService.deleteAccount(id); 53 + mpAccountService.deleteAccount(id);
51 return success(true); 54 return success(true);
52 } 55 }
53 56
54 @GetMapping("/get") 57 @GetMapping("/get")
55 - @ApiOperation("获得公众号账户") 58 + @ApiOperation("获得公众号账号")
56 @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) 59 @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
57 @PreAuthorize("@ss.hasPermission('mp:account:query')") 60 @PreAuthorize("@ss.hasPermission('mp:account:query')")
58 public CommonResult<MpAccountRespVO> getWxAccount(@RequestParam("id") Long id) { 61 public CommonResult<MpAccountRespVO> getWxAccount(@RequestParam("id") Long id) {
59 - MpAccountDO wxAccount = wxAccountService.getAccount(id); 62 + MpAccountDO wxAccount = mpAccountService.getAccount(id);
60 return success(MpAccountConvert.INSTANCE.convert(wxAccount)); 63 return success(MpAccountConvert.INSTANCE.convert(wxAccount));
61 } 64 }
62 65
63 @GetMapping("/page") 66 @GetMapping("/page")
64 - @ApiOperation("获得公众号账户分页") 67 + @ApiOperation("获得公众号账号分页")
65 @PreAuthorize("@ss.hasPermission('mp:account:query')") 68 @PreAuthorize("@ss.hasPermission('mp:account:query')")
66 public CommonResult<PageResult<MpAccountRespVO>> getWxAccountPage(@Valid MpAccountPageReqVO pageVO) { 69 public CommonResult<PageResult<MpAccountRespVO>> getWxAccountPage(@Valid MpAccountPageReqVO pageVO) {
67 - PageResult<MpAccountDO> pageResult = wxAccountService.getAccountPage(pageVO); 70 + PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO);
68 return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); 71 return success(MpAccountConvert.INSTANCE.convertPage(pageResult));
69 } 72 }
70 73
  74 + @PutMapping("/generate-qr-code")
  75 + @ApiOperation("生成公众号二维码")
  76 + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
  77 + @PreAuthorize("@ss.hasPermission('mp:account:qr-code')")
  78 + public CommonResult<Boolean> generateAccountQrCode(@RequestParam("id") Long id) {
  79 + mpAccountService.generateAccountQrCode(id);
  80 + return success(true);
  81 + }
  82 +
  83 + @PutMapping("/clear-quota")
  84 + @ApiOperation("清空公众号 API 配额")
  85 + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
  86 + @PreAuthorize("@ss.hasPermission('mp:account:clear-quota')")
  87 + public CommonResult<Boolean> clearAccountQuota(@RequestParam("id") Long id) {
  88 + mpAccountService.clearAccountQuota(id);
  89 + return success(true);
  90 + }
  91 +
71 } 92 }
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; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
3 import io.swagger.annotations.ApiModelProperty; 3 import io.swagger.annotations.ApiModelProperty;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -import javax.validation.constraints.NotNull; 6 +import javax.validation.constraints.NotEmpty;
7 7
8 /** 8 /**
9 - * 公众号账户 Base VO,提供给添加、修改、详细的子 VO 使用 9 + * 公众号账号 Base VO,提供给添加、修改、详细的子 VO 使用
10 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 10 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
11 * 11 *
12 * @author fengdan 12 * @author fengdan
@@ -14,29 +14,30 @@ import javax.validation.constraints.NotNull; @@ -14,29 +14,30 @@ import javax.validation.constraints.NotNull;
14 @Data 14 @Data
15 public class MpAccountBaseVO { 15 public class MpAccountBaseVO {
16 16
17 - @ApiModelProperty(value = "公众号名称", required = true)  
18 - @NotNull(message = "公众号名称不能为空") 17 + @ApiModelProperty(value = "公众号名称", required = true, example = "芋道源码")
  18 + @NotEmpty(message = "公众号名称不能为空")
19 private String name; 19 private String name;
20 20
21 - @ApiModelProperty(value = "公众号账户", required = true)  
22 - @NotNull(message = "公众号账户不能为空") 21 + @ApiModelProperty(value = "公众号微信号", required = true, example = "yudaoyuanma")
  22 + @NotEmpty(message = "公众号微信号不能为空")
23 private String account; 23 private String account;
24 24
25 @ApiModelProperty(value = "公众号 appId", required = true, example = "wx5b23ba7a5589ecbb") 25 @ApiModelProperty(value = "公众号 appId", required = true, example = "wx5b23ba7a5589ecbb")
26 - @NotNull(message = "公众号 appId 不能为空") 26 + @NotEmpty(message = "公众号 appId 不能为空")
27 private String appId; 27 private String appId;
28 28
29 - @ApiModelProperty(value = "公众号密钥", required = true)  
30 - @NotNull(message = "公众号密钥不能为空") 29 + @ApiModelProperty(value = "公众号密钥", required = true, example = "3a7b3b20c537e52e74afd395eb85f61f")
  30 + @NotEmpty(message = "公众号密钥不能为空")
31 private String appSecret; 31 private String appSecret;
32 32
33 - @ApiModelProperty(value = "公众号 token", required = true) 33 + @ApiModelProperty(value = "公众号 token", required = true, example = "kangdayuzhen")
  34 + @NotEmpty(message = "公众号 token 不能为空")
34 private String token; 35 private String token;
35 36
36 - @ApiModelProperty(value = "加密密钥") 37 + @ApiModelProperty(value = "加密密钥", example = "gjN+Ksei")
37 private String aesKey; 38 private String aesKey;
38 39
39 - @ApiModelProperty(value = "备注") 40 + @ApiModelProperty(value = "备注", example = "请关注芋道源码,学习技术")
40 private String remark; 41 private String remark;
41 42
42 } 43 }
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; @@ -5,7 +5,7 @@ import lombok.Data;
5 import lombok.EqualsAndHashCode; 5 import lombok.EqualsAndHashCode;
6 import lombok.ToString; 6 import lombok.ToString;
7 7
8 -@ApiModel("管理后台 - 公众号账户创建 Request VO") 8 +@ApiModel("管理后台 - 公众号账号创建 Request VO")
9 @Data 9 @Data
10 @EqualsAndHashCode(callSuper = true) 10 @EqualsAndHashCode(callSuper = true)
11 @ToString(callSuper = true) 11 @ToString(callSuper = true)
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.*; @@ -4,7 +4,7 @@ import lombok.*;
4 import io.swagger.annotations.*; 4 import io.swagger.annotations.*;
5 import cn.iocoder.yudao.framework.common.pojo.PageParam; 5 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 6
7 -@ApiModel("管理后台 - 公众号账户分页 Request VO") 7 +@ApiModel("管理后台 - 公众号账号分页 Request VO")
8 @Data 8 @Data
9 @EqualsAndHashCode(callSuper = true) 9 @EqualsAndHashCode(callSuper = true)
10 @ToString(callSuper = true) 10 @ToString(callSuper = true)
@@ -13,7 +13,7 @@ public class MpAccountPageReqVO extends PageParam { @@ -13,7 +13,7 @@ public class MpAccountPageReqVO extends PageParam {
13 @ApiModelProperty(value = "公众号名称", notes = "模糊匹配") 13 @ApiModelProperty(value = "公众号名称", notes = "模糊匹配")
14 private String name; 14 private String name;
15 15
16 - @ApiModelProperty(value = "公众号账户", notes = "模糊匹配") 16 + @ApiModelProperty(value = "公众号账号", notes = "模糊匹配")
17 private String account; 17 private String account;
18 18
19 @ApiModelProperty(value = "公众号 appid", notes = "模糊匹配") 19 @ApiModelProperty(value = "公众号 appid", notes = "模糊匹配")
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; @@ -8,25 +8,19 @@ import lombok.ToString;
8 8
9 import java.util.Date; 9 import java.util.Date;
10 10
11 -/**  
12 - * @author fengdan  
13 - */  
14 -@ApiModel("管理后台 - 公众号账户 Response VO") 11 +@ApiModel("管理后台 - 公众号账号 Response VO")
15 @Data 12 @Data
16 @EqualsAndHashCode(callSuper = true) 13 @EqualsAndHashCode(callSuper = true)
17 @ToString(callSuper = true) 14 @ToString(callSuper = true)
18 public class MpAccountRespVO extends MpAccountBaseVO { 15 public class MpAccountRespVO extends MpAccountBaseVO {
19 16
20 - @ApiModelProperty(value = "编号", required = true) 17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
21 private Long id; 18 private Long id;
22 19
23 - @ApiModelProperty(value = "二维码图片URL") 20 + @ApiModelProperty(value = "二维码图片URL", example = "https://www.iocoder.cn/1024.png")
24 private String qrCodeUrl; 21 private String qrCodeUrl;
25 22
26 @ApiModelProperty(value = "创建时间", required = true) 23 @ApiModelProperty(value = "创建时间", required = true)
27 private Date createTime; 24 private Date createTime;
28 25
29 - @ApiModelProperty(value = "公众号密钥", required = true)  
30 - private String appSecret;  
31 -  
32 } 26 }
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.*; @@ -7,7 +7,7 @@ import javax.validation.constraints.*;
7 /** 7 /**
8 * @author fengdan 8 * @author fengdan
9 */ 9 */
10 -@ApiModel("管理后台 - 公众号账户更新 Request VO") 10 +@ApiModel("管理后台 - 公众号账号更新 Request VO")
11 @Data 11 @Data
12 @EqualsAndHashCode(callSuper = true) 12 @EqualsAndHashCode(callSuper = true)
13 @ToString(callSuper = true) 13 @ToString(callSuper = true)
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java
1 package cn.iocoder.yudao.module.mp.dal.dataobject.account; 1 package cn.iocoder.yudao.module.mp.dal.dataobject.account;
2 2
3 -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;  
4 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; 3 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
5 import com.baomidou.mybatisplus.annotation.KeySequence; 4 import com.baomidou.mybatisplus.annotation.KeySequence;
6 import com.baomidou.mybatisplus.annotation.TableId; 5 import com.baomidou.mybatisplus.annotation.TableId;
7 import com.baomidou.mybatisplus.annotation.TableName; 6 import com.baomidou.mybatisplus.annotation.TableName;
8 -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;  
9 import lombok.*; 7 import lombok.*;
10 -import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;  
11 -import me.chanjar.weixin.mp.config.WxMpConfigStorage;  
12 -import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;  
13 8
14 /** 9 /**
15 - * 公众号账户 DO 10 + * 公众号账号 DO
16 * 11 *
17 * @author 芋道源码 12 * @author 芋道源码
18 */ 13 */
@@ -36,7 +31,7 @@ public class MpAccountDO extends TenantBaseDO { @@ -36,7 +31,7 @@ public class MpAccountDO extends TenantBaseDO {
36 */ 31 */
37 private String name; 32 private String name;
38 /** 33 /**
39 - * 公众号账户 34 + * 公众号账号
40 */ 35 */
41 private String account; 36 private String account;
42 /** 37 /**
@@ -52,7 +47,7 @@ public class MpAccountDO extends TenantBaseDO { @@ -52,7 +47,7 @@ public class MpAccountDO extends TenantBaseDO {
52 */ 47 */
53 private String token; 48 private String token;
54 /** 49 /**
55 - * 加密密钥 50 + * 消息加解密密钥
56 */ 51 */
57 private String aesKey; 52 private String aesKey;
58 /** 53 /**
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; @@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; 6 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 -import org.apache.ibatis.annotations.Select;  
10 -  
11 -import java.time.LocalDateTime;  
12 9
13 @Mapper 10 @Mapper
14 public interface MpAccountMapper extends BaseMapperX<MpAccountDO> { 11 public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
@@ -21,7 +18,8 @@ public interface MpAccountMapper extends BaseMapperX&lt;MpAccountDO&gt; { @@ -21,7 +18,8 @@ public interface MpAccountMapper extends BaseMapperX&lt;MpAccountDO&gt; {
21 .orderByDesc(MpAccountDO::getId)); 18 .orderByDesc(MpAccountDO::getId));
22 } 19 }
23 20
24 - @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}")  
25 - Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); 21 + default MpAccountDO selectByAppId(String appId) {
  22 + return selectOne(MpAccountDO::getAppId, appId);
  23 + }
26 24
27 } 25 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpConfigRefreshConsumer.java renamed to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpAccountRefreshConsumer.java
1 package cn.iocoder.yudao.module.mp.mq.consumer; 1 package cn.iocoder.yudao.module.mp.mq.consumer;
2 2
3 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; 3 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4 -import cn.iocoder.yudao.module.mp.mq.message.MpConfigRefreshMessage; 4 +import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage;
5 import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 5 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
6 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
7 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
@@ -9,21 +9,21 @@ import org.springframework.stereotype.Component; @@ -9,21 +9,21 @@ import org.springframework.stereotype.Component;
9 import javax.annotation.Resource; 9 import javax.annotation.Resource;
10 10
11 /** 11 /**
12 - * 针对 {@link MpConfigRefreshMessage} 的消费者 12 + * 针对 {@link MpAccountRefreshMessage} 的消费者
13 * 13 *
14 - * @author lyz 14 + * @author 芋道源码
15 */ 15 */
16 @Component 16 @Component
17 @Slf4j 17 @Slf4j
18 -public class MpConfigRefreshConsumer extends AbstractChannelMessageListener<MpConfigRefreshMessage> { 18 +public class MpAccountRefreshConsumer extends AbstractChannelMessageListener<MpAccountRefreshMessage> {
19 19
20 @Resource 20 @Resource
21 - private MpAccountService wxAccountService; 21 + private MpAccountService mpAccountService;
22 22
23 @Override 23 @Override
24 - public void onMessage(MpConfigRefreshMessage message) {  
25 - log.info("[onMessage][收到 MpConfig 刷新消息]");  
26 - wxAccountService.initLocalCache(); 24 + public void onMessage(MpAccountRefreshMessage message) {
  25 + log.info("[onMessage][收到 Account 刷新消息]");
  26 + mpAccountService.initLocalCache();
27 } 27 }
28 28
29 } 29 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpConfigRefreshMessage.java renamed to 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; @@ -5,15 +5,17 @@ import lombok.Data;
5 import lombok.EqualsAndHashCode; 5 import lombok.EqualsAndHashCode;
6 6
7 /** 7 /**
8 - * 微信配置数据刷新 Message 8 + * 公众号账号刷新 Message
  9 + *
  10 + * @author 芋道源码
9 */ 11 */
10 @Data 12 @Data
11 @EqualsAndHashCode(callSuper = true) 13 @EqualsAndHashCode(callSuper = true)
12 -public class MpConfigRefreshMessage extends AbstractChannelMessage { 14 +public class MpAccountRefreshMessage extends AbstractChannelMessage {
13 15
14 @Override 16 @Override
15 public String getChannel() { 17 public String getChannel() {
16 - return "mp.config-data.refresh"; 18 + return "mp.account.refresh";
17 } 19 }
18 20
19 } 21 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpConfigProducer.java renamed to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpAccountProducer.java
1 package cn.iocoder.yudao.module.mp.mq.producer; 1 package cn.iocoder.yudao.module.mp.mq.producer;
2 2
3 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; 3 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4 -import cn.iocoder.yudao.module.mp.mq.message.MpConfigRefreshMessage; 4 +import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage;
5 import org.springframework.stereotype.Component; 5 import org.springframework.stereotype.Component;
6 6
7 import javax.annotation.Resource; 7 import javax.annotation.Resource;
8 8
9 /** 9 /**
10 - * 微信配置数据相关消息的 Producer 10 + * 公众号账号 Producer
  11 + *
  12 + * @author 芋道源码
11 */ 13 */
12 @Component 14 @Component
13 -public class MpConfigProducer { 15 +public class MpAccountProducer {
14 16
15 @Resource 17 @Resource
16 private RedisMQTemplate redisMQTemplate; 18 private RedisMQTemplate redisMQTemplate;
17 19
18 /** 20 /**
19 - * 发送 {@link MpConfigRefreshMessage} 消息 21 + * 发送 {@link MpAccountRefreshMessage} 消息
20 */ 22 */
21 - public void sendDictDataRefreshMessage() {  
22 - MpConfigRefreshMessage message = new MpConfigRefreshMessage(); 23 + public void sendAccountRefreshMessage() {
  24 + MpAccountRefreshMessage message = new MpAccountRefreshMessage();
23 redisMQTemplate.send(message); 25 redisMQTemplate.send(message);
24 } 26 }
25 27
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 @@ -5,12 +5,11 @@ import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReq
5 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; 5 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
6 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; 6 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;
7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
8 -import com.baomidou.mybatisplus.core.toolkit.support.SFunction;  
9 8
10 import javax.validation.Valid; 9 import javax.validation.Valid;
11 10
12 /** 11 /**
13 - * 公众号账户 Service 接口 12 + * 公众号账号 Service 接口
14 * 13 *
15 * @author 芋道源码 14 * @author 芋道源码
16 */ 15 */
@@ -22,7 +21,7 @@ public interface MpAccountService { @@ -22,7 +21,7 @@ public interface MpAccountService {
22 void initLocalCache(); 21 void initLocalCache();
23 22
24 /** 23 /**
25 - * 创建公众号账户 24 + * 创建公众号账号
26 * 25 *
27 * @param createReqVO 创建信息 26 * @param createReqVO 创建信息
28 * @return 编号 27 * @return 编号
@@ -30,51 +29,57 @@ public interface MpAccountService { @@ -30,51 +29,57 @@ public interface MpAccountService {
30 Long createAccount(@Valid MpAccountCreateReqVO createReqVO); 29 Long createAccount(@Valid MpAccountCreateReqVO createReqVO);
31 30
32 /** 31 /**
33 - * 更新公众号账户 32 + * 更新公众号账号
34 * 33 *
35 * @param updateReqVO 更新信息 34 * @param updateReqVO 更新信息
36 */ 35 */
37 void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO); 36 void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO);
38 37
39 /** 38 /**
40 - * 删除公众号账户 39 + * 删除公众号账号
41 * 40 *
42 * @param id 编号 41 * @param id 编号
43 */ 42 */
44 void deleteAccount(Long id); 43 void deleteAccount(Long id);
45 44
46 /** 45 /**
47 - * 获得公众号账户 46 + * 获得公众号账号
48 * 47 *
49 * @param id 编号 48 * @param id 编号
50 - * @return 公众号账户 49 + * @return 公众号账号
51 */ 50 */
52 MpAccountDO getAccount(Long id); 51 MpAccountDO getAccount(Long id);
53 52
54 /** 53 /**
55 - * 从缓存中,获得公众号账户 54 + * 从缓存中,获得公众号账号
56 * 55 *
57 * @param appId 微信公众号 appId 56 * @param appId 微信公众号 appId
58 - * @return 公众号账户 57 + * @return 公众号账号
59 */ 58 */
60 MpAccountDO getAccountFromCache(String appId); 59 MpAccountDO getAccountFromCache(String appId);
61 60
62 /** 61 /**
63 - * 获得公众号账户分页 62 + * 获得公众号账号分页
64 * 63 *
65 * @param pageReqVO 分页查询 64 * @param pageReqVO 分页查询
66 - * @return 公众号账户分页 65 + * @return 公众号账号分页
67 */ 66 */
68 PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO); 67 PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO);
69 68
70 - // TODO 芋艿:去除这样的方法  
71 /** 69 /**
72 - * 查询账户 70 + * 生成公众号账号的二维码
73 * 71 *
74 - * @param field  
75 - * @param val  
76 - * @return 72 + * @param id 编号
  73 + */
  74 + void generateAccountQrCode(Long id);
  75 +
  76 + /**
  77 + * 清空公众号账号的 API 配额
  78 + *
  79 + * 参考文档:<a href="https://developers.weixin.qq.com/doc/offiaccount/Message_Management/API_Call_Limits.html">接口调用频次限制说明</a>
  80 + *
  81 + * @param id 编号
77 */ 82 */
78 - MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val); 83 + void clearAccountQuota(Long id);
79 84
80 } 85 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java
1 package cn.iocoder.yudao.module.mp.service.account; 1 package cn.iocoder.yudao.module.mp.service.account;
2 2
  3 +import cn.hutool.core.util.ObjUtil;
3 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 4 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -12,24 +13,27 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; @@ -12,24 +13,27 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
12 import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper; 13 import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper;
13 import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants; 14 import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
14 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; 15 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
15 -import cn.iocoder.yudao.module.mp.mq.producer.MpConfigProducer;  
16 -import com.baomidou.mybatisplus.core.toolkit.support.SFunction; 16 +import cn.iocoder.yudao.module.mp.mq.producer.MpAccountProducer;
  17 +import com.google.common.annotations.VisibleForTesting;
17 import lombok.Getter; 18 import lombok.Getter;
18 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import me.chanjar.weixin.common.error.WxErrorException;
  21 +import me.chanjar.weixin.mp.api.WxMpService;
  22 +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
19 import org.springframework.context.annotation.Lazy; 23 import org.springframework.context.annotation.Lazy;
20 -import org.springframework.scheduling.annotation.Scheduled;  
21 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
22 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
23 26
24 import javax.annotation.PostConstruct; 27 import javax.annotation.PostConstruct;
25 import javax.annotation.Resource; 28 import javax.annotation.Resource;
26 -import java.time.LocalDateTime;  
27 import java.util.List; 29 import java.util.List;
28 import java.util.Map; 30 import java.util.Map;
29 31
  32 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  33 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS;
30 34
31 /** 35 /**
32 - * 公众号账户 Service 实现类 36 + * 公众号账号 Service 实现类
33 * 37 *
34 * @author fengdan 38 * @author fengdan
35 */ 39 */
@@ -39,12 +43,6 @@ import java.util.Map; @@ -39,12 +43,6 @@ import java.util.Map;
39 public class MpAccountServiceImpl implements MpAccountService { 43 public class MpAccountServiceImpl implements MpAccountService {
40 44
41 /** 45 /**
42 - * 定时执行 {@link #schedulePeriodicRefresh()} 的周期  
43 - * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高  
44 - */  
45 - private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;  
46 -  
47 - /**  
48 * 账号缓存 46 * 账号缓存
49 * key:账号编号 {@link MpAccountDO#getAppId()} 47 * key:账号编号 {@link MpAccountDO#getAppId()}
50 * 48 *
@@ -52,11 +50,6 @@ public class MpAccountServiceImpl implements MpAccountService { @@ -52,11 +50,6 @@ public class MpAccountServiceImpl implements MpAccountService {
52 */ 50 */
53 @Getter 51 @Getter
54 private volatile Map<String, MpAccountDO> accountCache; 52 private volatile Map<String, MpAccountDO> accountCache;
55 - /**  
56 - * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新  
57 - */  
58 - @Getter  
59 - private volatile LocalDateTime maxUpdateTime;  
60 53
61 @Resource 54 @Resource
62 private MpAccountMapper mpAccountMapper; 55 private MpAccountMapper mpAccountMapper;
@@ -66,89 +59,85 @@ public class MpAccountServiceImpl implements MpAccountService { @@ -66,89 +59,85 @@ public class MpAccountServiceImpl implements MpAccountService {
66 private MpServiceFactory mpServiceFactory; 59 private MpServiceFactory mpServiceFactory;
67 60
68 @Resource 61 @Resource
69 - private MpConfigProducer mpConfigDataProducer; 62 + private MpAccountProducer mpAccountProducer;
70 63
71 @Override 64 @Override
72 @PostConstruct 65 @PostConstruct
73 public void initLocalCache() { 66 public void initLocalCache() {
74 - initLocalCacheIfUpdate(null);  
75 - }  
76 -  
77 - @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)  
78 - public void schedulePeriodicRefresh() {  
79 - initLocalCacheIfUpdate(this.maxUpdateTime);  
80 - }  
81 -  
82 - /**  
83 - * 刷新本地缓存  
84 - *  
85 - * @param maxUpdateTime 最大更新时间  
86 - * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存  
87 - * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存  
88 - */  
89 - private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {  
90 // 注意:忽略自动多租户,因为要全局初始化缓存 67 // 注意:忽略自动多租户,因为要全局初始化缓存
91 TenantUtils.executeIgnore(() -> { 68 TenantUtils.executeIgnore(() -> {
92 - // 第一步:基于 maxUpdateTime 判断缓存是否刷新。  
93 - // 如果没有增量的数据变化,则不进行本地缓存的刷新  
94 - if (maxUpdateTime != null  
95 - && mpAccountMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {  
96 - log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);  
97 - return;  
98 - } 69 + // 第一步:查询数据
99 List<MpAccountDO> accounts = mpAccountMapper.selectList(); 70 List<MpAccountDO> accounts = mpAccountMapper.selectList();
100 log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size()); 71 log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size());
101 72
102 // 第二步:构建缓存。创建或更新支付 Client 73 // 第二步:构建缓存。创建或更新支付 Client
103 mpServiceFactory.init(accounts); 74 mpServiceFactory.init(accounts);
104 accountCache = CollectionUtils.convertMap(accounts, MpAccountDO::getAppId); 75 accountCache = CollectionUtils.convertMap(accounts, MpAccountDO::getAppId);
105 -  
106 - // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。  
107 - this.maxUpdateTime = CollectionUtils.getMaxValue(accounts, MpAccountDO::getUpdateTime);  
108 }); 76 });
109 } 77 }
110 78
111 @Override 79 @Override
112 public Long createAccount(MpAccountCreateReqVO createReqVO) { 80 public Long createAccount(MpAccountCreateReqVO createReqVO) {
113 - // TODO 芋艿:校验唯一性 81 + // 校验 appId 唯一
  82 + validateAppIdUnique(null, createReqVO.getAppId());
  83 +
114 // 插入 84 // 插入
115 - MpAccountDO wxAccount = MpAccountConvert.INSTANCE.convert(createReqVO);  
116 - mpAccountMapper.insert(wxAccount); 85 + MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO);
  86 + mpAccountMapper.insert(account);
117 87
118 - // TODO 芋艿:刷新的方式  
119 - mpConfigDataProducer.sendDictDataRefreshMessage();  
120 - // 返回  
121 - return wxAccount.getId(); 88 + // 发送刷新消息
  89 + mpAccountProducer.sendAccountRefreshMessage();
  90 + return account.getId();
122 } 91 }
123 92
124 @Override 93 @Override
125 public void updateAccount(MpAccountUpdateReqVO updateReqVO) { 94 public void updateAccount(MpAccountUpdateReqVO updateReqVO) {
126 - // TODO 芋艿:校验唯一性  
127 // 校验存在 95 // 校验存在
128 - validateWxAccountExists(updateReqVO.getId()); 96 + validateAccountExists(updateReqVO.getId());
  97 + // 校验 appId 唯一
  98 + validateAppIdUnique(updateReqVO.getId(), updateReqVO.getAppId());
  99 +
129 // 更新 100 // 更新
130 MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO); 101 MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);
131 mpAccountMapper.updateById(updateObj); 102 mpAccountMapper.updateById(updateObj);
132 103
133 - // TODO 芋艿:刷新的方式  
134 - mpConfigDataProducer.sendDictDataRefreshMessage(); 104 + // 发送刷新消息
  105 + mpAccountProducer.sendAccountRefreshMessage();
135 } 106 }
136 107
137 @Override 108 @Override
138 public void deleteAccount(Long id) { 109 public void deleteAccount(Long id) {
139 // 校验存在 110 // 校验存在
140 - validateWxAccountExists(id); 111 + validateAccountExists(id);
141 // 删除 112 // 删除
142 mpAccountMapper.deleteById(id); 113 mpAccountMapper.deleteById(id);
143 114
144 - // TODO 芋艿:刷新的方式  
145 - mpConfigDataProducer.sendDictDataRefreshMessage(); 115 + // 发送刷新消息
  116 + mpAccountProducer.sendAccountRefreshMessage();
146 } 117 }
147 118
148 - private void validateWxAccountExists(Long id) {  
149 - if (mpAccountMapper.selectById(id) == null) {  
150 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.WX_ACCOUNT_NOT_EXISTS); 119 + private MpAccountDO validateAccountExists(Long id) {
  120 + MpAccountDO account = mpAccountMapper.selectById(id);
  121 + if (account == null) {
  122 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS);
151 } 123 }
  124 + return account;
  125 + }
  126 +
  127 + @VisibleForTesting
  128 + public void validateAppIdUnique(Long id, String appId) {
  129 + // 多个租户,appId 是不能重复,否则公众号回调会无法识别
  130 + TenantUtils.executeIgnore(() -> {
  131 + MpAccountDO account = mpAccountMapper.selectByAppId(appId);
  132 + if (account == null) {
  133 + return;
  134 + }
  135 + // 存在 account 记录的情况下
  136 + if (id == null // 新增时,说明重复
  137 + || ObjUtil.notEqual(id, account.getId())) { // 更新时,如果 id 不一致,说明重复
  138 + throw exception(USER_USERNAME_EXISTS);
  139 + }
  140 + });
152 } 141 }
153 142
154 @Override 143 @Override
@@ -167,8 +156,36 @@ public class MpAccountServiceImpl implements MpAccountService { @@ -167,8 +156,36 @@ public class MpAccountServiceImpl implements MpAccountService {
167 } 156 }
168 157
169 @Override 158 @Override
170 - public MpAccountDO findBy(SFunction<MpAccountDO, ?> field, Object val) {  
171 - return mpAccountMapper.selectOne(field, val); 159 + public void generateAccountQrCode(Long id) {
  160 + // 校验存在
  161 + MpAccountDO account = validateAccountExists(id);
  162 +
  163 + // 生成二维码
  164 + WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());
  165 + String qrCodeUrl;
  166 + try {
  167 + WxMpQrCodeTicket qrCodeTicket = mpService.getQrcodeService().qrCodeCreateLastTicket("default");
  168 + qrCodeUrl = mpService.getQrcodeService().qrCodePictureUrl(qrCodeTicket.getTicket());
  169 + } catch (WxErrorException e) {
  170 + throw exception(ErrorCodeConstants.ACCOUNT_GENERATE_QR_CODE_FAIL, e.getError().getErrorMsg());
  171 + }
  172 +
  173 + // 保存二维码
  174 + mpAccountMapper.updateById(new MpAccountDO().setId(id).setQrCodeUrl(qrCodeUrl));
  175 + }
  176 +
  177 + @Override
  178 + public void clearAccountQuota(Long id) {
  179 + // 校验存在
  180 + MpAccountDO account = validateAccountExists(id);
  181 +
  182 + // 生成二维码
  183 + WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());
  184 + try {
  185 + mpService.clearQuota(account.getAppId());
  186 + } catch (WxErrorException e) {
  187 + throw exception(ErrorCodeConstants.ACCOUNT_CLEAR_QUOTA_FAIL, e.getError().getErrorMsg());
  188 + }
172 } 189 }
173 190
174 } 191 }
yudao-ui-admin/src/api/mp/account.js
1 import request from '@/utils/request' 1 import request from '@/utils/request'
2 2
3 -// 创建公众号账户 3 +// 创建公众号账号
4 export function createAccount(data) { 4 export function createAccount(data) {
5 return request({ 5 return request({
6 url: '/mp/account/create', 6 url: '/mp/account/create',
@@ -9,7 +9,7 @@ export function createAccount(data) { @@ -9,7 +9,7 @@ export function createAccount(data) {
9 }) 9 })
10 } 10 }
11 11
12 -// 更新公众号账户 12 +// 更新公众号账号
13 export function updateAccount(data) { 13 export function updateAccount(data) {
14 return request({ 14 return request({
15 url: '/mp/account/update', 15 url: '/mp/account/update',
@@ -18,7 +18,7 @@ export function updateAccount(data) { @@ -18,7 +18,7 @@ export function updateAccount(data) {
18 }) 18 })
19 } 19 }
20 20
21 -// 删除公众号账户 21 +// 删除公众号账号
22 export function deleteAccount(id) { 22 export function deleteAccount(id) {
23 return request({ 23 return request({
24 url: '/mp/account/delete?id=' + id, 24 url: '/mp/account/delete?id=' + id,
@@ -26,7 +26,7 @@ export function deleteAccount(id) { @@ -26,7 +26,7 @@ export function deleteAccount(id) {
26 }) 26 })
27 } 27 }
28 28
29 -// 获得公众号账户 29 +// 获得公众号账号
30 export function getAccount(id) { 30 export function getAccount(id) {
31 return request({ 31 return request({
32 url: '/mp/account/get?id=' + id, 32 url: '/mp/account/get?id=' + id,
@@ -34,7 +34,7 @@ export function getAccount(id) { @@ -34,7 +34,7 @@ export function getAccount(id) {
34 }) 34 })
35 } 35 }
36 36
37 -// 获得公众号账户分页 37 +// 获得公众号账号分页
38 export function getAccountPage(query) { 38 export function getAccountPage(query) {
39 return request({ 39 return request({
40 url: '/mp/account/page', 40 url: '/mp/account/page',
@@ -43,12 +43,18 @@ export function getAccountPage(query) { @@ -43,12 +43,18 @@ export function getAccountPage(query) {
43 }) 43 })
44 } 44 }
45 45
46 -// 导出公众号账户 Excel  
47 -export function exportAccountExcel(query) { 46 +// 生成公众号二维码
  47 +export function generateAccountQrCode(id) {
48 return request({ 48 return request({
49 - url: '/mp/account/export-excel',  
50 - method: 'get',  
51 - params: query,  
52 - responseType: 'blob' 49 + url: '/mp/account/generate-qr-code?id=' + id,
  50 + method: 'put'
  51 + })
  52 +}
  53 +
  54 +// 清空公众号 API 配额
  55 +export function clearAccountQuota(id) {
  56 + return request({
  57 + url: '/mp/account/clear-quota?id=' + id,
  58 + method: 'put'
53 }) 59 })
54 } 60 }
yudao-ui-admin/src/views/mp/account/index.vue
@@ -28,16 +28,25 @@ @@ -28,16 +28,25 @@
28 <el-table-column label="名称" align="center" prop="name"/> 28 <el-table-column label="名称" align="center" prop="name"/>
29 <el-table-column label="微信号" align="center" prop="account" width="180"/> 29 <el-table-column label="微信号" align="center" prop="account" width="180"/>
30 <el-table-column label="appId" align="center" prop="appId" width="180"/> 30 <el-table-column label="appId" align="center" prop="appId" width="180"/>
31 - <el-table-column label="appSecret" align="center" prop="appSecret" width="180"/>  
32 - <el-table-column label="Token" align="center" prop="token"/>  
33 - <el-table-column label="密钥" align="center" prop="aesKey"/>  
34 - <el-table-column label="二维码" align="center" prop="qrCodeUrl"/>  
35 - <el-table-column label="备注" align="center" prop="remark"/>  
36 - <el-table-column label="创建时间" align="center" prop="createTime" width="180">  
37 - <template slot-scope="scope">  
38 - <span>{{ parseTime(scope.row.createTime) }}</span> 31 +<!-- <el-table-column label="appSecret" align="center" prop="appSecret" width="180"/>-->
  32 +<!-- <el-table-column label="token" align="center" prop="token"/>-->
  33 +<!-- <el-table-column label="消息加解密密钥" align="center" prop="aesKey"/>-->
  34 + <el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360">
  35 + <template v-slot="scope">
  36 + {{ 'http://服务端地址/mp/open/' + scope.row.appId }}
39 </template> 37 </template>
40 </el-table-column> 38 </el-table-column>
  39 + <el-table-column label="二维码" align="center" prop="qrCodeUrl">
  40 + <template v-slot="scope">
  41 + <img v-if="scope.row.qrCodeUrl" :src="scope.row.qrCodeUrl" alt="二维码" style="height: 100px;" />
  42 + </template>
  43 + </el-table-column>
  44 + <el-table-column label="备注" align="center" prop="remark"/>
  45 +<!-- <el-table-column label="创建时间" align="center" prop="createTime" width="180">-->
  46 +<!-- <template slot-scope="scope">-->
  47 +<!-- <span>{{ parseTime(scope.row.createTime) }}</span>-->
  48 +<!-- </template>-->
  49 +<!-- </el-table-column>-->
41 <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> 50 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
42 <template slot-scope="scope"> 51 <template slot-scope="scope">
43 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" 52 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
@@ -46,6 +55,12 @@ @@ -46,6 +55,12 @@
46 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" 55 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
47 v-hasPermi="['mp:account:delete']">删除 56 v-hasPermi="['mp:account:delete']">删除
48 </el-button> 57 </el-button>
  58 + <el-button size="mini" type="text" icon="el-icon-refresh" @click="handleGenerateQrCode(scope.row)"
  59 + v-hasPermi="['mp:account:qr-code']">生成二维码
  60 + </el-button>
  61 + <el-button size="mini" type="text" icon="el-icon-share" @click="handleCleanQuota(scope.row)"
  62 + v-hasPermi="['mp:account:clear-quota']">清空 API 配额
  63 + </el-button>
49 </template> 64 </template>
50 </el-table-column> 65 </el-table-column>
51 </el-table> 66 </el-table>
@@ -55,13 +70,13 @@ @@ -55,13 +70,13 @@
55 70
56 <!-- 对话框(添加 / 修改) --> 71 <!-- 对话框(添加 / 修改) -->
57 <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> 72 <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
58 - <el-form ref="form" :model="form" :rules="rules" label-width="100px"> 73 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
59 <el-form-item label="名称" prop="name"> 74 <el-form-item label="名称" prop="name">
60 <el-input v-model="form.name" placeholder="请输入名称"/> 75 <el-input v-model="form.name" placeholder="请输入名称"/>
61 </el-form-item> 76 </el-form-item>
62 <el-form-item label="微信号" prop="account"> 77 <el-form-item label="微信号" prop="account">
63 <span slot="label"> 78 <span slot="label">
64 - <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单【设置】-【公众号设置】-【帐号详情】中能找到原始ID" placement="top"> 79 + <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」" placement="top">
65 <i class="el-icon-question" /> 80 <i class="el-icon-question" />
66 </el-tooltip> 81 </el-tooltip>
67 微信号 82 微信号
@@ -70,30 +85,30 @@ @@ -70,30 +85,30 @@
70 </el-form-item> 85 </el-form-item>
71 <el-form-item label="appId" prop="appId"> 86 <el-form-item label="appId" prop="appId">
72 <span slot="label"> 87 <span slot="label">
73 - <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单【开发】-【基本配置】中能找到 appId" placement="top"> 88 + <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」" placement="top">
74 <i class="el-icon-question" /> 89 <i class="el-icon-question" />
75 </el-tooltip> 90 </el-tooltip>
76 appId 91 appId
77 </span> 92 </span>
78 <el-input v-model="form.appId" placeholder="请输入公众号 appId"/> 93 <el-input v-model="form.appId" placeholder="请输入公众号 appId"/>
79 </el-form-item> 94 </el-form-item>
80 - <el-form-item label="密钥" prop="appSecret"> 95 + <el-form-item label="appSecret" prop="appSecret">
81 <span slot="label"> 96 <span slot="label">
82 - <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单【开发】-【基本配置】中能找到密钥" placement="top"> 97 + <el-tooltip content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」" placement="top">
83 <i class="el-icon-question" /> 98 <i class="el-icon-question" />
84 </el-tooltip> 99 </el-tooltip>
85 - 密钥 100 + appSecret
86 </span> 101 </span>
87 <el-input v-model="form.appSecret" placeholder="请输入公众号 appSecret"/> 102 <el-input v-model="form.appSecret" placeholder="请输入公众号 appSecret"/>
88 </el-form-item> 103 </el-form-item>
89 <el-form-item label="token" prop="token"> 104 <el-form-item label="token" prop="token">
90 <el-input v-model="form.token" placeholder="请输入公众号token"/> 105 <el-input v-model="form.token" placeholder="请输入公众号token"/>
91 </el-form-item> 106 </el-form-item>
92 - <el-form-item label="加密密钥" prop="aesKey">  
93 - <el-input v-model="form.aesKey" placeholder="请输入加密密钥"/> 107 + <el-form-item label="消息加解密密钥" prop="aesKey">
  108 + <el-input v-model="form.aesKey" placeholder="请输入消息加解密密钥"/>
94 </el-form-item> 109 </el-form-item>
95 <el-form-item label="备注" prop="remark"> 110 <el-form-item label="备注" prop="remark">
96 - <el-input v-model="form.remark" placeholder="请输入备注"/> 111 + <el-input type="textarea" v-model="form.remark" placeholder="请输入备注"/>
97 </el-form-item> 112 </el-form-item>
98 </el-form> 113 </el-form>
99 <div slot="footer" class="dialog-footer"> 114 <div slot="footer" class="dialog-footer">
@@ -105,7 +120,15 @@ @@ -105,7 +120,15 @@
105 </template> 120 </template>
106 121
107 <script> 122 <script>
108 -import { createAccount, deleteAccount, getAccount, getAccountPage, updateAccount} from '@/api/mp/account' 123 +import {
  124 + clearAccountQuota,
  125 + createAccount,
  126 + deleteAccount,
  127 + generateAccountQrCode,
  128 + getAccount,
  129 + getAccountPage,
  130 + updateAccount
  131 +} from '@/api/mp/account'
109 132
110 export default { 133 export default {
111 name: 'mpAccount', 134 name: 'mpAccount',
@@ -120,7 +143,7 @@ export default { @@ -120,7 +143,7 @@ export default {
120 showSearch: true, 143 showSearch: true,
121 // 总条数 144 // 总条数
122 total: 0, 145 total: 0,
123 - // 公众号账户列表 146 + // 公众号账号列表
124 list: [], 147 list: [],
125 // 弹出层标题 148 // 弹出层标题
126 title: '', 149 title: '',
@@ -139,7 +162,7 @@ export default { @@ -139,7 +162,7 @@ export default {
139 // 表单校验 162 // 表单校验
140 rules: { 163 rules: {
141 name: [{required: true, message: '名称不能为空', trigger: 'blur'}], 164 name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
142 - account: [{required: true, message: '公众号账户不能为空', trigger: 'blur'}], 165 + account: [{required: true, message: '公众号账号不能为空', trigger: 'blur'}],
143 appId: [{required: true, message: '公众号 appId 不能为空', trigger: 'blur'}], 166 appId: [{required: true, message: '公众号 appId 不能为空', trigger: 'blur'}],
144 appSecret: [{required: true, message: '公众号密钥不能为空', trigger: 'blur'}], 167 appSecret: [{required: true, message: '公众号密钥不能为空', trigger: 'blur'}],
145 token: [{required: true, message: '公众号 token 不能为空', trigger: 'blur'}], 168 token: [{required: true, message: '公众号 token 不能为空', trigger: 'blur'}],
@@ -199,7 +222,7 @@ export default { @@ -199,7 +222,7 @@ export default {
199 handleAdd() { 222 handleAdd() {
200 this.reset() 223 this.reset()
201 this.open = true 224 this.open = true
202 - this.title = '添加公众号账户' 225 + this.title = '添加公众号账号'
203 }, 226 },
204 /** 修改按钮操作 */ 227 /** 修改按钮操作 */
205 handleUpdate(row) { 228 handleUpdate(row) {
@@ -208,7 +231,7 @@ export default { @@ -208,7 +231,7 @@ export default {
208 getAccount(id).then(response => { 231 getAccount(id).then(response => {
209 this.form = response.data 232 this.form = response.data
210 this.open = true 233 this.open = true
211 - this.title = '修改公众号账户' 234 + this.title = '修改公众号账号'
212 this.disabled = true 235 this.disabled = true
213 }) 236 })
214 }, 237 },
@@ -238,7 +261,7 @@ export default { @@ -238,7 +261,7 @@ export default {
238 /** 删除按钮操作 */ 261 /** 删除按钮操作 */
239 handleDelete(row) { 262 handleDelete(row) {
240 const id = row.id 263 const id = row.id
241 - this.$modal.confirm('是否确认删除公众号账户编号为"' + row.name + '"的数据项?').then(function () { 264 + this.$modal.confirm('是否确认删除公众号账号编号为"' + row.name + '"的数据项?').then(function () {
242 return deleteAccount(id) 265 return deleteAccount(id)
243 }).then(() => { 266 }).then(() => {
244 this.getList() 267 this.getList()
@@ -246,6 +269,27 @@ export default { @@ -246,6 +269,27 @@ export default {
246 }).catch(() => { 269 }).catch(() => {
247 }) 270 })
248 }, 271 },
  272 + /** 生成二维码的按钮操作 */
  273 + handleGenerateQrCode(row) {
  274 + const id = row.id
  275 + this.$modal.confirm('是否确认生成公众号账号编号为"' + row.name + '"的二维码?').then(function () {
  276 + return generateAccountQrCode(id)
  277 + }).then(() => {
  278 + this.getList()
  279 + this.$modal.msgSuccess('生成二维码成功')
  280 + }).catch(() => {
  281 + })
  282 + },
  283 + /** 清空二维码 API 配额的按钮操作 */
  284 + handleCleanQuota(row) {
  285 + const id = row.id
  286 + this.$modal.confirm('是否清空生成公众号账号编号为"' + row.name + '"的 API 配额?').then(function () {
  287 + return clearAccountQuota(id)
  288 + }).then(() => {
  289 + this.$modal.msgSuccess('清空 API 配额成功')
  290 + }).catch(() => {
  291 + })
  292 + },
249 } 293 }
250 } 294 }
251 </script> 295 </script>