Bläddra i källkod

Merge branch 'dev' of 113.31.163.91:quanshu/mp-server into dev

chunping 2 år sedan
förälder
incheckning
df886e2b6f
25 ändrade filer med 831 tillägg och 138 borttagningar
  1. 36 4
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/MarketingMgrController.java
  2. 69 5
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingController.java
  3. 108 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingHitPrizeController.java
  4. 1 1
      mp-admin/src/main/resources/application-test.yml
  5. 116 14
      mp-quartz/src/main/java/com/qs/mp/quartz/task/MarketingTask.java
  6. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/Marketing.java
  7. 42 0
      mp-service/src/main/java/com/qs/mp/admin/domain/excel/MarketingHitPrizeExcel.java
  8. 20 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingHitPrizeExportParam.java
  9. 7 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingUserCodeListVO.java
  10. 21 28
      mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponServiceImpl.java
  11. 76 84
      mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingServiceImpl.java
  12. 63 0
      mp-service/src/main/java/com/qs/mp/framework/domain/WxSubscribeMessage.java
  13. 22 0
      mp-service/src/main/java/com/qs/mp/framework/service/IWxSubscribeMessage.java
  14. 30 1
      mp-service/src/main/java/com/qs/mp/framework/service/impl/WxSubscribeMessageImpl.java
  15. 22 0
      mp-service/src/main/java/com/qs/mp/user/domain/param/UserMarketingHitPrizeQueryParam.java
  16. 34 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/UserMarketingHitPrizeDetailVO.java
  17. 42 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/UserMarketingHitPrizeListVO.java
  18. 20 0
      mp-service/src/main/java/com/qs/mp/user/mapper/MarketingHitPrizeMapper.java
  19. 7 0
      mp-service/src/main/java/com/qs/mp/user/mapper/MarketingUserCodeMapper.java
  20. 18 0
      mp-service/src/main/java/com/qs/mp/user/service/IMarketingHitPrizeService.java
  21. 8 0
      mp-service/src/main/java/com/qs/mp/user/service/IMarketingUserCodeService.java
  22. 15 0
      mp-service/src/main/java/com/qs/mp/user/service/impl/MarketingHitPrizeServiceImpl.java
  23. 5 0
      mp-service/src/main/java/com/qs/mp/user/service/impl/MarketingUserCodeServiceImpl.java
  24. 33 0
      mp-service/src/main/resources/mapper/user/MarketingHitPrizeMapper.xml
  25. 12 1
      mp-service/src/main/resources/mapper/user/MarketingUserCodeMapper.xml

+ 36 - 4
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/MarketingMgrController.java

@@ -3,10 +3,13 @@ package com.qs.mp.web.controller.api.admin;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.qs.mp.admin.domain.Marketing;
 import com.qs.mp.admin.domain.MarketingAwards;
 import com.qs.mp.admin.domain.MarketingAwardsPrize;
+import com.qs.mp.admin.domain.excel.MarketingHitPrizeExcel;
 import com.qs.mp.admin.domain.param.MarketingCreateParam;
+import com.qs.mp.admin.domain.param.MarketingHitPrizeExportParam;
 import com.qs.mp.admin.domain.param.MarketingQueryParam;
 import com.qs.mp.admin.domain.param.MarketingUpdateParam;
 import com.qs.mp.admin.domain.vo.MarketingAwardsVO;
@@ -22,13 +25,17 @@ import com.qs.mp.common.core.page.TableDataInfo;
 import com.qs.mp.common.enums.BusinessType;
 import com.qs.mp.common.enums.MarketingStatusEnum;
 import com.qs.mp.common.enums.UserTypeEnum;
+import com.qs.mp.user.domain.MarketingHitPrize;
 import com.qs.mp.user.domain.MarketingUserCode;
+import com.qs.mp.user.service.IMarketingHitPrizeService;
 import com.qs.mp.user.service.IMarketingUserCodeService;
+import com.qs.mp.utils.ExcelUtil;
 import com.qs.mp.web.controller.common.BaseApiController;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -41,6 +48,7 @@ import java.util.stream.Collectors;
 
 /**
  * 营销活动管理相关接口
+ *
  * @author Cup
  * @date 2022/5/16
  */
@@ -61,6 +69,9 @@ public class MarketingMgrController extends BaseApiController {
     @Autowired
     private IMarketingUserCodeService marketingUserCodeService;
 
+    @Autowired
+    private IMarketingHitPrizeService marketingHitPrizeService;
+
     @Log(title = "营销活动新增", businessType = BusinessType.INSERT)
     @ApiOperation(value = "营销活动新增")
     @PostMapping("/create")
@@ -82,11 +93,11 @@ public class MarketingMgrController extends BaseApiController {
     @PreAuthorize("@ss.hasPermi('business:marketing:list')")
     @ApiOperation("营销活动列表")
     @ApiResponses(
-            @ApiResponse(code = 200, message = "营销活动列表",response = MarketingListVO.class)
+            @ApiResponse(code = 200, message = "营销活动列表", response = MarketingListVO.class)
     )
     public TableDataInfo list(@RequestBody MarketingQueryParam marketingQueryParam) {
         startPage();
-        List<MarketingListVO> list =  marketingService.listMarketing(marketingQueryParam);
+        List<MarketingListVO> list = marketingService.listMarketing(marketingQueryParam);
         return getDataTable(list);
     }
 
@@ -94,7 +105,7 @@ public class MarketingMgrController extends BaseApiController {
     @PreAuthorize("@ss.hasPermi('business:marketing:query')")
     @ApiOperation("活动详情")
     @ApiResponses(
-            @ApiResponse(code = 200, message = "活动详情",response = MarketingVO.class)
+            @ApiResponse(code = 200, message = "活动详情", response = MarketingVO.class)
     )
     public AjaxResult detail(@PathVariable("id") Long id) {
         MarketingVO marketingVO = new MarketingVO();
@@ -188,7 +199,7 @@ public class MarketingMgrController extends BaseApiController {
     @PreAuthorize("@ss.hasPermi('business:marketing:queryData')")
     @ApiOperation("活动数据")
     @ApiResponses(
-            @ApiResponse(code = 200, message = "success",response = MarketingUserCodeListVO.class)
+            @ApiResponse(code = 200, message = "success", response = MarketingUserCodeListVO.class)
     )
     public TableDataInfo dataInfo(@PathVariable("id") Long id) {
         startPage();
@@ -203,4 +214,25 @@ public class MarketingMgrController extends BaseApiController {
         return getDataTable(list);
     }
 
+
+    @PostMapping("/export")
+    @PreAuthorize("@ss.hasPermi('business:marketing:export')")
+    @ApiOperation("活动中奖数据导出")
+    public AjaxResult export(@RequestBody MarketingHitPrizeExportParam marketingHitPrizeExportParam) {
+        QueryWrapper<MarketingHitPrize> queryWrapper = new QueryWrapper<>();
+        if (StringUtils.isNotBlank(marketingHitPrizeExportParam.getTitle())) {
+            queryWrapper.like("t2.title", marketingHitPrizeExportParam.getTitle());
+        }
+        queryWrapper.eq("t5.user_type", UserTypeEnum.ORDINARY.getValue());
+
+        List<MarketingHitPrizeExcel> exportList = marketingHitPrizeService.listHitPrizeExportByQueryWrapper(queryWrapper);
+        if (CollectionUtils.isEmpty(exportList)) {
+            return AjaxResult.error("没有符合条件的活动中奖数据");
+        }
+        ExcelUtil<MarketingHitPrizeExcel> util = new ExcelUtil<>(MarketingHitPrizeExcel.class);
+        return util.exportExcel(exportList, "活动中奖数据导出", false);
+
+
+    }
+
 }

+ 69 - 5
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingController.java

@@ -11,10 +11,13 @@ import com.qs.mp.admin.service.IMarketingAwardsService;
 import com.qs.mp.admin.service.IMarketingService;
 import com.qs.mp.common.core.domain.AjaxResult;
 import com.qs.mp.common.core.page.TableDataInfo;
+import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.enums.MarketingStatusEnum;
 import com.qs.mp.common.enums.UserTypeEnum;
+import com.qs.mp.common.exception.ServiceException;
 import com.qs.mp.common.utils.DateUtils;
 import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.framework.security.handle.HostHolder;
 import com.qs.mp.user.domain.MarketingHitPrize;
 import com.qs.mp.user.domain.MarketingUserCode;
 import com.qs.mp.user.domain.param.MarketingHelpParam;
@@ -40,6 +43,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -50,6 +54,8 @@ import java.util.stream.Collectors;
 @RequestMapping("/api/v1/mp/user/marketing")
 @Api(tags = "营销活动相关接口")
 public class MarketingController extends BaseApiController {
+    // 营销活动真实参与人数锁
+    public static final String MARKETING_REAL_NUM_LOCK = "MARKETING_REAL_NUM_LOCK:%s";
 
     @Autowired
     private IMarketingService marketingService;
@@ -66,6 +72,12 @@ public class MarketingController extends BaseApiController {
     @Autowired
     private IMarketingUserCodeService marketingUserCodeService;
 
+    @Autowired
+    private HostHolder hostHolder;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
     @PostMapping("/help")
     @ApiOperation("活动助力")
     public AjaxResult help(@RequestBody MarketingHelpParam marketingHelpParam) {
@@ -74,7 +86,48 @@ public class MarketingController extends BaseApiController {
             return AjaxResult.error("用户未登录");
         }
 
-        marketingService.help(marketingHelpParam, userId);
+        String inviteCode = marketingHelpParam.getInviteCode();
+        if (StringUtils.isBlank(inviteCode)) {
+            return AjaxResult.error("邀请码不能为空");
+        }
+
+        Marketing marketing = marketingService.getById(marketingHelpParam.getMarketingId());
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动不存在");
+        }
+        // 未开启的活动不能助力
+        if (!MarketingStatusEnum.ON.getValue().equals(marketing.getIsOn())) {
+            return AjaxResult.error("活动未开启~");
+        }
+        Date now = DateUtils.getNowDate();
+        // 结束的活动不支持助力
+        if (marketing.getTriggerStatus() == 1 || marketing.getEndTime().before(now)) {
+            return AjaxResult.error("活动已过期~");
+        }
+        // 未开始的活动不能助力
+        if (marketing.getStartTime().after(now)) {
+            return AjaxResult.error("活动未开始~");
+        }
+
+        // 邀请码解码,被助力人id
+        Long helpedUserId = MarketingUtils.decodeInviteCode(inviteCode);
+        if (userId.equals(helpedUserId)) {
+            return AjaxResult.error("不能助力自己哦");
+        }
+
+        String lockKey = String.format(MARKETING_REAL_NUM_LOCK, marketing.getId());
+
+        if (!distributedLocker.tryLock(lockKey,3,30, TimeUnit.SECONDS)) {
+            return AjaxResult.error("活动太火爆了,请稍后重试!");
+        }
+
+        try {
+            marketingService.help(marketingHelpParam, userId);
+        }finally {
+            distributedLocker.unlock(lockKey);
+        }
+
+
         return AjaxResult.success("助力成功");
     }
 
@@ -90,13 +143,18 @@ public class MarketingController extends BaseApiController {
     }
 
 
-    @PostMapping("list")
+    @PostMapping("/list")
     @ApiOperation("获取营销活动列表")
     @ApiResponses(
             @ApiResponse(code = 200, message = "成功", response = UserMarketingListVO.class)
     )
     public TableDataInfo list(@Validated @RequestBody UserMarketingQueryParam userMarketingQueryParam) {
-        Long userId = SecurityUtils.getLoginUser().getUserId();
+        // 获取用户 id
+        Long userId = 0L;
+
+        if (hostHolder.getUser() != null) {
+            userId = hostHolder.getUser().getUserId();
+        }
 
         startPage();
         LambdaQueryWrapper<Marketing> queryWrapper = new LambdaQueryWrapper<>();
@@ -116,13 +174,14 @@ public class MarketingController extends BaseApiController {
         List<UserMarketingListVO> userMarketingListVOList = new ArrayList<>();
         List<Marketing> list = marketingService.list(queryWrapper);
         if (CollectionUtils.isNotEmpty(list)) {
+            Long finalUserId = userId;
             userMarketingListVOList = list.stream().map(marketing -> {
                 UserMarketingListVO userMarketingListVO = new UserMarketingListVO();
                 BeanUtils.copyProperties(marketing, userMarketingListVO);
                 if (userMarketingQueryParam.getTriggerStatus() == 1) {
                     int count = marketingHitPrizeService.count(new LambdaQueryWrapper<MarketingHitPrize>()
                             .eq(MarketingHitPrize::getMarketingId, marketing.getId())
-                            .eq(MarketingHitPrize::getUserId, userId));
+                            .eq(MarketingHitPrize::getUserId, finalUserId));
                     userMarketingListVO.setIsHit(count);
                 }
                 return userMarketingListVO;
@@ -138,7 +197,12 @@ public class MarketingController extends BaseApiController {
             @ApiResponse(code = 200, message = "成功", response = UserMarketingDetailVO.class)
     )
     public AjaxResult detail(@PathVariable("id") Long id) {
-        Long userId = SecurityUtils.getLoginUser().getUserId();
+        // 获取用户 id
+        Long userId = 0L;
+
+        if (hostHolder.getUser() != null) {
+            userId = hostHolder.getUser().getUserId();
+        }
 
         Marketing marketing = marketingService.getById(id);
         if (Objects.isNull(marketing)) {

+ 108 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingHitPrizeController.java

@@ -0,0 +1,108 @@
+package com.qs.mp.web.controller.api.user;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.qs.mp.common.core.domain.AjaxResult;
+import com.qs.mp.common.core.page.TableDataInfo;
+import com.qs.mp.framework.security.handle.HostHolder;
+import com.qs.mp.user.domain.MarketingHitPrize;
+import com.qs.mp.user.domain.MarketingUserCode;
+import com.qs.mp.user.domain.param.UserMarketingHitPrizeQueryParam;
+import com.qs.mp.user.domain.vo.UserMarketingHitPrizeDetailVO;
+import com.qs.mp.user.domain.vo.UserMarketingHitPrizeListVO;
+import com.qs.mp.user.service.IMarketingHitPrizeService;
+import com.qs.mp.user.service.IMarketingUserCodeService;
+import com.qs.mp.utils.SecurityUtils;
+import com.qs.mp.web.controller.common.BaseApiController;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 营销活动中奖信息相关接口
+ * @author Cup
+ * @date 2022/5/19
+ */
+@RestController
+@RequestMapping("/api/v1/mp/user/marketing/hit/prize")
+@Api(tags = "营销活动中奖信息相关接口")
+public class MarketingHitPrizeController extends BaseApiController {
+
+    @Autowired
+    private IMarketingHitPrizeService marketingHitPrizeService;
+
+    @Autowired
+    private IMarketingUserCodeService marketingUserCodeService;
+
+    @Autowired
+    private HostHolder hostHolder;
+
+    @PostMapping("/list")
+    @ApiOperation("获取中奖信息列表")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = UserMarketingHitPrizeListVO.class)
+    )
+    public TableDataInfo list(@Validated @RequestBody UserMarketingHitPrizeQueryParam userMarketingHitPrizeQueryParam) {
+        startPage();
+        QueryWrapper<MarketingHitPrize> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("t1.marketing_id",userMarketingHitPrizeQueryParam.getMarketingId());
+        queryWrapper.orderByAsc("t2.sort");
+        List<UserMarketingHitPrizeListVO> marketingHitPrizeListVOList = marketingHitPrizeService.listHitPrizeByQueryWrapper(queryWrapper);
+        if (CollectionUtils.isNotEmpty(marketingHitPrizeListVOList)) {
+            for (UserMarketingHitPrizeListVO userMarketingHitPrizeListVO : marketingHitPrizeListVOList) {
+                String nickName = userMarketingHitPrizeListVO.getNickName();
+                if (StringUtils.isNotBlank(nickName)) {
+                    userMarketingHitPrizeListVO.setNickName(nickName.charAt(0) + "**");
+                }
+            }
+        }
+        return getDataTable(marketingHitPrizeListVOList);
+    }
+
+    @PostMapping("/isHit/{marketingId}")
+    @ApiOperation("判断当前用户是否中奖")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = UserMarketingHitPrizeDetailVO.class)
+    )
+    public AjaxResult isHit(@PathVariable("marketingId") Long marketingId) {
+
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+
+        UserMarketingHitPrizeDetailVO userMarketingHitPrizeDetailVO = new UserMarketingHitPrizeDetailVO();
+        userMarketingHitPrizeDetailVO.setIsHit(0);
+
+        // 判断是否参与了
+        int count = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
+                .eq(MarketingUserCode::getMarketingId, marketingId)
+                .eq(MarketingUserCode::getUserId, userId));
+        if (count == 0) {
+            userMarketingHitPrizeDetailVO.setIsHit(-1);
+            return AjaxResult.success(userMarketingHitPrizeDetailVO);
+        }
+
+        if (Objects.nonNull(userId) && userId != 0) {
+            QueryWrapper<MarketingHitPrize> queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("t1.marketing_id",marketingId);
+            queryWrapper.eq("t1.user_id", userId);
+            List<UserMarketingHitPrizeListVO> list = marketingHitPrizeService.listHitPrizeByQueryWrapper(queryWrapper);
+            if (CollectionUtils.isNotEmpty(list)) {
+                UserMarketingHitPrizeListVO userMarketingHitPrizeListVO = list.get(0);
+                userMarketingHitPrizeDetailVO.setPrizeTitle(userMarketingHitPrizeListVO.getPrizeTitle());
+                userMarketingHitPrizeDetailVO.setIsHit(1);
+                userMarketingHitPrizeDetailVO.setAwardName(userMarketingHitPrizeListVO.getAwardName());
+                userMarketingHitPrizeDetailVO.setPrizePicUrl(userMarketingHitPrizeListVO.getPrizePicUrl());
+            }
+        }
+        return AjaxResult.success(userMarketingHitPrizeDetailVO);
+    }
+
+}

+ 1 - 1
mp-admin/src/main/resources/application-test.yml

@@ -131,7 +131,7 @@ mq:
 # 小程序
 miniprogram:
   # 接收通知的状态
-  state: developer
+  state: trial
 
 # 盲票导出每页查询数量
 export:

+ 116 - 14
mp-quartz/src/main/java/com/qs/mp/quartz/task/MarketingTask.java

@@ -1,21 +1,34 @@
 package com.qs.mp.quartz.task;
 
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.qs.mp.admin.domain.Marketing;
 import com.qs.mp.admin.service.IMarketingAwardsService;
 import com.qs.mp.admin.service.IMarketingService;
 import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.core.redis.RedisCache;
 import com.qs.mp.common.enums.MarketingStatusEnum;
+import com.qs.mp.common.enums.UserTypeEnum;
 import com.qs.mp.common.utils.DateUtils;
+import com.qs.mp.common.utils.LogUtil;
+import com.qs.mp.framework.domain.WxSubscribeMessage;
+import com.qs.mp.framework.service.IWxSubscribeMessage;
+import com.qs.mp.user.domain.MarketingUserCode;
 import com.qs.mp.user.service.IMarketingUserCodeService;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
+import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
@@ -28,7 +41,9 @@ import java.util.concurrent.TimeUnit;
 @Component("marketingTask")
 public class MarketingTask {
 
-    public static final String MARKETING_LOTTERY_KEY = "MARKETING_LOTTERY_KEY";
+    public static final String MARKETING_LOTTERY_KEY = "MARKETING_LOTTERY_KEY:%s";
+
+    public static final String MARKETING_SEND_KEY = "MARKETING_SEND_KEY";
 
 
     @Autowired
@@ -37,40 +52,127 @@ public class MarketingTask {
     @Autowired
     private IMarketingService marketingService;
 
+    @Autowired
+    private IWxSubscribeMessage wxSubscribeMessage;
+
+    @Autowired
+    private IMarketingUserCodeService marketingUserCodeService;
+
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
+
 
     /**
      * 开奖任务方法
      */
     public void lottery() {
+        // 获取结束时间小于等于当前时间且未开奖的活动
+        Date now = DateUtils.getNowDate();
+        List<Marketing> marketingList = marketingService.list(new LambdaQueryWrapper<Marketing>()
+                .eq(Marketing::getTriggerStatus, 0)
+                .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
+                .le(Marketing::getEndTime, now));
+        if (CollectionUtils.isEmpty(marketingList)) {
+            return;
+        }
+
+        for (Marketing marketing : marketingList) {
+            String lockKey = String.format(MARKETING_LOTTERY_KEY, marketing.getId());
+            // 加锁
+            if (!distributedLocker.tryLock(lockKey)) {
+                continue;
+            }
+            try {
+                // 开奖
+                marketingService.lottery(marketing);
+
+                // 获取所有的参与用户
+                List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+                        .eq(MarketingUserCode::getMarketingId, marketing.getId())
+                        .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue()));
+                for (MarketingUserCode marketingUserCode : userCodeList) {
+                    // 发送开奖订阅通知
+                    wxSubscribeMessage.sendMarketingLottery(marketingUserCode.getUserId(), marketing);
+                }
+            } catch (Exception e) {
+                LogUtil.error(logger, e, "活动开奖异常。marketingId:{0}", marketing.getId());
+            } finally {
+                // 释放锁
+                distributedLocker.unlock(lockKey);
+            }
+        }
+
+    }
+
 
-        // 加锁
-        if (!distributedLocker.tryLock(MARKETING_LOTTERY_KEY)) {
+    /**
+     * 发送活动开始通知
+     */
+    public void sendMessage() {
+        // 加锁防止频繁发送
+        if (!distributedLocker.tryLock(MARKETING_SEND_KEY)) {
             return;
         }
-        try {
-            // 获取结束时间小于等于当前时间且未开奖的一个活动
+
+        try{
+            // 获取开始时间小于等于当前时间且未发开始通知的活动
             Date now = DateUtils.getNowDate();
-            Marketing marketing = marketingService.getOne(new LambdaQueryWrapper<Marketing>()
+            List<Marketing> marketingList = marketingService.list(new LambdaQueryWrapper<Marketing>()
                     .eq(Marketing::getTriggerStatus, 0)
                     .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
-                    .le(Marketing::getEndTime, now)
-                    .last("limit 1"));
-            if (Objects.isNull(marketing)) {
+                    .eq(Marketing::getIsSend, 0)
+                    .le(Marketing::getStartTime, now));
+            if (CollectionUtils.isEmpty(marketingList)) {
+                return;
+            }
+
+            // 获取所有参与过活动的普通用户
+            List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+                    .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue()));
+            if (CollectionUtils.isEmpty(userCodeList)) {
                 return;
             }
 
-            // 开奖
-            marketingService.lottery(marketing);
+            for (Marketing marketing : marketingList) {
+                for (MarketingUserCode marketingUserCode : userCodeList) {
+                    // 发送开始订阅通知
+                    wxSubscribeMessage.sendMarketingStart(marketingUserCode.getUserId(), marketing);
+                }
+
+                // 更新活动通知发送状态
+                marketingService.update(new LambdaUpdateWrapper<Marketing>()
+                        .set(Marketing::getIsSend, 1)
+                        .eq(Marketing::getId, marketing.getId()));
+            }
 
 
-        }finally {
+        } finally {
             // 释放锁
-            distributedLocker.unlock(MARKETING_LOTTERY_KEY);
+            distributedLocker.unlock(MARKETING_SEND_KEY);
         }
 
+    }
 
+    /**
+     * 增加活动参与的虚拟人数
+     */
+    public void addFake() {
+        // 获取开始时间大于等于当前时间且未结束和开奖的活动
+        Date now = DateUtils.getNowDate();
+        List<Marketing> marketingList = marketingService.list(new LambdaQueryWrapper<Marketing>()
+                .eq(Marketing::getTriggerStatus, 0)
+                .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
+                .lt(Marketing::getStartTime, now)
+                .gt(Marketing::getEndTime, now));
+        if (CollectionUtils.isEmpty(marketingList)) {
+            return;
+        }
 
+        for (Marketing marketing : marketingList) {
+            // 随机加1-19
+            marketingService.update(new LambdaUpdateWrapper<Marketing>()
+                    .set(Marketing::getFakeNum, marketing.getFakeNum() + RandomUtil.randomInt(1,20))
+                    .eq(Marketing::getId, marketing.getId()));
+        }
     }
 
-
 }

+ 4 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/Marketing.java

@@ -97,6 +97,10 @@ public class Marketing implements Serializable {
     @TableField("trigger_status")
     private Integer triggerStatus;
 
+    @ApiModelProperty("是否发送通知:0未发送,1已发送")
+    @TableField("is_send")
+    private Integer isSend;
+
     /**
      * 创建时间
      */

+ 42 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/excel/MarketingHitPrizeExcel.java

@@ -0,0 +1,42 @@
+package com.qs.mp.admin.domain.excel;
+
+import com.qs.mp.common.annotation.Excel;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 活动中奖数据导出类
+ *
+ * @author Cup
+ * @date 2022/5/19
+ */
+@ApiModel("活动中奖数据导出类")
+@Data
+public class MarketingHitPrizeExcel {
+
+    @Excel(name = "活动名称")
+    private String title;
+
+    @Excel(name = "活动编号")
+    private Long marketingId;
+
+    @Excel(name = "开奖时间")
+    private Date endTime;
+
+    @Excel(name = "奖级")
+    private String awardsName;
+
+    @Excel(name = "奖品")
+    private String prizeTitle;
+
+    @Excel(name = "商品编号")
+    private String refId;
+
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @Excel(name = "用户手机号")
+    private String userPhone;
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingHitPrizeExportParam.java

@@ -0,0 +1,20 @@
+package com.qs.mp.admin.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 活动中奖数据导出入参类
+ * @author Cup
+ * @date 2022/5/19
+ */
+@ApiModel("活动中奖数据导出入参类")
+@Data
+public class MarketingHitPrizeExportParam {
+
+
+    @ApiModelProperty("活动名称")
+    private String title;
+
+}

+ 7 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingUserCodeListVO.java

@@ -1,5 +1,6 @@
 package com.qs.mp.admin.domain.vo;
 
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -34,4 +35,10 @@ public class MarketingUserCodeListVO {
     @ApiModelProperty("奖品")
     private String prizeName;
 
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    private TicketPrizeTypeEnum prizeType;
+
+    @ApiModelProperty("奖品价值")
+    private Integer value;
+
 }

+ 21 - 28
mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponServiceImpl.java

@@ -79,19 +79,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
 	@Transactional(rollbackFor = Exception.class)
 	public void distributeByMarketing(Long userId, String couponId) {
 		Coupon coupon = getById(couponId);
-		UserCoupon userCoupon = new UserCoupon();
-		userCoupon.setId(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
-		userCoupon.setUserId(userId);
-		userCoupon.setVerifyCode(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
-		userCoupon.setCouponId(coupon.getCouponId());
-		if (coupon.getDueDays() > 0) {
-			userCoupon.setValidStart(DateUtils.getToday());
-			userCoupon.setValidEnd(DateUtils.addDays(userCoupon.getValidStart(), coupon.getDueDays() - 1));
-		} else {
-			userCoupon.setValidStart(coupon.getValidStart());
-			userCoupon.setValidEnd(coupon.getValidEnd());
-		}
-		userCoupon.setStatus(UserCouponStatusEnum.UNUSED);
+		UserCoupon userCoupon = getUserCoupon(userId, coupon);
 		userCoupon.setUseAreaDesc(coupon.getUseArea().getDesc());
 		userCouponService.save(userCoupon);
 		boolean rtn = update(new LambdaUpdateWrapper<Coupon>().set(Coupon::getDistributeQty, coupon.getDistributeQty() + 1)
@@ -104,22 +92,10 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
   @Transactional(rollbackFor = Exception.class)
   public void distribute(Ticket ticket, Long userId, String couponId) {
     Coupon coupon = getById(couponId);
-    UserCoupon userCoupon = new UserCoupon();
-    userCoupon.setId(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
-    userCoupon.setUserId(userId);
-    userCoupon.setVerifyCode(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
-    userCoupon.setCouponId(coupon.getCouponId());
-    if (coupon.getDueDays() > 0) {
-      userCoupon.setValidStart(DateUtils.getToday());
-      userCoupon.setValidEnd(DateUtils.addDays(userCoupon.getValidStart(), coupon.getDueDays() - 1));
-    } else {
-      userCoupon.setValidStart(coupon.getValidStart());
-      userCoupon.setValidEnd(coupon.getValidEnd());
-    }
-    userCoupon.setStatus(UserCouponStatusEnum.UNUSED);
+	UserCoupon userCoupon = getUserCoupon(userId, coupon);
 
 
-    // 确定限定范围
+		// 确定限定范围
     if (coupon.getUseArea() == CouponUseAreaEnum.POST_SCOPE) {
       UserTicketOrderItem orderItem = userTicketOrderItemService.queryFinishedOrderItem(userId, ticket.getTicketId());
 			UserTicketOrder ticketOrder = userTicketOrderService.getById(orderItem.getOrderId());
@@ -144,7 +120,24 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
 		Assert.isTrue(rtn, "领取优惠券奖品,更新优惠券发放量失败。couponId:" + coupon.getCouponId());
   }
 
-  @Override
+	private UserCoupon getUserCoupon(Long userId, Coupon coupon) {
+		UserCoupon userCoupon = new UserCoupon();
+		userCoupon.setId(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
+		userCoupon.setUserId(userId);
+		userCoupon.setVerifyCode(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
+		userCoupon.setCouponId(coupon.getCouponId());
+		if (coupon.getDueDays() > 0) {
+			userCoupon.setValidStart(DateUtils.getToday());
+			userCoupon.setValidEnd(DateUtils.addDays(userCoupon.getValidStart(), coupon.getDueDays() - 1));
+		} else {
+			userCoupon.setValidStart(coupon.getValidStart());
+			userCoupon.setValidEnd(coupon.getValidEnd());
+		}
+		userCoupon.setStatus(UserCouponStatusEnum.UNUSED);
+		return userCoupon;
+	}
+
+	@Override
 	@Transactional
 	public void saveCoupon(Coupon coupon, List<CouponTicket> ticketList) {
 		boolean res = couponService.save(coupon);

+ 76 - 84
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingServiceImpl.java

@@ -12,11 +12,14 @@ import com.qs.mp.admin.mapper.MarketingMapper;
 import com.qs.mp.admin.service.*;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qs.mp.common.core.domain.AjaxResult;
+import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.core.redis.RedisCache;
 import com.qs.mp.common.enums.*;
 import com.qs.mp.common.exception.ServiceException;
 import com.qs.mp.common.utils.DateUtils;
+import com.qs.mp.common.utils.LogUtil;
 import com.qs.mp.common.utils.bean.BeanUtils;
+import com.qs.mp.framework.service.IWxSubscribeMessage;
 import com.qs.mp.system.domain.SysUser;
 import com.qs.mp.system.service.ISysUserService;
 import com.qs.mp.system.service.id.BizIdGenerator;
@@ -29,6 +32,8 @@ import com.qs.mp.user.service.IUserCoinService;
 import com.qs.mp.user.service.IUserPrizeStorageService;
 import com.qs.mp.utils.MarketingUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -36,6 +41,7 @@ import org.springframework.util.Assert;
 
 import java.net.URLDecoder;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -50,11 +56,12 @@ import java.util.stream.Collectors;
 public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing> implements IMarketingService {
 
     // 内部奖池
-    public static final String MARKETING_LOTTERY_INSIDE_POOL = "MARKETING_LOTTERY_INSIDE_POOL";
+    public static final String MARKETING_LOTTERY_INSIDE_POOL = "MARKETING_LOTTERY_INSIDE_POOL:%s";
 
     // 普通奖池
-    public static final String MARKETING_LOTTERY_ORDINARY_POOL = "MARKETING_LOTTERY_ORDINARY_POOL";
+    public static final String MARKETING_LOTTERY_ORDINARY_POOL = "MARKETING_LOTTERY_ORDINARY_POOL:%s";
 
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
 
     @Autowired
     private IGoodsService goodsService;
@@ -93,10 +100,22 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
     private BizIdGenerator bizIdGenerator;
 
 
+    @Autowired
+    private IWxSubscribeMessage wxSubscribeMessage;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void lottery(Marketing marketing) {
 
+        // 用户抽奖池
+        String userLotteryPool = String.format(MARKETING_LOTTERY_ORDINARY_POOL, marketing.getId());
+        // 内部抽奖池
+        String insideLotteryPool = String.format(MARKETING_LOTTERY_INSIDE_POOL, marketing.getId());
+
         // 获取奖级信息
         List<MarketingAwards> marketingAwardsList = marketingAwardsService.list(new LambdaQueryWrapper<MarketingAwards>()
                 .eq(MarketingAwards::getMarketingId, marketing.getId()));
@@ -105,10 +124,7 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         int prizeQuantity = marketingAwardsList.stream().mapToInt(MarketingAwards::getQuantity).sum();
 
         // 校准并获取真实参与人数
-        int realNum = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
-                .eq(MarketingUserCode::getMarketingId, marketing.getId())
-                .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
-                .groupBy(MarketingUserCode::getMarketingId));
+        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
 
         // 设置需要的内定人数
         int insideNum = 0;
@@ -126,7 +142,8 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         // 获取一定数量的内定人员设置抽奖码加入抽奖关系表和抽奖池
         List<SysUser> insideUserList = sysUserService.selectUserListByRand(UserTypeEnum.INSIDE.getValue(), insideNum);
 
-        Set<String> codeList = new HashSet<>();
+        Set<String> insideCodes = new HashSet<>();
+        Set<String> userCodes = new HashSet<>();
         for (SysUser sysUser : insideUserList) {
             // 生成抽奖码
             String code = MarketingUtils.generatePrizeCode();
@@ -141,7 +158,7 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
                 }
                 break;
             }
-            codeList.add(code);
+            insideCodes.add(code);
             MarketingUserCode marketingUserCode = new MarketingUserCode();
             marketingUserCode.setMarketingId(marketing.getId());
             marketingUserCode.setUserId(sysUser.getUserId());
@@ -153,19 +170,18 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
             marketingUserCodeService.save(marketingUserCode);
         }
         // 将抽奖码放入内部抽奖池
-        redisCache.setCacheSet(MARKETING_LOTTERY_INSIDE_POOL, codeList);
+        redisCache.setCacheSet(insideLotteryPool, insideCodes);
 
 
         // 获取普通用户所有的抽奖码
         List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
                 .eq(MarketingUserCode::getMarketingId, marketing.getId())
                 .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue()));
-        codeList.clear();
         if (CollectionUtils.isNotEmpty(userCodeList)) {
-            codeList = userCodeList.stream().map(MarketingUserCode::getCode).collect(Collectors.toSet());
+            userCodes = userCodeList.stream().map(MarketingUserCode::getCode).collect(Collectors.toSet());
         }
         // 将抽奖码放入普通抽奖池
-        redisCache.setCacheSet(MARKETING_LOTTERY_ORDINARY_POOL, codeList);
+        redisCache.setCacheSet(userLotteryPool, userCodes);
 
         // 所有中奖名单
         List<MarketingHitPrize> allHitPrizeList = new ArrayList<>();
@@ -191,26 +207,14 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
 
             if (insideNum != 0) {
                 // 设置内部用户抽奖信息
-                List<String> insideCodeList = redisCache.popCacheSet(MARKETING_LOTTERY_INSIDE_POOL, insideNum);
+                List<String> insideCodeList = redisCache.popCacheSet(insideLotteryPool, insideNum);
                 for (String code : insideCodeList) {
                     MarketingUserCode marketingUserCode = marketingUserCodeService.getOne(new LambdaQueryWrapper<MarketingUserCode>()
                             .eq(MarketingUserCode::getMarketingId, marketing.getId())
                             .eq(MarketingUserCode::getCode, code));
 
                     // 设置中奖信息
-                    MarketingHitPrize marketingHitPrize = new MarketingHitPrize();
-                    marketingHitPrize.setId(bizIdGenerator.newId());
-                    marketingHitPrize.setMarketingId(marketing.getId());
-                    marketingHitPrize.setAwardsId(marketingAwards.getId());
-                    marketingHitPrize.setMarketingUserCodeId(marketingUserCode.getId());
-                    marketingHitPrize.setUserId(marketingUserCode.getUserId());
-                    marketingHitPrize.setUserType(marketingUserCode.getUserType());
-
-                    // 随机抽取奖品,[0, list.size)  左闭右开
-                    int randomIndex = RandomUtil.randomInt(0, marketingAwardsPrizeList.size());
-                    marketingHitPrize.setPrizeId(marketingAwardsPrizeList.get(randomIndex).getId());
-
-                    allHitPrizeList.add(marketingHitPrize);
+                    exchangeMarketingHitPrize(marketing, allHitPrizeList, marketingAwards, marketingAwardsPrizeList, marketingUserCode);
                 }
             }
 
@@ -219,7 +223,7 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
             if (userNum > 0) {
                 // 设置普通用户抽奖信息
                 for (int i = 0; i < userNum; i++) {
-                    List<String> insideCodeList = redisCache.popCacheSet(MARKETING_LOTTERY_INSIDE_POOL, 1);
+                    List<String> insideCodeList = redisCache.popCacheSet(userLotteryPool, 1);
                     MarketingUserCode marketingUserCode = marketingUserCodeService.getOne(new LambdaQueryWrapper<MarketingUserCode>()
                             .eq(MarketingUserCode::getMarketingId, marketing.getId())
                             .eq(MarketingUserCode::getCode, insideCodeList.get(0)));
@@ -229,23 +233,9 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
                             .eq(MarketingUserCode::getMarketingId, marketing.getId())
                             .eq(MarketingUserCode::getUserId, marketingUserCode.getUserId()));
                     List<Object> delUserCodeList = list.stream().map(MarketingUserCode::getCode).collect(Collectors.toList());
-                    redisCache.removeSetValueByKey(MARKETING_LOTTERY_ORDINARY_POOL, delUserCodeList);
+                    redisCache.removeSetValueByKey(userLotteryPool, delUserCodeList);
+                    MarketingHitPrize marketingHitPrize = exchangeMarketingHitPrize(marketing, allHitPrizeList, marketingAwards, marketingAwardsPrizeList, marketingUserCode);
 
-
-                    // 设置中奖信息
-                    MarketingHitPrize marketingHitPrize = new MarketingHitPrize();
-                    marketingHitPrize.setId(bizIdGenerator.newId());
-                    marketingHitPrize.setMarketingId(marketing.getId());
-                    marketingHitPrize.setAwardsId(marketingAwards.getId());
-                    marketingHitPrize.setMarketingUserCodeId(marketingUserCode.getId());
-                    marketingHitPrize.setUserId(marketingUserCode.getUserId());
-                    marketingHitPrize.setUserType(marketingUserCode.getUserType());
-
-                    // 随机抽取奖品,[0, list.size)  左闭右开
-                    int randomIndex = RandomUtil.randomInt(0, marketingAwardsPrizeList.size());
-                    marketingHitPrize.setPrizeId(marketingAwardsPrizeList.get(randomIndex).getId());
-
-                    allHitPrizeList.add(marketingHitPrize);
                     userHitPrizeList.add(marketingHitPrize);
 
                     // 更新用户中奖状态
@@ -255,11 +245,12 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
             }
         }
         // 保存中奖信息
-        marketingHitPrizeService.saveBatch(allHitPrizeList);
+        Assert.isTrue(marketingHitPrizeService.saveBatch(allHitPrizeList), "保存中奖信息失败。marketingId:" + marketing.getId());
 
         // 更新抽奖码中奖状态
         if (CollectionUtils.isNotEmpty(userCodeUpdateList)) {
-            marketingUserCodeService.updateBatchById(userCodeUpdateList);
+            boolean rtn = marketingUserCodeService.updateBatchById(userCodeUpdateList);
+            Assert.isTrue(rtn, "更新抽奖码中奖状态失败。marketingId:" + marketing.getId());
         }
 
         // 发奖品
@@ -287,46 +278,46 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
                 .eq(Marketing::getTriggerStatus, 0));
         Assert.isTrue(rtn, "更新活动开奖状态失败。marketingId:" + marketing.getId());
 
+        // 删除抽奖池
+        redisCache.deleteObject(userLotteryPool);
+        redisCache.deleteObject(insideLotteryPool);
+
+    }
+
+    private MarketingHitPrize exchangeMarketingHitPrize(Marketing marketing, List<MarketingHitPrize> allHitPrizeList, MarketingAwards marketingAwards, List<MarketingAwardsPrize> marketingAwardsPrizeList, MarketingUserCode marketingUserCode) {
+        // 设置中奖信息
+        MarketingHitPrize marketingHitPrize = new MarketingHitPrize();
+        marketingHitPrize.setId(bizIdGenerator.newId());
+        marketingHitPrize.setMarketingId(marketing.getId());
+        marketingHitPrize.setAwardsId(marketingAwards.getId());
+        marketingHitPrize.setMarketingUserCodeId(marketingUserCode.getId());
+        marketingHitPrize.setUserId(marketingUserCode.getUserId());
+        marketingHitPrize.setUserType(marketingUserCode.getUserType());
+
+        // 随机抽取奖品,[0, list.size)  左闭右开
+        int randomIndex = RandomUtil.randomInt(0, marketingAwardsPrizeList.size());
+        marketingHitPrize.setPrizeId(marketingAwardsPrizeList.get(randomIndex).getId());
+
+        allHitPrizeList.add(marketingHitPrize);
+        return marketingHitPrize;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void help(MarketingHelpParam marketingHelpParam, Long userId) {
         String inviteCode = marketingHelpParam.getInviteCode();
-        if (com.qs.mp.common.utils.StringUtils.isBlank(inviteCode)) {
-            throw new ServiceException("邀请码不能为空");
-        }
+        // 邀请码解码,被助力人id
+        Long helpedUserId = MarketingUtils.decodeInviteCode(inviteCode);
 
         Marketing marketing = this.getById(marketingHelpParam.getMarketingId());
-        if (Objects.isNull(marketing)) {
-            throw new ServiceException("活动不存在");
-        }
-        // 未开启的活动不能助力
-        if (MarketingStatusEnum.ON.getValue().equals(marketing.getIsOn())) {
-            throw new ServiceException("活动未开启~");
-        }
-        Date now = DateUtils.getNowDate();
-        // 结束的活动不支持助力
-        if (marketing.getTriggerStatus() == 1 || marketing.getEndTime().before(now)) {
-            throw new ServiceException("活动已过期~");
-        }
-        // 未开始的活动不能助力
-        if (marketing.getStartTime().after(now)) {
-            throw new ServiceException("活动未开始~");
-        }
-
 
+        // 处理助力信息
         // 判断是否已经助力过
         int count = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
-                .eq(MarketingUserCode::getUserId, userId)
-                .eq(MarketingUserCode::getMarketingId, marketingHelpParam.getMarketingId()));
+                .eq(MarketingUserCode::getHelpUserId, userId));
         if (count > 0) {
             throw new ServiceException("您已经助力过了");
         }
-
-        // 邀请码解码,被助力人id
-        Long helpedUserId = MarketingUtils.decodeInviteCode(inviteCode);
-
         // 生成抽奖码
         String code = "";
         while (true) {
@@ -342,7 +333,9 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         }
 
         MarketingUserCode marketingUserCode = new MarketingUserCode();
+        // 设置活动id
         marketingUserCode.setMarketingId(marketingHelpParam.getMarketingId());
+        // 设置抽奖码
         marketingUserCode.setCode(code);
         // 设置被助力人
         marketingUserCode.setUserId(helpedUserId);
@@ -350,27 +343,26 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         marketingUserCode.setHelpUserId(userId);
         marketingUserCode.setUserType(UserTypeEnum.ORDINARY.getValue());
 
-
         // 保存助力信息
         marketingUserCodeService.save(marketingUserCode);
 
-        // 增加活动参与人数
-        int userCount = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
-                .eq(MarketingUserCode::getMarketingId, marketing.getId())
-                .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
-                .groupBy(MarketingUserCode::getMarketingId));
 
-        Marketing marketingParam = new Marketing();
-        marketingParam.setId(marketing.getId());
-        marketingParam.setRealNum(userCount);
-        int randomNum = (int) 1 + (int) (Math.random() * 10);
-        marketingParam.setFakeNum(marketing.getFakeNum() + randomNum);
+        // 真实用户
+        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
 
+
+        // 获取最新活动信息
+        marketing.setRealNum(realNum);
+        int randomNum = (int) 1 + (int) (Math.random() * 10);
+        marketing.setFakeNum(marketing.getFakeNum() + randomNum);
         // 更新活动参与人数
-        this.updateById(marketingParam);
+        boolean rtn = this.updateById(marketing);
+        Assert.isTrue(rtn, "更新活动参与人数异常。marketingId:" + marketing.getId());
 
-    }
+        // 给被助力人发送成功消息
+        wxSubscribeMessage.sendMarketingHelp(helpedUserId, marketing);
 
+    }
 
     @Override
     @Transactional(rollbackFor = Exception.class)

+ 63 - 0
mp-service/src/main/java/com/qs/mp/framework/domain/WxSubscribeMessage.java

@@ -4,6 +4,7 @@
  */
 package com.qs.mp.framework.domain;
 
+import com.qs.mp.admin.domain.Marketing;
 import com.qs.mp.common.utils.DateUtils;
 import java.util.HashMap;
 
@@ -74,6 +75,68 @@ public class WxSubscribeMessage {
 //        page = page + actId ;
         miniprogram_state = state;
     }
+
+
+    /**
+     * 活动助力成功通知消息
+     * @param marketing
+     */
+    public void buildMarketingHelpSuccessMessage(Marketing marketing){
+        template_id = "92xxL8XTRPLROyNUtmN04OT0kyq4iwSCH3sz-j6zbZs";
+        page = "/packageOperate/activity/index?id=" + marketing.getId();
+
+        /**
+         * 活动名称
+         * {{thing1.DATA}}
+         *
+         * 温馨提示
+         * {{thing3.DATA}}
+         */
+        data.put("thing1", new WeappDataValue(marketing.getTitle()));
+        data.put("thing3", new WeappDataValue("获得一个抽奖码,点击查看>>"));
+    }
+
+
+
+    /**
+     * 活动开始提醒消息
+     * @param marketing
+     */
+    public void buildMarketingStartSuccessMessage(Marketing marketing){
+        template_id = "FGcPOV1j9ApESr3VUlWjFr6E1vF4X_h0LLN82T2CLL0";
+        page = "/pages/activity/index";
+
+        /**
+         * 活动名称
+         * {{thing1.DATA}}
+         *
+         * 温馨提示
+         * {{thing5.DATA}}
+         */
+        data.put("thing1", new WeappDataValue(marketing.getTitle()));
+        data.put("thing5", new WeappDataValue("点击马上参与>>"));
+    }
+
+
+
+        /**
+         * 活动开奖消息
+         */
+    public void buildMarketingLotterySuccessMessage(Marketing marketing){
+        template_id = "3y_My-yRmqmGd3-f-vAwNaK4LQeehzXBSNDi_5wcnFI";
+        page = "/packageOperate/activity/record?id=" + marketing.getId();
+        /**
+         * 活动名称
+         * {{thing1.DATA}}
+         *
+         * 温馨提示
+         * {{thing5.DATA}}
+         */
+        data.put("thing1", new WeappDataValue(marketing.getTitle()));
+        data.put("thing5", new WeappDataValue("看看你中奖了吗?"));
+    }
+
+
 /*
     public void buildBillPaySuccessMessage(MerchantBill merchantBill){
         template_id = "1F2Hrqfr8fS--3CWeUOkDCjP83U6WTcfnBfqG7CYdKM";

+ 22 - 0
mp-service/src/main/java/com/qs/mp/framework/service/IWxSubscribeMessage.java

@@ -1,9 +1,31 @@
 package com.qs.mp.framework.service;
 
 
+import com.qs.mp.admin.domain.Marketing;
+
 /**
  * @author zhongcp
  * @Date 2021/9/22
  */
 public interface IWxSubscribeMessage {
+
+    /**
+     * 发送开奖消息
+     * @param userId
+     * @param marketing
+     * @return
+     */
+    public boolean sendMarketingLottery(Long userId, Marketing marketing);
+
+    /**
+     * 活动开始提醒消息
+     */
+    public boolean sendMarketingStart(Long userId, Marketing marketing);
+
+    /**
+     * 助力成功提醒消息
+     */
+    public boolean sendMarketingHelp(Long userId, Marketing marketing);
+
+
 }

+ 30 - 1
mp-service/src/main/java/com/qs/mp/framework/service/impl/WxSubscribeMessageImpl.java

@@ -1,6 +1,7 @@
 package com.qs.mp.framework.service.impl;
 
 import com.alibaba.fastjson.JSON;
+import com.qs.mp.admin.domain.Marketing;
 import com.qs.mp.common.utils.LogUtil;
 import com.qs.mp.common.utils.http.HttpUtils;
 import com.qs.mp.framework.domain.WxSubscribeMessage;
@@ -40,10 +41,38 @@ public class WxSubscribeMessageImpl implements IWxSubscribeMessage {
   @Value("${miniprogram.state}")
   private String state;
 
+  @Override
+  public boolean sendMarketingStart(Long userId, Marketing marketing) {
+    String openId = getOpenIdByUserId(userId);
+    WxSubscribeMessage wxSubscribeMessage = new WxSubscribeMessage(userAppId, openId, state);
+    wxSubscribeMessage.buildMarketingStartSuccessMessage(marketing);
+    return this.sendNotify(userId, marketing.getId().toString(), wxSubscribeMessage);
+  }
+
+  @Override
+  public boolean sendMarketingHelp(Long userId, Marketing marketing) {
+    String openId = getOpenIdByUserId(userId);
+    WxSubscribeMessage wxSubscribeMessage = new WxSubscribeMessage(userAppId, openId, state);
+    wxSubscribeMessage.buildMarketingHelpSuccessMessage(marketing);
+    return this.sendNotify(userId, marketing.getId().toString(), wxSubscribeMessage);
+  }
+
+  @Override
+  public boolean sendMarketingLottery(Long userId, Marketing marketing) {
+    String openId = getOpenIdByUserId(userId);
+    WxSubscribeMessage wxSubscribeMessage = new WxSubscribeMessage(userAppId, openId, state);
+    wxSubscribeMessage.buildMarketingLotterySuccessMessage(marketing);
+    return this.sendNotify(userId, marketing.getId().toString(), wxSubscribeMessage);
+  }
+
+  private String getOpenIdByUserId(Long userId) {
+    return sysUserService.selectUserById(userId).getOpenId();
+  }
+
 
   private boolean sendNotify(Long userId, String bizId, WxSubscribeMessage message) {
     // 获取access_token
-    String accessToken = appTokenService.getAccessToken(channelAppId);
+    String accessToken = appTokenService.getAccessToken(userAppId);
     String res = HttpUtils.sendPost(
         "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken,
         JSON.toJSONString(message));

+ 22 - 0
mp-service/src/main/java/com/qs/mp/user/domain/param/UserMarketingHitPrizeQueryParam.java

@@ -0,0 +1,22 @@
+package com.qs.mp.user.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 用户中奖信息列表查询入参类
+ * @author Cup
+ * @date 2022/5/19
+ */
+@ApiModel("用户中奖信息列表查询入参类")
+@Data
+public class UserMarketingHitPrizeQueryParam {
+
+    @NotNull(message = "活动id不能为空")
+    @ApiModelProperty("活动id")
+    private Long marketingId;
+
+}

+ 34 - 0
mp-service/src/main/java/com/qs/mp/user/domain/vo/UserMarketingHitPrizeDetailVO.java

@@ -0,0 +1,34 @@
+package com.qs.mp.user.domain.vo;
+
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 用户活动中奖详情出参类
+ * @author Cup
+ * @date 2022/5/19
+ */
+@ApiModel("用户活动中奖详情出参类")
+@Data
+public class UserMarketingHitPrizeDetailVO {
+
+    @ApiModelProperty("是否中奖,-1未参与,0未中奖,1中奖")
+    private Integer isHit;
+
+    @ApiModelProperty("奖级")
+    private String awardName;
+
+    @ApiModelProperty("奖品")
+    private String prizeTitle;
+
+    @ApiModelProperty("奖品图片")
+    private String prizePicUrl;
+
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    private TicketPrizeTypeEnum prizeType;
+
+    @ApiModelProperty("奖品价值")
+    private Integer value;
+}

+ 42 - 0
mp-service/src/main/java/com/qs/mp/user/domain/vo/UserMarketingHitPrizeListVO.java

@@ -0,0 +1,42 @@
+package com.qs.mp.user.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 用户营销活动中奖列表出参类
+ * @author Cup
+ * @date 2022/5/19
+ */
+@ApiModel("用户营销活动中奖列表出参类")
+@Data
+public class UserMarketingHitPrizeListVO {
+
+    @ApiModelProperty("用户昵称")
+    private String nickName;
+
+    @ApiModelProperty("用户头像")
+    private String avatar;
+
+    @ApiModelProperty("中奖码")
+    private String code;
+
+    @ApiModelProperty("奖品图片")
+    private String prizePicUrl;
+
+    @ApiModelProperty("奖级")
+    private String awardName;
+
+    @ApiModelProperty("奖品")
+    private String prizeTitle;
+
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    private TicketPrizeTypeEnum prizeType;
+
+    @ApiModelProperty("奖品价值")
+    private Integer value;
+
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/user/mapper/MarketingHitPrizeMapper.java

@@ -1,7 +1,14 @@
 package com.qs.mp.user.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.qs.mp.admin.domain.excel.MarketingHitPrizeExcel;
 import com.qs.mp.user.domain.MarketingHitPrize;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qs.mp.user.domain.vo.UserMarketingHitPrizeListVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * @auther quanshu
@@ -10,4 +17,17 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  */
 public interface MarketingHitPrizeMapper extends BaseMapper<MarketingHitPrize> {
 
+    /**
+     * 根据条件获取中奖信息列表
+     * @param queryWrapper
+     * @return
+     */
+    List<UserMarketingHitPrizeListVO> listHitPrizeByQueryWrapper(@Param(Constants.WRAPPER) QueryWrapper<MarketingHitPrize> queryWrapper);
+
+    /**
+     * 根据条件获取中奖信息导出列表
+     * @param queryWrapper
+     * @return
+     */
+    List<MarketingHitPrizeExcel> listHitPrizeExportByQueryWrapper(@Param(Constants.WRAPPER) QueryWrapper<MarketingHitPrize> queryWrapper);
 }

+ 7 - 0
mp-service/src/main/java/com/qs/mp/user/mapper/MarketingUserCodeMapper.java

@@ -22,4 +22,11 @@ public interface MarketingUserCodeMapper extends BaseMapper<MarketingUserCode> {
      * @return
      */
     List<MarketingUserCodeListVO> listMarketingUserCodeByQueryWrapper(@Param(Constants.WRAPPER) QueryWrapper<MarketingUserCode> queryWrapper);
+
+    /**
+     * 获取活动参与的真实用户
+     * @param marketingId
+     * @return
+     */
+    int countRealUserNumByMarketingId(@Param("marketingId") Long marketingId);
 }

+ 18 - 0
mp-service/src/main/java/com/qs/mp/user/service/IMarketingHitPrizeService.java

@@ -1,7 +1,12 @@
 package com.qs.mp.user.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.excel.MarketingHitPrizeExcel;
 import com.qs.mp.user.domain.MarketingHitPrize;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.user.domain.vo.UserMarketingHitPrizeListVO;
+
+import java.util.List;
 
 /**
  * <p>
@@ -13,4 +18,17 @@ import com.baomidou.mybatisplus.extension.service.IService;
  */
 public interface IMarketingHitPrizeService extends IService<MarketingHitPrize> {
 
+    /**
+     * 根据条件获取中奖信息列表
+     * @param queryWrapper
+     * @return
+     */
+    List<UserMarketingHitPrizeListVO> listHitPrizeByQueryWrapper(QueryWrapper<MarketingHitPrize> queryWrapper);
+
+    /**
+     * 根据条件获取中奖信息导出列表
+     * @param queryWrapper
+     * @return
+     */
+    List<MarketingHitPrizeExcel> listHitPrizeExportByQueryWrapper(QueryWrapper<MarketingHitPrize> queryWrapper);
 }

+ 8 - 0
mp-service/src/main/java/com/qs/mp/user/service/IMarketingUserCodeService.java

@@ -24,4 +24,12 @@ public interface IMarketingUserCodeService extends IService<MarketingUserCode> {
      * @return
      */
     List<MarketingUserCodeListVO> listMarketingUserCodeByQueryWrapper(QueryWrapper<MarketingUserCode> queryWrapper);
+
+    /**
+     * 获取活动参与的真实用户
+     * @param marketingId
+     * @return
+     */
+    int countRealUserNumByMarketingId(Long marketingId);
+
 }

+ 15 - 0
mp-service/src/main/java/com/qs/mp/user/service/impl/MarketingHitPrizeServiceImpl.java

@@ -1,11 +1,16 @@
 package com.qs.mp.user.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.excel.MarketingHitPrizeExcel;
 import com.qs.mp.user.domain.MarketingHitPrize;
+import com.qs.mp.user.domain.vo.UserMarketingHitPrizeListVO;
 import com.qs.mp.user.mapper.MarketingHitPrizeMapper;
 import com.qs.mp.user.service.IMarketingHitPrizeService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * <p>
  * 营销活动中奖信息表 服务实现类
@@ -17,4 +22,14 @@ import org.springframework.stereotype.Service;
 @Service
 public class MarketingHitPrizeServiceImpl extends ServiceImpl<MarketingHitPrizeMapper, MarketingHitPrize> implements IMarketingHitPrizeService {
 
+
+    @Override
+    public List<UserMarketingHitPrizeListVO> listHitPrizeByQueryWrapper(QueryWrapper<MarketingHitPrize> queryWrapper) {
+        return getBaseMapper().listHitPrizeByQueryWrapper(queryWrapper);
+    }
+
+    @Override
+    public List<MarketingHitPrizeExcel> listHitPrizeExportByQueryWrapper(QueryWrapper<MarketingHitPrize> queryWrapper) {
+        return getBaseMapper().listHitPrizeExportByQueryWrapper(queryWrapper);
+    }
 }

+ 5 - 0
mp-service/src/main/java/com/qs/mp/user/service/impl/MarketingUserCodeServiceImpl.java

@@ -22,6 +22,11 @@ import java.util.List;
 public class MarketingUserCodeServiceImpl extends ServiceImpl<MarketingUserCodeMapper, MarketingUserCode> implements IMarketingUserCodeService {
 
 
+    @Override
+    public int countRealUserNumByMarketingId(Long marketingId) {
+        return getBaseMapper().countRealUserNumByMarketingId(marketingId);
+    }
+
     @Override
     public List<MarketingUserCodeListVO> listMarketingUserCodeByQueryWrapper(QueryWrapper<MarketingUserCode> queryWrapper) {
         return getBaseMapper().listMarketingUserCodeByQueryWrapper(queryWrapper);

+ 33 - 0
mp-service/src/main/resources/mapper/user/MarketingHitPrizeMapper.xml

@@ -20,4 +20,37 @@
         id, marketing_id, user_id, user_type, awards_id, prize_id, created_time, updated_time, is_deleted
     </sql>
 
+    <select id="listHitPrizeByQueryWrapper" resultType="com.qs.mp.user.domain.vo.UserMarketingHitPrizeListVO">
+        select t4.nick_name,
+               t4.avatar,
+               t5.code,
+               t2.name awardName,
+               t3.title prizeTitle,
+               t3.pic_url prizePicUrl,
+                t3.prize_type,
+                t3.value
+        from mp_marketing_hit_prize t1
+             left join mp_marketing_awards t2 on t1.awards_id = t2.id
+             left join mp_marketing_awards_prize t3 on t1.prize_id = t3.id
+             left join sys_user t4 on t1.user_id = t4.user_id
+             left join mp_marketing_user_code t5 on t1.marketing_user_code_id = t5.id
+        ${ew.customSqlSegment}
+    </select>
+
+    <select id="listHitPrizeExportByQueryWrapper" resultType="com.qs.mp.admin.domain.excel.MarketingHitPrizeExcel">
+        select t2.title,
+               t2.id marketingId,
+               t2.end_time,
+               t3.name awardsName,
+               t4.title prizeTitle,
+               t4.ref_id,
+               t5.nick_name,
+               t5.phonenumber userPhone
+        from mp_marketing_hit_prize t1
+                 left join mp_marketing t2 on t1.marketing_id = t2.id
+                 left join mp_marketing_awards t3 on t1.awards_id = t3.id
+                 left join mp_marketing_awards_prize t4 on t1.prize_id = t4.id
+                 left join sys_user t5 on t1.user_id = t5.user_id
+        ${ew.customSqlSegment}
+    </select>
 </mapper>

+ 12 - 1
mp-service/src/main/resources/mapper/user/MarketingUserCodeMapper.xml

@@ -27,7 +27,9 @@
                 t1.created_time,
                 t3.nick_name,
                 t4.name awardsName,
-                t5.title prizeName
+                t5.title prizeName,
+                t5.prize_type,
+                t5.value
         from mp_marketing_user_code t1
                 left join mp_marketing_hit_prize t2 on t1.id = t2.marketing_user_code_id
                 left join sys_user t3 on t1.user_id = t3.user_id
@@ -35,4 +37,13 @@
                 left join mp_marketing_awards_prize t5 on t2.prize_id = t5.id
         ${ew.customSqlSegment}
     </select>
+
+    <select id="countRealUserNumByMarketingId" resultType="int">
+        select count(*)
+        from (select user_id
+              from mp_marketing_user_code
+              where user_type = '00'
+              and marketing_id = #{marketingId}
+              group by user_id) t1;
+    </select>
 </mapper>