Commit d1cc9dde0c22e0192941c3739748c46c6d3d342c

Authored by YunaiV
1 parent acee0752

mp:实现 tag 模块的后端接口

yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java
... ... @@ -14,12 +14,19 @@ public interface ErrorCodeConstants {
14 14 ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}");
15 15 ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}");
16 16  
17   - // ========== 公众号账号 1006001000============
  17 + // ========== 公众号统计 1006001000============
18 18 ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取用户增减数据失败,原因:{}");
19 19 ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得用户累计数据失败,原因:{}");
20 20 ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1006001002, "获得消息发送概况数据失败,原因:{}");
21 21 ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1006001003, "获得接口分析数据失败,原因:{}");
22 22  
  23 + // ========== 公众号标签 1006002000============
  24 + ErrorCode TAG_NOT_EXISTS = new ErrorCode(1006002000, "标签不存在");
  25 + ErrorCode TAG_CREATE_FAIL = new ErrorCode(1006002001, "创建标签失败,原因:{}");
  26 + ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1006002001, "更新标签失败,原因:{}");
  27 + ErrorCode TAG_DELETE_FAIL = new ErrorCode(1006002001, "删除标签失败,原因:{}");
  28 + ErrorCode TAG_GET_FAIL = new ErrorCode(1006002001, "获得标签失败,原因:{}");
  29 +
23 30 // TODO 要处理下
24 31 ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在");
25 32  
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http
1   -### 请求 /login 接口 => 成功
  1 +### 请求 /mp/menu/save 接口 => 成功
2 2 POST {{baseUrl}}/mp/menu/save
3 3 Content-Type: application/json
4 4 Authorization: Bearer {{token}}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http 0 → 100644
  1 +### 请求 /mp/tag/create 接口 => 成功
  2 +POST {{baseUrl}}/mp/tag/create
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenentId}}
  6 +
  7 +{
  8 + "accountId": "1",
  9 + "name": "测试"
  10 +}
  11 +
  12 +### 请求 /mp/tag/update 接口 => 成功
  13 +PUT {{baseUrl}}/mp/tag/update
  14 +Content-Type: application/json
  15 +Authorization: Bearer {{token}}
  16 +tenant-id: {{adminTenentId}}
  17 +
  18 +{
  19 + "id": "3",
  20 + "name": "测试标签啦"
  21 +}
  22 +
  23 +### 请求 /mp/tag/delete 接口 => 成功
  24 +DELETE {{baseUrl}}/mp/tag/delete?id=3
  25 +Content-Type: application/json
  26 +Authorization: Bearer {{token}}
  27 +tenant-id: {{adminTenentId}}
  28 +
  29 +### 请求 /mp/tag/page 接口 => 成功
  30 +GET {{baseUrl}}/mp/tag/page?accountId=1&pageNo=1&pageSize=10
  31 +Content-Type: application/json
  32 +Authorization: Bearer {{token}}
  33 +tenant-id: {{adminTenentId}}
  34 +
  35 +### 请求 /mp/tag/sync 接口 => 成功
  36 +POST {{baseUrl}}/mp/tag/sync?accountId=1
  37 +Content-Type: application/json
  38 +Authorization: Bearer {{token}}
  39 +tenant-id: {{adminTenentId}}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java
... ... @@ -62,4 +62,13 @@ public class MpTagController {
62 62 return success(MpTagConvert.INSTANCE.convertPage(pageResult));
63 63 }
64 64  
  65 + @PostMapping("/sync")
  66 + @ApiOperation("同步公众标签")
  67 + @ApiImplicitParam(name = "id", value = "公众号账号的编号", required = true, dataTypeClass = Long.class)
  68 + @PreAuthorize("@ss.hasPermission('mp:tag:sync')")
  69 + public CommonResult<Boolean> syncTag(@RequestParam("accountId") Long accountId) {
  70 + mpTagService.syncTag(accountId);
  71 + return success(true);
  72 + }
  73 +
65 74 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java
... ... @@ -6,7 +6,6 @@ import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
7 7 import lombok.ToString;
8 8  
9   -import javax.validation.constraints.NotNull;
10 9 import java.util.Date;
11 10  
12 11 @ApiModel("管理后台 - 公众号标签 Response VO")
... ... @@ -15,10 +14,12 @@ import java.util.Date;
15 14 @ToString(callSuper = true)
16 15 public class MpTagRespVO extends MpTagBaseVO {
17 16  
18   - @ApiModelProperty(value = "编号", required = true)
19   - @NotNull(message = "编号不能为空")
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
20 18 private Long id;
21 19  
  20 + @ApiModelProperty(value = "此标签下粉丝数量", required = true, example = "0")
  21 + private Integer count;
  22 +
22 23 @ApiModelProperty(value = "创建时间", required = true)
23 24 private Date createTime;
24 25  
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java
1 1 package cn.iocoder.yudao.module.mp.convert.tag;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO;
5 4 import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagRespVO;
6 5 import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;
  6 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
7 7 import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
8 8 import me.chanjar.weixin.mp.bean.tag.WxUserTag;
9 9 import org.mapstruct.Mapper;
  10 +import org.mapstruct.Mapping;
  11 +import org.mapstruct.Mappings;
10 12 import org.mapstruct.factory.Mappers;
11 13  
12 14 import java.util.List;
... ... @@ -16,8 +18,6 @@ public interface MpTagConvert {
16 18  
17 19 MpTagConvert INSTANCE = Mappers.getMapper(MpTagConvert.class);
18 20  
19   - WxUserTag convert(MpTagCreateReqVO bean);
20   -
21 21 WxUserTag convert(MpTagUpdateReqVO bean);
22 22  
23 23 MpTagRespVO convert(WxUserTag bean);
... ... @@ -26,4 +26,14 @@ public interface MpTagConvert {
26 26  
27 27 PageResult<MpTagRespVO> convertPage(PageResult<MpTagDO> page);
28 28  
  29 + @Mappings({
  30 + @Mapping(target = "id", ignore = true),
  31 + @Mapping(source = "tag.id", target = "tagId"),
  32 + @Mapping(source = "tag.name", target = "name"),
  33 + @Mapping(source = "tag.count", target = "count"),
  34 + @Mapping(source = "account.id", target = "accountId"),
  35 + @Mapping(source = "account.appId", target = "appId"),
  36 + })
  37 + MpTagDO convert(WxUserTag tag, MpAccountDO account);
  38 +
29 39 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java
... ... @@ -5,6 +5,7 @@ import lombok.*;
5 5  
6 6 import com.baomidou.mybatisplus.annotation.*;
7 7 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  8 +import me.chanjar.weixin.mp.bean.tag.WxUserTag;
8 9  
9 10 /**
10 11 * 公众号标签 DO
... ... @@ -31,6 +32,12 @@ public class MpTagDO extends BaseDO {
31 32 * 标签名
32 33 */
33 34 private String name;
  35 + /**
  36 + * 此标签下粉丝数
  37 + *
  38 + * 冗余:{@link WxUserTag#getCount()} 字段,需要管理员点击【同步】后,更新该字段
  39 + */
  40 + private Integer count;
34 41  
35 42 /**
36 43 * 微信公众号 ID
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java
... ... @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO;
7 7 import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
  10 +import java.util.List;
  11 +
10 12 @Mapper
11 13 public interface MpTagMapper extends BaseMapperX<MpTagDO> {
12 14  
... ... @@ -17,4 +19,8 @@ public interface MpTagMapper extends BaseMapperX&lt;MpTagDO&gt; {
17 19 .orderByDesc(MpTagDO::getId));
18 20 }
19 21  
  22 + default List<MpTagDO> selectListByAccountId(Long accountId) {
  23 + return selectList(MpTagDO::getAccountId, accountId);
  24 + }
  25 +
20 26 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java
... ... @@ -9,6 +9,9 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
9 9 import javax.validation.Valid;
10 10 import java.util.List;
11 11  
  12 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  13 +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.ACCOUNT_NOT_EXISTS;
  14 +
12 15 /**
13 16 * 公众号账号 Service 接口
14 17 *
... ... @@ -52,6 +55,20 @@ public interface MpAccountService {
52 55 MpAccountDO getAccount(Long id);
53 56  
54 57 /**
  58 + * 获得公众号账号。若不存在,则抛出业务异常
  59 + *
  60 + * @param id 编号
  61 + * @return 公众号账号
  62 + */
  63 + default MpAccountDO getRequiredAccount(Long id) {
  64 + MpAccountDO account = getAccount(id);
  65 + if (account == null) {
  66 + throw exception(ACCOUNT_NOT_EXISTS);
  67 + }
  68 + return account;
  69 + }
  70 +
  71 + /**
55 72 * 从缓存中,获得公众号账号
56 73 *
57 74 * @param appId 微信公众号 appId
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java
... ... @@ -45,4 +45,11 @@ public interface MpTagService {
45 45 */
46 46 PageResult<MpTagDO> getTagPage(MpTagPageReqVO pageReqVO);
47 47  
  48 + /**
  49 + * 同步公众号标签
  50 + *
  51 + * @param accountId 公众号账号的编号
  52 + */
  53 + void syncTag(Long accountId);
  54 +
48 55 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java
1 1 package cn.iocoder.yudao.module.mp.service.tag;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO;
5 6 import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO;
6 7 import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;
  8 +import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert;
  9 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
7 10 import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
8 11 import cn.iocoder.yudao.module.mp.dal.mysql.tag.MpTagMapper;
9 12 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
  13 +import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
10 14 import lombok.extern.slf4j.Slf4j;
  15 +import me.chanjar.weixin.common.error.WxErrorException;
  16 +import me.chanjar.weixin.mp.api.WxMpService;
  17 +import me.chanjar.weixin.mp.bean.tag.WxUserTag;
11 18 import org.springframework.context.annotation.Lazy;
12 19 import org.springframework.stereotype.Service;
  20 +import org.springframework.transaction.annotation.Transactional;
13 21 import org.springframework.validation.annotation.Validated;
14 22  
15 23 import javax.annotation.Resource;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  30 +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
16 31  
17 32 /**
18 33 * 公众号标签 Service 实现类
... ... @@ -28,28 +43,112 @@ public class MpTagServiceImpl implements MpTagService {
28 43 private MpTagMapper mpTagMapper;
29 44  
30 45 @Resource
  46 + private MpAccountService mpAccountService;
  47 +
  48 + @Resource
31 49 @Lazy // 延迟加载,为了解决延迟加载
32 50 private MpServiceFactory mpServiceFactory;
33 51  
34 52 @Override
35 53 public Long createTag(MpTagCreateReqVO createReqVO) {
36   -// return wxMpService.getUserTagService().tagCreate(createReqVO.getName());
37   - return null;
  54 + // 获得公众号账号
  55 + MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());
  56 +
  57 + // 第一步,新增标签到公众号平台。标签名的唯一,交给公众号平台
  58 + WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());
  59 + WxUserTag wxTag;
  60 + try {
  61 + wxTag = mpService.getUserTagService().tagCreate(createReqVO.getName());
  62 + } catch (WxErrorException e) {
  63 + throw exception(TAG_CREATE_FAIL, e.getError().getErrorMsg());
  64 + }
  65 +
  66 + // 第二步,新增标签到数据库
  67 + MpTagDO tag = MpTagConvert.INSTANCE.convert(wxTag, account);
  68 + mpTagMapper.insert(tag);
  69 + return tag.getId();
38 70 }
39 71  
40 72 @Override
41 73 public void updateTag(MpTagUpdateReqVO updateReqVO) {
42   -// return wxMpService.getUserTagService().tagUpdate(updateReqVO.getId(), updateReqVO.getName());
  74 + // 校验标签存在
  75 + MpTagDO tag = validateTagExists(updateReqVO.getId());
  76 +
  77 + // 第一步,更新标签到公众号平台。标签名的唯一,交给公众号平台
  78 + WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId());
  79 + try {
  80 + mpService.getUserTagService().tagUpdate(tag.getTagId(), updateReqVO.getName());
  81 + } catch (WxErrorException e) {
  82 + throw exception(TAG_UPDATE_FAIL, e.getError().getErrorMsg());
  83 + }
  84 +
  85 + // 第二步,更新标签到数据库
  86 + mpTagMapper.updateById(new MpTagDO().setId(tag.getId()).setName(updateReqVO.getName()));
43 87 }
44 88  
45 89 @Override
46 90 public void deleteTag(Long id) {
47   -// return wxMpService.getUserTagService().tagDelete(id);
  91 + // 校验标签存在
  92 + MpTagDO tag = validateTagExists(id);
  93 +
  94 + // 第一步,删除标签到公众号平台。
  95 + WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId());
  96 + try {
  97 + mpService.getUserTagService().tagDelete(tag.getTagId());
  98 + } catch (WxErrorException e) {
  99 + throw exception(TAG_DELETE_FAIL, e.getError().getErrorMsg());
  100 + }
  101 +
  102 + // 第二步,删除标签到数据库
  103 + mpTagMapper.deleteById(tag.getId());
  104 + }
  105 +
  106 + private MpTagDO validateTagExists(Long id) {
  107 + MpTagDO tag = mpTagMapper.selectById(id);
  108 + if (tag == null) {
  109 + throw exception(TAG_NOT_EXISTS);
  110 + }
  111 + return tag;
48 112 }
49 113  
50 114 @Override
51 115 public PageResult<MpTagDO> getTagPage(MpTagPageReqVO pageReqVO) {
52   - return null;
  116 + return mpTagMapper.selectPage(pageReqVO);
  117 + }
  118 +
  119 + @Override
  120 + @Transactional(rollbackFor = Exception.class)
  121 + public void syncTag(Long accountId) {
  122 + MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
  123 +
  124 + // 第一步,从公众号平台获取最新的标签列表
  125 + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
  126 + List<WxUserTag> wxTags;
  127 + try {
  128 + wxTags = mpService.getUserTagService().tagGet();
  129 + } catch (WxErrorException e) {
  130 + throw new RuntimeException(e);
  131 + }
  132 +
  133 + // 第二步,合并更新回自己的数据库;由于标签只有 100 个,所以直接 for 循环操作
  134 + Map<Long, MpTagDO> tagMap = convertMap(mpTagMapper.selectListByAccountId(accountId),
  135 + MpTagDO::getTagId);
  136 + wxTags.forEach(wxTag -> {
  137 + MpTagDO tag = tagMap.remove(wxTag.getId());
  138 + // 情况一,不存在,新增
  139 + if (tag == null) {
  140 + tag = MpTagConvert.INSTANCE.convert(wxTag, account);
  141 + mpTagMapper.insert(tag);
  142 + return;
  143 + }
  144 + // 情况二,存在,则更新
  145 + mpTagMapper.updateById(new MpTagDO().setId(tag.getId())
  146 + .setName(wxTag.getName()).setCount(wxTag.getCount()));
  147 + });
  148 + // 情况三,部分标签已经不存在了,删除
  149 + if (CollUtil.isNotEmpty(tagMap)) {
  150 + mpTagMapper.deleteBatchIds(convertList(tagMap.values(), MpTagDO::getId));
  151 + }
53 152 }
54 153  
55 154 }
... ...