Przeglądaj źródła

feat(notification): 实现活动审核和报名通知功能

- 在商户平台活动中添加审核通知发送逻辑
- 在用户报名审核中添加通过和拒绝通知功能
- 在用户报名时向商家发送报名通知
- 集成WebSocket实现实时通知推送
- 使用LifeNotice实体存储系统通知
- 添加JSON数据处理和消息格式化功能
- 实现基于手机号的用户识别和通知发送
- 添加异常处理和日志记录机制
fcw 2 miesięcy temu
rodzic
commit
90c336fb3a

+ 1 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -143,7 +143,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     
                     // 审核通过,根据活动时间自动设置状态
                     Date currentTime = new Date();
-                    Date startTime = activity.getStartTime();
+                    Date startTime = activity.getSignupStartTime() != null ? activity.getSignupStartTime() : activity.getStartTime();
                     Date endTime = activity.getEndTime();
                     
                     int status;

+ 109 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java

@@ -1,5 +1,6 @@
 package shop.alien.storeplatform.service.impl;
 
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -8,14 +9,19 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.storeplatform.feign.AlienStoreFeign;
 import shop.alien.storeplatform.service.OperationalActivitySignupService;
+import shop.alien.util.common.Constants;
 
 import java.util.Date;
 import java.util.List;
@@ -35,6 +41,8 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
     private final StoreOperationalActivitySignupMapper signupMapper;
     private final StoreOperationalActivityMapper activityMapper;
     private final LifeUserMapper lifeUserMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final AlienStoreFeign alienStoreFeign;
 
     @Override
     public IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
@@ -169,7 +177,18 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
                 .set(StoreOperationalActivitySignup::getStatus, 2) // 2-通过
                 .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
 
-        return signupMapper.update(null, wrapper);
+        int result = signupMapper.update(null, wrapper);
+        
+        // 审核成功后发送通知
+        if (result > 0) {
+            try {
+                sendApprovalNotice(id, true);
+            } catch (Exception e) {
+                log.error("发送审核通过通知失败,signupId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        
+        return result;
     }
 
     @Override
@@ -192,7 +211,18 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
             wrapper.set(StoreOperationalActivitySignup::getRejectReason, "审核未通过");
         }
 
-        return signupMapper.update(null, wrapper);
+        int result = signupMapper.update(null, wrapper);
+        
+        // 审核拒绝后发送通知
+        if (result > 0) {
+            try {
+                sendApprovalNotice(id, false);
+            } catch (Exception e) {
+                log.error("发送审核拒绝通知失败,signupId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        
+        return result;
     }
 
     /**
@@ -216,5 +246,82 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
                 return "未知";
         }
     }
+
+    /**
+     * 发送审核通知
+     *
+     * @param signupId 报名ID
+     * @param approved 是否通过:true-通过,false-拒绝
+     */
+    private void sendApprovalNotice(Integer signupId, boolean approved) {
+        log.info("OperationalActivitySignupServiceImpl.sendApprovalNotice: signupId={}, approved={}", signupId, approved);
+
+        // 查询报名信息
+        StoreOperationalActivitySignup signup = signupMapper.selectById(signupId);
+        if (signup == null) {
+            log.warn("报名记录不存在,无法发送通知,signupId={}", signupId);
+            return;
+        }
+
+        // 获取用户信息
+        if (signup.getUserId() == null) {
+            log.warn("报名记录中用户ID为空,无法发送通知,signupId={}", signupId);
+            return;
+        }
+
+        LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+        if (lifeUser == null || lifeUser.getUserPhone() == null) {
+            log.warn("用户信息不存在或手机号为空,无法发送通知,signupId={}, userId={}", signupId, signup.getUserId());
+            return;
+        }
+
+        String phone = lifeUser.getUserPhone();
+        String receiverId = "user_" + phone;
+
+        // 构建通知内容
+        String message;
+        if (approved) {
+            // 审核通过
+            message = "您报名参与的活动已通过商家报名条件,请耐心等待活动开始。";
+        } else {
+            // 审核拒绝
+            message = "您报名参与的活动未通过商家报名条件,如想继续参与请重新报名。";
+        }
+
+        // 创建通知对象
+        JSONObject contextJson = new JSONObject();
+        contextJson.put("signupId", signupId);
+        contextJson.put("activityId", signup.getActivityId());
+        contextJson.put("status", approved ? "通过" : "拒绝");
+        contextJson.put("message", message);
+
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId("system");
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setBusinessId(signupId);
+        lifeNotice.setTitle("活动通知");
+        lifeNotice.setContext(contextJson.toJSONString());
+        lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
+        lifeNotice.setIsRead(0);
+
+        // 保存通知到数据库
+        lifeNoticeMapper.insert(lifeNotice);
+
+        // 通过WebSocket发送实时通知
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+            
+            alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.toJSONString(webSocketVo));
+            log.info("活动审核通知发送成功,signupId={}, receiverId={}, approved={}", signupId, receiverId, approved);
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,signupId={}, receiverId={}, error={}", signupId, receiverId, e.getMessage(), e);
+        }
+    }
 }
 

+ 104 - 2
alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java

@@ -1,6 +1,7 @@
 package shop.alien.store.service.impl;
 
 import com.alibaba.excel.util.StringUtils;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,15 +10,22 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
 import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.OperationalActivityService;
+import shop.alien.util.common.Constants;
 
 import java.util.*;
 
@@ -46,6 +54,12 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final StoreInfoMapper storeInfoMapper;
 
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+    private final StoreUserMapper storeUserMapper;
+
+    private final WebSocketProcess webSocketProcess;
+
     @Override
     public int auditActivity(Integer id, Integer status, String approvalComments) {
         log.info("OperationalActivityServiceImpl.auditActivity: id={}, status={}, approvalComments={}", id, status, approvalComments);
@@ -63,7 +77,8 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         StoreOperationalActivity update = new StoreOperationalActivity();
         update.setId(id);
-        if (Objects.equals(status, 1)) {
+        boolean approved = Objects.equals(status, 1);
+        if (approved) {
             StoreOperationalActivity activity = activityMapper.selectById(id);
             if (activity == null) {
                 throw new IllegalArgumentException("活动不存在");
@@ -75,7 +90,21 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             update.setApprovalComments(approvalComments);
         }
 
-        return activityMapper.updateById(update);
+        int result = activityMapper.updateById(update);
+        
+        // 审核结束后给商户发送通知
+        if (result > 0) {
+            try {
+                StoreOperationalActivity activity = activityMapper.selectById(id);
+                if (activity != null) {
+                    sendAuditNoticeToStore(activity, approved);
+                }
+            } catch (Exception e) {
+                log.error("发送活动审核通知失败,activityId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        
+        return result;
     }
 
     @Override
@@ -294,5 +323,78 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 return activityType;
         }
     }
+
+    /**
+     * 发送活动审核通知给商户
+     *
+     * @param activity 活动信息
+     * @param approved 是否通过:true-通过,false-拒绝
+     */
+    private void sendAuditNoticeToStore(StoreOperationalActivity activity, boolean approved) {
+        log.info("OperationalActivityServiceImpl.sendAuditNoticeToStore: activityId={}, approved={}", activity.getId(), approved);
+
+        if (activity.getStoreId() == null) {
+            log.warn("活动商户ID为空,无法发送通知,activityId={}", activity.getId());
+            return;
+        }
+
+        // 查询商户用户信息(获取手机号)
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.eq(StoreUser::getStoreId, activity.getStoreId())
+                .eq(StoreUser::getDeleteFlag, 0)
+                .last("LIMIT 1");
+        StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+        if (storeUser == null || storeUser.getPhone() == null) {
+            log.warn("商户用户信息不存在或手机号为空,无法发送通知,storeId={}", activity.getStoreId());
+            return;
+        }
+
+        String phone = storeUser.getPhone();
+        String receiverId = "store_" + phone;
+
+        // 构建通知内容
+        String message;
+        if (approved) {
+            // 审核通过
+            message = "您提交的活动已审核通过";
+        } else {
+            // 审核拒绝
+            message = "您提交的活动已被拒绝";
+        }
+
+        JSONObject contextJson = new JSONObject();
+        contextJson.put("activityId", activity.getId());
+        contextJson.put("activityName", activity.getActivityName());
+        contextJson.put("status", approved ? "通过" : "拒绝");
+        contextJson.put("message", message);
+
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId("system");
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setBusinessId(activity.getId());
+        lifeNotice.setTitle("活动审核通知");
+        lifeNotice.setContext(contextJson.toJSONString());
+        lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
+        lifeNotice.setIsRead(0);
+
+        // 保存通知到数据库
+        lifeNoticeMapper.insert(lifeNotice);
+
+        // 通过WebSocket发送实时通知
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+
+            webSocketProcess.sendMessage(receiverId, JSONObject.toJSONString(webSocketVo));
+            log.info("活动审核通知发送成功,activityId={}, receiverId={}, approved={}", activity.getId(), receiverId, approved);
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,activityId={}, receiverId={}, error={}", activity.getId(), receiverId, e.getMessage(), e);
+        }
+    }
 }
 

+ 96 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

@@ -1,13 +1,17 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson.JSONObject;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
 import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
@@ -15,14 +19,18 @@ import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDetailVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
 import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
 import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.dto.StoreOperationalActivitySignupDto;
 import shop.alien.store.service.StoreOperationalActivityService;
+import shop.alien.util.common.Constants;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
 import java.util.ArrayList;
@@ -48,6 +56,9 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
     private final StoreInfoMapper storeInfoMapper;
     private final StoreOperationalActivitySignupMapper signupMapper;
     private final BaseRedisService baseRedisService;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final WebSocketProcess webSocketProcess;
 
     @Override
     public StoreOperationalActivityDetailVo getActivityDetail(Integer id, Integer userId) {
@@ -260,7 +271,18 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
             signup.setSignupTime(new Date());
             signup.setDeleteFlag(0);
             signup.setCreatedUserId(dto.getUserId());
-            return signupMapper.insert(signup) > 0;
+            boolean result = signupMapper.insert(signup) > 0;
+            
+            // 报名成功后给商家发送通知
+            if (result) {
+                try {
+                    sendSignupNoticeToStore(signup.getId(), activity);
+                } catch (Exception e) {
+                    log.error("发送活动报名通知失败,signupId={}, error={}", signup.getId(), e.getMessage(), e);
+                }
+            }
+            
+            return result;
         } finally {
             baseRedisService.unlock(lockKey, lockVal);
         }
@@ -495,4 +517,77 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         }
         return null;
     }
+
+    /**
+     * 发送活动报名通知给商家
+     *
+     * @param signupId 报名ID
+     * @param activity 活动信息
+     */
+    private void sendSignupNoticeToStore(Integer signupId, StoreOperationalActivity activity) {
+        log.info("StoreOperationalActivityServiceImpl.sendSignupNoticeToStore: signupId={}, activityId={}", signupId, activity.getId());
+
+        if (activity.getStoreId() == null) {
+            log.warn("活动商户ID为空,无法发送通知,activityId={}", activity.getId());
+            return;
+        }
+
+        // 查询商户信息
+        StoreInfo storeInfo = storeInfoMapper.selectById(activity.getStoreId());
+        if (storeInfo == null) {
+            log.warn("商户信息不存在,无法发送通知,storeId={}", activity.getStoreId());
+            return;
+        }
+
+        // 查询商户用户信息(获取手机号)
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.eq(StoreUser::getStoreId, activity.getStoreId())
+                .eq(StoreUser::getDeleteFlag, 0)
+                .last("LIMIT 1");
+        StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+        if (storeUser == null || storeUser.getPhone() == null) {
+            log.warn("商户用户信息不存在或手机号为空,无法发送通知,storeId={}", activity.getStoreId());
+            return;
+        }
+
+        String phone = storeUser.getPhone();
+        String receiverId = "store_" + phone;
+
+        // 构建通知内容
+        String message = "您的活动收到了一条新的报名";
+
+        JSONObject contextJson = new JSONObject();
+        contextJson.put("signupId", signupId);
+        contextJson.put("activityId", activity.getId());
+        contextJson.put("activityName", activity.getActivityName());
+        contextJson.put("message", message);
+
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId("system");
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setBusinessId(signupId);
+        lifeNotice.setTitle("活动报名通知");
+        lifeNotice.setContext(contextJson.toJSONString());
+        lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
+        lifeNotice.setIsRead(0);
+
+        // 保存通知到数据库
+        lifeNoticeMapper.insert(lifeNotice);
+
+        // 通过WebSocket发送实时通知
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+
+            webSocketProcess.sendMessage(receiverId, JSONObject.toJSONString(webSocketVo));
+            log.info("活动报名通知发送成功,signupId={}, receiverId={}", signupId, receiverId);
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,signupId={}, receiverId={}, error={}", signupId, receiverId, e.getMessage(), e);
+        }
+    }
 }