Переглянути джерело

Merge branch 'mp-server-test' into 'master'

Mp server test

See merge request quanshu/mp-server!620
zhong chunping 2 роки тому
батько
коміт
652ed843fe
100 змінених файлів з 4401 додано та 152 видалено
  1. 2 1
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/CouponMgrController.java
  2. 1 1
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/ExchangeBannerMgrController.java
  3. 56 2
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsCategoryMgrController.java
  4. 15 7
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsMgrController.java
  5. 250 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/MarketingMgrController.java
  6. 3 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserDeliverOrderMgrController.java
  7. 118 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserPrizeRecoveryMgrController.java
  8. 270 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingController.java
  9. 110 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingHitPrizeController.java
  10. 58 6
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserDeliverOrderController.java
  11. 85 26
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserExchangeController.java
  12. 17 6
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserMineController.java
  13. 178 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserPrizeRecoveryController.java
  14. 108 29
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserTicketController.java
  15. 5 0
      mp-admin/src/main/resources/application-dev.yml
  16. 1 1
      mp-admin/src/main/resources/application-test.yml
  17. 7 0
      mp-common/pom.xml
  18. 10 0
      mp-common/src/main/java/com/qs/mp/common/constant/Constants.java
  19. 29 0
      mp-common/src/main/java/com/qs/mp/common/core/redis/DistributedLocker.java
  20. 22 0
      mp-common/src/main/java/com/qs/mp/common/core/redis/RedisCache.java
  21. 75 0
      mp-common/src/main/java/com/qs/mp/common/core/redis/RedissonDistributedLocker.java
  22. 45 0
      mp-common/src/main/java/com/qs/mp/common/enums/AwardsLabelEnum.java
  23. 6 1
      mp-common/src/main/java/com/qs/mp/common/enums/CoinLogTypeEnum.java
  24. 58 0
      mp-common/src/main/java/com/qs/mp/common/enums/DeliverOrderResourceEnum.java
  25. 88 0
      mp-common/src/main/java/com/qs/mp/common/enums/MarketingStatusEnum.java
  26. 3 2
      mp-common/src/main/java/com/qs/mp/common/enums/PrizeStorageInTypeEnum.java
  27. 2 1
      mp-common/src/main/java/com/qs/mp/common/enums/PrizeStorageStatusEnum.java
  28. 40 0
      mp-common/src/main/java/com/qs/mp/common/enums/UserTypeEnum.java
  29. 5 1
      mp-framework/src/main/java/com/qs/mp/framework/config/SecurityConfig.java
  30. 163 0
      mp-quartz/src/main/java/com/qs/mp/quartz/task/MarketingTask.java
  31. 2 0
      mp-quartz/src/main/java/com/qs/mp/quartz/task/UserDeliverOrderTask.java
  32. 5 0
      mp-service/src/main/java/com/qs/mp/admin/domain/Goods.java
  33. 12 0
      mp-service/src/main/java/com/qs/mp/admin/domain/GoodsCategory.java
  34. 5 0
      mp-service/src/main/java/com/qs/mp/admin/domain/GoodsSku.java
  35. 127 0
      mp-service/src/main/java/com/qs/mp/admin/domain/Marketing.java
  36. 103 0
      mp-service/src/main/java/com/qs/mp/admin/domain/MarketingAwards.java
  37. 127 0
      mp-service/src/main/java/com/qs/mp/admin/domain/MarketingAwardsPrize.java
  38. 134 0
      mp-service/src/main/java/com/qs/mp/admin/domain/PrizeRecovery.java
  39. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/TicketAwards.java
  40. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/TicketBox.java
  41. 42 0
      mp-service/src/main/java/com/qs/mp/admin/domain/excel/MarketingHitPrizeExcel.java
  42. 48 0
      mp-service/src/main/java/com/qs/mp/admin/domain/excel/PrizeRecoveryExcel.java
  43. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsParam.java
  44. 5 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsQueryParam.java
  45. 39 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingAwardsParam.java
  46. 32 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingAwardsPrizeParam.java
  47. 49 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingCreateParam.java
  48. 20 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingHitPrizeExportParam.java
  49. 23 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingQueryParam.java
  50. 52 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingUpdateParam.java
  51. 30 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/PrizeRecoveryListQueryParam.java
  52. 5 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketAwardsParam.java
  53. 3 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketBoxCreateParam.java
  54. 3 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketBoxUpdateParam.java
  55. 3 3
      mp-service/src/main/java/com/qs/mp/admin/domain/param/UserDeliverOrderQueryParam.java
  56. 29 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GoodsCategoryTreeVO.java
  57. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GoodsVO.java
  58. 23 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingAwardsVO.java
  59. 51 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingListVO.java
  60. 44 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingUserCodeListVO.java
  61. 23 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingVO.java
  62. 24 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/PrizeRecoveryListVO.java
  63. 16 1
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/TicketAwardsPrizeVO.java
  64. 4 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/TicketBoxVO.java
  65. 13 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingAwardsMapper.java
  66. 13 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingAwardsPrizeMapper.java
  67. 13 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingMapper.java
  68. 34 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/PrizeRecoveryMapper.java
  69. 12 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/TicketAwardsMapper.java
  70. 6 0
      mp-service/src/main/java/com/qs/mp/admin/service/ICouponPkgService.java
  71. 7 0
      mp-service/src/main/java/com/qs/mp/admin/service/ICouponService.java
  72. 16 0
      mp-service/src/main/java/com/qs/mp/admin/service/IMarketingAwardsPrizeService.java
  73. 16 0
      mp-service/src/main/java/com/qs/mp/admin/service/IMarketingAwardsService.java
  74. 60 0
      mp-service/src/main/java/com/qs/mp/admin/service/IMarketingService.java
  75. 44 0
      mp-service/src/main/java/com/qs/mp/admin/service/IPrizeRecoveryService.java
  76. 10 0
      mp-service/src/main/java/com/qs/mp/admin/service/ITicketAwardsService.java
  77. 19 2
      mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponPkgServiceImpl.java
  78. 34 16
      mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponServiceImpl.java
  79. 20 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingAwardsPrizeServiceImpl.java
  80. 20 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingAwardsServiceImpl.java
  81. 526 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingServiceImpl.java
  82. 89 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/PrizeRecoveryServiceImpl.java
  83. 20 1
      mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketAwardsPrizeServiceImpl.java
  84. 9 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketAwardsServiceImpl.java
  85. 73 19
      mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketBoxServiceImpl.java
  86. 26 20
      mp-service/src/main/java/com/qs/mp/channel/service/impl/ChannelOrderServiceImpl.java
  87. 63 0
      mp-service/src/main/java/com/qs/mp/framework/domain/WxSubscribeMessage.java
  88. 2 1
      mp-service/src/main/java/com/qs/mp/framework/redis/RedisKey.java
  89. 28 0
      mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java
  90. 22 0
      mp-service/src/main/java/com/qs/mp/framework/service/IWxSubscribeMessage.java
  91. 30 1
      mp-service/src/main/java/com/qs/mp/framework/service/impl/WxSubscribeMessageImpl.java
  92. 11 0
      mp-service/src/main/java/com/qs/mp/system/domain/SysUser.java
  93. 7 0
      mp-service/src/main/java/com/qs/mp/system/mapper/SysUserMapper.java
  94. 8 0
      mp-service/src/main/java/com/qs/mp/system/service/ISysUserService.java
  95. 5 0
      mp-service/src/main/java/com/qs/mp/system/service/impl/SysUserServiceImpl.java
  96. 100 0
      mp-service/src/main/java/com/qs/mp/user/domain/MarketingHitPrize.java
  97. 102 0
      mp-service/src/main/java/com/qs/mp/user/domain/MarketingUserCode.java
  98. 7 0
      mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrder.java
  99. 4 4
      mp-service/src/main/java/com/qs/mp/user/domain/UserPrizeStorage.java
  100. 3 0
      mp-service/src/main/java/com/qs/mp/user/domain/excel/DeliverOrderExcel.java

+ 2 - 1
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/CouponMgrController.java

@@ -21,6 +21,7 @@ import com.qs.mp.admin.service.ITicketBoxService;
 import com.qs.mp.channel.domain.Channel;
 import com.qs.mp.channel.service.IChannelService;
 import com.qs.mp.common.annotation.Log;
+import com.qs.mp.common.constant.Constants;
 import com.qs.mp.common.constant.UserConstants;
 import com.qs.mp.common.core.domain.AjaxResult;
 import com.qs.mp.common.core.page.TableDataInfo;
@@ -213,7 +214,7 @@ public class CouponMgrController extends BaseApiController {
 //			return AjaxResult.error("使用范围为指定盲票时需要至少选择一种盲票");
 //		}
 		if(StringUtils.isBlank(coupon.getPicUrl())) {
-			coupon.setPicUrl("coupon_def.jpeg");
+			coupon.setPicUrl(Constants.COUPON_PIC);
 		}
 		// 3.插入数据
 		try {

+ 1 - 1
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/ExchangeBannerMgrController.java

@@ -28,7 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
  * @create 2022-04-07 23:45:48
  * @describe 商品banner管理前端控制器
  */
-@Api("商品banner管理API")
+@Api(tags = "商品banner管理API")
 @RestController
 @RequestMapping("/api/v1/mp/admin/goods/banner/*")
 @Component

+ 56 - 2
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsCategoryMgrController.java

@@ -2,8 +2,10 @@ package com.qs.mp.web.controller.api.admin;
 
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.qs.mp.admin.domain.Goods;
 import com.qs.mp.admin.domain.GoodsCategory;
+import com.qs.mp.admin.domain.vo.GoodsCategoryTreeVO;
 import com.qs.mp.admin.service.IGoodsCategoryService;
 import com.qs.mp.admin.service.IGoodsService;
 import com.qs.mp.common.annotation.Log;
@@ -14,8 +16,15 @@ import com.qs.mp.common.enums.ErrorCodeEnum;
 import com.qs.mp.web.controller.common.BaseApiController;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
+
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
@@ -30,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController;
  * @create 2022-04-07 23:45:48
  * @describe 商品分类管理前端控制器
  */
-@Api("商品分类管理API")
+@Api(tags = "商品分类管理API")
 @RestController
 @RequestMapping("/api/v1/mp/admin/goods/category/*")
 @Component
@@ -42,6 +51,41 @@ public class GoodsCategoryMgrController extends BaseApiController {
   @Autowired
   private IGoodsService goodsService;
 
+  @PostMapping("/listTree")
+  @PreAuthorize("@ss.hasPermi('business:category:list')")
+  @ApiOperation("分类列表树状接口")
+  @ApiResponses(
+          @ApiResponse(code = 200, message = "成功", response = GoodsCategoryTreeVO.class)
+  )
+  public TableDataInfo listTree(@RequestBody JSONObject param) {
+    startPage();
+
+    // 获取父分类
+    List<GoodsCategory> categoryList = goodsCategoryService.list(
+            new LambdaQueryWrapper<GoodsCategory>()
+                    .eq(GoodsCategory::getParentId, 0)
+                    .orderByDesc(GoodsCategory::getSort));
+    if (CollectionUtils.isEmpty(categoryList)) {
+      return getErrorDataTable("分类信息不存在");
+    }
+
+    List<GoodsCategoryTreeVO> treeList =  categoryList.stream().map(goodsCategory -> {
+      GoodsCategoryTreeVO goodsCategoryTreeVO = new GoodsCategoryTreeVO();
+      BeanUtils.copyProperties(goodsCategory, goodsCategoryTreeVO);
+      // 获取子分类
+      List<GoodsCategory> list = goodsCategoryService.list(
+              new LambdaQueryWrapper<GoodsCategory>()
+                      .eq(GoodsCategory::getParentId, goodsCategory.getCategoryId())
+                      .orderByDesc(GoodsCategory::getSort));
+      goodsCategoryTreeVO.setGoodsCategoryList(list);
+      return goodsCategoryTreeVO;
+    }).collect(Collectors.toList());
+    TableDataInfo res = getDataTable(categoryList);
+    res.setRows(treeList);
+
+    return res;
+  }
+
 
   /**
    * 查询商品分类列表, 支持翻页
@@ -50,6 +94,10 @@ public class GoodsCategoryMgrController extends BaseApiController {
    */
   @PostMapping("/list")
   @PreAuthorize("@ss.hasPermi('business:category:list')")
+  @ApiOperation("分类列表接口")
+  @ApiResponses(
+          @ApiResponse(code = 200,message = "查询成功",response = GoodsCategory.class)
+  )
   public TableDataInfo list(@RequestBody JSONObject param) {
     startPage();
     List<GoodsCategory> categoryList = goodsCategoryService.list(
@@ -105,7 +153,6 @@ public class GoodsCategoryMgrController extends BaseApiController {
       return AjaxResult.error("分类名称已存在!");
     }
     // 2.保存
-    goodsCategory.setParentId(0L);
     goodsCategoryService.save(goodsCategory);
     return AjaxResult.success("保存成功");
   }
@@ -146,6 +193,7 @@ public class GoodsCategoryMgrController extends BaseApiController {
   @Log(title = "删除商品分类", businessType = BusinessType.DELETE)
   @PostMapping(value = "/remove")
   @PreAuthorize("@ss.hasPermi('business:category:remove')")
+  @ApiOperation("删除商品分类")
   public AjaxResult remove(@RequestBody GoodsCategory goodsCategory) {
 
     if (null == goodsCategory.getCategoryId() || 0 == goodsCategory.getCategoryId()) {
@@ -157,6 +205,12 @@ public class GoodsCategoryMgrController extends BaseApiController {
       return error(ErrorCodeEnum.ERROR_CODE_1024);
     }
 
+    int count = goodsCategoryService.count(new LambdaQueryWrapper<GoodsCategory>()
+            .eq(GoodsCategory::getParentId, goodsCategory.getCategoryId()));
+    if (count > 0) {
+      return error("删除失败,分类下存在子分类");
+    }
+
     boolean res = goodsCategoryService.removeById(goodsCategory.getCategoryId());
     return res ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
   }

+ 15 - 7
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/GoodsMgrController.java

@@ -119,8 +119,9 @@ public class GoodsMgrController extends BaseApiController {
     queryWrapper.lambda().eq(null != queryParam.getExchangeShow(), Goods::getExchangeShow,
         queryParam.getExchangeShow());
     // 商品分类
-    queryWrapper.lambda()
-        .eq(null != queryParam.getCategoryId(), Goods::getCategoryId, queryParam.getCategoryId());
+    if (!CollectionUtils.isEmpty(queryParam.getCategoryIdList())) {
+      queryWrapper.lambda().in(Goods::getCategoryId, queryParam.getCategoryIdList());
+    }
     // 标签过滤的商品ID
     queryWrapper.lambda().in(null != queryParam.getTagId(), Goods::getGoodsId, goodsIds);
     queryWrapper.lambda().eq(Goods::getIsDeleted, 0);
@@ -217,22 +218,25 @@ public class GoodsMgrController extends BaseApiController {
     goods.setStatus(GoodsStatusEnum.PUT_INIT);
     // 多SKU
     List<GoodsSku> skuList = goodsParam.getSkuList();
-    if (goods.getMultiSku() == 1) { //
+    if (goods.getMultiSku() == 1) {
       if ((null == skuList || skuList.size() == 0)) {
         return AjaxResult.error("商品" + goods.getTitle() + "不满足多SKU条件!");
       } else {
         GoodsSku firstGoodsSku = skuList.get(0);
-        goods.setOriginPrice(firstGoodsSku.getOriginPrice());
         goods.setExchangePrice(firstGoodsSku.getExchangePrice());
         goods.setValue(firstGoodsSku.getValue());
         goods.setCost(firstGoodsSku.getCost());
+        goods.setDiscountRate(firstGoodsSku.getDiscountRate());
         // 累计库存
         int totalQuantity = skuList.stream().mapToInt(GoodsSku::getQuantity).sum();
         goods.setQuantity(totalQuantity);
       }
     }
     if (null == goods.getOriginPrice()) {
-      goods.setOriginPrice(goods.getExchangePrice());
+      goods.setOriginPrice(0);
+    }
+    if (null == goods.getDiscountRate()) {
+      goods.setDiscountRate(goodsParam.getDiscountRate());
     }
     // 3.插入数据
     try {
@@ -286,10 +290,14 @@ public class GoodsMgrController extends BaseApiController {
         goods.setExchangePrice(firstGoodsSku.getExchangePrice());
         goods.setValue(firstGoodsSku.getValue());
         goods.setCost(firstGoodsSku.getCost());
+        goods.setOriginPrice(firstGoodsSku.getOriginPrice());
         if (null == firstGoodsSku.getOriginPrice()) {
-          goods.setOriginPrice(firstGoodsSku.getExchangePrice());
+          goods.setOriginPrice(0);
+        }
+        if (null == firstGoodsSku.getDiscountRate()) {
+          goods.setDiscountRate(goodsParam.getDiscountRate());
         } else {
-          goods.setOriginPrice(firstGoodsSku.getOriginPrice());
+          goods.setDiscountRate(firstGoodsSku.getDiscountRate());
         }
         // 累计库存
         int totalQuantity = skuList.stream().mapToInt(GoodsSku::getQuantity).sum();

+ 250 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/MarketingMgrController.java

@@ -0,0 +1,250 @@
+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;
+import com.qs.mp.admin.domain.vo.MarketingListVO;
+import com.qs.mp.admin.domain.vo.MarketingUserCodeListVO;
+import com.qs.mp.admin.domain.vo.MarketingVO;
+import com.qs.mp.admin.service.IMarketingAwardsPrizeService;
+import com.qs.mp.admin.service.IMarketingAwardsService;
+import com.qs.mp.admin.service.IMarketingService;
+import com.qs.mp.common.annotation.Log;
+import com.qs.mp.common.core.domain.AjaxResult;
+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;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 营销活动管理相关接口
+ *
+ * @author Cup
+ * @date 2022/5/16
+ */
+@RestController
+@RequestMapping("/api/v1/mp/admin/marketing")
+@Api(tags = "营销活动管理相关接口")
+public class MarketingMgrController extends BaseApiController {
+
+    @Autowired
+    private IMarketingService marketingService;
+
+    @Autowired
+    private IMarketingAwardsService marketingAwardsService;
+
+    @Autowired
+    private IMarketingAwardsPrizeService marketingAwardsPrizeService;
+
+    @Autowired
+    private IMarketingUserCodeService marketingUserCodeService;
+
+    @Autowired
+    private IMarketingHitPrizeService marketingHitPrizeService;
+
+    @Log(title = "营销活动新增", businessType = BusinessType.INSERT)
+    @ApiOperation(value = "营销活动新增")
+    @PostMapping("/create")
+    @PreAuthorize("@ss.hasPermi('business:marketing:add')")
+    public AjaxResult create(@Validated @RequestBody MarketingCreateParam marketingCreateParam) {
+        marketingService.createMarketing(marketingCreateParam);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/update")
+    @PreAuthorize("@ss.hasPermi('business:marketing:edit')")
+    @ApiOperation("修改营销活动")
+    public AjaxResult update(@Validated @RequestBody MarketingUpdateParam marketingUpdateParam) {
+        marketingService.updateMarketing(marketingUpdateParam);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/list")
+    @PreAuthorize("@ss.hasPermi('business:marketing:list')")
+    @ApiOperation("营销活动列表")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "营销活动列表", response = MarketingListVO.class)
+    )
+    public TableDataInfo list(@RequestBody MarketingQueryParam marketingQueryParam) {
+        startPage();
+        List<Marketing> marketingList = marketingService.listMarketing(marketingQueryParam);
+        List<MarketingListVO> marketingListVOS = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(marketingList)) {
+            marketingListVOS = marketingList.stream().map(marketing -> {
+                MarketingListVO marketingListVO = new MarketingListVO();
+                com.qs.mp.common.utils.bean.BeanUtils.copyProperties(marketing, marketingListVO);
+                return marketingListVO;
+            }).collect(Collectors.toList());
+        }
+        TableDataInfo res = getDataTable(marketingList);
+        res.setRows(marketingListVOS);
+
+        return res;
+    }
+
+    @PostMapping("/detail/{id}")
+    @PreAuthorize("@ss.hasPermi('business:marketing:query')")
+    @ApiOperation("活动详情")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "活动详情", response = MarketingVO.class)
+    )
+    public AjaxResult detail(@PathVariable("id") Long id) {
+        MarketingVO marketingVO = new MarketingVO();
+        Marketing marketing = marketingService.getById(id);
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动信息不存在");
+        }
+        BeanUtils.copyProperties(marketing, marketingVO);
+
+        // 获取奖级信息
+        List<MarketingAwards> marketingAwardsList = marketingAwardsService.list(new LambdaQueryWrapper<MarketingAwards>()
+                .eq(MarketingAwards::getMarketingId, id)
+                .orderByAsc(MarketingAwards::getSort));
+
+        List<MarketingAwardsVO> marketingAwardsVOS = marketingAwardsList.stream().map(marketingAwards -> {
+            MarketingAwardsVO marketingAwardsVO = new MarketingAwardsVO();
+            BeanUtils.copyProperties(marketingAwards, marketingAwardsVO);
+            List<MarketingAwardsPrize> marketingAwardsPrizeList = marketingAwardsPrizeService.list(new LambdaQueryWrapper<MarketingAwardsPrize>()
+                    .eq(MarketingAwardsPrize::getAwardsId, marketingAwards.getId()));
+            marketingAwardsVO.setPrizeList(marketingAwardsPrizeList);
+            return marketingAwardsVO;
+        }).collect(Collectors.toList());
+
+        marketingVO.setAwardsList(marketingAwardsVOS);
+
+        return AjaxResult.success(marketingVO);
+    }
+
+
+    @PostMapping("/on/{id}")
+    @PreAuthorize("@ss.hasPermi('business:marketing:on')")
+    @ApiOperation("开启活动")
+    public AjaxResult putOn(@PathVariable("id") Long id) {
+        Marketing marketing = marketingService.getById(id);
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动信息不存在");
+        }
+        if (MarketingStatusEnum.CLOSE.getValue().equals(marketing.getIsOn())) {
+            return AjaxResult.error("已关闭的活动不支持开启");
+        }
+
+        if (MarketingStatusEnum.ON.getValue().equals(marketing.getIsOn())) {
+            return AjaxResult.error("频繁操作,请刷新后重试");
+        }
+
+        // 开启活动
+        marketingService.update(new LambdaUpdateWrapper<Marketing>()
+                .set(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
+                .eq(Marketing::getId, id));
+
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/off/{id}")
+    @PreAuthorize("@ss.hasPermi('business:marketing:off')")
+    @ApiOperation("关闭活动")
+    public AjaxResult putOff(@PathVariable("id") Long id) {
+        Marketing marketing = marketingService.getById(id);
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动信息不存在");
+        }
+        if (MarketingStatusEnum.CLOSE.getValue().equals(marketing.getIsOn())) {
+            return AjaxResult.error("频繁操作,请刷新后重试");
+        }
+
+        // 关闭活动
+        marketingService.update(new LambdaUpdateWrapper<Marketing>()
+                .set(Marketing::getIsOn, MarketingStatusEnum.CLOSE.getValue())
+                .eq(Marketing::getId, id));
+
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/delete/{id}")
+    @PreAuthorize("@ss.hasPermi('business:marketing:remove')")
+    @ApiOperation("删除活动")
+    public AjaxResult delete(@PathVariable("id") Long id) {
+        Marketing marketing = marketingService.getById(id);
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动信息不存在");
+        }
+
+        if (!MarketingStatusEnum.OFF.getValue().equals(marketing.getIsOn())) {
+            return AjaxResult.error("频繁操作,请刷新后重试");
+        }
+        marketingService.deleteById(id);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/data/{id}")
+    @PreAuthorize("@ss.hasPermi('business:marketing:queryData')")
+    @ApiOperation("活动数据")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "success", response = MarketingUserCodeListVO.class)
+    )
+    public TableDataInfo dataInfo(@PathVariable("id") Long id) {
+        startPage();
+        QueryWrapper<MarketingUserCode> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("t1.marketing_id", id);
+        queryWrapper.eq("t1.user_type", UserTypeEnum.ORDINARY.getValue());
+
+        queryWrapper.orderByAsc("t4.sort");
+        queryWrapper.orderByDesc("t1.created_time");
+
+        List<MarketingUserCodeListVO> list = marketingUserCodeService.listMarketingUserCodeByQueryWrapper(queryWrapper);
+        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);
+
+
+    }
+
+}

+ 3 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserDeliverOrderMgrController.java

@@ -88,6 +88,7 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 		queryWrapper.eq(null != queryParam && StringUtils.isNotBlank(queryParam.getOrderId()), "t1.order_id", queryParam.getOrderId());
 		queryWrapper.eq(null != queryParam && null != queryParam.getStatus(), "t1.`status`", queryParam.getStatus());
 		queryWrapper.eq(null != queryParam && StringUtils.isNotBlank(queryParam.getTel()) , "t1.`tel`", queryParam.getTel());
+		queryWrapper.eq(null != queryParam && null != queryParam.getResource(), "t1.`resource`", queryParam.getResource());
 		queryWrapper.ge(null != queryParam && null != queryParam.getStartTime(), "t1.created_time", queryParam.getStartTime());
 		queryWrapper.le(null != queryParam && null != queryParam.getEndTime(), "t1.created_time", queryParam.getEndTime());
 		queryWrapper.like(null != queryParam && StringUtils.isNotBlank(queryParam.getNickName()), "t2.nick_name", queryParam.getNickName());
@@ -235,6 +236,7 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 		QueryWrapper<UserDeliverOrder> queryWrapper = new QueryWrapper<UserDeliverOrder>();
 		queryWrapper.eq(null != queryParam && StringUtils.isNotBlank(queryParam.getOrderId()), "t1.order_id", queryParam.getOrderId());
 		queryWrapper.eq(null != queryParam && null != queryParam.getStatus(), "t1.`status`", queryParam.getStatus());
+		queryWrapper.eq(null != queryParam && null != queryParam.getResource(), "t1.`resource`", queryParam.getResource());
 		queryWrapper.ge(null != queryParam && null != queryParam.getStartTime(), "t1.created_time", queryParam.getStartTime());
 		queryWrapper.le(null != queryParam && null != queryParam.getEndTime(), "t1.created_time", queryParam.getEndTime());
 		queryWrapper.like(null != queryParam && StringUtils.isNotBlank(queryParam.getNickName()), "t2.nick_name", queryParam.getNickName());
@@ -294,6 +296,7 @@ public class UserDeliverOrderMgrController extends BaseApiController {
 							deliverOrderExcel.setGoodsNum(userDeliverOrderItem.getGoodsNum());
 							deliverOrderExcel.setCreatedTime(deliverOrder.getCreatedTime());
 							deliverOrderExcel.setOrderId(deliverOrder.getOrderId());
+							deliverOrderExcel.setResource(deliverOrder.getResource().getDesc());
 							deliverOrderExcel.setMemo(deliverOrder.getMemo());
 							deliverOrderExcel.setSupplierName(userDeliverOrderItem.getSupplierName());
 							deliverOrderExcel.setShoppingLink(userDeliverOrderItem.getShoppingLink());

+ 118 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/UserPrizeRecoveryMgrController.java

@@ -0,0 +1,118 @@
+package com.qs.mp.web.controller.api.admin;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.PrizeRecovery;
+import com.qs.mp.admin.domain.excel.PrizeRecoveryExcel;
+import com.qs.mp.admin.domain.param.PrizeRecoveryListQueryParam;
+import com.qs.mp.admin.domain.vo.PrizeRecoveryListVO;
+import com.qs.mp.admin.service.IPrizeRecoveryService;
+import com.qs.mp.common.core.domain.AjaxResult;
+import com.qs.mp.common.core.page.TableDataInfo;
+import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.user.domain.excel.DeliverOrderExcel;
+import com.qs.mp.user.domain.vo.UserDeliverOrderItemVO;
+import com.qs.mp.user.domain.vo.UserDeliverOrderVO;
+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.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 用户中奖商品回收相关接口
+ * @author Cup
+ * @date 2022/5/25
+ */
+@RestController
+@RequestMapping("/api/v1/mp/admin/prize/recovery")
+@Api(tags = "用户奖品回收相关接口")
+public class UserPrizeRecoveryMgrController extends BaseApiController {
+
+    @Autowired
+    private IPrizeRecoveryService prizeRecoveryService;
+
+
+    @PostMapping("/list")
+    @PreAuthorize("@ss.hasPermi('business:recovery:list')")
+    @ApiOperation("回收订单列表,支持分页")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "成功", response = PrizeRecoveryListVO.class)
+    })
+    public TableDataInfo list(@RequestBody PrizeRecoveryListQueryParam prizeRecoveryListQueryParam) {
+        startPage();
+
+        QueryWrapper<PrizeRecovery> queryWrapper = new QueryWrapper<>();
+        if (StringUtils.isNotBlank(prizeRecoveryListQueryParam.getTitle())) {
+            queryWrapper.like("t1.title",prizeRecoveryListQueryParam.getTitle());
+        }
+        if (StringUtils.isNotBlank(prizeRecoveryListQueryParam.getUserPhone())) {
+            queryWrapper.eq("t2.phonenumber", prizeRecoveryListQueryParam.getUserPhone());
+        }
+        if (Objects.nonNull(prizeRecoveryListQueryParam.getStartTime()) && Objects.nonNull(prizeRecoveryListQueryParam.getEndTime())) {
+            queryWrapper.between("t1.created_time", prizeRecoveryListQueryParam.getStartTime(), prizeRecoveryListQueryParam.getEndTime());
+        }
+        queryWrapper.orderByDesc("t1.created_time");
+        List<PrizeRecoveryListVO> list =  prizeRecoveryService.listByWrapper(queryWrapper);
+
+        return getDataTable(list);
+    }
+
+    @PostMapping("/export")
+    @PreAuthorize("@ss.hasPermi('business:recovery:export')")
+    @ApiOperation("回收订单列表导出")
+    public AjaxResult export(@RequestBody PrizeRecoveryListQueryParam prizeRecoveryListQueryParam) {
+        QueryWrapper<PrizeRecovery> queryWrapper = new QueryWrapper<>();
+        if (StringUtils.isNotBlank(prizeRecoveryListQueryParam.getTitle())) {
+            queryWrapper.like("t1.title",prizeRecoveryListQueryParam.getTitle());
+        }
+        if (StringUtils.isNotBlank(prizeRecoveryListQueryParam.getUserPhone())) {
+            queryWrapper.eq("t2.phonenumber", prizeRecoveryListQueryParam.getUserPhone());
+        }
+        if (Objects.nonNull(prizeRecoveryListQueryParam.getStartTime()) && Objects.nonNull(prizeRecoveryListQueryParam.getEndTime())) {
+            queryWrapper.between("t1.created_time", prizeRecoveryListQueryParam.getStartTime(), prizeRecoveryListQueryParam.getEndTime());
+        }
+        queryWrapper.orderByDesc("t1.created_time");
+        int totalSize =  prizeRecoveryService.countByWrapper(queryWrapper);
+        if (totalSize == 0) {
+            return AjaxResult.error("没有符合条件的用户提货订单");
+        }
+
+        List<PrizeRecoveryExcel> excelList = new ArrayList<>();
+        List<PrizeRecoveryListVO> listAll = new ArrayList<>();
+
+        int pageSize = 2000;
+        if (totalSize > pageSize) {
+            int totalPage = totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1;
+            for (int i = 0; i < totalPage; i++) {
+                queryWrapper.last("limit " + (i * pageSize) + ", " + pageSize);
+                List<PrizeRecoveryListVO> prizeRecoveryListVOS = prizeRecoveryService.listByWrapper(queryWrapper);
+                if (null != prizeRecoveryListVOS && prizeRecoveryListVOS.size() > 0) {
+                    listAll.addAll(prizeRecoveryListVOS);
+                }
+            }
+        } else {
+            listAll = prizeRecoveryService.listByWrapper(queryWrapper);
+        }
+        for (PrizeRecoveryListVO prizeRecoveryListVO : listAll) {
+            PrizeRecoveryExcel prizeRecoveryExcel = new PrizeRecoveryExcel();
+            BeanUtils.copyProperties(prizeRecoveryListVO, prizeRecoveryExcel);
+            excelList.add(prizeRecoveryExcel);
+        }
+
+        ExcelUtil<PrizeRecoveryExcel> util = new ExcelUtil<>(PrizeRecoveryExcel.class);
+        return util.exportExcel(excelList, "商品回收订单导出", false);
+    }
+
+}

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

@@ -0,0 +1,270 @@
+package com.qs.mp.web.controller.api.user;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.vo.MarketingAwardsVO;
+import com.qs.mp.admin.service.IMarketingAwardsPrizeService;
+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.system.domain.SysUser;
+import com.qs.mp.system.service.ISysUserService;
+import com.qs.mp.user.domain.MarketingHitPrize;
+import com.qs.mp.user.domain.MarketingUserCode;
+import com.qs.mp.user.domain.param.MarketingHelpParam;
+import com.qs.mp.user.domain.param.UserMarketingQueryParam;
+import com.qs.mp.user.domain.vo.UserMarketingDetailVO;
+import com.qs.mp.user.domain.vo.UserMarketingListVO;
+import com.qs.mp.user.service.IMarketingHitPrizeService;
+import com.qs.mp.user.service.IMarketingUserCodeService;
+import com.qs.mp.utils.MarketingUtils;
+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.aspectj.weaver.loadtime.Aj;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+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;
+
+/**
+ * @author Cup
+ * @date 2022/5/17
+ */
+@RestController
+@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;
+
+    @Autowired
+    private IMarketingHitPrizeService marketingHitPrizeService;
+
+    @Autowired
+    private IMarketingAwardsService marketingAwardsService;
+
+    @Autowired
+    private IMarketingAwardsPrizeService marketingAwardsPrizeService;
+
+    @Autowired
+    private IMarketingUserCodeService marketingUserCodeService;
+
+    @Autowired
+    private HostHolder hostHolder;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @PostMapping("/userInfo/{inviteCode}")
+    @ApiOperation("通过邀请码获取用户信息")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = SysUser.class)
+    )
+    public AjaxResult userInfoByInviteCode(@PathVariable("inviteCode") String inviteCode) {
+        if (StringUtils.isBlank(inviteCode)) {
+            return AjaxResult.error("邀请码不能为空");
+        }
+
+        Long userId = MarketingUtils.decodeInviteCode(inviteCode);
+        SysUser sysUser = sysUserService.selectUserById(userId);
+        if (Objects.isNull(sysUser)) {
+            return AjaxResult.error("邀请码无效");
+        }
+        return AjaxResult.success(sysUser);
+    }
+
+    @PostMapping("/help")
+    @ApiOperation("活动助力")
+    public AjaxResult help(@RequestBody MarketingHelpParam marketingHelpParam) {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        if (Objects.isNull(userId)) {
+            return AjaxResult.error("用户未登录");
+        }
+
+        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)) {
+            return AjaxResult.error("活动太火爆了,请稍后重试!");
+        }
+
+        try {
+            marketingService.help(marketingHelpParam, userId);
+        }finally {
+            distributedLocker.unlock(lockKey);
+        }
+
+
+        return AjaxResult.success("助力成功");
+    }
+
+
+    @PostMapping("/inviteCode")
+    @ApiOperation(value = "邀请码生成", notes = "邀请码生成")
+    public AjaxResult inviteCode() {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        if (Objects.isNull(userId)) {
+            return AjaxResult.error("用户未登录");
+        }
+        return AjaxResult.success("邀请码生成成功",MarketingUtils.generateInviteCode(userId));
+    }
+
+
+    @PostMapping("/list")
+    @ApiOperation("获取营销活动列表")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = UserMarketingListVO.class)
+    )
+    public TableDataInfo list(@Validated @RequestBody UserMarketingQueryParam userMarketingQueryParam) {
+        // 获取用户 id
+        Long userId = 0L;
+
+        if (hostHolder.getUser() != null) {
+            userId = hostHolder.getUser().getUserId();
+        }
+
+        startPage();
+        LambdaQueryWrapper<Marketing> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue());
+        queryWrapper.eq(Marketing::getTriggerStatus, userMarketingQueryParam.getTriggerStatus());
+        if (userMarketingQueryParam.getTriggerStatus() == 0) {
+            // 已开启的列表
+            Date now = DateUtils.getNowDate();
+            // 结束时间大于当前时间的
+            queryWrapper.gt(Marketing::getEndTime, now);
+            queryWrapper.orderByAsc(Marketing::getStartTime);
+            queryWrapper.orderByAsc(Marketing::getEndTime);
+        }else if(userMarketingQueryParam.getTriggerStatus() == 1) {
+            // 开奖的排序
+            queryWrapper.orderByDesc(Marketing::getEndTime);
+        }
+        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, finalUserId));
+                    userMarketingListVO.setIsHit(count);
+                }
+                return userMarketingListVO;
+            }).collect(Collectors.toList());
+        }
+
+        return getDataTable(userMarketingListVOList);
+    }
+
+    @PostMapping("/detail/{id}")
+    @ApiOperation("活动详情")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = UserMarketingDetailVO.class)
+    )
+    public AjaxResult detail(@PathVariable("id") Long id) {
+        // 获取用户 id
+        Long userId = 0L;
+
+        if (hostHolder.getUser() != null) {
+            userId = hostHolder.getUser().getUserId();
+        }
+
+        Marketing marketing = marketingService.getById(id);
+        if (Objects.isNull(marketing)) {
+            return AjaxResult.error("活动不存在");
+        }
+
+        UserMarketingDetailVO userMarketingDetailVO = new UserMarketingDetailVO();
+        BeanUtils.copyProperties(marketing, userMarketingDetailVO);
+
+        List<MarketingAwards> awardsList = marketingAwardsService.list(new LambdaQueryWrapper<MarketingAwards>().eq(MarketingAwards::getMarketingId, id));
+        if (CollectionUtils.isNotEmpty(awardsList)) {
+            List<MarketingAwardsVO> list = awardsList.stream().map(marketingAwards -> {
+                MarketingAwardsVO marketingAwardsVO = new MarketingAwardsVO();
+                BeanUtils.copyProperties(marketingAwards, marketingAwardsVO);
+
+                // 获取奖品信息
+                List<MarketingAwardsPrize> prizeList = marketingAwardsPrizeService.list(new LambdaQueryWrapper<MarketingAwardsPrize>()
+                        .eq(MarketingAwardsPrize::getMarketingId, id)
+                        .eq(MarketingAwardsPrize::getAwardsId, marketingAwards.getId()));
+                marketingAwardsVO.setPrizeList(prizeList);
+                return marketingAwardsVO;
+            }).collect(Collectors.toList());
+
+            userMarketingDetailVO.setAwardsList(list);
+        }
+
+        // 设置抽奖码列表
+        List<MarketingUserCode> codeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+                .select(MarketingUserCode::getCode)
+                .eq(MarketingUserCode::getMarketingId, id)
+                .eq(MarketingUserCode::getUserId, userId));
+
+        if (CollectionUtils.isNotEmpty(codeList)) {
+            userMarketingDetailVO.setCodeList(codeList.stream().map(MarketingUserCode::getCode).collect(Collectors.toList()));
+        }
+
+        return AjaxResult.success(userMarketingDetailVO);
+    }
+
+
+
+}

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

@@ -0,0 +1,110 @@
+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());
+                userMarketingHitPrizeDetailVO.setPrizeType(userMarketingHitPrizeListVO.getPrizeType());
+                userMarketingHitPrizeDetailVO.setValue(userMarketingHitPrizeListVO.getValue());
+            }
+        }
+        return AjaxResult.success(userMarketingHitPrizeDetailVO);
+    }
+
+}

+ 58 - 6
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserDeliverOrderController.java

@@ -4,7 +4,11 @@ import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.NumberUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.GoodsSku;
 import com.qs.mp.admin.domain.vo.ShippingTemplateCalculateVO;
+import com.qs.mp.admin.service.IGoodsService;
+import com.qs.mp.admin.service.IGoodsSkuService;
 import com.qs.mp.channel.domain.ChannelOrder;
 import com.qs.mp.channel.domain.param.OrderPayParam;
 import com.qs.mp.common.core.domain.AjaxResult;
@@ -24,6 +28,7 @@ import com.qs.mp.system.domain.SysUser;
 import com.qs.mp.system.service.ISysUserService;
 import com.qs.mp.user.domain.*;
 import com.qs.mp.user.domain.param.DeliverOrderParam;
+import com.qs.mp.user.domain.param.UserDeliverOrderCancelParam;
 import com.qs.mp.user.domain.vo.DeliverOrderSettleVO;
 import com.qs.mp.user.domain.vo.DeliverOrderVO;
 import com.qs.mp.user.service.IUserAddrService;
@@ -90,6 +95,12 @@ public class UserDeliverOrderController extends BaseApiController {
   @Autowired
   private IShippingTemplateService shippingTemplateService;
 
+  @Autowired
+  private IGoodsService goodsService;
+
+  @Autowired
+  private IGoodsSkuService goodsSkuService;
+
   @Value("${shipping.user}")
   public Long shippingTemplateId;
 
@@ -98,6 +109,9 @@ public class UserDeliverOrderController extends BaseApiController {
    */
   @PostMapping("/order/list")
   @ApiOperation(value = "订单列表" , notes = "获取所有订单信息")
+  @ApiResponses(
+          @ApiResponse(code = 200, message = "success", response = DeliverOrderVO.class)
+  )
   public TableDataInfo list(@RequestBody JSONObject param) {
     Long userId = SecurityUtils.getLoginUser().getUserId();
     String status = param.getString("status");
@@ -175,9 +189,48 @@ public class UserDeliverOrderController extends BaseApiController {
     Integer orderAmt = 0;
     Integer freightAmt = 0;
 
-    List<UserPrizeStorage> prizeStorageList = userPrizeStorageService.list(new LambdaQueryWrapper<UserPrizeStorage>()
-        .eq(UserPrizeStorage::getUserId, userId).eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED)
-        .in(UserPrizeStorage::getStorageId, param.getIds()));
+
+    // 商品数量
+    int goodsNum = 0;
+    List<UserPrizeStorage> prizeStorageList = new ArrayList<>();
+    if (DeliverOrderResourceEnum.PAYMENT.equals(param.getResource())) {
+      // 现金支付,设置金额信息
+      if (Objects.isNull(param.getGoodsId()) || Objects.isNull(param.getNum())) {
+        return AjaxResult.error("商品信息不能为空");
+      }
+      goodsNum = param.getNum();
+      // 获取商品信息
+      Goods goods = goodsService.getById(param.getGoodsId());
+      GoodsSku goodsSku = goodsSkuService.getById(param.getSkuId());
+      if (Objects.isNull(goods)) {
+        return AjaxResult.error("商品不存在");
+      }
+      // 封装商品信息出参
+      UserPrizeStorage prizeStorage = new UserPrizeStorage();
+      prizeStorage.setGoodsId(goods.getGoodsId());
+      prizeStorage.setPicUrl(goods.getPicUrl());
+      prizeStorage.setTitle(goods.getTitle());
+      prizeStorage.setGoodsNum(goodsNum);
+      if (Objects.nonNull(goodsSku)) {
+        prizeStorage.setPicUrl(goodsSku.getPicUrl());
+        prizeStorage.setSkuId(goodsSku.getSkuId());
+        prizeStorage.setProperties(goodsSku.getProperties());
+
+        orderAmt = goodsNum * goodsSku.getValue();
+      }else {
+        orderAmt = goodsNum * goods.getValue();
+
+      }
+
+      prizeStorageList.add(prizeStorage);
+      orderSettleVO.setResource(DeliverOrderResourceEnum.PAYMENT.getValue());
+    }else {
+      prizeStorageList = userPrizeStorageService.list(new LambdaQueryWrapper<UserPrizeStorage>()
+              .eq(UserPrizeStorage::getUserId, userId).eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED)
+              .in(UserPrizeStorage::getStorageId, param.getIds()));
+      goodsNum = prizeStorageList.stream().mapToInt(UserPrizeStorage::getGoodsNum).sum();
+      orderSettleVO.setResource(DeliverOrderResourceEnum.DELIVER.getValue());
+    }
 
 
     // 计算运费
@@ -192,7 +245,6 @@ public class UserDeliverOrderController extends BaseApiController {
         orderSettleVO.setFreightType(shippingTemplateCalculateVO.getType());
 
         if (ShippingTemplateRuleTypeEnum.SHIP.getValue().equals(shippingTemplateCalculateVO.getType())) {
-          int goodsNum = prizeStorageList.stream().mapToInt(UserPrizeStorage::getGoodsNum).sum();
           // 计算运费
           freightAmt = shippingTemplateService.calculateFreightAmt(goodsNum,shippingTemplateCalculateVO);
         }
@@ -245,8 +297,8 @@ public class UserDeliverOrderController extends BaseApiController {
    */
   @PostMapping("/order/cancel")
   @ApiOperation(value = "取消订单" , notes = "在订单列表页面取消")
-  public AjaxResult cancel(@RequestBody ChannelOrder order) {
-    userDeliverOrderService.cancelOrder(order.getOrderId());
+  public AjaxResult cancel(@RequestBody UserDeliverOrderCancelParam param) {
+    userDeliverOrderService.cancelOrder(param.getOrderId());
     return AjaxResult.success("取消成功");
   }
 

+ 85 - 26
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserExchangeController.java

@@ -10,6 +10,7 @@ import com.qs.mp.admin.domain.GoodsCategory;
 import com.qs.mp.admin.domain.GoodsSku;
 import com.qs.mp.admin.domain.GoodsTag;
 import com.qs.mp.admin.domain.GoodsTagRel;
+import com.qs.mp.admin.domain.vo.GoodsCategoryTreeVO;
 import com.qs.mp.admin.domain.vo.GoodsListVO;
 import com.qs.mp.admin.domain.vo.GoodsVO;
 import com.qs.mp.admin.service.IExchangeBannerService;
@@ -23,16 +24,15 @@ import com.qs.mp.common.core.page.TableDataInfo;
 import com.qs.mp.common.enums.ErrorCodeEnum;
 import com.qs.mp.common.enums.GoodsStatusEnum;
 import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.user.domain.param.ExchangeGoodsListQueryParam;
 import com.qs.mp.user.domain.param.ExchangeOrderParam;
 import com.qs.mp.user.service.IUserExchangeOrderService;
 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 java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
+
+import java.util.*;
 import java.util.stream.Collectors;
 
 import io.swagger.annotations.ApiResponse;
@@ -79,43 +79,101 @@ public class UserExchangeController extends BaseApiController {
   @Autowired
   private MapperFacade mapperFacade;
 
+  @PostMapping("/category/listTree")
+  @ApiOperation("商品分类树状列表")
+  @ApiResponses(
+          @ApiResponse(code = 200, message = "success" , response = GoodsCategoryTreeVO.class)
+  )
+  public TableDataInfo listTree(@RequestBody JSONObject param) {
+    startPage();
+
+    // 获取父分类
+    List<GoodsCategory> categoryList = goodsCategoryService.list(
+            new LambdaQueryWrapper<GoodsCategory>()
+                    .eq(GoodsCategory::getParentId, 0)
+                    .eq(GoodsCategory::getIsShow, 1)
+                    .orderByDesc(GoodsCategory::getSort));
+    if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(categoryList)) {
+      return getErrorDataTable("分类信息不存在");
+    }
+
+    List<GoodsCategoryTreeVO> treeList =  categoryList.stream().map(goodsCategory -> {
+      GoodsCategoryTreeVO goodsCategoryTreeVO = new GoodsCategoryTreeVO();
+      BeanUtils.copyProperties(goodsCategory, goodsCategoryTreeVO);
+      // 获取子分类
+      List<GoodsCategory> list = goodsCategoryService.list(
+              new LambdaQueryWrapper<GoodsCategory>()
+                      .eq(GoodsCategory::getParentId, goodsCategory.getCategoryId())
+                      .eq(GoodsCategory::getIsShow, 1)
+                      .orderByDesc(GoodsCategory::getSort));
+      goodsCategoryTreeVO.setGoodsCategoryList(list);
+      return goodsCategoryTreeVO;
+    }).collect(Collectors.toList());
+
+    return getDataTable(treeList);
+  }
+
+
   /**
    * 商品列表
    */
   @PostMapping("/goods/list")
   @ApiOperation(value = "商品列表", notes = "获取所有可兑换商品")
-  @DynamicParameters(properties = {
-          @DynamicParameter(name = "categoryId",value = "分类id",dataTypeClass = Long.class),
-          @DynamicParameter(name = "tagIds",value = "标签id",dataTypeClass = String.class),
-          @DynamicParameter(name = "startPrice",value = "开始价格",dataTypeClass = Integer.class),
-          @DynamicParameter(name = "endPrice",value = "结束价格",dataTypeClass = Integer.class)
-  })
   @ApiResponses(
           @ApiResponse(code = 200, message = "success" , response = GoodsListVO.class)
   )
-  public TableDataInfo list(@RequestBody JSONObject param) {
-    Long categoryId = param.getLong("categoryId");
-    String tagIds = param.getString("tagIds");
-    Integer startPrice = param.getInteger("startPrice");
-    Integer endPrice = param.getInteger("endPrice");
+  public TableDataInfo list(@RequestBody ExchangeGoodsListQueryParam param) {
 
     List<Long> goodsIds = new ArrayList<>();
-    if (StringUtils.isNotBlank(tagIds)) {
+    if (StringUtils.isNotBlank(param.getTagIds())) {
       List<GoodsTagRel> goodsTagRelList = goodsTagRelService.list(
           new LambdaQueryWrapper<GoodsTagRel>()
-              .in(GoodsTagRel::getTagId, Arrays.asList(tagIds.split(","))));
+              .in(GoodsTagRel::getTagId, Arrays.asList(param.getTagIds().split(","))));
       goodsIds = goodsTagRelList.stream().map(GoodsTagRel::getGoodsId).collect(Collectors.toList());
     }
+    List<Long> categoryIdList = param.getCategoryIdList();
+    if (categoryIdList == null) {
+      categoryIdList = new ArrayList<>();
+    }
+    if (Objects.nonNull(param.getCategoryId()) && param.getCategoryId() != 0) {
+      List<GoodsCategory> goodsCategoryList = goodsCategoryService.list(new LambdaQueryWrapper<GoodsCategory>().eq(GoodsCategory::getParentId, param.getCategoryId()));
+      if (!CollectionUtils.isEmpty(goodsCategoryList)) {
+        List<Long> list = goodsCategoryList.stream().map(GoodsCategory::getCategoryId).collect(Collectors.toList());
+        categoryIdList.addAll(list);
+      }
+      categoryIdList.add(param.getCategoryId());
+    }
     startPage();
-    List<Goods> goodsList = goodsService.list(new LambdaQueryWrapper<Goods>()
-        .eq(null != categoryId && 0 != categoryId, Goods::getCategoryId, categoryId)
-        .ge(null != startPrice && 0 != startPrice, Goods::getExchangePrice, startPrice)
-        .le(null != endPrice && 0 != endPrice, Goods::getExchangePrice, endPrice)
-        .eq(Goods::getStatus, GoodsStatusEnum.PUT_ON)
-        .eq(Goods::getExchangeShow, 1)
-        .in(!CollectionUtils.isEmpty(goodsIds), Goods::getGoodsId, goodsIds)
-        .orderByDesc(Goods::getSortWeight)
-        .orderByAsc(Goods::getExchangePrice));
+    LambdaQueryWrapper<Goods> queryWrapper = new LambdaQueryWrapper<Goods>()
+            .ge(null != param.getStartPrice() && 0 != param.getStartPrice(), Goods::getExchangePrice, param.getStartPrice())
+            .le(null != param.getEndPrice() && 0 != param.getEndPrice(), Goods::getExchangePrice, param.getEndPrice())
+            .in(!CollectionUtils.isEmpty(categoryIdList), Goods::getCategoryId, categoryIdList)
+            .like(StringUtils.isNotBlank(param.getGoodsName()), Goods::getTitle, param.getGoodsName())
+            .eq(Goods::getStatus, GoodsStatusEnum.PUT_ON)
+            .eq(Goods::getExchangeShow, 1)
+            .in(!CollectionUtils.isEmpty(goodsIds), Goods::getGoodsId, goodsIds);
+
+    if (Objects.isNull(param.getSaleSort()) && Objects.isNull(param.getPriceSort())){
+      queryWrapper.orderByDesc(Goods::getSortWeight)
+              .orderByAsc(Goods::getExchangePrice);
+    }
+    if (Objects.nonNull(param.getSaleSort())) {
+      if (param.getSaleSort() == 1) {
+        queryWrapper.orderByAsc(Goods::getExchangedQty);
+      } else if (param.getSaleSort() == 2) {
+        queryWrapper.orderByDesc(Goods::getExchangedQty);
+      }
+    }
+
+    if (Objects.nonNull(param.getPriceSort())){
+      if (param.getPriceSort() == 1) {
+        queryWrapper.orderByAsc(Goods::getExchangePrice);
+      }else if (param.getPriceSort() == 2) {
+        queryWrapper.orderByDesc(Goods::getExchangePrice);
+      }
+    }
+
+    List<Goods> goodsList = goodsService.list(queryWrapper);
     List<GoodsListVO> goodsListVOList = mapperFacade.mapAsList(goodsList, GoodsListVO.class);
     TableDataInfo rspData = getDataTable(goodsList);
     rspData.setRows(goodsListVOList);
@@ -189,6 +247,7 @@ public class UserExchangeController extends BaseApiController {
   public AjaxResult listCategory(@RequestBody JSONObject param) {
     List<GoodsCategory> categoryList = goodsCategoryService.list(
         new LambdaQueryWrapper<GoodsCategory>()
+                .eq(GoodsCategory::getParentId, 0)
             .eq(GoodsCategory::getIsShow, 1).orderByDesc(GoodsCategory::getSort));
     return AjaxResult.success(categoryList);
   }

+ 17 - 6
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/UserMineController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qs.mp.admin.domain.Ticket;
 import com.qs.mp.admin.domain.vo.TicketListVO;
+import com.qs.mp.admin.service.IGoodsService;
 import com.qs.mp.common.core.domain.AjaxResult;
 import com.qs.mp.common.core.page.TableDataInfo;
 import com.qs.mp.common.enums.PrizeStorageStatusEnum;
@@ -17,7 +18,9 @@ import com.qs.mp.user.domain.UserCoin;
 import com.qs.mp.user.domain.UserCoinLog;
 import com.qs.mp.user.domain.UserCoupon;
 import com.qs.mp.user.domain.UserPrizeStorage;
+import com.qs.mp.user.domain.param.UserPrizeStorageQueryParam;
 import com.qs.mp.user.domain.vo.UserCouponVO;
+import com.qs.mp.user.domain.vo.UserPrizeStorageVO;
 import com.qs.mp.user.service.IUserCoinLogService;
 import com.qs.mp.user.service.IUserCoinService;
 import com.qs.mp.user.service.IUserCouponService;
@@ -27,6 +30,8 @@ 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 java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -34,6 +39,7 @@ import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import lombok.AllArgsConstructor;
 import ma.glasnost.orika.MapperFacade;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -67,6 +73,9 @@ public class UserMineController extends BaseApiController {
   @Autowired
   private MapperFacade mapperFacade;
 
+  @Autowired
+  private IGoodsService goodsService;
+
   /**
    * 我的票包
    */
@@ -90,7 +99,7 @@ public class UserMineController extends BaseApiController {
         }
         // 如果为盲豆需要拼接数量
         if (TicketPrizeTypeEnum.COIN.getValue().equals(ticketListVO.getPrizeType())) {
-          ticketListVO.setPrizeInfo(ticketListVO.getPrizeInfo() + ticketListVO.getValue() + "");
+          ticketListVO.setPrizeInfo(ticketListVO.getPrizeInfo() + ticketListVO.getValue() + "");
         }
       }
     }
@@ -144,18 +153,20 @@ public class UserMineController extends BaseApiController {
    */
   @PostMapping("/prize/list")
   @ApiOperation(value = "我的实物奖品库", notes = "获取我的实物奖品库")
+  @ApiResponses(
+          @ApiResponse(code = 200, message = "成功", response = UserPrizeStorage.class)
+  )
   public TableDataInfo listPrize(@RequestBody JSONObject param) {
     Long userId = SecurityUtils.getLoginUser().getUserId();
     startPage();
     List<UserPrizeStorage> userPrizeStorageList = userPrizeStorageService.list(
-        new LambdaQueryWrapper<UserPrizeStorage>()
-            .eq(UserPrizeStorage::getUserId, userId)
-            .eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED)
-            .orderByDesc(UserPrizeStorage::getCreatedTime));
+            new LambdaQueryWrapper<UserPrizeStorage>()
+                    .eq(UserPrizeStorage::getUserId, userId)
+                    .eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED)
+                    .orderByDesc(UserPrizeStorage::getCreatedTime));
     return getDataTable(userPrizeStorageList);
   }
 
-
   /**
    * 我的
    */

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

@@ -0,0 +1,178 @@
+package com.qs.mp.web.controller.api.user;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.GoodsSku;
+import com.qs.mp.admin.domain.PrizeRecovery;
+import com.qs.mp.admin.service.IGoodsService;
+import com.qs.mp.admin.service.IGoodsSkuService;
+import com.qs.mp.admin.service.IPrizeRecoveryService;
+import com.qs.mp.common.core.domain.AjaxResult;
+import com.qs.mp.common.core.page.TableDataInfo;
+import com.qs.mp.common.core.redis.RedisCache;
+import com.qs.mp.common.enums.PrizeStorageInTypeEnum;
+import com.qs.mp.common.enums.PrizeStorageStatusEnum;
+import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.framework.redis.RedisKey;
+import com.qs.mp.user.domain.UserExchangeOrder;
+import com.qs.mp.user.domain.UserPrizeStorage;
+import com.qs.mp.user.domain.param.UserPrizeRecoveryCreateParam;
+import com.qs.mp.user.domain.vo.PrizeRecoverySettleVO;
+import com.qs.mp.user.domain.vo.TicketOrderSettleVO;
+import com.qs.mp.user.service.IUserExchangeOrderService;
+import com.qs.mp.user.service.IUserPrizeStorageService;
+import com.qs.mp.utils.SecurityUtils;
+import com.qs.mp.web.controller.common.BaseApiController;
+import io.swagger.annotations.*;
+import org.aspectj.weaver.loadtime.Aj;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Cup
+ * @date 2022/5/25
+ */
+@RestController
+@RequestMapping("/api/v1/mp/user/prize/recovery")
+@Api(tags = "用户奖品回收相关接口")
+public class UserPrizeRecoveryController extends BaseApiController {
+
+    @Autowired
+    private IPrizeRecoveryService prizeRecoveryService;
+
+    @Autowired
+    private IGoodsService goodsService;
+
+    @Autowired
+    private IGoodsSkuService goodsSkuService;
+
+    @Autowired
+    private IUserPrizeStorageService userPrizeStorageService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private IUserExchangeOrderService userExchangeOrderService;
+
+    @PostMapping("/list")
+    @ApiOperation("已回收列表")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = PrizeRecovery.class)
+    )
+    public TableDataInfo list() {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        startPage();
+        List<PrizeRecovery> list = prizeRecoveryService.list(new LambdaQueryWrapper<PrizeRecovery>().eq(PrizeRecovery::getUserId, userId).orderByDesc(PrizeRecovery::getCreatedTime));
+        return getDataTable(list);
+    }
+
+
+    @PostMapping("/settle")
+    @ApiOperation("回收奖品结算页面")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "success", response = PrizeRecoverySettleVO.class)
+    )
+    public AjaxResult settle(@RequestBody UserPrizeRecoveryCreateParam param) {
+        if (StringUtils.isBlank(param.getStorageId()) || Objects.isNull(param.getGoodsId())) {
+            return AjaxResult.error("参数错误");
+        }
+
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+
+        PrizeRecoverySettleVO settleVO = new PrizeRecoverySettleVO();
+
+        UserPrizeStorage userPrizeStorage = userPrizeStorageService.getOne(new LambdaQueryWrapper<UserPrizeStorage>()
+                .eq(UserPrizeStorage::getStorageId, param.getStorageId())
+                .eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED)
+                .eq(UserPrizeStorage::getUserId, userId));
+        if (Objects.isNull(userPrizeStorage)) {
+            return AjaxResult.error("奖品不存在");
+        }
+
+        if (userPrizeStorage.getGoodsNum() < param.getNum() || param.getNum() <= 0) {
+            return AjaxResult.error("奖品数量非法");
+        }
+
+        if (!PrizeStorageInTypeEnum.TICKET_CASHED.equals(userPrizeStorage.getInType()) && !PrizeStorageInTypeEnum.COIN_EXCHANGE.equals(userPrizeStorage.getInType())) {
+            return AjaxResult.error("当前奖品类型不支持回收");
+        }
+
+        // 回收折扣
+        BigDecimal discountRate = new BigDecimal(70);
+        // 兑换价格
+        Integer exchangePrice = 0;
+
+        if (PrizeStorageInTypeEnum.COIN_EXCHANGE.equals(userPrizeStorage.getInType())) {
+            UserExchangeOrder userExchangeOrder = userExchangeOrderService.getOne(new LambdaQueryWrapper<UserExchangeOrder>().eq(UserExchangeOrder::getOrderId, userPrizeStorage.getRefId()));
+            if (Objects.isNull(userExchangeOrder)) {
+                return AjaxResult.error("无兑换订单信息");
+            }
+            GoodsSku goodsSku = null;
+            if (Objects.nonNull(userExchangeOrder.getSkuId())) {
+                goodsSku = goodsSkuService.getById(userExchangeOrder.getSkuId());
+            }
+            if (Objects.nonNull(goodsSku)) {
+                discountRate = goodsSku.getDiscountRate();
+            } else {
+                Goods goods = goodsService.getById(userPrizeStorage.getGoodsId());
+                discountRate = goods.getDiscountRate();
+            }
+            exchangePrice = BigDecimal.valueOf(userExchangeOrder.getOrderCoin()).divide(BigDecimal.valueOf(userExchangeOrder.getNum()),0, RoundingMode.HALF_UP).intValue();
+        } else if (PrizeStorageInTypeEnum.TICKET_CASHED.equals(userPrizeStorage.getInType())) {
+            Goods goods = goodsService.getById(userPrizeStorage.getGoodsId());
+            if (Objects.isNull(goods)) {
+                return AjaxResult.error("商品信息有误");
+            }
+            discountRate = goods.getDiscountRate();
+            exchangePrice = goods.getExchangePrice();
+        }
+
+        int returnNum = BigDecimal.valueOf((long) exchangePrice * param.getNum()).multiply(discountRate).divide(BigDecimal.valueOf(100),0, RoundingMode.HALF_UP).intValue();
+
+        settleVO.setReturnCoin(returnNum);
+        // 封装结算对象
+        settleVO.setExchangePrice(exchangePrice);
+        settleVO.setStorageId(userPrizeStorage.getStorageId());
+        settleVO.setGoodsId(userPrizeStorage.getGoodsId());
+        settleVO.setTitle(userPrizeStorage.getTitle());
+        settleVO.setPicUrl(userPrizeStorage.getPicUrl());
+        settleVO.setSkuId(param.getSkuId());
+        settleVO.setProperties(param.getProperties());
+        settleVO.setNum(param.getNum());
+        settleVO.setGoodsNum(userPrizeStorage.getGoodsNum());
+        settleVO.setDiscountRate(discountRate);
+
+        // 缓存回收结算对象
+        redisCache.setCacheObject(RedisKey.build(RedisKey.USER_PRIZE_RECOVERY_KEY, userId), settleVO, 10, TimeUnit.MINUTES);
+        return AjaxResult.success(settleVO);
+    }
+
+    @PostMapping("/submit")
+    @ApiOperation("提交回收")
+    public AjaxResult submit() {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+
+        PrizeRecoverySettleVO settleVO = redisCache.getCacheObject(RedisKey.build(RedisKey.USER_PRIZE_RECOVERY_KEY, userId));
+        if (null == settleVO) {
+            return AjaxResult.error("订单已过期,请重新下单");
+        }
+
+        prizeRecoveryService.submitRecovery(userId, settleVO);
+
+        // 清除缓存的回收订单
+        redisCache.deleteObject(RedisKey.build(RedisKey.USER_PRIZE_RECOVERY_KEY, userId));
+
+        return AjaxResult.success();
+    }
+
+}

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

@@ -6,29 +6,25 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.github.xiaoymin.knife4j.annotations.DynamicParameter;
 import com.github.xiaoymin.knife4j.annotations.DynamicResponseParameters;
-import com.qs.mp.admin.domain.Ticket;
-import com.qs.mp.admin.domain.TicketAwardsPrize;
-import com.qs.mp.admin.domain.TicketBox;
-import com.qs.mp.admin.domain.TicketPackage;
+import com.qs.mp.admin.domain.*;
 import com.qs.mp.admin.domain.param.TicketBoxParam;
 import com.qs.mp.admin.domain.param.TicketParam;
-import com.qs.mp.admin.domain.vo.TicketBoxListVO;
-import com.qs.mp.admin.domain.vo.TicketBoxVO;
-import com.qs.mp.admin.domain.vo.TicketListVO;
-import com.qs.mp.admin.domain.vo.TicketVO;
-import com.qs.mp.admin.service.ITicketAwardsPrizeService;
-import com.qs.mp.admin.service.ITicketBoxService;
-import com.qs.mp.admin.service.ITicketPackageService;
-import com.qs.mp.admin.service.ITicketService;
+import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.admin.service.*;
 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.*;
+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.StringUtils;
+import com.qs.mp.framework.redis.RedisLockKey;
 import com.qs.mp.framework.security.handle.HostHolder;
 import com.qs.mp.user.domain.UserHitPrize;
 import com.qs.mp.user.domain.UserTicketOrderItem;
+import com.qs.mp.user.domain.param.TicketAutoCashPrizeParam;
+import com.qs.mp.user.domain.vo.TicketAwardsLabelVO;
 import com.qs.mp.user.domain.vo.TicketHitPrizeBarrageVO;
 import com.qs.mp.user.service.IUserExchangeOrderService;
 import com.qs.mp.user.service.IUserHitPrizeService;
@@ -39,6 +35,8 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 
 import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.*;
@@ -48,6 +46,7 @@ import io.swagger.annotations.ApiResponses;
 import lombok.AllArgsConstructor;
 import ma.glasnost.orika.MapperFacade;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.ws.rs.POST;
@@ -88,6 +87,12 @@ public class UserTicketController extends BaseApiController {
   @Autowired
   private IUserExchangeOrderService userExchangeOrderService;
 
+  @Autowired
+  private ITicketAwardsService ticketAwardsService;
+
+  @Autowired
+  private DistributedLocker distributedLocker;
+
   /**
    * 盲票进货列表
    */
@@ -111,21 +116,77 @@ public class UserTicketController extends BaseApiController {
   }
 
 
-  /**
-   * 查看盲票详情
-   */
-  @PostMapping("/mall/ticket/detail")
-  @ApiOperation(value = "查看盲票详情", notes = "根据盲票组ID,获取盲票信息")
-  @ApiResponses(
-          @ApiResponse(code = 200, message = "成功", response = TicketBoxVO.class)
-  )
-  public AjaxResult getInfo(@RequestBody TicketBoxParam param) {
-    if (StringUtils.isBlank(param.getBoxId())) {
-      return AjaxResult.error("参数异常,盲票组ID缺失");
+    /**
+     * 查看盲票详情
+     */
+    @PostMapping("/mall/ticket/detail")
+    @ApiOperation(value = "查看盲票详情", notes = "根据盲票组ID,获取盲票信息")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "成功", response = TicketBoxVO.class)
+    )
+    public AjaxResult getInfo(@RequestBody TicketBoxParam param) {
+      if (StringUtils.isBlank(param.getBoxId())) {
+        return AjaxResult.error("参数异常,盲票组ID缺失");
+      }
+      TicketBox ticketBox = ticketBoxService.getById(param.getBoxId());
+      TicketBoxVO ticketBoxVO = mapperFacade.map(ticketBox, TicketBoxVO.class);
+      // 设置奖项标签信息
+      QueryWrapper<TicketAwards> queryWrapper = new QueryWrapper<>();
+      queryWrapper.eq("box_id",ticketBox.getBoxId());
+      queryWrapper.groupBy("awards_label");
+      StringBuilder sortField = new StringBuilder("FIELD(awards_label,'");
+      int index = 0;
+      for (AwardsLabelEnum awardsLabelEnum : AwardsLabelEnum.values()) {
+        index++;
+        if (index == AwardsLabelEnum.values().length) {
+          sortField.append(awardsLabelEnum.getValue()).append("'");
+        }else {
+          sortField.append(awardsLabelEnum.getValue()).append("','");
+        }
+      }
+      sortField.append(")");
+      queryWrapper.orderByAsc(sortField.toString());
+      List<TicketAwardsLabelVO> ticketAwardsLabelVOS = ticketAwardsService.groupSumQuantityByQueryWrapper(queryWrapper);
+      if (CollectionUtils.isNotEmpty(ticketAwardsLabelVOS)) {
+        for (TicketAwardsLabelVO ticketAwardsLabelVO : ticketAwardsLabelVOS) {
+          AwardsLabelEnum awardsLabelEnum = AwardsLabelEnum.getByValue(ticketAwardsLabelVO.getLabelTitle());
+          if (Objects.isNull(awardsLabelEnum)) {
+            continue;
+          }
+          ticketAwardsLabelVO.setPicUrl(awardsLabelEnum.getPicUrl());
+          Integer quantity = ticketBox.getQuantity();
+          int num = ticketAwardsLabelVO.getQuantity();
+          ticketAwardsLabelVO.setHitRate(BigDecimal.valueOf(num).divide(BigDecimal.valueOf(quantity), 3, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
+        }
+      }
+
+    if (CollectionUtils.isNotEmpty(ticketAwardsLabelVOS)) {
+      // 精度问题处理
+      int maxIndex = 0;
+      BigDecimal maxHitRate = BigDecimal.valueOf(100);
+      for (int i = 0; i < ticketAwardsLabelVOS.size(); i++) {
+        TicketAwardsLabelVO ticketAwardsLabelVO = ticketAwardsLabelVOS.get(i);
+        if (BigDecimal.ZERO.equals(ticketAwardsLabelVO.getHitRate())) {
+          ticketAwardsLabelVO.setHitRate(BigDecimal.valueOf(0.1));
+        }
+        if (ticketAwardsLabelVO.getHitRate().compareTo(ticketAwardsLabelVOS.get(maxIndex).getHitRate()) > 0) {
+          maxIndex = i;
+        }
+        maxHitRate = maxHitRate.subtract(ticketAwardsLabelVO.getHitRate());
+      }
+      if (maxHitRate.compareTo(BigDecimal.ZERO) > 0) {
+        TicketAwardsLabelVO ticketAwardsLabelVO = ticketAwardsLabelVOS.get(maxIndex);
+        ticketAwardsLabelVO.setHitRate(ticketAwardsLabelVO.getHitRate().subtract(maxHitRate));
+      } else {
+        TicketAwardsLabelVO ticketAwardsLabelVO = ticketAwardsLabelVOS.get(maxIndex);
+        ticketAwardsLabelVO.setHitRate(ticketAwardsLabelVO.getHitRate().add(maxHitRate));
+      }
     }
-    TicketBox ticketBox = ticketBoxService.getById(param.getBoxId());
-    TicketBoxVO ticketBoxVO = mapperFacade.map(ticketBox, TicketBoxVO.class);
-    ticketBoxVO.setPrizeList(ticketAwardsPrizeService.listPrizeVO(ticketBox.getBoxId()));
+
+    ticketBoxVO.setTicketAwardsLabelList(ticketAwardsLabelVOS);
+
+    List<TicketAwardsPrizeVO> ticketAwardsPrizeVOS = ticketAwardsPrizeService.listPrizeVO(ticketBox.getBoxId());
+    ticketBoxVO.setPrizeList(ticketAwardsPrizeVOS);
     return AjaxResult.success(ticketBoxVO);
   }
 
@@ -224,6 +285,13 @@ public class UserTicketController extends BaseApiController {
     return AjaxResult.success(jsonObject);
   }
 
+  @PostMapping("/ticket/autoCashPrize")
+  @ApiOperation("自动开奖")
+  public AjaxResult autoCashPrize(@Validated @RequestBody TicketAutoCashPrizeParam param) {
+    Long userId = SecurityUtils.getLoginUser().getUserId();
+    return AjaxResult.success(userHitPrizeService.autoCashPrize(userId, param));
+  }
+
   /**
    * 兑奖
    */
@@ -247,7 +315,18 @@ public class UserTicketController extends BaseApiController {
       return AjaxResult.error(ErrorCodeEnum.ERROR_CODE_1019);
     }
 
-    userHitPrizeService.cashPrize(ticket, userId, param.getAwardsId(), param.getPrizeId());
+    // 兑奖
+    String lockKey = RedisLockKey.build(RedisLockKey.USER_TICKET_CASH_LOCK, param.getPrizeId());
+    if (!distributedLocker.tryLock(lockKey)) {
+      return AjaxResult.error("系统繁忙,请稍后再试");
+    }
+    try {
+      // 兑奖
+      userHitPrizeService.cashPrize(ticket, userId, param.getAwardsId(), param.getPrizeId());
+    } finally {
+      distributedLocker.unlock(lockKey);
+    }
+
     return AjaxResult.success();
   }
 
@@ -305,7 +384,7 @@ public class UserTicketController extends BaseApiController {
         for (int i = 0; i < ticketHitPrizeBarrageVOList.size(); i++) {
           // 类型为盲豆则拼接数量
           if(TicketPrizeTypeEnum.COIN.getValue().equals(ticketHitPrizeBarrageVOList.get(i).getPrizeType())){
-            ticketHitPrizeBarrageVOList.get(i).setPrizeInfo(ticketHitPrizeBarrageVOList.get(i).getPrizeInfo() + ticketHitPrizeBarrageVOList.get(i).getValue() + "");
+            ticketHitPrizeBarrageVOList.get(i).setPrizeInfo(ticketHitPrizeBarrageVOList.get(i).getPrizeInfo() + ticketHitPrizeBarrageVOList.get(i).getValue() + "");
           }
 
           // 设置类型为中奖
@@ -347,7 +426,7 @@ public class UserTicketController extends BaseApiController {
     for (int i = 0; i < ticketHitPrizeBarrageVOList.size(); i++) {
       // 类型为盲豆则拼接数量
       if(TicketPrizeTypeEnum.COIN.getValue().equals(ticketHitPrizeBarrageVOList.get(i).getPrizeType())){
-        ticketHitPrizeBarrageVOList.get(i).setPrizeInfo(ticketHitPrizeBarrageVOList.get(i).getPrizeInfo() + ticketHitPrizeBarrageVOList.get(i).getValue() + "");
+        ticketHitPrizeBarrageVOList.get(i).setPrizeInfo(ticketHitPrizeBarrageVOList.get(i).getPrizeInfo() + ticketHitPrizeBarrageVOList.get(i).getValue() + "");
       }
 
       // 插入大奖弹幕

+ 5 - 0
mp-admin/src/main/resources/application-dev.yml

@@ -132,3 +132,8 @@ miniprogram:
 export:
     page-size: 200
 
+# 运费默认模板配置
+shipping:
+    user: 4
+    channel: 7
+

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

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

+ 7 - 0
mp-common/pom.xml

@@ -22,6 +22,13 @@
 
     <dependencies>
 
+        <!-- redisson -->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+            <version>3.16.8</version>
+        </dependency>
+
         <!-- swagger 增强-->
         <dependency>
             <groupId>com.github.xiaoymin</groupId>

+ 10 - 0
mp-common/src/main/java/com/qs/mp/common/constant/Constants.java

@@ -158,4 +158,14 @@ public class Constants
      * LDAP 远程方法调用
      */
     public static final String LOOKUP_LDAP = "ldap://";
+
+    /**
+     * 优惠券默认图片
+     */
+    public static final String COUPON_PIC = "coupon.png";
+
+    /**
+     * 盲豆默认图片
+     */
+    public static final String MANGDOU_PIC = "md.png";
 }

+ 29 - 0
mp-common/src/main/java/com/qs/mp/common/core/redis/DistributedLocker.java

@@ -0,0 +1,29 @@
+package com.qs.mp.common.core.redis;
+
+import org.redisson.api.RLock;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Cup
+ * @date 2022/5/18
+ */
+public interface DistributedLocker {
+
+    RLock lock(String lockKey);
+
+    RLock lock(String lockKey, long leaseTime);
+
+    RLock lock(String lockKey, long leaseTime, TimeUnit unit);
+
+    RLock getLock(String lockKey);
+
+    boolean tryLock(String lockKey);
+
+    boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit);
+
+    void unlock(String lockKey);
+
+    void unlock(RLock lock);
+
+}

+ 22 - 0
mp-common/src/main/java/com/qs/mp/common/core/redis/RedisCache.java

@@ -160,6 +160,28 @@ public class RedisCache
         return redisTemplate.opsForSet().members(key);
     }
 
+    /**
+     * 随机获取N个缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> List<T> popCacheSet(final String key, final long count)
+    {
+        return redisTemplate.opsForSet().pop(key,count);
+    }
+
+    /**
+     * 删除缓存的set中指定的值
+     *
+     * @param key 缓存键值
+     */
+    public void removeSetValueByKey(final String key, final List<Object> values)
+    {
+        redisTemplate.opsForSet().remove(key, values.toArray());
+    }
+
+
     /**
      * 缓存Map
      *

+ 75 - 0
mp-common/src/main/java/com/qs/mp/common/core/redis/RedissonDistributedLocker.java

@@ -0,0 +1,75 @@
+package com.qs.mp.common.core.redis;
+
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Cup
+ * @date 2022/5/18
+ */
+@Component
+public class RedissonDistributedLocker implements DistributedLocker {
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Override
+    public RLock lock(String lockKey) {
+        RLock lock = redissonClient.getLock(lockKey);
+        lock.lock();
+        return lock;
+    }
+
+    @Override
+    public RLock lock(String lockKey, long leaseTime) {
+        RLock lock = redissonClient.getLock(lockKey);
+        lock.lock(leaseTime, TimeUnit.SECONDS);
+        return lock;
+    }
+
+    @Override
+    public RLock lock(String lockKey, long leaseTime, TimeUnit unit) {
+        RLock lock = redissonClient.getLock(lockKey);
+        lock.lock(leaseTime, unit);
+        return lock;
+    }
+
+    @Override
+    public RLock getLock(String lockKey) {
+        return redissonClient.getLock(lockKey);
+    }
+
+    @Override
+    public boolean tryLock(String lockKey) {
+        return tryLock(lockKey, 3, 10, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
+        RLock lock = redissonClient.getLock(lockKey);
+        try {
+            return lock.tryLock(waitTime, leaseTime, unit);
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void unlock(String lockKey) {
+        RLock lock = redissonClient.getLock(lockKey);
+        if(lock.isLocked()){
+            lock.unlock();
+        }
+    }
+
+    @Override
+    public void unlock(RLock lock) {
+        if(lock.isLocked()) {
+            lock.unlock();
+        }
+    }
+}

+ 45 - 0
mp-common/src/main/java/com/qs/mp/common/enums/AwardsLabelEnum.java

@@ -0,0 +1,45 @@
+package com.qs.mp.common.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 奖级标签枚举类
+ *
+ * @author Cup
+ * @date 2022/5/23
+ */
+@ApiModel("奖级标签枚举类")
+public enum AwardsLabelEnum implements IEnum<String> {
+
+    LABEL_ONE("传说", "v2/cs.jpg"),
+    LABEL_TWO("史诗", "v2/ss.jpg"),
+    LABEL_THREE("稀有", "v2/xy.jpg"),
+    LABEL_FOUR("尊贵", "v2/zg.jpg");
+
+    private final String value;
+    private final String picUrl;
+
+    AwardsLabelEnum(String value, String picUrl) {
+        this.value = value;
+        this.picUrl = picUrl;
+    }
+
+    public String getPicUrl() {
+        return picUrl;
+    }
+
+    @Override
+    public String getValue() {
+        return this.value;
+    }
+
+    public static AwardsLabelEnum getByValue(String value){
+        for (AwardsLabelEnum awardsLabelEnum : AwardsLabelEnum.values()) {
+            if (awardsLabelEnum.getValue().equals(value)) {
+                return awardsLabelEnum;
+            }
+        }
+        return null;
+    }
+}

+ 6 - 1
mp-common/src/main/java/com/qs/mp/common/enums/CoinLogTypeEnum.java

@@ -11,7 +11,9 @@ import com.baomidou.mybatisplus.annotation.IEnum;
 public enum CoinLogTypeEnum implements IEnum<Integer> {
 
   PRIZE(1, "盲票奖品"),
-  EXCHANGE(2, "商品兑换");
+  EXCHANGE(2, "商品兑换"),
+  MARKETING(3,"营销活动"),
+  RECOVERY(4,"商品回收");
 
 
   private final int value;
@@ -38,6 +40,9 @@ public enum CoinLogTypeEnum implements IEnum<Integer> {
     return value;
   }
 
+  public String getDesc() {
+    return desc;
+  }
 
   @Override
   public String toString() {

+ 58 - 0
mp-common/src/main/java/com/qs/mp/common/enums/DeliverOrderResourceEnum.java

@@ -0,0 +1,58 @@
+package com.qs.mp.common.enums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONType;
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.qs.mp.common.json.EnumValueDeserializer;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 提货订单来源枚举类
+ *
+ * @author Cup
+ * @date 2022/5/23
+ */
+@ApiModel("提货订单来源枚举类")
+@JSONType(deserializer = EnumValueDeserializer.class)
+public enum DeliverOrderResourceEnum implements IEnum<Integer> {
+
+    DELIVER(1, "提货订单"),
+    PAYMENT(2, "现金购买");
+
+    private final int value;
+    private final String desc;
+
+
+    DeliverOrderResourceEnum(int value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+
+    public static DeliverOrderResourceEnum getByValue(Integer value) {
+        for (DeliverOrderResourceEnum deliverOrderResourceEnum : DeliverOrderResourceEnum.values()) {
+            if (deliverOrderResourceEnum.getValue().equals(value)) {
+                return deliverOrderResourceEnum;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        JSONObject object = new JSONObject();
+        object.put("value", value);
+        object.put("desc", desc);
+        return object.toString();
+    }
+}

+ 88 - 0
mp-common/src/main/java/com/qs/mp/common/enums/MarketingStatusEnum.java

@@ -0,0 +1,88 @@
+package com.qs.mp.common.enums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.qs.mp.common.utils.DateUtils;
+import io.swagger.annotations.ApiModel;
+
+import java.util.Date;
+
+/**
+ * 活动状态枚举类
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动状态枚举类")
+public enum MarketingStatusEnum implements IEnum<Integer> {
+
+    CLOSE(-1, "已关闭"),
+
+    OFF(0,"草稿"),
+
+    ON(1,"开启"),
+
+    UNSTART(2,"未开始"),
+
+    START(3,"进行中"),
+
+    END(4,"已结束");
+
+    private Integer value;
+    private String desc;
+
+    MarketingStatusEnum(Integer value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+
+    /**
+     * 重写toString,单个转化成json
+     * @return
+     */
+    @Override
+    public String toString() {
+        JSONObject object = new JSONObject();
+        object.put("value",value);
+        object.put("desc", desc);
+        return object.toString();
+    }
+
+    public static MarketingStatusEnum getByValue(Integer value) {
+        for (MarketingStatusEnum statusEnum : MarketingStatusEnum.values()) {
+            if (statusEnum.getValue().equals(value)) {
+                return statusEnum;
+            }
+        }
+        return null;
+    }
+
+    public static String getStatus(Date startTime, Date endTime, Integer isOn) {
+        if (MarketingStatusEnum.OFF.getValue().equals(isOn)) {
+            return MarketingStatusEnum.OFF.toString();
+        }
+        if (MarketingStatusEnum.CLOSE.getValue().equals(isOn)) {
+            return MarketingStatusEnum.CLOSE.toString();
+        }
+
+        Date now = DateUtils.getNowDate();
+        if (now.before(startTime)) {
+            return MarketingStatusEnum.UNSTART.toString();
+        }
+
+        if (now.after(endTime)) {
+            return MarketingStatusEnum.END.toString();
+        }
+
+        return MarketingStatusEnum.START.toString();
+    }
+}

+ 3 - 2
mp-common/src/main/java/com/qs/mp/common/enums/PrizeStorageInTypeEnum.java

@@ -11,8 +11,9 @@ import com.baomidou.mybatisplus.annotation.IEnum;
  */
 public enum PrizeStorageInTypeEnum implements IEnum<Integer> {
 
-  TICKET_CASHED(1, "奖品兑奖"),
-  COIN_EXCHANGE(2, "盲豆兑换");
+  TICKET_CASHED(1, "盲票奖品"),
+  COIN_EXCHANGE(2, "盲豆兑换"),
+  MARKETING(3, "营销活动");
 
 
   private final int value;

+ 2 - 1
mp-common/src/main/java/com/qs/mp/common/enums/PrizeStorageStatusEnum.java

@@ -11,7 +11,8 @@ import com.baomidou.mybatisplus.annotation.IEnum;
 public enum PrizeStorageStatusEnum implements IEnum<Integer> {
 
   NOT_DISTRIBUTED(1, "待提货"),
-  HAS_DISTRIBUTED(2, "已提货");
+  HAS_DISTRIBUTED(2, "已提货"),
+  RECOVERY(3,"已回收");
 
 
   private final int value;

+ 40 - 0
mp-common/src/main/java/com/qs/mp/common/enums/UserTypeEnum.java

@@ -0,0 +1,40 @@
+package com.qs.mp.common.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 用户类型枚举类
+ * @author Cup
+ * @date 2022/5/17
+ */
+@ApiModel("用户类型枚举类")
+public enum UserTypeEnum implements IEnum<String> {
+
+    ORDINARY("00", "普通用户"),
+
+    INSIDE("11", "内部用户");
+
+
+    private String value;
+
+    private String desc;
+
+    UserTypeEnum(String value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+
+    public String getDesc() {
+        return desc;
+    }
+
+    @Override
+    public String getValue() {
+        return this.value;
+    }
+
+
+
+}

+ 5 - 1
mp-framework/src/main/java/com/qs/mp/framework/config/SecurityConfig.java

@@ -140,7 +140,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                     "/api/v1/mp/user/exchange/goods/detail",
                     "/api/v1/mp/user/exchange/banner/list",
                     "/api/v1/mp/user/exchange/category/list",
-                    "/api/v1/mp/user/exchange/activity/list"
+                    "/api/v1/mp/user/exchange/activity/list",
+                    "/api/v1/mp/user/marketing/list",
+                    "/api/v1/mp/user/marketing/detail/*",
+                    "/api/v1/mp/user/marketing/userInfo/*",
+                    "/api/v1/mp/user/exchange/category/listTree"
                 ).permitAll()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()

+ 163 - 0
mp-quartz/src/main/java/com/qs/mp/quartz/task/MarketingTask.java

@@ -0,0 +1,163 @@
+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.redis.RedisKey;
+import com.qs.mp.framework.redis.RedisLockKey;
+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;
+
+/**
+ * 营销活动相关任务
+ *
+ * @author Cup
+ * @date 2022/5/18
+ */
+@Component("marketingTask")
+public class MarketingTask {
+
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+    @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 = RedisLockKey.build(RedisLockKey.MARKETING_LOTTERY_KEY, marketing.getId());
+            // 加锁,自动续期
+            if (!distributedLocker.tryLock(lockKey,0,-1,TimeUnit.SECONDS)) {
+                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);
+            }
+        }
+
+    }
+
+
+    /**
+     * 发送活动开始通知
+     */
+    public void sendMessage() {
+        // 获取开始时间小于等于当前时间且未发开始通知的活动
+        Date now = DateUtils.getNowDate();
+        List<Marketing> marketingList = marketingService.list(new LambdaQueryWrapper<Marketing>()
+                .eq(Marketing::getTriggerStatus, 0)
+                .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
+                .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;
+        }
+
+        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()));
+        }
+    }
+
+    /**
+     * 增加活动参与的虚拟人数
+     */
+    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-10
+            marketingService.update(new LambdaUpdateWrapper<Marketing>()
+                    .set(Marketing::getFakeNum, marketing.getFakeNum() + RandomUtil.randomInt(1,10))
+                    .eq(Marketing::getId, marketing.getId()));
+        }
+    }
+
+}

+ 2 - 0
mp-quartz/src/main/java/com/qs/mp/quartz/task/UserDeliverOrderTask.java

@@ -6,10 +6,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.qs.mp.channel.domain.ChannelOrder;
 import com.qs.mp.common.enums.ChannelOrderStatusEnum;
+import com.qs.mp.common.enums.DeliverOrderResourceEnum;
 import com.qs.mp.common.enums.DeliverOrderStatusEnum;
 import com.qs.mp.common.utils.DateUtils;
 import com.qs.mp.common.utils.LogUtil;
 import com.qs.mp.user.domain.UserDeliverOrder;
+import com.qs.mp.user.domain.param.UserDeliverOrderCancelParam;
 import com.qs.mp.user.service.IUserDeliverOrderService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 5 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/Goods.java

@@ -12,6 +12,7 @@ import com.qs.mp.common.enums.GoodsStatusEnum;
 
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -103,6 +104,10 @@ public class Goods implements Serializable {
   @TableField("cost")
   private Integer cost;
 
+  @ApiModelProperty("回收折扣")
+  @TableField("discount_rate")
+  private BigDecimal discountRate;
+
   /**
    * 库存数量
    */

+ 12 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/GoodsCategory.java

@@ -6,6 +6,9 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import java.io.Serializable;
 import java.util.Date;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 /**
@@ -13,6 +16,7 @@ import lombok.Data;
  * @auther quanshu
  * @create 2022-03-07 20:43:44
  */
+@ApiModel("商品类目实体类")
 @TableName("mp_goods_category")
 @Data
 public class GoodsCategory implements Serializable {
@@ -22,48 +26,56 @@ public class GoodsCategory implements Serializable {
   /**
    * 主键
    */
+  @ApiModelProperty("分类id")
   @TableId(value = "category_id", type = IdType.AUTO)
   private Long categoryId;
 
   /**
    * 类目名称
    */
+  @ApiModelProperty("类目名称")
   @TableField("name")
   private String name;
 
   /**
    * 图片
    */
+  @ApiModelProperty("图片")
   @TableField("pic_url")
   private String picUrl;
 
   /**
    * 父级类目ID
    */
+  @ApiModelProperty("父级分类ID")
   @TableField("parent_id")
   private Long parentId;
 
   /**
    * 排序
    */
+  @ApiModelProperty("排序")
   @TableField("sort")
   private Integer sort;
 
   /**
    * 是否显示
    */
+  @ApiModelProperty("是否显示")
   @TableField("is_show")
   private Integer isShow;
 
   /**
    * 创建时间
    */
+  @ApiModelProperty("创建时间")
   @TableField("created_time")
   private Date createdTime;
 
   /**
    * 更新时间
    */
+  @ApiModelProperty("更新时间")
   @TableField("updated_time")
   private Date updatedTime;
 

+ 5 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/GoodsSku.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 
 import io.swagger.annotations.ApiModel;
@@ -90,6 +91,10 @@ public class GoodsSku implements Serializable {
   @TableField("cost")
   private Integer cost;
 
+  @ApiModelProperty("回收折扣")
+  @TableField("discount_rate")
+  private BigDecimal discountRate;
+
   /**
    * 库存数
    */

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

@@ -0,0 +1,127 @@
+package com.qs.mp.admin.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 营销活动表实体类
+ * @auther quanshu
+ * @create 2022-05-16 14:25:44
+ */
+@TableName("mp_marketing")
+@Data
+@ApiModel("营销活动表实体类")
+public class Marketing implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 活动id,主键
+     */
+    @ApiModelProperty(value = "活动id,主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 活动标题
+     */
+    @ApiModelProperty("活动标题")
+    @TableField("title")
+    private String title;
+
+    /**
+     * 活动主图
+     */
+    @ApiModelProperty("活动主图")
+    @TableField("pic_url")
+    private String picUrl;
+
+    /**
+     * 真实人数
+     */
+    @ApiModelProperty("真实人数")
+    @TableField("real_num")
+    private Integer realNum;
+
+    /**
+     * 虚假人数
+     */
+    @ApiModelProperty("虚假人数")
+    @TableField("fake_num")
+    private Integer fakeNum;
+
+    /**
+     * 开始时间
+     */
+    @ApiModelProperty("开始时间")
+    @TableField("start_time")
+    private Date startTime;
+
+    /**
+     * 结束时间
+     */
+    @ApiModelProperty("结束时间")
+    @TableField("end_time")
+    private Date endTime;
+
+    /**
+     * 活动描述
+     */
+    @ApiModelProperty("活动描述")
+    @TableField("description")
+    private String description;
+
+    /**
+     * 是否开启: -1关闭,0待开启, 1开启  默认0
+     */
+    @ApiModelProperty("是否开启: -1关闭,0待开启, 1开启  默认0")
+    @TableField("is_on")
+    private Integer isOn;
+
+    /**
+     * 开奖状态: 0未开奖,1已开奖, 默认0
+     */
+    @ApiModelProperty("开奖状态: 0未开奖,1已开奖, 默认0")
+    @TableField("trigger_status")
+    private Integer triggerStatus;
+
+    @ApiModelProperty("是否发送通知:0未发送,1已发送")
+    @TableField("is_send")
+    private Integer isSend;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 103 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/MarketingAwards.java

@@ -0,0 +1,103 @@
+package com.qs.mp.admin.domain;
+
+import java.math.BigDecimal;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 营销活动奖项设置实体类
+ * @auther quanshu
+ * @create 2022-05-16 14:29:18
+ */
+@TableName("mp_marketing_awards")
+@Data
+@ApiModel("营销活动奖项设置实体类")
+public class MarketingAwards implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 奖项id
+     */
+    @ApiModelProperty("奖项id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 营销活动id
+     */
+    @ApiModelProperty("营销活动id")
+    @TableField("marketing_id")
+    private Long marketingId;
+
+    /**
+     * 奖项名
+     */
+    @ApiModelProperty("奖项名")
+    @TableField("name")
+    private String name;
+
+    /**
+     * 奖项顺序
+     */
+    @ApiModelProperty("奖项顺序")
+    @TableField("sort")
+    private Integer sort;
+
+    /**
+     * 奖项数量
+     */
+    @ApiModelProperty("奖项数量")
+    @TableField("quantity")
+    private Integer quantity;
+
+
+    @ApiModelProperty("内定数量")
+    @TableField("inside_num")
+    private Integer insideNum;
+
+    /**
+     * 奖项描述
+     */
+    @ApiModelProperty("奖项描述")
+    @TableField("memo")
+    private String memo;
+
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 127 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/MarketingAwardsPrize.java

@@ -0,0 +1,127 @@
+package com.qs.mp.admin.domain;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 营销活动奖项对应奖品设置实体类
+ * @auther quanshu
+ * @create 2022-05-16 14:29:31
+ */
+@TableName("mp_marketing_awards_prize")
+@Data
+@ApiModel("营销活动奖项对应奖品设置实体类")
+public class MarketingAwardsPrize implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 奖品ID
+     */
+    @ApiModelProperty("奖品ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 营销活动id
+     */
+    @ApiModelProperty("营销活动id")
+    @TableField("marketing_id")
+    private Long marketingId;
+
+    /**
+     * 奖项ID
+     */
+    @ApiModelProperty("奖项ID")
+    @TableField("awards_id")
+    private Long awardsId;
+
+    /**
+     * 奖品ID(GOODS_ID或TEMPL_ID)
+     */
+    @ApiModelProperty("奖品ID(GOODS_ID或TEMPL_ID)")
+    @TableField("ref_id")
+    private String refId;
+
+    /**
+     * 奖品图片
+     */
+    @ApiModelProperty("奖品图片")
+    @TableField("pic_url")
+    private String picUrl;
+
+    /**
+     * 奖品名称
+     */
+    @ApiModelProperty("奖品名称")
+    @TableField("title")
+    private String title;
+
+    /**
+     * 奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包
+     */
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+    @TableField("prize_type")
+    private TicketPrizeTypeEnum prizeType;
+
+    /**
+     * 奖品数
+     */
+    @ApiModelProperty("奖品数")
+    @TableField("quantity")
+    private Integer quantity;
+
+    /**
+     * 奖品价值
+     */
+    @ApiModelProperty("奖品价值")
+    @TableField("value")
+    private Integer value;
+
+    /**
+     * 排序权重,值越大越靠前
+     */
+    @ApiModelProperty("权重排序,值越大越靠前")
+    @TableField("sort_weight")
+    private Integer sortWeight;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+
+}

+ 134 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/PrizeRecovery.java

@@ -0,0 +1,134 @@
+package com.qs.mp.admin.domain;
+
+import java.math.BigDecimal;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.qs.mp.common.enums.PrizeStorageInTypeEnum;
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 奖品回收表实体类
+ * @auther quanshu
+ * @create 2022-05-23 14:18:27
+ */
+@TableName("mp_prize_recovery")
+@Data
+@ApiModel("奖品回收表实体类")
+public class PrizeRecovery implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 关联奖品库id
+     */
+    @ApiModelProperty("关联奖品库id")
+    @TableField("storage_id")
+    private String storageId;
+
+    /**
+     * 用户ID
+     */
+    @ApiModelProperty("用户id")
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    @TableField("title")
+    private String title;
+
+    /**
+     * 图片
+     */
+    @ApiModelProperty("图片")
+    @TableField("pic_url")
+    private String picUrl;
+
+    /**
+     * 数量
+     */
+    @ApiModelProperty("数量")
+    @TableField("num")
+    private Integer num;
+
+    /**
+     * 商品id
+     */
+    @ApiModelProperty("商品id")
+    @TableField("goods_id")
+    private Long goodsId;
+
+    @ApiModelProperty("商品id")
+    @TableField("sku_id")
+    private Long skuId;
+
+    @ApiModelProperty("属性组合字符串 格式是p1:v1;p2:v2")
+    @TableField("properties")
+    private String properties;
+
+    @ApiModelProperty("兑换单价")
+    @TableField("exchange_price")
+    private Integer exchangePrice;
+
+    /**
+     * 回收折扣比例
+     */
+    @ApiModelProperty("回收折扣比例")
+    @TableField("discount_rate")
+    private BigDecimal discountRate;
+
+    /**
+     * 返回盲豆数
+     */
+    @ApiModelProperty("返还盲豆数")
+    @TableField("return_coin")
+    private Integer returnCoin;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+
+}

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

@@ -45,6 +45,10 @@ public class TicketAwards implements Serializable {
   @TableField("name")
   private String name;
 
+  @ApiModelProperty("奖级标签")
+  @TableField("awards_label")
+  private String awardsLabel;
+
   /**
    * 奖项顺序
    */

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

@@ -128,6 +128,10 @@ public class TicketBox implements Serializable {
   @TableField("sale_pkg_qty")
   private Integer salePkgQty;
 
+  @ApiModelProperty("原价")
+  @TableField("origin_price")
+  private Integer originPrice;
+
   /**
    * 盲票票面价格
    */

+ 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;
+}

+ 48 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/excel/PrizeRecoveryExcel.java

@@ -0,0 +1,48 @@
+package com.qs.mp.admin.domain.excel;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.qs.mp.common.annotation.Excel;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author Cup
+ * @date 2022/5/25
+ */
+@ApiModel("商品回收订单导出类")
+@Data
+public class PrizeRecoveryExcel {
+
+    @Excel(name = "回收时间")
+    private Date createdTime;
+
+    @Excel(name = "商品名称")
+    private String title;
+
+    @Excel(name = "商品规格")
+    private String properties;
+
+    @Excel(name = "商品单价(盲豆)")
+    private Integer exchangePrice;
+
+    @Excel(name = "回收数量")
+    private Integer num;
+
+    @Excel(name = "回收折扣(%)")
+    private BigDecimal discountRate;
+
+    @Excel(name = "返还盲豆数")
+    private Integer returnCoin;
+
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @Excel(name = "用户手机号")
+    private String userPhone;
+
+
+}

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

@@ -9,6 +9,7 @@ import com.qs.mp.common.enums.GoodsStatusEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 import javax.validation.constraints.NotNull;
@@ -76,6 +77,9 @@ public class GoodsParam {
 	@ApiModelProperty(value = "商品成本", required = false)
 	private Integer cost;
 
+	@ApiModelProperty("回收折扣")
+	private BigDecimal discountRate;
+
 	/**
 	 * 库存数量
 	 */

+ 5 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/GoodsQueryParam.java

@@ -11,6 +11,8 @@ import com.qs.mp.common.enums.GoodsStatusEnum;
 
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * @describe 商品库实体类
  * @auther quanshu
@@ -26,6 +28,9 @@ public class GoodsQueryParam {
 	@ApiModelProperty(value = "分类ID", required = false)
 	private Long categoryId;
 
+	@ApiModelProperty(value = "分类ID列表", required = false)
+	private List<Long> categoryIdList;
+
 	@ApiModelProperty(value = "标签ID", required = false)
 	private Long tagId;
 

+ 39 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingAwardsParam.java

@@ -0,0 +1,39 @@
+package com.qs.mp.admin.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel(value= "营销活动奖级入参类")
+@Data
+public class MarketingAwardsParam {
+
+
+    @NotEmpty(message = "奖品列表不能为空")
+    @ApiModelProperty(value = "奖品列表",required=true)
+    List<MarketingAwardsPrizeParam> prizeList;
+
+    @NotNull(message = "奖级名称不能为空")
+    @ApiModelProperty(value = "奖级名称",required=true)
+    private String name;
+
+    @NotNull(message = "奖级数量不能为空")
+    @ApiModelProperty(value = "奖级数量",required=true)
+    private Integer quantity;
+
+    @NotNull(message = "内定数量不能为空")
+    @ApiModelProperty(value = "内定数量",required = true)
+    private Integer insideNum;
+
+    @NotNull(message = "奖级顺序不能为空")
+    @ApiModelProperty(value = "奖级顺序",required=true)
+    private Integer sort;
+}

+ 32 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingAwardsPrizeParam.java

@@ -0,0 +1,32 @@
+package com.qs.mp.admin.domain.param;
+
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动奖级对应奖品入参类")
+@Data
+public class MarketingAwardsPrizeParam {
+
+    @NotNull(message = "奖品关联ID不能为空")
+    @ApiModelProperty(value = "奖品关联ID",required=true)
+    private String refId;
+
+    @NotNull(message = "奖品类型不能为空")
+    @ApiModelProperty(value = "奖品类型",required=true)
+    private TicketPrizeTypeEnum prizeType;
+
+    @NotNull(message = "奖品数量不能为空")
+    @ApiModelProperty(value = "奖品数量",required=true)
+    private Integer quantity;
+
+    @ApiModelProperty(value = "奖品价值",required=true)
+    private Integer value;
+}

+ 49 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingCreateParam.java

@@ -0,0 +1,49 @@
+package com.qs.mp.admin.domain.param;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 营销活动创建入参类
+ *
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动创建入参类")
+@Data
+public class MarketingCreateParam {
+
+    @ApiModelProperty(value = "活动标题", required = true)
+    @NotBlank(message = "活动标题不能为空")
+    private String title;
+
+    @ApiModelProperty(value = "活动主图", required = true)
+    @NotBlank(message = "活动主图不能为空")
+    private String picUrl;
+
+    @ApiModelProperty(value = "初始人数", required = true)
+    private Integer fakeNum;
+
+    @ApiModelProperty(value = "开始时间", required = true)
+    @NotNull(message = "开始时间不能为空")
+    private Date startTime;
+
+    @ApiModelProperty(value = "结束时间", required = true)
+    @NotNull(message = "结束时间不能为空")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动描述",required = false)
+    private String description;
+
+    @NotEmpty(message = "奖级列表不能为空")
+    @ApiModelProperty(value = "奖级列表", required = true)
+    List<MarketingAwardsParam> awardsList;
+}

+ 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;
+
+}

+ 23 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingQueryParam.java

@@ -0,0 +1,23 @@
+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/16
+ */
+@ApiModel("营销活动查询入参类")
+@Data
+public class MarketingQueryParam {
+
+
+    @ApiModelProperty("营销活动名称")
+    private String title;
+
+    @ApiModelProperty("活动状态:-1关闭,0草稿,1开启,2未开始,3进行中,4已结束")
+    private Integer status;
+
+}

+ 52 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/MarketingUpdateParam.java

@@ -0,0 +1,52 @@
+package com.qs.mp.admin.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 营销活动更新入参类
+ *
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动更新入参类")
+@Data
+public class MarketingUpdateParam {
+
+    @ApiModelProperty("营销活动id")
+    @NotNull(message = "营销活动id不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "活动标题", required = true)
+    @NotBlank(message = "活动标题不能为空")
+    private String title;
+
+    @ApiModelProperty(value = "活动主图", required = true)
+    @NotBlank(message = "活动主图不能为空")
+    private String picUrl;
+
+    @ApiModelProperty(value = "初始人数", required = true)
+    private Integer fakeNum;
+
+    @ApiModelProperty(value = "开始时间", required = true)
+    @NotNull(message = "开始时间不能为空")
+    private Date startTime;
+
+    @ApiModelProperty(value = "结束时间", required = true)
+    @NotNull(message = "结束时间不能为空")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动描述",required = false)
+    private String description;
+
+    @NotEmpty(message = "奖级列表不能为空")
+    @ApiModelProperty(value = "奖级列表", required = true)
+    List<MarketingAwardsParam> awardsList;
+}

+ 30 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/PrizeRecoveryListQueryParam.java

@@ -0,0 +1,30 @@
+package com.qs.mp.admin.domain.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 回收列表查询入参类
+ * @author Cup
+ * @date 2022/5/25
+ */
+@ApiModel("回收列表查询入参类")
+@Data
+public class PrizeRecoveryListQueryParam {
+
+    @ApiModelProperty("商品名称")
+    private String title;
+
+    @ApiModelProperty("用户手机号")
+    private String userPhone;
+
+    @ApiModelProperty("开始时间")
+    private Date startTime;
+
+    @ApiModelProperty("结束时间")
+    private Date endTime;
+
+}

+ 5 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketAwardsParam.java

@@ -3,6 +3,7 @@ package com.qs.mp.admin.domain.param;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import java.util.List;
+import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import lombok.Data;
@@ -26,6 +27,10 @@ public class TicketAwardsParam {
   @ApiModelProperty(value = "奖级名称",required=true)
   private String name;
 
+  @NotBlank(message = "奖级标签不能为空")
+  @ApiModelProperty("奖级标签")
+  private String awardsLabel;
+
   @NotNull(message = "奖级数量不能为空")
   @ApiModelProperty(value = "奖级数量",required=true)
   private Integer quantity;

+ 3 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketBoxCreateParam.java

@@ -31,6 +31,9 @@ public class TicketBoxCreateParam {
 	@ApiModelProperty("渠道商id列表")
 	private List<Long> channelIdList;
 
+	@ApiModelProperty(value = "原价", required = false)
+	private Integer originPrice;
+
 	@NotNull(message = "面值不能为空")
 	@ApiModelProperty(value = "面值",required=true)
 	private Integer facePrice;

+ 3 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/TicketBoxUpdateParam.java

@@ -32,6 +32,9 @@ public class TicketBoxUpdateParam {
     @ApiModelProperty(value = "售价",required=true)
     private Integer salePrice;
 
+    @ApiModelProperty(value = "原价", required = false)
+    private Integer originPrice;
+
     @ApiModelProperty("销售渠道类型  1 所有渠道 2 指定渠道")
     private Integer saleChannelType;
 

+ 3 - 3
mp-service/src/main/java/com/qs/mp/admin/domain/param/UserDeliverOrderQueryParam.java

@@ -1,13 +1,11 @@
 package com.qs.mp.admin.domain.param;
 
+import com.qs.mp.common.enums.DeliverOrderResourceEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
 import java.util.Date;
 
-import com.alibaba.fastjson.annotation.JSONField;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.qs.mp.common.enums.DeliverOrderStatusEnum;
 import lombok.Data;
 
 /**
@@ -46,5 +44,7 @@ public class UserDeliverOrderQueryParam {
 	@ApiModelProperty(value = "截止时间", required = false)
 	private Date endTime;
 
+	@ApiModelProperty("订单来源:1提货订单,2现金购买")
+	private DeliverOrderResourceEnum resource;
 
 }

+ 29 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/GoodsCategoryTreeVO.java

@@ -0,0 +1,29 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qs.mp.admin.domain.GoodsCategory;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @describe 商品类目树状出参类
+ * @auther quanshu
+ * @create 2022-03-07 20:43:44
+ */
+@ApiModel("商品类目树状出参类")
+@Data
+public class GoodsCategoryTreeVO extends GoodsCategory {
+
+    @ApiModelProperty("商品分类信息列表")
+    private List<GoodsCategory> goodsCategoryList;
+
+
+}

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

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.qs.mp.admin.domain.GoodsSku;
 import com.qs.mp.common.enums.GoodsStatusEnum;
 
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 
@@ -81,6 +82,9 @@ public class GoodsVO {
 	@ApiModelProperty("商品成本")
 	private Integer cost;
 
+	@ApiModelProperty("回收折扣")
+	private BigDecimal discountRate;
+
 	/**
 	 * 库存数量
 	 */

+ 23 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingAwardsVO.java

@@ -0,0 +1,23 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.qs.mp.admin.domain.MarketingAwards;
+import com.qs.mp.admin.domain.MarketingAwardsPrize;
+import com.qs.mp.admin.domain.TicketAwardsPrize;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 营销活动奖项出参类
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动奖项出参类")
+@Data
+public class MarketingAwardsVO extends MarketingAwards {
+
+    @ApiModelProperty("奖品列表")
+    List<MarketingAwardsPrize> prizeList;
+}

+ 51 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingListVO.java

@@ -0,0 +1,51 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.qs.mp.common.enums.MarketingStatusEnum;
+import com.qs.mp.common.utils.DateUtils;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * 营销活动列表出参类
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动列表出参类")
+@Data
+public class MarketingListVO {
+
+    @ApiModelProperty(value = "活动id,主键")
+    private Long id;
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @ApiModelProperty("真实人数")
+    private Integer realNum;
+
+    @ApiModelProperty("开始时间")
+    private Date startTime;
+
+    @ApiModelProperty("结束时间")
+    private Date endTime;
+
+    @ApiModelProperty("是否开启: -1关闭,0待开启, 1开启 ")
+    private Integer isOn;
+
+    @ApiModelProperty("活动状态-1关闭,0待开启,1开启,2未开始,3进行中,4已结束")
+    private String status;
+
+    public String getStatus() {
+        return MarketingStatusEnum.getStatus(this.startTime, this.endTime, this.isOn);
+    }
+
+}

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

@@ -0,0 +1,44 @@
+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;
+
+import java.util.Date;
+
+/**
+ * 活动抽奖码列表数据出参类
+ * @author Cup
+ * @date 2022/5/17
+ */
+@ApiModel("活动抽奖码列表数据出参类")
+@Data
+public class MarketingUserCodeListVO {
+
+
+    @ApiModelProperty("用户编号")
+    private Long userId;
+
+    @ApiModelProperty("用户名称")
+    private String nickName;
+
+    @ApiModelProperty("抽奖码")
+    private String code;
+
+    @ApiModelProperty("抽奖码获取时间")
+    private Date createdTime;
+
+    @ApiModelProperty("奖级")
+    private String awardsName;
+
+    @ApiModelProperty("奖品")
+    private String prizeName;
+
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    private TicketPrizeTypeEnum prizeType;
+
+    @ApiModelProperty("奖品价值")
+    private Integer value;
+
+}

+ 23 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/MarketingVO.java

@@ -0,0 +1,23 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.qs.mp.admin.domain.Marketing;
+import com.qs.mp.admin.domain.MarketingAwards;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 营销活动详情出参类
+ * @author Cup
+ * @date 2022/5/16
+ */
+@ApiModel("营销活动详情出参类")
+@Data
+public class MarketingVO extends Marketing {
+
+    @ApiModelProperty("奖级列表(后台显示)")
+    List<MarketingAwardsVO> awardsList;
+
+}

+ 24 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/PrizeRecoveryListVO.java

@@ -0,0 +1,24 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.qs.mp.admin.domain.PrizeRecovery;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 商品回收列表出参类
+ * @author Cup
+ * @date 2022/5/25
+ */
+@ApiModel("商品回收列表出参类")
+@Data
+public class PrizeRecoveryListVO extends PrizeRecovery {
+
+
+    @ApiModelProperty("用户昵称")
+    private String nickName;
+
+    @ApiModelProperty("用户手机号")
+    private String userPhone;
+
+}

+ 16 - 1
mp-service/src/main/java/com/qs/mp/admin/domain/vo/TicketAwardsPrizeVO.java

@@ -7,7 +7,9 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.Objects;
 
+import com.qs.mp.common.enums.AwardsLabelEnum;
 import com.qs.mp.common.enums.CouponDiscountTypeEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -40,6 +42,12 @@ public class TicketAwardsPrizeVO {
   @ApiModelProperty("奖项名")
   private String name;
 
+  @ApiModelProperty("奖级标签")
+  private String awardsLabel;
+
+  @ApiModelProperty("奖级标签图片")
+  private String awardsLabelPicUrl;
+
   /**
    * 奖项顺序
    */
@@ -110,7 +118,14 @@ public class TicketAwardsPrizeVO {
   /**
    * 排序权重
    */
+  @ApiModelProperty("排序权重")
   private Integer sortWeight;
 
-
+  public String getAwardsLabelPicUrl() {
+    AwardsLabelEnum awardsLabelEnum = AwardsLabelEnum.getByValue(this.awardsLabel);
+    if (Objects.nonNull(awardsLabelEnum)) {
+      return awardsLabelEnum.getPicUrl();
+    }
+    return "";
+  }
 }

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

@@ -5,6 +5,7 @@ import java.util.List;
 
 import com.qs.mp.channel.domain.Channel;
 import com.qs.mp.channel.domain.vo.ChannelVO;
+import com.qs.mp.user.domain.vo.TicketAwardsLabelVO;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -21,6 +22,9 @@ public class TicketBoxVO extends TicketBox {
   @ApiModelProperty("经销商信息")
   List<Channel> channelList;
 
+  @ApiModelProperty("奖级标签信息列表")
+  List<TicketAwardsLabelVO> ticketAwardsLabelList;
+
   // 奖品列表(小程序端显示)
   @ApiModelProperty("奖品列表(小程序端显示)")
   List<TicketAwardsPrizeVO> prizeList;

+ 13 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingAwardsMapper.java

@@ -0,0 +1,13 @@
+package com.qs.mp.admin.mapper;
+
+import com.qs.mp.admin.domain.MarketingAwards;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @auther quanshu
+ * @create 2022-05-16 14:29:18
+ * @describe 营销活动奖项设置mapper类
+ */
+public interface MarketingAwardsMapper extends BaseMapper<MarketingAwards> {
+
+}

+ 13 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingAwardsPrizeMapper.java

@@ -0,0 +1,13 @@
+package com.qs.mp.admin.mapper;
+
+import com.qs.mp.admin.domain.MarketingAwardsPrize;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @auther quanshu
+ * @create 2022-05-16 14:29:31
+ * @describe 营销活动奖项对应奖品设置mapper类
+ */
+public interface MarketingAwardsPrizeMapper extends BaseMapper<MarketingAwardsPrize> {
+
+}

+ 13 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingMapper.java

@@ -0,0 +1,13 @@
+package com.qs.mp.admin.mapper;
+
+import com.qs.mp.admin.domain.Marketing;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @auther quanshu
+ * @create 2022-05-16 14:25:44
+ * @describe 营销活动表mapper类
+ */
+public interface MarketingMapper extends BaseMapper<Marketing> {
+
+}

+ 34 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/PrizeRecoveryMapper.java

@@ -0,0 +1,34 @@
+package com.qs.mp.admin.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.qs.mp.admin.domain.PrizeRecovery;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qs.mp.admin.domain.vo.PrizeRecoveryListVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @auther quanshu
+ * @create 2022-05-23 14:18:27
+ * @describe 奖品回收表mapper类
+ */
+public interface PrizeRecoveryMapper extends BaseMapper<PrizeRecovery> {
+
+
+    /**
+     * 根据条件获取回收订单列表信息
+     * @param queryWrapper
+     * @return
+     */
+    List<PrizeRecoveryListVO> listByWrapper(@Param(Constants.WRAPPER) QueryWrapper<PrizeRecovery> queryWrapper);
+
+    /**
+     * 根据条件统计回收列表信息总数
+     * @param queryWrapper
+     * @return
+     */
+    int countByWrapper(@Param(Constants.WRAPPER) QueryWrapper<PrizeRecovery> queryWrapper);
+
+}

+ 12 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/TicketAwardsMapper.java

@@ -1,7 +1,13 @@
 package com.qs.mp.admin.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.qs.mp.admin.domain.TicketAwards;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qs.mp.user.domain.vo.TicketAwardsLabelVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * @auther quanshu
@@ -10,4 +16,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  */
 public interface TicketAwardsMapper extends BaseMapper<TicketAwards> {
 
+    /**
+     * 根据条件分组获取奖品总数
+     * @param queryWrapper
+     * @return
+     */
+    List<TicketAwardsLabelVO> groupSumQuantityByQueryWrapper(@Param(Constants.WRAPPER) QueryWrapper<TicketAwards> queryWrapper);
 }

+ 6 - 0
mp-service/src/main/java/com/qs/mp/admin/service/ICouponPkgService.java

@@ -57,4 +57,10 @@ public interface ICouponPkgService extends IService<CouponPkg> {
      */
     void distribute(Ticket ticket, Long userId, String couponPkgId);
 
+    /**
+     * 营销活动券包发券
+     * @param userId
+     * @param couponPkgId
+     */
+    void distributeByMarketing(Long userId, String couponPkgId);
 }

+ 7 - 0
mp-service/src/main/java/com/qs/mp/admin/service/ICouponService.java

@@ -41,4 +41,11 @@ public interface ICouponService extends IService<Coupon> {
 	 * @param couponId
 	 */
 	void distribute(Ticket ticket, Long userId, String couponId);
+
+	/**
+	 * 营销活动优惠券发放
+	 * @param userId
+	 * @param couponId
+	 */
+    void distributeByMarketing(Long userId, String couponId);
 }

+ 16 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IMarketingAwardsPrizeService.java

@@ -0,0 +1,16 @@
+package com.qs.mp.admin.service;
+
+import com.qs.mp.admin.domain.MarketingAwardsPrize;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 营销活动奖项对应奖品设置 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+public interface IMarketingAwardsPrizeService extends IService<MarketingAwardsPrize> {
+
+}

+ 16 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IMarketingAwardsService.java

@@ -0,0 +1,16 @@
+package com.qs.mp.admin.service;
+
+import com.qs.mp.admin.domain.MarketingAwards;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 营销活动奖项设置 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+public interface IMarketingAwardsService extends IService<MarketingAwards> {
+
+}

+ 60 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IMarketingService.java

@@ -0,0 +1,60 @@
+package com.qs.mp.admin.service;
+
+import com.qs.mp.admin.domain.Marketing;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.admin.domain.param.MarketingCreateParam;
+import com.qs.mp.admin.domain.param.MarketingQueryParam;
+import com.qs.mp.admin.domain.param.MarketingUpdateParam;
+import com.qs.mp.admin.domain.vo.MarketingListVO;
+import com.qs.mp.user.domain.param.MarketingHelpParam;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 营销活动表 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+public interface IMarketingService extends IService<Marketing> {
+
+    /**
+     * 创建营销活动
+     * @param marketingCreateParam
+     */
+    void createMarketing(MarketingCreateParam marketingCreateParam);
+
+    /**
+     * 更新营销活动
+     * @param marketingUpdateParam
+     */
+    void updateMarketing(MarketingUpdateParam marketingUpdateParam);
+
+    /**
+     * 获取营销活动列表
+     * @param
+     * @return
+     */
+    List<Marketing> listMarketing(MarketingQueryParam marketingQueryParam);
+
+    /**
+     * 根据活动id删除活动相关信息
+     * @param id
+     */
+    void deleteById(Long id);
+
+    /**
+     * 活动助力
+     * @param marketingHelpParam
+     * @param userId
+     */
+    void help(MarketingHelpParam marketingHelpParam, Long userId);
+
+    /**
+     * 开奖
+     * @param marketing
+     */
+    void lottery(Marketing marketing);
+}

+ 44 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IPrizeRecoveryService.java

@@ -0,0 +1,44 @@
+package com.qs.mp.admin.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qs.mp.admin.domain.PrizeRecovery;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.admin.domain.vo.PrizeRecoveryListVO;
+import com.qs.mp.user.domain.param.UserPrizeRecoveryCreateParam;
+import com.qs.mp.user.domain.vo.PrizeRecoverySettleVO;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 奖品回收表 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-23
+ */
+public interface IPrizeRecoveryService extends IService<PrizeRecovery> {
+
+
+    /**
+     * 提交回收
+     * @param userId
+     * @param settleVO
+     */
+    void submitRecovery(Long userId, PrizeRecoverySettleVO settleVO);
+
+    /**
+     * 根据条件获取回收列表信息
+     * @param queryWrapper
+     * @return
+     */
+    List<PrizeRecoveryListVO> listByWrapper(QueryWrapper<PrizeRecovery> queryWrapper);
+
+    /**
+     * 根据条件统计回收列表信息总数
+     * @param queryWrapper
+     * @return
+     */
+    int countByWrapper(QueryWrapper<PrizeRecovery> queryWrapper);
+
+}

+ 10 - 0
mp-service/src/main/java/com/qs/mp/admin/service/ITicketAwardsService.java

@@ -1,7 +1,11 @@
 package com.qs.mp.admin.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qs.mp.admin.domain.TicketAwards;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.user.domain.vo.TicketAwardsLabelVO;
+
+import java.util.List;
 
 /**
  * <p>
@@ -13,4 +17,10 @@ import com.baomidou.mybatisplus.extension.service.IService;
  */
 public interface ITicketAwardsService extends IService<TicketAwards> {
 
+    /**
+     * 更具条件分组获取奖品总数
+     * @param queryWrapper
+     * @return
+     */
+    List<TicketAwardsLabelVO> groupSumQuantityByQueryWrapper(QueryWrapper<TicketAwards> queryWrapper);
 }

+ 19 - 2
mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponPkgServiceImpl.java

@@ -19,6 +19,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qs.mp.admin.service.ICouponService;
 import com.qs.mp.channel.domain.Channel;
 import com.qs.mp.channel.service.IChannelService;
+import com.qs.mp.common.constant.Constants;
 import com.qs.mp.common.enums.CouponPkgStatusEnum;
 import com.qs.mp.common.enums.CouponUseAreaEnum;
 import com.qs.mp.common.enums.UserCouponStatusEnum;
@@ -66,6 +67,22 @@ public class CouponPkgServiceImpl extends ServiceImpl<CouponPkgMapper, CouponPkg
     @Autowired
     private ICouponService couponService;
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+
+    public void distributeByMarketing(Long userId, String couponPkgId) {
+        List<CouponPkgItem> couponPkgItems = couponPkgItemService.list(new LambdaQueryWrapper<CouponPkgItem>().eq(CouponPkgItem::getCouponPkgId, couponPkgId));
+
+        if (CollectionUtils.isEmpty(couponPkgItems)) {
+            throw new ServiceException("券包下优惠券不存在");
+        }
+        for (CouponPkgItem couponPkgItem : couponPkgItems) {
+            for (int i = 0; i < couponPkgItem.getCouponNum(); i++) {
+                couponService.distributeByMarketing(userId, couponPkgItem.getCouponId().toString());
+            }
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void distribute(Ticket ticket, Long userId, String couponPkgId) {
@@ -142,7 +159,7 @@ public class CouponPkgServiceImpl extends ServiceImpl<CouponPkgMapper, CouponPkg
         // 更新券包
         couponPkg = new CouponPkg();
         BeanUtils.copyProperties(couponPkgParam, couponPkg);
-        couponPkg.setPicUrl("coupon_def.jpeg");
+        couponPkg.setPicUrl(Constants.COUPON_PIC);
         this.updateById(couponPkg);
 
         // 创建券包明细
@@ -170,7 +187,7 @@ public class CouponPkgServiceImpl extends ServiceImpl<CouponPkgMapper, CouponPkg
 
         CouponPkg couponPkg = new CouponPkg();
         BeanUtils.copyProperties(couponPkgParam, couponPkg);
-        couponPkg.setPicUrl("coupon_def.jpeg");
+        couponPkg.setPicUrl(Constants.COUPON_PIC);
         this.save(couponPkg);
 
         List<CouponPkgItem> params = new ArrayList<>();

+ 34 - 16
mp-service/src/main/java/com/qs/mp/admin/service/impl/CouponServiceImpl.java

@@ -75,26 +75,27 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
 	@Autowired
 	private IChannelService channelService;
 
-  @Override
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public void distributeByMarketing(Long userId, String couponId) {
+		Coupon coupon = getById(couponId);
+		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)
+				.eq(Coupon::getCouponId, coupon.getCouponId()).eq(Coupon::getDistributeQty, coupon.getDistributeQty()));
+		Assert.isTrue(rtn, "发放优惠券奖品,更新优惠券发放量失败。couponId:" + coupon.getCouponId());
+	}
+
+
+	@Override
   @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());
@@ -119,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);

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingAwardsPrizeServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qs.mp.admin.service.impl;
+
+import com.qs.mp.admin.domain.MarketingAwardsPrize;
+import com.qs.mp.admin.mapper.MarketingAwardsPrizeMapper;
+import com.qs.mp.admin.service.IMarketingAwardsPrizeService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 营销活动奖项对应奖品设置 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+@Service
+public class MarketingAwardsPrizeServiceImpl extends ServiceImpl<MarketingAwardsPrizeMapper, MarketingAwardsPrize> implements IMarketingAwardsPrizeService {
+
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingAwardsServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qs.mp.admin.service.impl;
+
+import com.qs.mp.admin.domain.MarketingAwards;
+import com.qs.mp.admin.mapper.MarketingAwardsMapper;
+import com.qs.mp.admin.service.IMarketingAwardsService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 营销活动奖项设置 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+@Service
+public class MarketingAwardsServiceImpl extends ServiceImpl<MarketingAwardsMapper, MarketingAwards> implements IMarketingAwardsService {
+
+}

+ 526 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingServiceImpl.java

@@ -0,0 +1,526 @@
+package com.qs.mp.admin.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+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.*;
+import com.qs.mp.admin.domain.param.*;
+import com.qs.mp.admin.domain.vo.MarketingListVO;
+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.constant.Constants;
+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;
+import com.qs.mp.user.domain.MarketingHitPrize;
+import com.qs.mp.user.domain.MarketingUserCode;
+import com.qs.mp.user.domain.param.MarketingHelpParam;
+import com.qs.mp.user.service.IMarketingHitPrizeService;
+import com.qs.mp.user.service.IMarketingUserCodeService;
+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;
+import org.springframework.util.Assert;
+
+import java.net.URLDecoder;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 营销活动表 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-16
+ */
+@Service
+public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing> implements IMarketingService {
+
+    // 内部奖池
+    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:%s";
+
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
+
+    @Autowired
+    private IGoodsService goodsService;
+
+    @Autowired
+    private ICouponService couponService;
+
+    @Autowired
+    private ICouponPkgService couponPkgService;
+
+    @Autowired
+    private IMarketingAwardsService marketingAwardsService;
+
+    @Autowired
+    private IMarketingAwardsPrizeService marketingAwardsPrizeService;
+
+    @Autowired
+    private IMarketingUserCodeService marketingUserCodeService;
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private IMarketingHitPrizeService marketingHitPrizeService;
+
+    @Autowired
+    private IUserCoinService userCoinService;
+
+    @Autowired
+    private IUserPrizeStorageService userPrizeStorageService;
+
+    @Autowired
+    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()));
+
+        // 奖品总数
+        int prizeQuantity = marketingAwardsList.stream().mapToInt(MarketingAwards::getQuantity).sum();
+
+        // 校准并获取真实参与人数
+        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
+
+        // 设置需要的内定人数
+        int insideNum = 0;
+
+        boolean isInside = false;
+
+        // 参与人数不足,全部采用内定人员开奖
+        if (prizeQuantity >= realNum) {
+            insideNum = prizeQuantity;
+            isInside = true;
+        } else {
+            insideNum = marketingAwardsList.stream().mapToInt(MarketingAwards::getInsideNum).sum();
+        }
+
+        // 获取一定数量的内定人员设置抽奖码加入抽奖关系表和抽奖池
+        List<SysUser> insideUserList = sysUserService.selectUserListByRand(UserTypeEnum.INSIDE.getValue(), insideNum);
+
+        Set<String> insideCodes = new HashSet<>();
+        Set<String> userCodes = new HashSet<>();
+        for (SysUser sysUser : insideUserList) {
+            // 生成抽奖码
+            String code = MarketingUtils.generatePrizeCode();
+            while (true) {
+                // 判断抽奖码是否已经存在
+                int codeCount = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
+                        .eq(MarketingUserCode::getMarketingId, marketing.getId())
+                        .eq(MarketingUserCode::getCode, code));
+                if (codeCount > 0) {
+                    code = MarketingUtils.generatePrizeCode();
+                    continue;
+                }
+                break;
+            }
+            insideCodes.add(code);
+            MarketingUserCode marketingUserCode = new MarketingUserCode();
+            marketingUserCode.setMarketingId(marketing.getId());
+            marketingUserCode.setUserId(sysUser.getUserId());
+            marketingUserCode.setUserType(UserTypeEnum.INSIDE.getValue());
+            marketingUserCode.setCode(code);
+            marketingUserCode.setHelpUserId(0L);
+            // 设置中奖
+            marketingUserCode.setIsHit(1);
+            marketingUserCodeService.save(marketingUserCode);
+        }
+        // 将抽奖码放入内部抽奖池
+        redisCache.setCacheSet(insideLotteryPool, insideCodes);
+
+
+        // 获取普通用户所有的抽奖码
+        List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+                .eq(MarketingUserCode::getMarketingId, marketing.getId())
+                .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue()));
+        if (CollectionUtils.isNotEmpty(userCodeList)) {
+            userCodes = userCodeList.stream().map(MarketingUserCode::getCode).collect(Collectors.toSet());
+        }
+        // 将抽奖码放入普通抽奖池
+        redisCache.setCacheSet(userLotteryPool, userCodes);
+
+        // 所有中奖名单
+        List<MarketingHitPrize> allHitPrizeList = new ArrayList<>();
+
+        // 普通用户中奖名单
+        List<MarketingHitPrize> userHitPrizeList = new ArrayList<>();
+
+        // 抽奖码中奖状态更新列表
+        List<MarketingUserCode> userCodeUpdateList = new ArrayList<>();
+
+        // 遍历奖级进行抽奖
+        for (MarketingAwards marketingAwards : marketingAwardsList) {
+            // 获取内部人数
+            if (isInside) {
+                insideNum = marketingAwards.getQuantity();
+            } else {
+                insideNum = marketingAwards.getInsideNum();
+            }
+
+            List<MarketingAwardsPrize> marketingAwardsPrizeList = marketingAwardsPrizeService.list(new LambdaQueryWrapper<MarketingAwardsPrize>()
+                    .eq(MarketingAwardsPrize::getMarketingId, marketing.getId())
+                    .eq(MarketingAwardsPrize::getAwardsId, marketingAwards.getId()));
+
+            if (insideNum != 0) {
+                // 设置内部用户抽奖信息
+                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));
+
+                    // 设置中奖信息
+                    exchangeMarketingHitPrize(marketing, allHitPrizeList, marketingAwards, marketingAwardsPrizeList, marketingUserCode);
+                }
+            }
+
+            // 用户中奖数量为 名额 - 内定人数
+            int userNum = marketingAwards.getQuantity() - insideNum;
+            if (userNum > 0) {
+                // 设置普通用户抽奖信息
+                for (int i = 0; i < userNum; i++) {
+                    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)));
+
+                    // 删除redis中该用户所有的抽奖码
+                    List<MarketingUserCode> list = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+                            .eq(MarketingUserCode::getMarketingId, marketing.getId())
+                            .eq(MarketingUserCode::getUserId, marketingUserCode.getUserId()));
+                    List<Object> delUserCodeList = list.stream().map(MarketingUserCode::getCode).collect(Collectors.toList());
+                    redisCache.removeSetValueByKey(userLotteryPool, delUserCodeList);
+                    MarketingHitPrize marketingHitPrize = exchangeMarketingHitPrize(marketing, allHitPrizeList, marketingAwards, marketingAwardsPrizeList, marketingUserCode);
+
+                    userHitPrizeList.add(marketingHitPrize);
+
+                    // 更新用户中奖状态
+                    marketingUserCode.setIsHit(1);
+                    userCodeUpdateList.add(marketingUserCode);
+                }
+            }
+        }
+        // 保存中奖信息
+        Assert.isTrue(marketingHitPrizeService.saveBatch(allHitPrizeList), "保存中奖信息失败。marketingId:" + marketing.getId());
+
+        // 更新抽奖码中奖状态
+        if (CollectionUtils.isNotEmpty(userCodeUpdateList)) {
+            boolean rtn = marketingUserCodeService.updateBatchById(userCodeUpdateList);
+            Assert.isTrue(rtn, "更新抽奖码中奖状态失败。marketingId:" + marketing.getId());
+        }
+
+        // 发奖品
+        if (CollectionUtils.isNotEmpty(userHitPrizeList)) {
+            for (MarketingHitPrize marketingHitPrize : userHitPrizeList) {
+                MarketingAwardsPrize marketingAwardsPrize = marketingAwardsPrizeService.getById(marketingHitPrize.getPrizeId());
+
+                // 放入仓库
+                if (marketingAwardsPrize.getPrizeType() == TicketPrizeTypeEnum.COIN) {
+                    userCoinService.produce(marketingHitPrize.getUserId(), marketingAwardsPrize.getValue(), String.valueOf(marketingHitPrize.getId()), CoinLogTypeEnum.MARKETING);
+                } else if (marketingAwardsPrize.getPrizeType() == TicketPrizeTypeEnum.COUPON) {
+                    couponService.distributeByMarketing(marketingHitPrize.getUserId(), marketingAwardsPrize.getRefId());
+                } else if (marketingAwardsPrize.getPrizeType() == TicketPrizeTypeEnum.COUPON_PKG) {
+                    couponPkgService.distributeByMarketing(marketingHitPrize.getUserId(), marketingAwardsPrize.getRefId());
+                } else {
+                    // 实物奖品
+                    userPrizeStorageService.takeInStorage(marketingHitPrize.getUserId(), marketingAwardsPrize.getTitle(), marketingAwardsPrize.getPicUrl(), marketingAwardsPrize.getRefId(), PrizeStorageInTypeEnum.MARKETING, marketingHitPrize.getId());
+                }
+            }
+        }
+
+        // 更新活动状态
+        boolean rtn = this.update(new LambdaUpdateWrapper<Marketing>().set(Marketing::getTriggerStatus, 1)
+                .eq(Marketing::getId, marketing.getId())
+                .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();
+        // 邀请码解码,被助力人id
+        Long helpedUserId = MarketingUtils.decodeInviteCode(inviteCode);
+
+        Marketing marketing = this.getById(marketingHelpParam.getMarketingId());
+
+        // 处理助力信息
+        // 判断是否已经助力过
+        int count = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
+                .eq(MarketingUserCode::getHelpUserId, userId));
+        if (count > 0) {
+            throw new ServiceException("您已经助力过了");
+        }
+        // 生成抽奖码
+        String code = "";
+        while (true) {
+            code = MarketingUtils.generatePrizeCode();
+            // 判断抽奖码是否已经存在
+            int codeCount = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
+                    .eq(MarketingUserCode::getMarketingId, marketing.getId())
+                    .eq(MarketingUserCode::getCode, code));
+            if (codeCount > 0) {
+                continue;
+            }
+            break;
+        }
+
+        MarketingUserCode marketingUserCode = new MarketingUserCode();
+        // 设置活动id
+        marketingUserCode.setMarketingId(marketingHelpParam.getMarketingId());
+        // 设置抽奖码
+        marketingUserCode.setCode(code);
+        // 设置被助力人
+        marketingUserCode.setUserId(helpedUserId);
+        // 设置助力人
+        marketingUserCode.setHelpUserId(userId);
+        marketingUserCode.setUserType(UserTypeEnum.ORDINARY.getValue());
+
+        // 保存助力信息
+        marketingUserCodeService.save(marketingUserCode);
+
+
+        // 真实用户
+        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
+
+
+        // 获取最新活动信息
+        marketing.setRealNum(realNum);
+        int randomNum = (int) 1 + (int) (Math.random() * 10);
+        marketing.setFakeNum(marketing.getFakeNum() + randomNum);
+        // 更新活动参与人数
+        boolean rtn = this.updateById(marketing);
+        Assert.isTrue(rtn, "更新活动参与人数异常。marketingId:" + marketing.getId());
+
+        // 给被助力人发送成功消息
+        wxSubscribeMessage.sendMarketingHelp(helpedUserId, marketing);
+
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteById(Long id) {
+        this.removeById(id);
+
+        // 删除奖级和奖品信息
+        marketingAwardsService.remove(new LambdaQueryWrapper<MarketingAwards>().eq(MarketingAwards::getMarketingId, id));
+
+        marketingAwardsPrizeService.remove(new LambdaQueryWrapper<MarketingAwardsPrize>().eq(MarketingAwardsPrize::getMarketingId, id));
+
+    }
+
+    @Override
+    public List<Marketing> listMarketing(MarketingQueryParam marketingQueryParam) {
+        LambdaQueryWrapper<Marketing> queryWrapper = new LambdaQueryWrapper<>();
+        if (StringUtils.isNotBlank(marketingQueryParam.getTitle())) {
+            queryWrapper.like(Marketing::getTitle, marketingQueryParam.getTitle());
+        }
+
+        if (Objects.nonNull(marketingQueryParam.getStatus())) {
+            if (MarketingStatusEnum.CLOSE.getValue().equals(marketingQueryParam.getStatus()) ||
+                    MarketingStatusEnum.OFF.getValue().equals(marketingQueryParam.getStatus()) ||
+                    MarketingStatusEnum.ON.getValue().equals(marketingQueryParam.getStatus())) {
+                queryWrapper.eq(Marketing::getIsOn, marketingQueryParam.getStatus());
+            } else if (MarketingStatusEnum.UNSTART.getValue().equals(marketingQueryParam.getStatus())) {
+                queryWrapper.gt(Marketing::getStartTime, new Date());
+                queryWrapper.eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue());
+            } else if (MarketingStatusEnum.START.getValue().equals(marketingQueryParam.getStatus())) {
+                queryWrapper.le(Marketing::getStartTime, new Date());
+                queryWrapper.ge(Marketing::getEndTime, new Date());
+                queryWrapper.eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue());
+            } else if (MarketingStatusEnum.END.getValue().equals(marketingQueryParam.getStatus())) {
+                queryWrapper.lt(Marketing::getEndTime, new Date());
+                queryWrapper.eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue());
+            }
+        }
+
+        queryWrapper.orderByDesc(Marketing::getCreatedTime);
+
+
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createMarketing(MarketingCreateParam marketingCreateParam) {
+        // 保存营销活动
+        Marketing marketing = new Marketing();
+        BeanUtils.copyProperties(marketingCreateParam, marketing);
+
+        if (StringUtils.isNotBlank(marketingCreateParam.getDescription())) {
+            marketing.setDescription(URLDecoder.decode(marketingCreateParam.getDescription()));
+        }
+
+        this.save(marketing);
+
+        // 创建奖级和奖品信息
+        createAwardsAndPrize(marketingCreateParam.getAwardsList(), marketing);
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateMarketing(MarketingUpdateParam marketingUpdateParam) {
+        Marketing marketing = this.getById(marketingUpdateParam.getId());
+        if (Objects.isNull(marketing)) {
+            throw new ServiceException("营销活动不存在");
+        }
+
+        if (MarketingStatusEnum.CLOSE.getValue().equals(marketing.getIsOn())) {
+            throw new ServiceException("活动当前状态不支持修改");
+        }
+
+        Date now = DateUtils.getNowDate();
+        if (MarketingStatusEnum.ON.getValue().equals(marketing.getIsOn()) && now.after(marketing.getEndTime())) {
+            throw new ServiceException("活动当前状态不支持修改");
+        }
+
+
+        // 更新营销活动
+        BeanUtils.copyProperties(marketingUpdateParam, marketing);
+
+        if (StringUtils.isNotBlank(marketingUpdateParam.getDescription())) {
+            marketing.setDescription(URLDecoder.decode(marketingUpdateParam.getDescription()));
+        }
+
+        this.updateById(marketing);
+
+        // 删除奖级和奖品信息
+        marketingAwardsService.remove(new LambdaUpdateWrapper<MarketingAwards>().eq(MarketingAwards::getMarketingId, marketing.getId()));
+        marketingAwardsPrizeService.remove(new LambdaUpdateWrapper<MarketingAwardsPrize>().eq(MarketingAwardsPrize::getMarketingId, marketing.getId()));
+
+        // 创建奖级和奖品信息
+        createAwardsAndPrize(marketingUpdateParam.getAwardsList(), marketing);
+    }
+
+    /**
+     * 创建奖级和奖品信息
+     *
+     * @param awardsList
+     * @param marketing
+     */
+    private void createAwardsAndPrize(List<MarketingAwardsParam> awardsList, Marketing marketing) {
+        int sum = awardsList.stream().mapToInt(MarketingAwardsParam::getQuantity).sum();
+        if (sum > 100) {
+            throw new ServiceException("中奖名额不能超过100");
+        }
+
+        // 创建奖级
+        List<MarketingAwardsPrize> marketingAwardsPrizeList = new ArrayList<>();
+        for (MarketingAwardsParam awardsParam : awardsList) {
+            if (awardsParam.getInsideNum() > awardsParam.getQuantity()) {
+                throw new ServiceException("内定数量不能大于奖品数量");
+            }
+
+            MarketingAwards marketingAwards = new MarketingAwards();
+            BeanUtils.copyProperties(awardsParam, marketingAwards);
+            marketingAwards.setId(null);
+            // 设置活动id
+            marketingAwards.setMarketingId(marketing.getId());
+            marketingAwardsService.save(marketingAwards);
+
+            for (MarketingAwardsPrizeParam prizeParam : awardsParam.getPrizeList()) {
+                MarketingAwardsPrize awardsPrize = new MarketingAwardsPrize();
+                BeanUtils.copyProperties(prizeParam, awardsPrize);
+                awardsPrize.setId(null);
+                awardsPrize.setMarketingId(marketing.getId());
+                awardsPrize.setAwardsId(marketingAwards.getId());
+                if (null == awardsPrize.getQuantity() || 0 == awardsPrize.getQuantity()) {
+                    // 页面没设置奖品的具体数量,则默认为整个奖项的数量
+                    awardsPrize.setQuantity(marketingAwards.getQuantity());
+                }
+                if (prizeParam.getPrizeType() == TicketPrizeTypeEnum.GOODS) {
+                    Goods goods = goodsService.getById(awardsPrize.getRefId());
+                    awardsPrize.setTitle(goods.getTitle());
+                    awardsPrize.setPicUrl(goods.getPicUrl());
+                    awardsPrize.setValue(goods.getValue());
+                } else if (prizeParam.getPrizeType() == TicketPrizeTypeEnum.COUPON) {
+                    Coupon coupon = couponService.getById(awardsPrize.getRefId());
+                    awardsPrize.setTitle(coupon.getTitle());
+                    awardsPrize.setPicUrl(coupon.getPicUrl());
+                    awardsPrize.setValue(coupon.getDiscount());
+                } else if (prizeParam.getPrizeType() == TicketPrizeTypeEnum.COUPON_PKG) {
+                    CouponPkg couponPkg = couponPkgService.getById(awardsPrize.getRefId());
+                    awardsPrize.setTitle(couponPkg.getTitle());
+                    awardsPrize.setValue(couponPkg.getFacePrice());
+                    awardsPrize.setPicUrl(couponPkg.getPicUrl());
+                } else {
+                    awardsPrize.setTitle("盲豆");
+                    awardsPrize.setPicUrl(Constants.MANGDOU_PIC);
+                }
+                marketingAwardsPrizeList.add(awardsPrize);
+            }
+        }
+        marketingAwardsPrizeService.saveBatch(marketingAwardsPrizeList);
+    }
+
+
+}

+ 89 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/PrizeRecoveryServiceImpl.java

@@ -0,0 +1,89 @@
+package com.qs.mp.admin.service.impl;
+
+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.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.PrizeRecovery;
+import com.qs.mp.admin.domain.vo.PrizeRecoveryListVO;
+import com.qs.mp.admin.mapper.PrizeRecoveryMapper;
+import com.qs.mp.admin.service.IGoodsService;
+import com.qs.mp.admin.service.IPrizeRecoveryService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.common.enums.CoinLogTypeEnum;
+import com.qs.mp.common.enums.PrizeStorageInTypeEnum;
+import com.qs.mp.common.enums.PrizeStorageStatusEnum;
+import com.qs.mp.common.enums.TicketPrizeTypeEnum;
+import com.qs.mp.common.exception.ServiceException;
+import com.qs.mp.user.domain.UserPrizeStorage;
+import com.qs.mp.user.domain.param.UserPrizeRecoveryCreateParam;
+import com.qs.mp.user.domain.vo.PrizeRecoverySettleVO;
+import com.qs.mp.user.service.IUserCoinService;
+import com.qs.mp.user.service.IUserPrizeStorageService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 奖品回收表 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-05-23
+ */
+@Service
+public class PrizeRecoveryServiceImpl extends ServiceImpl<PrizeRecoveryMapper, PrizeRecovery> implements IPrizeRecoveryService {
+
+    @Autowired
+    private IUserPrizeStorageService userPrizeStorageService;
+
+    @Autowired
+    private IUserCoinService userCoinService;
+
+    @Override
+    public int countByWrapper(QueryWrapper<PrizeRecovery> queryWrapper) {
+        return this.baseMapper.countByWrapper(queryWrapper);
+    }
+
+    @Override
+    public List<PrizeRecoveryListVO> listByWrapper(QueryWrapper<PrizeRecovery> queryWrapper) {
+        return this.baseMapper.listByWrapper(queryWrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitRecovery(Long userId, PrizeRecoverySettleVO settleVO) {
+        PrizeRecovery prizeRecovery = new PrizeRecovery();
+        BeanUtils.copyProperties(settleVO, prizeRecovery);
+        prizeRecovery.setUserId(userId);
+
+        UserPrizeStorage userPrizeStorage = userPrizeStorageService.getById(settleVO.getStorageId());
+
+        // 更新用户奖品状态
+        boolean rtn = userPrizeStorageService.update(new LambdaUpdateWrapper<UserPrizeStorage>()
+                .set(userPrizeStorage.getGoodsNum() - settleVO.getNum() == 0, UserPrizeStorage::getStatus, PrizeStorageStatusEnum.RECOVERY)
+                .set(UserPrizeStorage::getGoodsNum, userPrizeStorage.getGoodsNum() - settleVO.getNum())
+                .eq(UserPrizeStorage::getUserId, userId)
+                .eq(UserPrizeStorage::getStorageId, settleVO.getStorageId())
+                .eq(UserPrizeStorage::getGoodsNum, userPrizeStorage.getGoodsNum())
+                .eq(UserPrizeStorage::getStatus, PrizeStorageStatusEnum.NOT_DISTRIBUTED));
+
+        Assert.isTrue(rtn, "更新奖品状态失败。storageId:" + settleVO.getStorageId());
+
+        // 保存回收记录
+        this.save(prizeRecovery);
+
+        // 发放盲豆
+        userCoinService.produce(userId, settleVO.getReturnCoin(), prizeRecovery.getId().toString(), CoinLogTypeEnum.RECOVERY);
+
+
+    }
+}

+ 20 - 1
mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketAwardsPrizeServiceImpl.java

@@ -8,6 +8,8 @@ import com.qs.mp.admin.mapper.TicketAwardsPrizeMapper;
 import com.qs.mp.admin.service.ITicketAwardsPrizeService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import java.util.List;
+
+import com.qs.mp.common.enums.AwardsLabelEnum;
 import org.springframework.stereotype.Service;
 
 /**
@@ -29,7 +31,24 @@ public class TicketAwardsPrizeServiceImpl extends ServiceImpl<TicketAwardsPrizeM
 
   @Override
   public List<TicketAwardsPrizeVO> listPrizeVO(String boxId) {
+    StringBuilder sortField = new StringBuilder("FIELD(t1.awards_label,'");
+    int i = 0;
+    for (AwardsLabelEnum awardsLabelEnum : AwardsLabelEnum.values()) {
+      i++;
+      if (i == AwardsLabelEnum.values().length) {
+        sortField.append(awardsLabelEnum.getValue()).append("'");
+      }else {
+        sortField.append(awardsLabelEnum.getValue()).append("','");
+      }
+
+    }
+    sortField.append(")");
+
     return listPrizeVO(new QueryWrapper<TicketAwardsPrize>()
-        .eq("t1.box_id", boxId).eq("t2.is_deleted", 0).orderByAsc("t1.sort").orderByDesc("t2.sort_weight").orderByDesc("t2.value"));
+            .eq("t1.box_id", boxId)
+            .eq("t2.is_deleted", 0)
+            .orderByAsc(sortField.toString())
+            .orderByDesc("t2.sort_weight")
+            .orderByDesc("t2.value"));
   }
 }

+ 9 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketAwardsServiceImpl.java

@@ -1,11 +1,15 @@
 package com.qs.mp.admin.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qs.mp.admin.domain.TicketAwards;
 import com.qs.mp.admin.mapper.TicketAwardsMapper;
 import com.qs.mp.admin.service.ITicketAwardsService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.user.domain.vo.TicketAwardsLabelVO;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * <p>
  * 盲票盒奖项设置 服务实现类
@@ -18,4 +22,9 @@ import org.springframework.stereotype.Service;
 public class TicketAwardsServiceImpl extends ServiceImpl<TicketAwardsMapper, TicketAwards> implements
     ITicketAwardsService {
 
+
+    @Override
+    public List<TicketAwardsLabelVO> groupSumQuantityByQueryWrapper(QueryWrapper<TicketAwards> queryWrapper) {
+        return this.baseMapper.groupSumQuantityByQueryWrapper(queryWrapper);
+    }
 }

+ 73 - 19
mp-service/src/main/java/com/qs/mp/admin/service/impl/TicketBoxServiceImpl.java

@@ -16,6 +16,7 @@ import com.qs.mp.admin.mapper.TicketBoxMapper;
 import com.qs.mp.admin.service.*;
 import com.qs.mp.channel.domain.Channel;
 import com.qs.mp.channel.service.IChannelService;
+import com.qs.mp.common.constant.Constants;
 import com.qs.mp.common.enums.*;
 import com.qs.mp.common.exception.ServiceException;
 import com.qs.mp.common.pulsar.PulsarClientService;
@@ -26,14 +27,7 @@ import com.qs.mp.system.service.id.BizIdGenerator;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import lombok.Data;
@@ -109,12 +103,17 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
       throw new ServiceException("盲票组ID不能为空");
     }
     // 设置更新盲票基本信息
-    TicketBox ticketBox = new TicketBox();
+    TicketBox ticketBox = this.getById(param.getBoxId());
+    if (Objects.isNull(ticketBox)) {
+      throw new ServiceException("盲票信息不存在");
+    }
+
     ticketBox.setBoxId(param.getBoxId());
     ticketBox.setTitle(param.getTitle());
     ticketBox.setPicUrl(param.getPicUrl());
     ticketBox.setSalePrice(param.getSalePrice());
     ticketBox.setPkgSalePrice(param.getPkgSalePrice());
+    ticketBox.setOriginPrice(param.getOriginPrice());
     ticketBox.setSaleCommRate(param.getSaleCommRate());
     ticketBox.setSaleChannelType(param.getSaleChannelType());
     this.updateById(ticketBox);
@@ -163,13 +162,26 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
       throw new ServiceException("奖级数量与原来不同");
     }
 
+    List<TicketAwards> ticketAwardsList = new ArrayList<>();
     // 更新奖品信息
     for (TicketAwardsParam ticketAwardsParam : param.getAwardsList()) {
        if (CollectionUtils.isEmpty(ticketAwardsParam.getPrizeList())) {
         throw new ServiceException("奖品列表不能为空");
        }
 
-       List<TicketAwardsPrize> ticketAwardsPrizeList = new ArrayList<>();
+      // 线上票需要判断商品数量合法性
+      if (TicketTypeEnum.ONLINE.equals(ticketBox.getType())) {
+        int quantity = ticketAwardsParam.getPrizeList().stream().mapToInt(TicketAwardsPrizeParam::getQuantity).sum();
+        if (quantity != ticketAwardsParam.getQuantity()) {
+          throw new ServiceException("奖品总数值不相同");
+        }
+      }
+      TicketAwards ticketAwards = new TicketAwards();
+      ticketAwards.setAwardsId(ticketAwardsParam.getAwardsId());
+      ticketAwards.setAwardsLabel(ticketAwardsParam.getAwardsLabel());
+      ticketAwardsList.add(ticketAwards);
+
+      List<TicketAwardsPrize> ticketAwardsPrizeList = new ArrayList<>();
       List<String> prizeIdList = ticketAwardsParam.getPrizeList().stream().map(ticketAwardsPrizeParam -> {
         // 奖品id为空则封装要新增的奖品
         if (StringUtils.isBlank(ticketAwardsPrizeParam.getPrizeId())) {
@@ -178,8 +190,8 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
           awardsPrize.setPrizeId(bizIdGenerator.newId());
           awardsPrize.setBoxId(ticketBox.getBoxId());
           awardsPrize.setAwardsId(ticketAwardsParam.getAwardsId());
-          if (null == awardsPrize.getQuantity() || 0 == awardsPrize.getQuantity()) {
-            // 页面没设置奖品的具体数量,则默认为整个奖项的数量
+          if (TicketTypeEnum.OFFLINE.equals(ticketBox.getType())) {
+            // 线下票的则默认为整个奖项的数量
             awardsPrize.setQuantity(ticketAwardsParam.getQuantity());
           }
           awardsPrize.setRemainQty(awardsPrize.getQuantity());
@@ -201,18 +213,50 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
             awardsPrize.setPicUrl(couponPkg.getPicUrl());
           } else {
             awardsPrize.setTitle("盲豆");
-            awardsPrize.setPicUrl("md.jpeg");
+            awardsPrize.setPicUrl(Constants.MANGDOU_PIC);
           }
           ticketAwardsPrizeList.add(awardsPrize);
         } else {
-          // 更新排序权重字段
-          ticketAwardsPrizeService.update(new LambdaUpdateWrapper<TicketAwardsPrize>().set(TicketAwardsPrize::getSortWeight, ticketAwardsPrizeParam.getSortWeight()).eq(TicketAwardsPrize::getPrizeId, ticketAwardsPrizeParam.getPrizeId()));
+          boolean rtn = true;
+          if (TicketTypeEnum.ONLINE.equals(ticketBox.getType())) {
+            TicketAwardsPrize ticketAwardsPrize = ticketAwardsPrizeService.getById(ticketAwardsPrizeParam.getPrizeId());
+            // 校验奖品数量不能少于已兑奖数量
+            if (ticketAwardsPrizeParam.getQuantity() < ticketAwardsPrize.getCashedQty()) {
+              throw new ServiceException("奖品数量不能少于已兑奖数量");
+            }
+            // 更新奖级信息等
+            rtn = ticketAwardsPrizeService.update(new LambdaUpdateWrapper<TicketAwardsPrize>()
+                    .set(TicketAwardsPrize::getSortWeight, ticketAwardsPrizeParam.getSortWeight())
+                    .set(TicketAwardsPrize::getQuantity, ticketAwardsPrizeParam.getQuantity())
+                    .set(TicketAwardsPrize::getRemainQty, ticketAwardsPrizeParam.getQuantity() - ticketAwardsPrize.getCashedQty())
+                    .eq(TicketAwardsPrize::getPrizeId, ticketAwardsPrizeParam.getPrizeId())
+                    .eq(TicketAwardsPrize::getRemainQty, ticketAwardsPrize.getRemainQty()));
+
+          } else {
+            // 更新排序权重字段
+            rtn = ticketAwardsPrizeService.update(new LambdaUpdateWrapper<TicketAwardsPrize>()
+                    .set(TicketAwardsPrize::getSortWeight, ticketAwardsPrizeParam.getSortWeight())
+                    .eq(TicketAwardsPrize::getPrizeId, ticketAwardsPrizeParam.getPrizeId()));
+          }
+          if (!rtn) {
+            throw new ServiceException("操作盲票频繁,请重试");
+          }
+
         }
         return ticketAwardsPrizeParam.getPrizeId();
       }).collect(Collectors.toList());
 
       // 如果新的奖品id为空,则清除原来的所有奖品
       if (CollectionUtils.isEmpty(prizeIdList)) {
+        if (TicketTypeEnum.ONLINE.equals(ticketBox.getType())) {
+          int cashedCount = ticketAwardsPrizeService.count(new LambdaQueryWrapper<TicketAwardsPrize>()
+                  .eq(TicketAwardsPrize::getAwardsId, ticketAwardsParam.getAwardsId())
+                  .ne(TicketAwardsPrize::getCashedQty, 0));
+          if (cashedCount != 0) {
+            throw new ServiceException("包含已兑奖记录的奖品不能从奖级中删除");
+          }
+        }
+
         ticketAwardsPrizeService.update(new LambdaUpdateWrapper<TicketAwardsPrize>().set(TicketAwardsPrize::getIsDeleted, 1).eq(TicketAwardsPrize::getAwardsId, ticketAwardsParam.getAwardsId()));
       } else {
         // 去重后,清除原来的奖品
@@ -229,6 +273,15 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
         oldPrizeIdList.removeAll(prizeIdList);
 
         if (CollectionUtils.isNotEmpty(oldPrizeIdList)) {
+          if (TicketTypeEnum.ONLINE.equals(ticketBox.getType())) {
+            int cashedCount = ticketAwardsPrizeService.count(new LambdaQueryWrapper<TicketAwardsPrize>()
+                    .in(TicketAwardsPrize::getPrizeId, oldPrizeIdList)
+                    .ne(TicketAwardsPrize::getCashedQty, 0));
+            if (cashedCount != 0) {
+              throw new ServiceException("包含已兑奖记录的奖品不能从奖级中删除");
+            }
+          }
+
           // 删除原来的奖品信息
           ticketAwardsPrizeService.update(new LambdaUpdateWrapper<TicketAwardsPrize>().set(TicketAwardsPrize::getIsDeleted, 1).in(TicketAwardsPrize::getPrizeId, oldPrizeIdList));
         }
@@ -240,7 +293,8 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
       }
 
     }
-
+    //更新奖级信息
+    ticketAwardsService.updateBatchById(ticketAwardsList);
     return true;
   }
 
@@ -313,8 +367,8 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
         awardsPrize.setPrizeId(bizIdGenerator.newId());
         awardsPrize.setBoxId(ticketBox.getBoxId());
         awardsPrize.setAwardsId(ticketAwards.getAwardsId());
-        if (null == awardsPrize.getQuantity() || 0 == awardsPrize.getQuantity()) {
-          // 页面没设置奖品的具体数量,则默认为整个奖项的数量
+        if (TicketTypeEnum.OFFLINE.equals(ticketBox.getType())) {
+          // 线下票的则默认为整个奖项的数量
           awardsPrize.setQuantity(ticketAwards.getQuantity());
         }
         awardsPrize.setRemainQty(awardsPrize.getQuantity());
@@ -336,7 +390,7 @@ public class TicketBoxServiceImpl extends ServiceImpl<TicketBoxMapper, TicketBox
           awardsPrize.setPicUrl(couponPkg.getPicUrl());
         } else {
           awardsPrize.setTitle("盲豆");
-          awardsPrize.setPicUrl("md.jpeg");
+          awardsPrize.setPicUrl(Constants.MANGDOU_PIC);
         }
         awardsPrizeList.add(awardsPrize);
       }

+ 26 - 20
mp-service/src/main/java/com/qs/mp/channel/service/impl/ChannelOrderServiceImpl.java

@@ -326,26 +326,32 @@ public class ChannelOrderServiceImpl extends
 		}
 
 		boolean res = channelOrderDetailervice.saveBatch(detailList);
-		if(res) {
-			// 修改票包已销售状态
-			LambdaUpdateWrapper<TicketPackage> updateWrapper = new LambdaUpdateWrapper<TicketPackage>();
-			updateWrapper.set(TicketPackage::getStatus, TicketPkgStatusEnum.SOLD);
-			updateWrapper.in(TicketPackage::getPkgId, pkgIdList);
-			boolean rtn = ticketPackageService.update(updateWrapper);
-			if(rtn){
-				// 修改订单信息
-                String deliveryFlowId = channelOrder.getDeliveryFlowId();
-
-                // 物流单号为空则表示无需物流发货
-                if (StringUtils.isBlank(deliveryFlowId)) {
-                    channelOrder.setStatus(ChannelOrderStatusEnum.FINISHED);
-                } else {
-                    channelOrder.setStatus(ChannelOrderStatusEnum.NOT_CONFIRM);
-                }
-                return updateById(channelOrder);
-			}
-		}
-		return false;
+		if(!res) {
+      throw new ServiceException("保存订单票包发货明细失败");
+    }
+    // 修改票包已销售状态
+    LambdaUpdateWrapper<TicketPackage> updateWrapper = new LambdaUpdateWrapper<TicketPackage>();
+    updateWrapper.set(TicketPackage::getStatus, TicketPkgStatusEnum.SOLD);
+    updateWrapper.in(TicketPackage::getPkgId, pkgIdList);
+    updateWrapper.eq(TicketPackage::getStatus, TicketPkgStatusEnum.FOR_SALE);
+    boolean rtn = ticketPackageService.update(updateWrapper);
+    if(!rtn) {
+      throw new ServiceException("更新票包发货状态失败");
+    }
+    // 修改订单信息
+    String deliveryFlowId = channelOrder.getDeliveryFlowId();
+
+    // 物流单号为空则表示无需物流发货
+    if (StringUtils.isBlank(deliveryFlowId)) {
+        channelOrder.setStatus(ChannelOrderStatusEnum.FINISHED);
+    } else {
+        channelOrder.setStatus(ChannelOrderStatusEnum.NOT_CONFIRM);
+    }
+    boolean updateOrderRtn = updateById(channelOrder);
+    if (!updateOrderRtn) {
+      throw new ServiceException("更新订单状态失败");
+    }
+    return true;
 	}
 
 	@Override

+ 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";

+ 2 - 1
mp-service/src/main/java/com/qs/mp/framework/redis/RedisKey.java

@@ -13,7 +13,8 @@ public enum RedisKey {
     CHANNEL_ORDER_KEY("channel_order_{0}", "经销商下的盲票采购单"),
     USER_TICKET_ORDER_KEY("user_ticket_order_key_{0}", "用户购票订单"),
     USER_DELIVER_ORDER_KEY("user_deliver_order_key_{0}", "用户提货订单"),
-    OFFLINE_TICKET_ID_KEY("offline_ticket_id_key_{0}", "线下盲票ID"),;
+    OFFLINE_TICKET_ID_KEY("offline_ticket_id_key_{0}", "线下盲票ID"),
+    USER_PRIZE_RECOVERY_KEY("user_prize_recovery_key_{0}", "用户奖品回收");
 
     public String keyTemplate;
     public String desc;

+ 28 - 0
mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java

@@ -0,0 +1,28 @@
+package com.qs.mp.framework.redis;
+
+import com.qs.mp.common.utils.MessageHelper;
+
+/**
+ * redis Lock Key管理模版
+ *
+ * @author duota
+ *
+ */
+public enum RedisLockKey {
+
+    MARKETING_LOTTERY_KEY("marketing_lottery_key_{0}","免费抽奖活动开奖"),
+    USER_TICKET_CASH_LOCK("user_ticket_cash_lock_{0}", "盲票兑奖锁");
+
+    public String keyTemplate;
+    public String desc;
+
+    RedisLockKey(String keyTemplate, String desc) {
+        this.keyTemplate = keyTemplate;
+        this.desc = desc;
+    }
+
+    public static String build(RedisLockKey redisLockKey, Object... param) {
+        return MessageHelper.formatMsg(redisLockKey.keyTemplate, param);
+    }
+
+}

+ 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));

+ 11 - 0
mp-service/src/main/java/com/qs/mp/system/domain/SysUser.java

@@ -52,6 +52,9 @@ public class SysUser extends BaseEntity
     @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
     private String sex;
 
+    /** 用户类型(00系统用户)(11内部用户) */
+    private String userType;
+
     /** 用户头像 */
     private String avatar;
 
@@ -101,6 +104,14 @@ public class SysUser extends BaseEntity
     private String unionId;  //用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回
     private String sessionKey; //会话密钥
 
+    public String getUserType() {
+        return userType;
+    }
+
+    public void setUserType(String userType) {
+        this.userType = userType;
+    }
+
     public void setOpenId(String openId) {
         this.openId = openId;
     }

+ 7 - 0
mp-service/src/main/java/com/qs/mp/system/mapper/SysUserMapper.java

@@ -145,4 +145,11 @@ public interface SysUserMapper  extends BaseMapper<SysUser>
      */
     int updateUserGzhOpenId(@Param("unionId") String unionId, @Param("gzhOpenId") String gzhOpenId);
 
+    /**
+     * 获取一定数量用户
+     * @param userType
+     * @param insideNum
+     * @return
+     */
+    List<SysUser> selectUserListByRand(@Param("userType") String userType,@Param("num") int num);
 }

+ 8 - 0
mp-service/src/main/java/com/qs/mp/system/service/ISysUserService.java

@@ -221,4 +221,12 @@ public interface ISysUserService  extends IService<SysUser>
      * @return 结果
      */
     public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
+
+    /**
+     * 随机获取一定数量的用户
+     * @param userType
+     * @param insideNum
+     * @return
+     */
+    List<SysUser> selectUserListByRand(String userType, int num);
 }

+ 5 - 0
mp-service/src/main/java/com/qs/mp/system/service/impl/SysUserServiceImpl.java

@@ -572,4 +572,9 @@ public class SysUserServiceImpl  extends ServiceImpl<SysUserMapper, SysUser> imp
         }
         return successMsg.toString();
     }
+
+    @Override
+    public List<SysUser> selectUserListByRand(String userType, int num) {
+        return userMapper.selectUserListByRand(userType, num);
+    }
 }

+ 100 - 0
mp-service/src/main/java/com/qs/mp/user/domain/MarketingHitPrize.java

@@ -0,0 +1,100 @@
+package com.qs.mp.user.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 营销活动中奖信息表实体类
+ * @auther quanshu
+ * @create 2022-05-16 14:29:06
+ */
+@TableName("mp_marketing_hit_prize")
+@Data
+@ApiModel("营销活动中奖信息表实体类")
+public class MarketingHitPrize implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("中奖记录id")
+    @TableId(value = "id", type = IdType.INPUT)
+    private String id;
+
+    /**
+     * 营销活动id
+     */
+    @ApiModelProperty("营销活动id")
+    @TableField("marketing_id")
+    private Long marketingId;
+
+
+    @ApiModelProperty("用户参与码关系id")
+    @TableField("marketing_user_code_id")
+    private Long marketingUserCodeId;
+
+    /**
+     * 用户id
+     */
+    @ApiModelProperty("用户id")
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 用户类型(00系统用户)(11内部用户)
+     */
+    @ApiModelProperty("用户类型(00系统用户)(11内部用户)")
+    @TableField("user_type")
+    private String userType;
+
+    /**
+     * 奖项id
+     */
+    @ApiModelProperty("奖项id")
+    @TableField("awards_id")
+    private Long awardsId;
+
+    /**
+     * 奖品id
+     */
+    @ApiModelProperty("奖品id")
+    @TableField("prize_id")
+    private Long prizeId;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 102 - 0
mp-service/src/main/java/com/qs/mp/user/domain/MarketingUserCode.java

@@ -0,0 +1,102 @@
+package com.qs.mp.user.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @describe 营销活动用户参与码关系表实体类
+ * @auther quanshu
+ * @create 2022-05-16 14:28:52
+ */
+@TableName("mp_marketing_user_code")
+@Data
+@ApiModel("营销活动用户参与码关系表实体类")
+public class MarketingUserCode implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 营销活动id
+     */
+    @ApiModelProperty("营销活动id")
+    @TableField("marketing_id")
+    private Long marketingId;
+
+    /**
+     * 用户id
+     */
+    @ApiModelProperty("用户id")
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 用户类型(00系统用户)(11内部用户)
+     */
+    @ApiModelProperty("用户类型(00系统用户)(11内部用户)")
+    @TableField("user_type")
+    private String userType;
+
+    /**
+     * 助力用户id
+     */
+    @ApiModelProperty("助力用户id")
+    @TableField("help_user_id")
+    private Long helpUserId;
+
+    /**
+     * 参与码
+     */
+    @ApiModelProperty("参与码")
+    @TableField("code")
+    private String code;
+
+    /**
+     * 是否中奖 0未中奖,1中奖
+     */
+    @ApiModelProperty("是否中奖 0未中奖,1中奖")
+    @TableField("is_hit")
+    private Integer isHit;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 7 - 0
mp-service/src/main/java/com/qs/mp/user/domain/UserDeliverOrder.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.Version;
+import com.qs.mp.common.enums.DeliverOrderResourceEnum;
 import com.qs.mp.common.enums.DeliverOrderStatusEnum;
 import java.io.Serializable;
 import java.util.Date;
@@ -111,6 +112,12 @@ public class UserDeliverOrder implements Serializable {
   @TableField("memo")
   private String memo;
 
+
+  @ApiModelProperty("订单来源:1提货订单,2现金购买")
+  @TableField("resource")
+  @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+  private DeliverOrderResourceEnum resource;
+
   /**
    * 订单状态;-1 已取消 0:待付款 1:待发货 2:待收货 3:已完成
    */

+ 4 - 4
mp-service/src/main/java/com/qs/mp/user/domain/UserPrizeStorage.java

@@ -84,11 +84,11 @@ public class UserPrizeStorage implements Serializable {
   private Integer goodsNum;
 
   /**
-   * 入库类型;1盲票兑奖、2盲豆兑换
+   * 入库类型;1盲票兑奖、2盲豆兑换、3营销活动
    */
   @TableField("in_type")
   @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
-  @ApiModelProperty("入库类型;1盲票兑奖、2盲豆兑换")
+  @ApiModelProperty("入库类型;1盲票兑奖、2盲豆兑换,3营销活动")
   private PrizeStorageInTypeEnum inType;
 
   /**
@@ -99,11 +99,11 @@ public class UserPrizeStorage implements Serializable {
   private String refId;
 
   /**
-   * 状态;1待提货、2已提货
+   * 状态;1待提货、2已提货,3已回收
    */
   @TableField("status")
   @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
-  @ApiModelProperty("状态;1待提货、2已提货")
+  @ApiModelProperty("状态;1待提货、2已提货,3已回收")
   private PrizeStorageStatusEnum status;
 
   /**

+ 3 - 0
mp-service/src/main/java/com/qs/mp/user/domain/excel/DeliverOrderExcel.java

@@ -57,6 +57,9 @@ public class DeliverOrderExcel {
     @Excel(name = "订单编号")
     private String orderId;
 
+    @Excel(name = "订单类型")
+    private String resource;
+
     /**
      * 收货人
      */

Деякі файли не було показано, через те що забагато файлів було змінено