package cn.iocoder.yudao.module.wxcp.service.activity;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentListReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentSaveReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO;
|
import cn.iocoder.yudao.module.system.service.storage.SystemStorageAttachmentService;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.*;
|
import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO;
|
import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO;
|
import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivityMapper;
|
import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivitySignupMapper;
|
import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum;
|
import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
|
import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO;
|
import cn.iocoder.yudao.module.wxcp.framework.config.WxCpProperties;
|
import cn.iocoder.yudao.module.wxcp.framework.qrcode.WxCpQrCodeUtil;
|
import cn.iocoder.yudao.module.wxcp.service.push.WxCpMessagePushService;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.TreeMap;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.*;
|
|
/**
|
* 企业微信活动 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class WxCpActivityServiceImpl implements WxCpActivityService {
|
|
private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
|
/**
|
* 报名附件的关联记录类型,对应 system_storage_attachment.record_type
|
*/
|
public static final String SIGNUP_RECORD_TYPE = "wxcp_activity_signup";
|
|
/**
|
* 活动封面的关联记录类型,对应 system_storage_attachment.record_type
|
*/
|
public static final String ACTIVITY_RECORD_TYPE = "wxcp_activity";
|
|
/**
|
* 附件用途,与其它模块保持一致
|
*/
|
private static final String ATTACHMENT_APPLICATION = "file";
|
|
@Resource
|
private WxCpActivityMapper wxcpActivityMapper;
|
@Resource
|
private WxCpActivitySignupMapper wxcpSignupMapper;
|
|
@Resource
|
private SystemStorageAttachmentService storageAttachmentService;
|
|
@Resource
|
private WxCpMessagePushService messagePushService;
|
@Resource
|
private WxCpProperties wxCpProperties;
|
|
// ==================== 活动管理 ====================
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createActivity(WxCpActivitySaveReqVO createReqVO) {
|
WxCpActivityDO activity = BeanUtils.toBean(createReqVO, WxCpActivityDO.class);
|
// 新建的一律是草稿,避免未配置完就被扫码报名
|
activity.setStatus(WxCpActivityStatusEnum.DRAFT.getStatus());
|
activity.setSignupCount(0);
|
activity.setPushStatus(0);
|
// 推送范围选填,不传默认全员;推送时还能在弹窗里改
|
if (activity.getPushScope() == null) {
|
activity.setPushScope(WxCpPushScopeEnum.ALL.getScope());
|
}
|
wxcpActivityMapper.insert(activity);
|
// 封面走 system 的附件中间表,业务表不落文件地址字段
|
if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
|
bindActivityAttachments(activity.getId(), createReqVO.getBlobIds());
|
}
|
return activity.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateActivity(WxCpActivitySaveReqVO updateReqVO) {
|
WxCpActivityDO activity = validateActivityExists(updateReqVO.getId());
|
// 已结束或已取消的活动不再允许修改
|
if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.FINISHED.getStatus())
|
|| Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.CANCELED.getStatus())) {
|
throw exception(ACTIVITY_CANNOT_UPDATE);
|
}
|
WxCpActivityDO updateObj = BeanUtils.toBean(updateReqVO, WxCpActivityDO.class);
|
// 以下字段由系统维护,不允许前端覆盖
|
updateObj.setStatus(null);
|
updateObj.setSignupCount(null);
|
updateObj.setPushStatus(null);
|
updateObj.setPushTime(null);
|
wxcpActivityMapper.updateById(updateObj);
|
// blobIds 为 null 表示请求里没带这个字段,视为不改动封面;传空数组才是明确要清空封面
|
if (updateReqVO.getBlobIds() != null) {
|
bindActivityAttachments(activity.getId(), updateReqVO.getBlobIds());
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteActivity(Long id) {
|
validateActivityExists(id);
|
wxcpActivityMapper.deleteById(id);
|
// 活动删了,封面附件关系一并清掉,避免残留成孤儿附件
|
storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(ACTIVITY_RECORD_TYPE, id);
|
}
|
|
@Override
|
public WxCpActivityDO getActivity(Long id) {
|
return wxcpActivityMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<WxCpActivityDO> getActivityPage(WxCpActivityPageReqVO pageReqVO) {
|
return wxcpActivityMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<SystemStorageBlobRespVO> getActivityAttachments(Long activityId) {
|
return listAttachments(ACTIVITY_RECORD_TYPE, activityId);
|
}
|
|
/**
|
* 绑定活动封面附件
|
*
|
* 走 system 模块的附件中间表(record_type = wxcp_activity),
|
* 业务表不加文件地址列、不自建 upload 接口,符合项目统一文件管理规范。
|
*
|
* 传空集合会清掉关联记录但保留文件本身(bindAttachments 只删中间表记录)。
|
* 注意不要改用 deleteAttachmentsByRecordTypeAndRecordId 来"先删后加":
|
* 那个方法会连 blob 一起删掉,同一个文件重新绑定时会因为文件已不存在而查不出来。
|
*/
|
private void bindActivityAttachments(Long activityId, List<Long> blobIds) {
|
SystemStorageAttachmentSaveReqVO bindReqVO = new SystemStorageAttachmentSaveReqVO();
|
bindReqVO.setApplication(ATTACHMENT_APPLICATION);
|
bindReqVO.setRecordType(ACTIVITY_RECORD_TYPE);
|
bindReqVO.setRecordId(activityId);
|
bindReqVO.setBlobItems(toBlobItems(blobIds));
|
storageAttachmentService.bindAttachments(bindReqVO);
|
}
|
|
private List<SystemStorageAttachmentSaveReqVO.BlobItem> toBlobItems(List<Long> blobIds) {
|
if (CollUtil.isEmpty(blobIds)) {
|
return Collections.emptyList();
|
}
|
return blobIds.stream()
|
.filter(Objects::nonNull)
|
.distinct()
|
.map(blobId -> {
|
SystemStorageAttachmentSaveReqVO.BlobItem item = new SystemStorageAttachmentSaveReqVO.BlobItem();
|
item.setBlobId(blobId);
|
return item;
|
})
|
.toList();
|
}
|
|
private List<SystemStorageBlobRespVO> listAttachments(String recordType, Long recordId) {
|
SystemStorageAttachmentListReqVO listReqVO = new SystemStorageAttachmentListReqVO();
|
listReqVO.setApplication(ATTACHMENT_APPLICATION);
|
listReqVO.setRecordType(recordType);
|
listReqVO.setRecordId(recordId);
|
return storageAttachmentService.listAttachments(listReqVO);
|
}
|
|
@Override
|
public void publishActivity(Long id) {
|
WxCpActivityDO activity = validateActivityExists(id);
|
if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) {
|
return; // 已发布,幂等返回
|
}
|
if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.FINISHED.getStatus())
|
|| Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.CANCELED.getStatus())) {
|
throw exception(ACTIVITY_STATUS_FINISHED);
|
}
|
wxcpActivityMapper.updateById(WxCpActivityDO.builder()
|
.id(id).status(WxCpActivityStatusEnum.PUBLISHED.getStatus()).build());
|
}
|
|
@Override
|
public void finishActivity(Long id) {
|
validateActivityExists(id);
|
wxcpActivityMapper.updateById(WxCpActivityDO.builder()
|
.id(id).status(WxCpActivityStatusEnum.FINISHED.getStatus()).build());
|
}
|
|
@Override
|
public void cancelActivity(Long id) {
|
validateActivityExists(id);
|
wxcpActivityMapper.updateById(WxCpActivityDO.builder()
|
.id(id).status(WxCpActivityStatusEnum.CANCELED.getStatus()).build());
|
}
|
|
// ==================== 推送 ====================
|
|
@Override
|
public WxCpPushResultVO pushActivity(WxCpPushReqVO pushReqVO) {
|
WxCpActivityDO activity = validateActivityExists(pushReqVO.getId());
|
if (!Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) {
|
throw exception(ACTIVITY_STATUS_NOT_PUBLISHED);
|
}
|
|
// 推送范围与目标:请求里传了就用请求的,否则沿用活动上配置的
|
Integer scope = pushReqVO.getPushScope() != null ? pushReqVO.getPushScope() : activity.getPushScope();
|
String target = StrUtil.isNotBlank(pushReqVO.getPushTarget())
|
? pushReqVO.getPushTarget() : activity.getPushTarget();
|
if (scope == null) {
|
scope = WxCpPushScopeEnum.ALL.getScope();
|
}
|
|
// 文本卡片消息:按钮直达报名页,点消息或扫码都能报名
|
WxCpPushResultVO result = messagePushService.pushCard(
|
activity.getTitle(), buildPushDescription(activity),
|
buildSignupUrl(activity.getId()), "立即报名", scope, target);
|
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
wxcpActivityMapper.updateById(WxCpActivityDO.builder()
|
.id(activity.getId())
|
.pushScope(scope)
|
.pushTarget(target)
|
.pushStatus(1)
|
.pushTime(LocalDateTime.now())
|
.build());
|
}
|
return result;
|
}
|
|
/**
|
* 拼装推送消息正文
|
*
|
* 只放成员报名前最关心的信息:时间、地点、报名截止;详情让用户点进报名页看
|
*/
|
private String buildPushDescription(WxCpActivityDO activity) {
|
StringBuilder sb = new StringBuilder();
|
if (activity.getStartTime() != null) {
|
sb.append("活动时间:").append(activity.getStartTime().format(TIME_FMT));
|
if (activity.getEndTime() != null) {
|
sb.append(" ~ ").append(activity.getEndTime().format(TIME_FMT));
|
}
|
sb.append("\n");
|
}
|
if (StrUtil.isNotBlank(activity.getLocation())) {
|
sb.append("活动地点:").append(activity.getLocation()).append("\n");
|
}
|
if (activity.getSignupEndTime() != null) {
|
sb.append("报名截止:").append(activity.getSignupEndTime().format(TIME_FMT)).append("\n");
|
}
|
if (activity.getSignupLimit() != null && activity.getSignupLimit() > 0) {
|
sb.append("名额:").append(activity.getSignupCount()).append("/")
|
.append(activity.getSignupLimit()).append("\n");
|
}
|
sb.append("点击下方按钮查看详情并报名");
|
return sb.toString();
|
}
|
|
@Override
|
public byte[] generateSignupQrCode(Long id, int size) {
|
validateActivityExists(id);
|
String url = buildSignupUrl(id);
|
Integer configured = wxCpProperties.getQrcodeSize();
|
int actualSize = size > 0 ? size : (configured != null ? configured : WxCpQrCodeUtil.DEFAULT_SIZE);
|
return WxCpQrCodeUtil.generatePng(url, actualSize);
|
}
|
|
@Override
|
public String buildSignupUrl(Long id) {
|
String prefix = wxCpProperties.getSignupUrlPrefix();
|
if (StrUtil.isBlank(prefix)) {
|
throw exception(QRCODE_SIGNUP_URL_NOT_CONFIGURED);
|
}
|
// 去掉结尾斜杠,避免拼出 //admin-api
|
String base = StrUtil.removeSuffix(prefix.trim(), "/");
|
return base + "/admin-api/wxcp/activity/signup-page?id=" + id;
|
}
|
|
// ==================== 报名 ====================
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createSignup(WxCpSignupPublicReqVO reqVO) {
|
WxCpActivityDO activity = validateActivityExists(reqVO.getActivityId());
|
|
// 1. 活动必须是已发布状态
|
if (!Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) {
|
throw exception(SIGNUP_ACTIVITY_NOT_PUBLISHED);
|
}
|
|
// 2. 校验报名时间窗口
|
LocalDateTime now = LocalDateTime.now();
|
if (activity.getSignupStartTime() != null && now.isBefore(activity.getSignupStartTime())) {
|
throw exception(SIGNUP_NOT_IN_PERIOD);
|
}
|
if (activity.getSignupEndTime() != null && now.isAfter(activity.getSignupEndTime())) {
|
throw exception(SIGNUP_NOT_IN_PERIOD);
|
}
|
|
// 3. 校验名额
|
if (activity.getSignupLimit() != null && activity.getSignupLimit() > 0
|
&& activity.getSignupCount() != null && activity.getSignupCount() >= activity.getSignupLimit()) {
|
throw exception(SIGNUP_FULL);
|
}
|
|
// 4. 去重:有企业微信账号按账号查;没有账号但有手机号则按手机号查;
|
// 两者都没有时无法判定身份,不做去重——否则空手机号会互相误判成同一人
|
String wxUserId = StrUtil.trimToEmpty(reqVO.getWxUserId());
|
String mobile = StrUtil.trimToEmpty(reqVO.getMobile());
|
WxCpActivitySignupDO exists = null;
|
if (StrUtil.isNotBlank(wxUserId)) {
|
exists = wxcpSignupMapper.selectByActivityIdAndWxUserId(activity.getId(), wxUserId);
|
} else if (StrUtil.isNotBlank(mobile)) {
|
exists = wxcpSignupMapper.selectByActivityIdAndMobile(activity.getId(), mobile);
|
}
|
if (exists != null && !Objects.equals(exists.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) {
|
throw exception(SIGNUP_DUPLICATED);
|
}
|
// 之前取消过,则复用原记录重新报名,避免同一个人留多条历史
|
if (exists != null) {
|
wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder()
|
.id(exists.getId())
|
.name(StrUtil.trimToEmpty(reqVO.getName()))
|
.mobile(mobile)
|
.deptName(reqVO.getDeptName())
|
.content(reqVO.getContent())
|
.remark(reqVO.getRemark())
|
.status(WxCpSignupStatusEnum.SIGNED_UP.getStatus())
|
.signupTime(now)
|
.checkInTime(null)
|
.build());
|
// 重新报名时旧附件作废,直接重新绑定(bindAttachments 会先清旧关联,且不会删掉文件本身)
|
bindSignupAttachments(exists.getId(), reqVO.getBlobIds());
|
incrementSignupCount(activity.getId());
|
return exists.getId();
|
}
|
|
// 5. 新增报名记录
|
WxCpActivitySignupDO signup = BeanUtils.toBean(reqVO, WxCpActivitySignupDO.class);
|
signup.setWxUserId(wxUserId);
|
// 姓名、手机号改为选填:统一存空串而不是 null,避免后续按手机号查重时空值语义混乱
|
signup.setName(StrUtil.trimToEmpty(reqVO.getName()));
|
signup.setMobile(mobile);
|
signup.setStatus(WxCpSignupStatusEnum.SIGNED_UP.getStatus());
|
signup.setSignupTime(now);
|
wxcpSignupMapper.insert(signup);
|
|
// 6. 绑定报名人上传的图片/附件
|
bindSignupAttachments(signup.getId(), reqVO.getBlobIds());
|
|
// 7. 用 SQL 自增,避免并发下的读改写丢失更新
|
incrementSignupCount(activity.getId());
|
return signup.getId();
|
}
|
|
/**
|
* 绑定报名附件(全量覆盖:先清掉该报名记录下的旧关联,再绑新的)
|
*
|
* 走 system 模块的附件中间表(record_type = wxcp_activity_signup),
|
* 不新建 upload 接口、不落文件地址字段,符合项目统一文件管理规范。
|
* 传空集合只清关联、不删文件本身。
|
*/
|
private void bindSignupAttachments(Long signupId, List<Long> blobIds) {
|
SystemStorageAttachmentSaveReqVO bindReqVO = new SystemStorageAttachmentSaveReqVO();
|
bindReqVO.setApplication(ATTACHMENT_APPLICATION);
|
bindReqVO.setRecordType(SIGNUP_RECORD_TYPE);
|
bindReqVO.setRecordId(signupId);
|
bindReqVO.setBlobItems(toBlobItems(blobIds));
|
storageAttachmentService.bindAttachments(bindReqVO);
|
}
|
|
@Override
|
public List<SystemStorageBlobRespVO> getSignupAttachments(Long signupId) {
|
return listAttachments(SIGNUP_RECORD_TYPE, signupId);
|
}
|
|
private void incrementSignupCount(Long activityId) {
|
wxcpActivityMapper.update(null, new LambdaUpdateWrapper<WxCpActivityDO>()
|
.setSql("signup_count = signup_count + 1")
|
.eq(WxCpActivityDO::getId, activityId));
|
}
|
|
private void decrementSignupCount(Long activityId) {
|
wxcpActivityMapper.update(null, new LambdaUpdateWrapper<WxCpActivityDO>()
|
.setSql("signup_count = CASE WHEN signup_count > 0 THEN signup_count - 1 ELSE 0 END")
|
.eq(WxCpActivityDO::getId, activityId));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void cancelSignup(Long id) {
|
WxCpActivitySignupDO signup = validateSignupExists(id);
|
if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) {
|
throw exception(SIGNUP_ALREADY_CANCELED);
|
}
|
wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder()
|
.id(id).status(WxCpSignupStatusEnum.CANCELED.getStatus()).build());
|
decrementSignupCount(signup.getActivityId());
|
}
|
|
@Override
|
public void checkInSignup(Long id) {
|
WxCpActivitySignupDO signup = validateSignupExists(id);
|
if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) {
|
throw exception(SIGNUP_ALREADY_CANCELED);
|
}
|
if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CHECKED_IN.getStatus())) {
|
throw exception(SIGNUP_ALREADY_CHECKED_IN);
|
}
|
wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder()
|
.id(id)
|
.status(WxCpSignupStatusEnum.CHECKED_IN.getStatus())
|
.checkInTime(LocalDateTime.now())
|
.build());
|
}
|
|
@Override
|
public WxCpActivitySignupDO getSignup(Long id) {
|
return wxcpSignupMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<WxCpActivitySignupDO> getSignupPage(WxCpSignupPageReqVO pageReqVO) {
|
return wxcpSignupMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public WxCpActivityStatsRespVO getActivityStats(Long activityId) {
|
WxCpActivityDO activity = validateActivityExists(activityId);
|
List<WxCpActivitySignupDO> all = wxcpSignupMapper.selectListByActivityId(activityId);
|
|
// 一次遍历同时算总数与三类分布,避免多次全表扫描
|
int signedUp = 0, checkedIn = 0, canceled = 0;
|
Map<String, Integer> deptMap = new LinkedHashMap<>();
|
Map<String, Integer> dateMap = new TreeMap<>();
|
int fromWxCp = 0, fromForm = 0;
|
for (WxCpActivitySignupDO s : all) {
|
Integer st = s.getStatus();
|
if (Objects.equals(st, WxCpSignupStatusEnum.CANCELED.getStatus())) {
|
canceled++;
|
continue; // 已取消不计入部门/日期分布,否则统计会虚高
|
}
|
if (Objects.equals(st, WxCpSignupStatusEnum.CHECKED_IN.getStatus())) {
|
checkedIn++;
|
} else {
|
signedUp++;
|
}
|
deptMap.merge(StrUtil.blankToDefault(s.getDeptName(), "未填写部门"), 1, Integer::sum);
|
if (s.getSignupTime() != null) {
|
dateMap.merge(s.getSignupTime().toLocalDate().toString(), 1, Integer::sum);
|
}
|
if (StrUtil.isNotBlank(s.getWxUserId())) {
|
fromWxCp++;
|
} else {
|
fromForm++;
|
}
|
}
|
|
int totalSignup = signedUp + checkedIn;
|
WxCpActivityStatsRespVO vo = new WxCpActivityStatsRespVO();
|
vo.setActivityId(activity.getId());
|
vo.setActivityTitle(activity.getTitle());
|
vo.setSignupLimit(activity.getSignupLimit());
|
vo.setTotalSignup(totalSignup);
|
vo.setSignedUpCount(signedUp);
|
vo.setCheckedInCount(checkedIn);
|
vo.setCanceledCount(canceled);
|
|
// 名额相关指标:不限名额时保持 null,前端据此显示"不限"
|
Integer limit = activity.getSignupLimit();
|
if (limit != null && limit > 0) {
|
vo.setRemaining(Math.max(0, limit - totalSignup));
|
vo.setSignupPercent(Math.min(100, (int) Math.round(totalSignup * 100.0 / limit)));
|
}
|
vo.setCheckInPercent(totalSignup == 0 ? 0
|
: (int) Math.round(checkedIn * 100.0 / totalSignup));
|
|
vo.setDeptStats(toNameCountList(deptMap));
|
vo.setDateStats(toNameCountList(dateMap));
|
// 来源分布:用显式 put,避免双大括号初始化捕获循环中变更的局部变量
|
Map<String, Integer> sourceMap = new LinkedHashMap<>();
|
sourceMap.put("企业微信身份", fromWxCp);
|
sourceMap.put("表单填写", fromForm);
|
vo.setSourceStats(toNameCountList(sourceMap));
|
return vo;
|
}
|
|
private List<WxCpActivityStatsRespVO.NameCount> toNameCountList(Map<String, Integer> map) {
|
return map.entrySet().stream()
|
.map(e -> new WxCpActivityStatsRespVO.NameCount(e.getKey(), e.getValue()))
|
.toList();
|
}
|
|
// ==================== 校验辅助 ====================
|
|
private WxCpActivityDO validateActivityExists(Long id) {
|
if (id == null) {
|
throw exception(ACTIVITY_NOT_EXISTS);
|
}
|
WxCpActivityDO activity = wxcpActivityMapper.selectById(id);
|
if (activity == null) {
|
throw exception(ACTIVITY_NOT_EXISTS);
|
}
|
return activity;
|
}
|
|
private WxCpActivitySignupDO validateSignupExists(Long id) {
|
WxCpActivitySignupDO signup = wxcpSignupMapper.selectById(id);
|
if (signup == null) {
|
throw exception(SIGNUP_NOT_EXISTS);
|
}
|
return signup;
|
}
|
|
}
|