From cb08f11a5d47d1d0a6d7e312816406a261dc84fb Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Mon, 5 Oct 2026 11:58:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(wx-account-fans):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=B2=89=E4=B8=9D=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=84=B1=E6=95=8F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 WxAccountFansExcelVO 类用于粉丝数据的 Excel 导出 - 在 WxAccountFansController 中实现导出接口,支持按条件筛选粉丝 - 更新 WxAccountFansServiceImpl 中的粉丝更新逻辑,确保数据同步至微信 - 对粉丝的 OpenID 进行脱敏处理,确保隐私安全 (cherry picked from commit a277413d3d7681407daca46bbb635f57a032b25f) --- pigx-visual/pigx-mp-platform/pom.xml | 4 + .../controller/WxAccountFansController.java | 62 ++- .../pigx/mp/entity/WxAccountFans.java | 3 + .../mp/entity/vo/WxAccountFansExcelVO.java | 50 ++ .../impl/WxAccountFansServiceImpl.java | 475 +++++++++--------- 5 files changed, 353 insertions(+), 241 deletions(-) create mode 100644 pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/vo/WxAccountFansExcelVO.java diff --git a/pigx-visual/pigx-mp-platform/pom.xml b/pigx-visual/pigx-mp-platform/pom.xml index 127c987da..3de6a1e1e 100644 --- a/pigx-visual/pigx-mp-platform/pom.xml +++ b/pigx-visual/pigx-mp-platform/pom.xml @@ -18,6 +18,10 @@ + + com.pig4cloud + pigx-common-excel + com.alibaba.cloud diff --git a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/controller/WxAccountFansController.java b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/controller/WxAccountFansController.java index 9ccb4ed6a..a20ee98f7 100644 --- a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/controller/WxAccountFansController.java +++ b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/controller/WxAccountFansController.java @@ -16,7 +16,21 @@ */ package com.pig4cloud.pigx.mp.controller; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import cn.hutool.core.util.ArrayUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.sensitive.util.DesensitizedUtils; +import com.pig4cloud.pigx.mp.entity.vo.WxAccountFansExcelVO; +import java.util.List; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.log.annotation.SysLog; import com.pig4cloud.pigx.common.security.annotation.HasPermission; @@ -26,7 +40,7 @@ import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; /** - * 微信公众号粉丝管理 + * 微信公众号粉丝管理,委托服务层维护粉丝及微信状态,并提供按列表条件筛选的脱敏 Excel 导出。 * * @author lengleng * @date 2019-03-26 22:08:08 @@ -34,10 +48,43 @@ import org.springframework.web.bind.annotation.*; @RestController @AllArgsConstructor @RequestMapping("/wx-account-fans") +@Tag(name = "公众号粉丝管理") public class WxAccountFansController { private final WxAccountFansService wxAccountFansService; + /** + * 导出当前租户内符合筛选条件的全部粉丝,不受分页参数限制。 OpenID 始终按密钥规则脱敏,仅转换导出副本,不修改数据库实体。 + * @param query 查询条件;公众号 AppID 为空时不限定公众号,昵称为空时不限定昵称 + * @return Excel 行数据;无匹配记录时返回空列表,由 ResponseExcel 输出表头 + */ + @Operation(summary = "导出公众号粉丝", description = "按公众号和昵称筛选全部记录,OpenID 脱敏导出") + @ResponseExcel(name = "fans") + @GetMapping("/export") + @HasPermission("mp_wxaccountfans_sync") + public List export(WxAccountFans query) { + return wxAccountFansService + .list(Wrappers.lambdaQuery() + .eq(StrUtil.isNotBlank(query.getWxAccountAppid()), WxAccountFans::getWxAccountAppid, + query.getWxAccountAppid()) + .like(StrUtil.isNotBlank(query.getNickname()), WxAccountFans::getNickname, query.getNickname())) + .stream() + .map(fan -> { + WxAccountFansExcelVO row = new WxAccountFansExcelVO(); + BeanUtil.copyProperties(fan, row, "openid", "subscribeStatus", "subscribeTime", "isBlack", "tagIds"); + // Excel 不经过 Jackson 的 @Sensitive 序列化,单独对导出副本脱敏。 + row.setOpenid(DesensitizedUtils.key(fan.getOpenid())); + row.setSubscribeStatus(YesNoEnum.YES.getCode().equals(fan.getSubscribeStatus()) ? "已关注" : "未关注"); + row.setSubscribeTime(fan.getSubscribeTime() == null ? StrUtil.EMPTY + : LocalDateTimeUtil.format(fan.getSubscribeTime(), DatePattern.NORM_DATETIME_PATTERN)); + row.setIsBlack(CommonConstants.FAIL.equals(fan.getIsBlack()) ? YesNoEnum.YES.getDesc() + : YesNoEnum.NO.getDesc()); + row.setTagIds(ArrayUtil.join(fan.getTagIds(), StrUtil.COMMA)); + return row; + }) + .toList(); + } + /** * 分页查询 * @param page 分页对象 @@ -60,15 +107,20 @@ public class WxAccountFansController { } /** - * 修改微信公众号粉丝 - * @param wxAccountFans 微信公众号粉丝 - * @return R + * 修改粉丝备注及标签,通过 ID 恢复数据库中的真实 OpenID 和公众号 AppID 后调用微信。 + * @param wxAccountFans 待更新数据,须包含粉丝 ID;回显 OpenID 可为脱敏值 + * @return 更新成功返回成功响应;记录不存在返回失败响应,客户端应保留编辑状态并提示刷新 + * @throws Exception 数据库或微信调用异常,交由统一异常处理器转换为失败响应 */ + @Operation(summary = "修改公众号粉丝") @SysLog("修改微信公众号粉丝") @PutMapping @HasPermission("mp_wxaccountfans_edit") public R updateById(@RequestBody WxAccountFans wxAccountFans) { - return R.ok(wxAccountFansService.updateFans(wxAccountFans)); + if (!Boolean.TRUE.equals(wxAccountFansService.updateFans(wxAccountFans))) { + return R.failed("粉丝记录不存在,请刷新后重试"); + } + return R.ok(Boolean.TRUE); } /** diff --git a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/WxAccountFans.java b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/WxAccountFans.java index 30bece300..29106cc2a 100644 --- a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/WxAccountFans.java +++ b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/WxAccountFans.java @@ -19,6 +19,8 @@ package com.pig4cloud.pigx.mp.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.pig4cloud.pigx.common.core.util.TenantTable; +import com.pig4cloud.pigx.common.sensitive.annotation.Sensitive; +import com.pig4cloud.pigx.common.sensitive.core.SensitiveTypeEnum; import lombok.Data; import lombok.EqualsAndHashCode; @@ -46,6 +48,7 @@ public class WxAccountFans extends Model { /** * 用户标识 */ + @Sensitive(type = SensitiveTypeEnum.KEY) private String openid; /** diff --git a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/vo/WxAccountFansExcelVO.java b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/vo/WxAccountFansExcelVO.java new file mode 100644 index 000000000..1de1f1f24 --- /dev/null +++ b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/entity/vo/WxAccountFansExcelVO.java @@ -0,0 +1,50 @@ +package com.pig4cloud.pigx.mp.entity.vo; + +import lombok.Data; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.write.style.ColumnWidth; + +/** + * 公众号粉丝 Excel 行,由导出接口从实体转换;状态和日期为展示文本,OpenID 为脱敏副本。 该对象仅用于文件输出,不接受写入请求,不参与粉丝身份识别。 + */ +@Data +@ColumnWidth(24) +public class WxAccountFansExcelVO { + + /** 用户标识,固定六位掩码,仅保留末三位;null 时单元格为空。 */ + @ExcelProperty("OpenID") + private String openid; + + /** 关注状态展示文本:已关注或未关注。 */ + @ExcelProperty("关注状态") + private String subscribeStatus; + + /** 关注时间,格式 yyyy-MM-dd HH:mm:ss;未记录时为空。 */ + @ExcelProperty("关注时间") + private String subscribeTime; + + /** 粉丝昵称,可为空。 */ + @ExcelProperty("昵称") + private String nickname; + + /** 微信返回的语言代码,可为空。 */ + @ExcelProperty("语言") + private String language; + + /** 黑名单状态展示文本:是或否。 */ + @ExcelProperty("黑名单") + private String isBlack; + + /** 标签 ID 的逗号分隔文本,无标签时为空。 */ + @ExcelProperty("标签ID") + private String tagIds; + + /** 粉丝备注,可为空。 */ + @ExcelProperty("备注") + private String remark; + + /** 粉丝所属公众号名称,可为空。 */ + @ExcelProperty("公众号名称") + private String wxAccountName; + +} diff --git a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/service/impl/WxAccountFansServiceImpl.java b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/service/impl/WxAccountFansServiceImpl.java index 9521d7ef9..2250a7a27 100644 --- a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/service/impl/WxAccountFansServiceImpl.java +++ b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/service/impl/WxAccountFansServiceImpl.java @@ -69,273 +69,276 @@ import java.util.List; @Service @AllArgsConstructor public class WxAccountFansServiceImpl extends ServiceImpl - implements WxAccountFansService { + implements WxAccountFansService { - private static final int SIZE = 100; + private static final int SIZE = 100; - private final WxAccountTagMapper wxAccountTagMapper; + private final WxAccountTagMapper wxAccountTagMapper; - private final WxAccountMapper wxAccountMapper; + private final WxAccountMapper wxAccountMapper; - /** - * 保存粉丝 - * - * @param wxMpUser wx mp 用户 - * @return boolean - */ - @Override - @Transactional(rollbackFor = Exception.class) - public WxAccountFans saveFans(WxMpUser wxMpUser) { - // 删除历史关注保存的信息 - baseMapper.delete(Wrappers.lambdaQuery().eq(WxAccountFans::getOpenid, wxMpUser.getOpenId())); + /** + * 保存粉丝 + * @param wxMpUser wx mp 用户 + * @return boolean + */ + @Override + @Transactional(rollbackFor = Exception.class) + public WxAccountFans saveFans(WxMpUser wxMpUser) { + // 删除历史关注保存的信息 + baseMapper.delete(Wrappers.lambdaQuery().eq(WxAccountFans::getOpenid, wxMpUser.getOpenId())); - WxAccountFans wxAccountFans = new WxAccountFans(); - wxAccountFans.setOpenid(wxMpUser.getOpenId()); - wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe()))); - wxAccountFans.setSubscribeTime(LocalDateTime - .ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault())); + WxAccountFans wxAccountFans = new WxAccountFans(); + wxAccountFans.setOpenid(wxMpUser.getOpenId()); + wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe()))); + wxAccountFans.setSubscribeTime(LocalDateTime + .ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault())); - // 随机生成一个昵称,方便平台内部使用 - String generatedName = ChineseNameGenerator.getInstance().generate(); - wxAccountFans.setNickname(generatedName); - wxAccountFans.setLanguage(wxMpUser.getLanguage()); - wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl()); - wxAccountFans.setRemark(wxMpUser.getRemark()); - wxAccountFans.setTagIds(wxAccountFans.getTagIds()); + // 随机生成一个昵称,方便平台内部使用 + String generatedName = ChineseNameGenerator.getInstance().generate(); + wxAccountFans.setNickname(generatedName); + wxAccountFans.setLanguage(wxMpUser.getLanguage()); + wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl()); + wxAccountFans.setRemark(wxMpUser.getRemark()); + wxAccountFans.setTagIds(wxAccountFans.getTagIds()); - WxAccount wxAccount = wxAccountMapper - .selectOne(Wrappers.lambdaQuery().eq(WxAccount::getAppid, WxMpContextHolder.getAppId())); - wxAccountFans.setWxAccountId(wxAccount.getId()); - wxAccountFans.setWxAccountAppid(wxAccount.getAppid()); - wxAccountFans.setWxAccountName(wxAccount.getName()); - return wxAccountFans; - } + WxAccount wxAccount = wxAccountMapper + .selectOne(Wrappers.lambdaQuery().eq(WxAccount::getAppid, WxMpContextHolder.getAppId())); + wxAccountFans.setWxAccountId(wxAccount.getId()); + wxAccountFans.setWxAccountAppid(wxAccount.getAppid()); + wxAccountFans.setWxAccountName(wxAccount.getName()); + return wxAccountFans; + } - /** - * 分页查询粉丝 - * - * @param page 粉丝 - * @param wxAccountFans 查询条件 - * @return - */ - @SneakyThrows - @Override - public IPage getFansWithTagPage(Page page, WxAccountFans wxAccountFans) { - // 查询当前公众号的标签 - List wxAccountTags = wxAccountTagMapper.selectList(Wrappers.emptyWrapper()); + /** + * 分页查询粉丝 + * @param page 粉丝 + * @param wxAccountFans 查询条件 + * @return + */ + @SneakyThrows + @Override + public IPage getFansWithTagPage(Page page, WxAccountFans wxAccountFans) { + // 查询当前公众号的标签 + List wxAccountTags = wxAccountTagMapper.selectList(Wrappers.emptyWrapper()); - // 翻页查询粉丝 - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() - .eq(StrUtil.isNotBlank(wxAccountFans.getWxAccountAppid()), WxAccountFans::getWxAccountAppid, - wxAccountFans.getWxAccountAppid()) - .like(StrUtil.isNotBlank(wxAccountFans.getNickname()), WxAccountFans::getNickname, - wxAccountFans.getNickname()); + // 翻页查询粉丝 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .eq(StrUtil.isNotBlank(wxAccountFans.getWxAccountAppid()), WxAccountFans::getWxAccountAppid, + wxAccountFans.getWxAccountAppid()) + .like(StrUtil.isNotBlank(wxAccountFans.getNickname()), WxAccountFans::getNickname, + wxAccountFans.getNickname()); - IPage fansPage = baseMapper.selectPage(page, wrapper); + IPage fansPage = baseMapper.selectPage(page, wrapper); - List voList = fansPage.getRecords().stream().map(fans -> {// - WxAccountFansVo vo = new WxAccountFansVo(); - BeanUtils.copyProperties(fans, vo); - // 赋值 TAG_NAME - if (ArrayUtil.isNotEmpty(fans.getTagIds())) { - List tagList = wxAccountTags.stream() - .filter(tag -> tag.getWxAccountAppid().endsWith(fans.getWxAccountAppid()) // - && ArrayUtil.contains(fans.getTagIds(), tag.getTagId())) - .toList(); - vo.setTagList(tagList); - } - return vo; - }).toList(); + List voList = fansPage.getRecords().stream().map(fans -> {// + WxAccountFansVo vo = new WxAccountFansVo(); + BeanUtils.copyProperties(fans, vo); + // 赋值 TAG_NAME + if (ArrayUtil.isNotEmpty(fans.getTagIds())) { + List tagList = wxAccountTags.stream() + .filter(tag -> tag.getWxAccountAppid().endsWith(fans.getWxAccountAppid()) // + && ArrayUtil.contains(fans.getTagIds(), tag.getTagId())) + .toList(); + vo.setTagList(tagList); + } + return vo; + }).toList(); - page.setRecords(voList); - page.setTotal(fansPage.getTotal()); - return page; - } + page.setRecords(voList); + page.setTotal(fansPage.getTotal()); + return page; + } - /** - * 更新粉丝信息 - * - * @param wxAccountFans 信息 - * @return - */ - @Override - @SneakyThrows - @Transactional(rollbackFor = Exception.class) - public Boolean updateFans(WxAccountFans wxAccountFans) { - baseMapper.updateById(wxAccountFans); + /** + * 更新粉丝数据并同步微信备注、标签;数据库中的 OpenID 和公众号 AppID 覆盖回显身份字段。 + * 非空备注才同步至微信,非空标签数组先移除原标签再添加新标签,沿用现有更新行为。 + * @param wxAccountFans 待更新粉丝,不能为 null,须包含有效 ID;OpenID 可为脱敏回显值 + * @return 记录存在且更新完成返回 true;记录不存在返回 false,不写库也不调用微信 + * @throws Exception 数据库或微信 API 调用失败时向上抛出,数据库事务回滚 + */ + @Override + @SneakyThrows + @Transactional(rollbackFor = Exception.class) + public Boolean updateFans(WxAccountFans wxAccountFans) { + WxAccountFans stored = baseMapper.selectById(wxAccountFans.getId()); + if (stored == null) { + return Boolean.FALSE; + } + // OpenID 回显经过脱敏,持久化与微信操作必须使用数据库中的真实身份。 + wxAccountFans.setOpenid(stored.getOpenid()); + wxAccountFans.setWxAccountAppid(stored.getWxAccountAppid()); + baseMapper.updateById(wxAccountFans); - // 获取操作微信接口类 - WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(wxAccountFans.getWxAccountAppid()); + // 获取操作微信接口类 + WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(wxAccountFans.getWxAccountAppid()); - if (StrUtil.isNotBlank(wxAccountFans.getRemark())) { - WxMpUserService wxMpUserService = wxMpService.getUserService(); - wxMpUserService.userUpdateRemark(wxAccountFans.getOpenid(), wxAccountFans.getRemark()); + if (StrUtil.isNotBlank(wxAccountFans.getRemark())) { + WxMpUserService wxMpUserService = wxMpService.getUserService(); + wxMpUserService.userUpdateRemark(wxAccountFans.getOpenid(), wxAccountFans.getRemark()); - } + } - // 更新用户标签 - WxMpUserTagService userTagService = wxMpService.getUserTagService(); - if (ArrayUtil.isNotEmpty(wxAccountFans.getTagIds())) { - // 移除原有标签 - List oldTag = userTagService.userTagList(wxAccountFans.getOpenid()); - for (Long tagId : oldTag) { - userTagService.batchUntagging(tagId, new String[]{wxAccountFans.getOpenid()}); - } + // 更新用户标签 + WxMpUserTagService userTagService = wxMpService.getUserTagService(); + if (ArrayUtil.isNotEmpty(wxAccountFans.getTagIds())) { + // 移除原有标签 + List oldTag = userTagService.userTagList(wxAccountFans.getOpenid()); + for (Long tagId : oldTag) { + userTagService.batchUntagging(tagId, new String[] { wxAccountFans.getOpenid() }); + } - // 添加新标签 - for (Long tagId : wxAccountFans.getTagIds()) { + // 添加新标签 + for (Long tagId : wxAccountFans.getTagIds()) { - userTagService.batchTagging(tagId, new String[]{wxAccountFans.getOpenid()}); - } - } - return Boolean.TRUE; - } + userTagService.batchTagging(tagId, new String[] { wxAccountFans.getOpenid() }); + } + } + return Boolean.TRUE; + } - @SneakyThrows - @Override - public Boolean unblack(Long[] ids, String appId) { - List wxAccountFans = this.listByIds(CollUtil.toList(ids)); - WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); - WxMpUserBlacklistService blackListService = wxMpService.getBlackListService(); - List collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).toList(); - blackListService.pullFromBlacklist(collect); - // 更新数据 - List fansList = wxAccountFans.stream().peek(item -> { - item.setIsBlack(CommonConstants.SUCCESS); - }).toList(); - this.updateBatchById(fansList); + @SneakyThrows + @Override + public Boolean unblack(Long[] ids, String appId) { + List wxAccountFans = this.listByIds(CollUtil.toList(ids)); + WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); + WxMpUserBlacklistService blackListService = wxMpService.getBlackListService(); + List collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).toList(); + blackListService.pullFromBlacklist(collect); + // 更新数据 + List fansList = wxAccountFans.stream().peek(item -> { + item.setIsBlack(CommonConstants.SUCCESS); + }).toList(); + this.updateBatchById(fansList); - return Boolean.TRUE; - } + return Boolean.TRUE; + } - @SneakyThrows - @Override - public Boolean black(Long[] ids, String appId) { - List wxAccountFans = this.listByIds(CollUtil.toList(ids)); - WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); - WxMpUserBlacklistService blackListService = wxMpService.getBlackListService(); - List collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).toList(); - blackListService.pushToBlacklist(collect); - // 更新数据 - List fansList = wxAccountFans.stream().peek(item -> { - item.setIsBlack(CommonConstants.FAIL); - }).toList(); - this.updateBatchById(fansList); + @SneakyThrows + @Override + public Boolean black(Long[] ids, String appId) { + List wxAccountFans = this.listByIds(CollUtil.toList(ids)); + WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); + WxMpUserBlacklistService blackListService = wxMpService.getBlackListService(); + List collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).toList(); + blackListService.pushToBlacklist(collect); + // 更新数据 + List fansList = wxAccountFans.stream().peek(item -> { + item.setIsBlack(CommonConstants.FAIL); + }).toList(); + this.updateBatchById(fansList); - return Boolean.TRUE; - } + return Boolean.TRUE; + } - /** - * 获取公众号粉丝,生产建议异步 - * - * @param appId - * @return - */ - @Async - @Override - public void syncAccountFans(String appId) { - WxAccount wxAccount = wxAccountMapper - .selectOne(Wrappers.query().lambda().eq(WxAccount::getAppid, appId)); + /** + * 获取公众号粉丝,生产建议异步 + * @param appId + * @return + */ + @Async + @Override + public void syncAccountFans(String appId) { + WxAccount wxAccount = wxAccountMapper + .selectOne(Wrappers.query().lambda().eq(WxAccount::getAppid, appId)); - // 获取操作微信接口类 - WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); - WxMpUserService wxMpUserService = wxMpService.getUserService(); - // 根据公众号查询已同步用户openid 查询最新的一条 (注意不能按订阅时间排序) - String finalNextOpenId = queryNextOpenId(appId); - TenantBroker.runAs(() -> WxMpInitConfigRunner.getTenants().get(appId), - (id) -> fetchUser(finalNextOpenId, wxAccount, wxMpUserService)); + // 获取操作微信接口类 + WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId); + WxMpUserService wxMpUserService = wxMpService.getUserService(); + // 根据公众号查询已同步用户openid 查询最新的一条 (注意不能按订阅时间排序) + String finalNextOpenId = queryNextOpenId(appId); + TenantBroker.runAs(() -> WxMpInitConfigRunner.getTenants().get(appId), + (id) -> fetchUser(finalNextOpenId, wxAccount, wxMpUserService)); - log.info("公众号 {} 粉丝同步完成", wxAccount.getName()); - } + log.info("公众号 {} 粉丝同步完成", wxAccount.getName()); + } - /** - * 获取微信用户 - * - * @param nextOpenid 下一组开始的openid - * @param wxAccount 公众号信息 - * @param wxMpUserService mp操作类 - */ - private void fetchUser(String nextOpenid, WxAccount wxAccount, WxMpUserService wxMpUserService) { - try { - WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid); - // openId 分组 每组 100个 openid - List> openIdsList = CollUtil.split(wxMpUserList.getOpenids(), SIZE) - .stream() - .filter(CollUtil::isNotEmpty) - .toList(); - // 处理每个分组. 调用查询用户信息 - for (List openIdList : openIdsList) { - log.info("开始批量获取用户信息 {}", openIdList); - List wxAccountFansList = new ArrayList<>(); - wxMpUserService.userInfoList(openIdList).forEach(wxMpUser -> { - WxAccountFans wxAccountFans = buildDbUser(wxAccount, wxMpUser); - wxAccountFansList.add(wxAccountFans); - }); - this.saveOrUpdateBatch(wxAccountFansList); - log.info("批量插入用户信息完成 {}", openIdList); - } - // 如果nextOpenId 不为空,则继续获取 - if (StrUtil.isNotBlank(wxMpUserList.getNextOpenid())) { - fetchUser(wxMpUserList.getNextOpenid(), wxAccount, wxMpUserService); - } - } catch (Exception e) { - log.warn("同步微信公众号 {} 粉丝异常 {}", wxAccount.getName(), e.getMessage()); - // 失败重试逻辑 - String finalNextOpenId = queryNextOpenId(wxAccount.getAppid()); - fetchUser(finalNextOpenId, wxAccount, wxMpUserService); - } + /** + * 获取微信用户 + * @param nextOpenid 下一组开始的openid + * @param wxAccount 公众号信息 + * @param wxMpUserService mp操作类 + */ + private void fetchUser(String nextOpenid, WxAccount wxAccount, WxMpUserService wxMpUserService) { + try { + WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid); + // openId 分组 每组 100个 openid + List> openIdsList = CollUtil.split(wxMpUserList.getOpenids(), SIZE) + .stream() + .filter(CollUtil::isNotEmpty) + .toList(); + // 处理每个分组. 调用查询用户信息 + for (List openIdList : openIdsList) { + log.info("开始批量获取用户信息 {}", openIdList); + List wxAccountFansList = new ArrayList<>(); + wxMpUserService.userInfoList(openIdList).forEach(wxMpUser -> { + WxAccountFans wxAccountFans = buildDbUser(wxAccount, wxMpUser); + wxAccountFansList.add(wxAccountFans); + }); + this.saveOrUpdateBatch(wxAccountFansList); + log.info("批量插入用户信息完成 {}", openIdList); + } + // 如果nextOpenId 不为空,则继续获取 + if (StrUtil.isNotBlank(wxMpUserList.getNextOpenid())) { + fetchUser(wxMpUserList.getNextOpenid(), wxAccount, wxMpUserService); + } + } + catch (Exception e) { + log.warn("同步微信公众号 {} 粉丝异常 {}", wxAccount.getName(), e.getMessage()); + // 失败重试逻辑 + String finalNextOpenId = queryNextOpenId(wxAccount.getAppid()); + fetchUser(finalNextOpenId, wxAccount, wxMpUserService); + } - } + } - /** - * 构建数据库存取对象 - * - * @param wxAccount 公众号信息 - * @param wxMpUser 用户微信信息 - */ - private WxAccountFans buildDbUser(WxAccount wxAccount, WxMpUser wxMpUser) { - WxAccountFans wxAccountFans = new WxAccountFans(); - wxAccountFans.setOpenid(wxMpUser.getOpenId()); - wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe()))); + /** + * 构建数据库存取对象 + * @param wxAccount 公众号信息 + * @param wxMpUser 用户微信信息 + */ + private WxAccountFans buildDbUser(WxAccount wxAccount, WxMpUser wxMpUser) { + WxAccountFans wxAccountFans = new WxAccountFans(); + wxAccountFans.setOpenid(wxMpUser.getOpenId()); + wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe()))); - // 2020-11-25 部分用户订阅时间为空,跳过此字段处理 - if (ObjectUtil.isNotEmpty(wxMpUser.getSubscribeTime())) { - wxAccountFans.setSubscribeTime(LocalDateTime - .ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault())); - } + // 2020-11-25 部分用户订阅时间为空,跳过此字段处理 + if (ObjectUtil.isNotEmpty(wxMpUser.getSubscribeTime())) { + wxAccountFans.setSubscribeTime(LocalDateTime + .ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault())); + } - // 随机生成一个昵称,方便平台内部使用 - String generatedName = ChineseNameGenerator.getInstance().generate(); - wxAccountFans.setNickname(generatedName); - wxAccountFans.setLanguage(wxMpUser.getLanguage()); - wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl()); - wxAccountFans.setRemark(wxMpUser.getRemark()); - wxAccountFans.setTagIds(wxAccountFans.getTagIds()); - wxAccountFans.setWxAccountId(wxAccount.getId()); - wxAccountFans.setWxAccountAppid(wxAccount.getAppid()); - wxAccountFans.setWxAccountName(wxAccount.getName()); - wxAccountFans.setIsBlack(CommonConstants.SUCCESS); - return wxAccountFans; - } + // 随机生成一个昵称,方便平台内部使用 + String generatedName = ChineseNameGenerator.getInstance().generate(); + wxAccountFans.setNickname(generatedName); + wxAccountFans.setLanguage(wxMpUser.getLanguage()); + wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl()); + wxAccountFans.setRemark(wxMpUser.getRemark()); + wxAccountFans.setTagIds(wxAccountFans.getTagIds()); + wxAccountFans.setWxAccountId(wxAccount.getId()); + wxAccountFans.setWxAccountAppid(wxAccount.getAppid()); + wxAccountFans.setWxAccountName(wxAccount.getName()); + wxAccountFans.setIsBlack(CommonConstants.SUCCESS); + return wxAccountFans; + } - /** - * 查询当前公众号最后插入的openId - * - * @param appId 公众号标识 - * @return openid / null - */ - private String queryNextOpenId(String appId) { - Page queryPage = new Page<>(0, 1); - Page fansPage = baseMapper.selectPage(queryPage, - Wrappers.query() - .lambda() - .eq(WxAccountFans::getWxAccountAppid, appId) - .orderByDesc(WxAccountFans::getCreateTime)); - String nextOpenId = null; - if (fansPage.getTotal() > 0) { - nextOpenId = fansPage.getRecords().get(0).getOpenid(); - } - return nextOpenId; - } + /** + * 查询当前公众号最后插入的openId + * @param appId 公众号标识 + * @return openid / null + */ + private String queryNextOpenId(String appId) { + Page queryPage = new Page<>(0, 1); + Page fansPage = baseMapper.selectPage(queryPage, + Wrappers.query() + .lambda() + .eq(WxAccountFans::getWxAccountAppid, appId) + .orderByDesc(WxAccountFans::getCreateTime)); + String nextOpenId = null; + if (fansPage.getTotal() > 0) { + nextOpenId = fansPage.getRecords().get(0).getOpenid(); + } + return nextOpenId; + } }