| | |
| | | |
| | | 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; |
| | |
| | | */ |
| | | 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 |
| | |
| | | // ==================== 活动管理 ==================== |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createActivity(WxCpActivitySaveReqVO createReqVO) { |
| | | WxCpActivityDO activity = BeanUtils.toBean(createReqVO, WxCpActivityDO.class); |
| | | // 新建的一律是草稿,避免未配置完就被扫码报名 |
| | |
| | | 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()); |
| | | // 已结束或已取消的活动不再允许修改 |
| | |
| | | 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 |
| | |
| | | @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 |
| | |
| | | throw exception(SIGNUP_FULL); |
| | | } |
| | | |
| | | // 4. 去重:有企业微信账号按账号查,否则按手机号查 |
| | | WxCpActivitySignupDO exists = StrUtil.isNotBlank(reqVO.getWxUserId()) |
| | | ? wxcpSignupMapper.selectByActivityIdAndWxUserId(activity.getId(), reqVO.getWxUserId().trim()) |
| | | : wxcpSignupMapper.selectByActivityIdAndMobile(activity.getId(), reqVO.getMobile().trim()); |
| | | // 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(reqVO.getName()) |
| | | .mobile(reqVO.getMobile()) |
| | | .name(StrUtil.trimToEmpty(reqVO.getName())) |
| | | .mobile(mobile) |
| | | .deptName(reqVO.getDeptName()) |
| | | .content(reqVO.getContent()) |
| | | .remark(reqVO.getRemark()) |
| | |
| | | .signupTime(now) |
| | | .checkInTime(null) |
| | | .build()); |
| | | // 重新报名时旧附件已失效,先清掉再绑新的 |
| | | storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId( |
| | | SIGNUP_RECORD_TYPE, exists.getId()); |
| | | // 重新报名时旧附件作废,直接重新绑定(bindAttachments 会先清旧关联,且不会删掉文件本身) |
| | | bindSignupAttachments(exists.getId(), reqVO.getBlobIds()); |
| | | incrementSignupCount(activity.getId()); |
| | | return exists.getId(); |
| | |
| | | |
| | | // 5. 新增报名记录 |
| | | WxCpActivitySignupDO signup = BeanUtils.toBean(reqVO, WxCpActivitySignupDO.class); |
| | | signup.setWxUserId(StrUtil.trimToEmpty(reqVO.getWxUserId())); |
| | | 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); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 绑定报名附件 |
| | | * 绑定报名附件(全量覆盖:先清掉该报名记录下的旧关联,再绑新的) |
| | | * |
| | | * 走 system 模块的附件中间表(record_type = wxcp_activity_signup), |
| | | * 不新建 upload 接口、不落文件地址字段,符合项目统一文件管理规范。 |
| | | * 传空集合只清关联、不删文件本身。 |
| | | */ |
| | | private void bindSignupAttachments(Long signupId, List<Long> blobIds) { |
| | | if (CollUtil.isEmpty(blobIds)) { |
| | | return; |
| | | } |
| | | SystemStorageAttachmentSaveReqVO bindReqVO = new SystemStorageAttachmentSaveReqVO(); |
| | | bindReqVO.setApplication("file"); |
| | | bindReqVO.setApplication(ATTACHMENT_APPLICATION); |
| | | bindReqVO.setRecordType(SIGNUP_RECORD_TYPE); |
| | | bindReqVO.setRecordId(signupId); |
| | | bindReqVO.setBlobItems(blobIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(blobId -> { |
| | | SystemStorageAttachmentSaveReqVO.BlobItem item = new SystemStorageAttachmentSaveReqVO.BlobItem(); |
| | | item.setBlobId(blobId); |
| | | return item; |
| | | }) |
| | | .toList()); |
| | | bindReqVO.setBlobItems(toBlobItems(blobIds)); |
| | | storageAttachmentService.bindAttachments(bindReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public List<SystemStorageBlobRespVO> getSignupAttachments(Long signupId) { |
| | | SystemStorageAttachmentListReqVO listReqVO = new SystemStorageAttachmentListReqVO(); |
| | | listReqVO.setRecordType(SIGNUP_RECORD_TYPE); |
| | | listReqVO.setRecordId(signupId); |
| | | return storageAttachmentService.listAttachments(listReqVO); |
| | | return listAttachments(SIGNUP_RECORD_TYPE, signupId); |
| | | } |
| | | |
| | | private void incrementSignupCount(Long activityId) { |