Browse Source

feature: 拼团

liufeng 1 year ago
parent
commit
56fd9be5fa
27 changed files with 1656 additions and 6 deletions
  1. 61 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/GroupBuyingController.java
  2. 306 0
      mp-admin/src/main/java/com/qs/mp/web/controller/api/user/GroupBuyingOrderController.java
  3. 9 0
      mp-common/src/main/java/com/qs/mp/common/core/redis/RedisCache.java
  4. 58 0
      mp-common/src/main/java/com/qs/mp/common/core/redis/redissondelay/PTDelayQueue.java
  5. 1 0
      mp-common/src/main/java/com/qs/mp/common/enums/BizTypeEnum.java
  6. 62 0
      mp-common/src/main/java/com/qs/mp/common/enums/GroupingStatusEnum.java
  7. 3 1
      mp-common/src/main/java/com/qs/mp/common/enums/MqTopicType.java
  8. 51 0
      mp-common/src/main/java/com/qs/mp/common/pulsar/PulsarClientService.java
  9. 14 0
      mp-common/src/main/java/com/qs/mp/common/utils/DateUtils.java
  10. 20 0
      mp-service/src/main/java/com/qs/mp/admin/domain/param/GroupBuyingGroupParam.java
  11. 2 2
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GroupBuying.java
  12. 1 1
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GroupBuyingGroup.java
  13. 1 1
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/GroupBuyingOrder.java
  14. 41 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MyGroupBuyingGroupEndVO.java
  15. 44 0
      mp-service/src/main/java/com/qs/mp/admin/domain/vo/MyGroupBuyingGroupStartVO.java
  16. 10 0
      mp-service/src/main/java/com/qs/mp/admin/mapper/GroupBuyingHitPrizeMapper.java
  17. 16 0
      mp-service/src/main/java/com/qs/mp/admin/service/IGroupBuyingHitPrizeService.java
  18. 19 0
      mp-service/src/main/java/com/qs/mp/admin/service/impl/GroupBuyingHitPrizeServiceImpl.java
  19. 1 0
      mp-service/src/main/java/com/qs/mp/framework/redis/RedisKey.java
  20. 8 1
      mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java
  21. 10 0
      mp-service/src/main/java/com/qs/mp/mq/impl/PulsarConsumerImpl.java
  22. 113 0
      mp-service/src/main/java/com/qs/mp/user/domain/GroupBuyingHitPrize.java
  23. 180 0
      mp-service/src/main/java/com/qs/mp/user/domain/UserGroupOrder.java
  24. 124 0
      mp-service/src/main/java/com/qs/mp/user/domain/param/GroupOrderParam.java
  25. 102 0
      mp-service/src/main/java/com/qs/mp/user/domain/vo/GroupOrderSettleVO.java
  26. 75 0
      mp-service/src/main/java/com/qs/mp/user/service/IUserGroupOrderService.java
  27. 324 0
      mp-service/src/main/java/com/qs/mp/user/service/impl/UserGroupOrderServiceImpl.java

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

@@ -0,0 +1,61 @@
+package com.qs.mp.web.controller.api.user;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.admin.service.IGroupBuyingGroupService;
+import com.qs.mp.admin.service.IGroupBuyingService;
+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.GroupingStatusEnum;
+import com.qs.mp.common.enums.MqTopicType;
+import com.qs.mp.common.pulsar.PulsarClientService;
+import com.qs.mp.common.utils.DateUtils;
+import com.qs.mp.common.utils.LogUtil;
+import com.qs.mp.framework.redis.RedisLockKey;
+import com.qs.mp.system.domain.SysUser;
+import com.qs.mp.system.service.ISysUserService;
+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.pulsar.client.api.PulsarClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api/v1/mp/user/group")
+@Api(tags = "拼团相关接口")
+public class GroupBuyingController extends BaseApiController {
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
+    //拼团活动拼团
+    @Autowired
+    private IGroupBuyingGroupService groupBuyingGroupService;
+    @Autowired
+    private IGroupBuyingService groupBuyingService;
+
+
+
+
+
+
+
+
+
+
+}

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

@@ -0,0 +1,306 @@
+package com.qs.mp.web.controller.api.user;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+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.admin.domain.Ticket;
+import com.qs.mp.admin.domain.TicketBox;
+import com.qs.mp.admin.domain.TicketPackage;
+import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.admin.service.*;
+import com.qs.mp.channel.domain.Channel;
+import com.qs.mp.channel.domain.ChannelOrder;
+import com.qs.mp.channel.domain.param.OrderPayParam;
+import com.qs.mp.channel.domain.vo.PromoterVO;
+import com.qs.mp.channel.service.IPromoterUserService;
+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.core.redis.RedisCache;
+import com.qs.mp.common.enums.*;
+import com.qs.mp.common.exception.ServiceException;
+import com.qs.mp.common.pulsar.PulsarClientService;
+import com.qs.mp.common.utils.LogUtil;
+import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.framework.redis.RedisKey;
+import com.qs.mp.framework.redis.RedisLockKey;
+import com.qs.mp.pay.service.IWalletService;
+import com.qs.mp.system.domain.SysUser;
+import com.qs.mp.system.service.ISysUserService;
+import com.qs.mp.user.domain.UserTicketOrder;
+import com.qs.mp.user.domain.param.GroupOrderParam;
+import com.qs.mp.user.domain.param.TicketOrderParam;
+import com.qs.mp.user.domain.vo.*;
+import com.qs.mp.user.service.IGroupBuyingOrderService;
+import com.qs.mp.user.service.IUserCouponService;
+import com.qs.mp.user.service.IUserGroupOrderService;
+import com.qs.mp.user.service.IUserTicketOrderService;
+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 lombok.AllArgsConstructor;
+import ma.glasnost.orika.MapperFacade;
+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 javax.validation.Valid;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+
+@RestController
+@RequestMapping("/api/v1/mp/user/group/order")
+@Api(tags = "用户端购买盲票接口")
+@AllArgsConstructor
+public class GroupBuyingOrderController extends BaseApiController {
+
+    @Autowired
+    private IWalletService walletService;
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private MapperFacade mapperFacade;
+
+    @Autowired
+    private RedisCache redisCache;
+
+
+    @Autowired
+    private IPromoterUserService promoterUserService;
+
+    @Autowired
+    private IGroupBuyingGroupService groupBuyingGroupService;
+    @Autowired
+    private IGroupBuyingService groupBuyingService;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+    @Autowired
+    private IUserGroupOrderService userGroupOrderService;
+    @Autowired
+    private IGroupBuyingOrderService groupBuyingOrderService;
+
+
+    @PostMapping("/myStartGroup")
+    @ApiOperation(value = "我的拼团-进行中", notes = "我的拼团-进行中")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "success", response = MyGroupBuyingGroupStartVO.class)
+    )
+    public AjaxResult myStartGroup() {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        if (Objects.isNull(userId)) {
+            return AjaxResult.error("用户未登录");
+        }
+        List<GroupBuyingGroup> groupList = groupBuyingGroupService.list(new LambdaQueryWrapper<GroupBuyingGroup>()
+                .eq(GroupBuyingGroup::getPartinIds,userId)
+                .eq(GroupBuyingGroup::getGroupStatus, GroupingStatusEnum.START.getValue())
+                .orderByDesc(GroupBuyingGroup::getCreateTime)
+        );
+        List<MyGroupBuyingGroupStartVO> startVOS = new ArrayList<>();
+        if(com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(groupList)){
+            Set<Long> ids = groupList.stream().map(GroupBuyingGroup::getGroupbuyingId).collect(Collectors.toSet());
+            //获取拼团活动信息
+            List<GroupBuying> groupBuyings = groupBuyingService.list(new LambdaQueryWrapper<GroupBuying>()
+                    .in(GroupBuying::getId,ids)
+            );
+            startVOS =  groupList.stream().map(groupBuyingGroup -> {
+                MyGroupBuyingGroupStartVO startVO = new MyGroupBuyingGroupStartVO();
+                startVO.setGroupEndTime(groupBuyingGroup.getGroupEndTime());
+                startVO.setPartinNum(groupBuyingGroup.getPartinNum());
+                startVO.setGroupId(groupBuyingGroup.getId());
+                Optional<GroupBuying> buyingOption = groupBuyings.stream()
+                        .filter(groupBuying -> groupBuyingGroup.getGroupbuyingId().equals(groupBuying.getId())).findFirst();
+                if(buyingOption.isPresent()){
+                    startVO.setGroupSize(buyingOption.get().getGroupSize());
+                    startVO.setPicUrl(buyingOption.get().getPicUrl());
+                    startVO.setEndTime(buyingOption.get().getEndTime());
+                    startVO.setTitle(buyingOption.get().getTitle());
+                    startVO.setGroupBuyingId(buyingOption.get().getId());
+                }
+                return  startVO;
+            }).collect(Collectors.toList());
+        }
+        return AjaxResult.success(startVOS);
+    }
+
+    @PostMapping("/myEndGroup")
+    @ApiOperation(value = "我的拼团-已开奖", notes = "我的拼团-已开奖")
+    @ApiResponses(
+            @ApiResponse(code = 200, message = "success", response = MyGroupBuyingGroupEndVO.class)
+    )
+    public AjaxResult myEndGroup(){
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        if (Objects.isNull(userId)) {
+            return AjaxResult.error("用户未登录");
+        }
+        List<GroupBuyingGroup> groupList = groupBuyingGroupService.list(new LambdaQueryWrapper<GroupBuyingGroup>()
+                .eq(GroupBuyingGroup::getPartinIds,userId)
+                .eq(GroupBuyingGroup::getGroupStatus,GroupingStatusEnum.END.getValue())
+                .orderByDesc(GroupBuyingGroup::getCreateTime)
+        );
+        List<MyGroupBuyingGroupEndVO> endVOS = new ArrayList<>();
+        if(CollectionUtils.isNotEmpty(groupList)){
+            Set<Long> ids = groupList.stream().map(GroupBuyingGroup::getGroupbuyingId).collect(Collectors.toSet());
+            //获取拼团活动信息
+            List<GroupBuying> groupBuyings = groupBuyingService.list(new LambdaQueryWrapper<GroupBuying>()
+                    .in(GroupBuying::getId,ids)
+            );
+            endVOS =  groupList.stream().map(groupBuyingGroup -> {
+                MyGroupBuyingGroupEndVO endVO = new MyGroupBuyingGroupEndVO();
+                endVO.setGroupEndTime(groupBuyingGroup.getGroupEndTime());
+                endVO.setGroupId(groupBuyingGroup.getId());
+                Optional<GroupBuying> buyingOption = groupBuyings.stream()
+                        .filter(groupBuying -> groupBuyingGroup.getGroupbuyingId().equals(groupBuying.getId())).findFirst();
+                if(buyingOption.isPresent()){
+                    endVO.setPicUrl(buyingOption.get().getPicUrl());
+                    endVO.setTitle(buyingOption.get().getTitle());
+                    endVO.setGroupBuyingId(buyingOption.get().getId());
+                }
+                return  endVO;
+            }).collect(Collectors.toList());
+        }
+        return AjaxResult.success(endVOS);
+    }
+
+
+    /**
+     * 订单结算
+     */
+    @PostMapping("/order/settle")
+    @ApiOperation(value = "订单结算", notes = "参团立即支付")
+    @ApiResponses(
+        @ApiResponse(code = 200, message = "OK", response = TicketOrderSettleVO.class)
+    )
+    public AjaxResult settle(@Valid @RequestBody GroupOrderParam param) {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        if (Objects.isNull(userId)) {
+            return AjaxResult.error("用户未登录");
+        }
+        if (ObjectUtil.isNull(param.getGroupbuyingId()) && ObjectUtil.isNull(param.getGroupId())) {
+            return AjaxResult.error("参数缺失");
+        }
+        GroupOrderSettleVO orderSettleVO = new GroupOrderSettleVO();
+        AppSourceEnum appSourceEnum = AppSourceEnum.getByValue(param.getAppSource());
+        orderSettleVO.setAppId(appSourceEnum.getAppId());
+        orderSettleVO.setGroupbuyingId(param.getGroupbuyingId());
+        orderSettleVO.setGroupId(param.getGroupId());
+        orderSettleVO.setUserId(userId);
+        orderSettleVO.setOrderAmt(param.getOrderAmt());
+        GroupBuying groupBuying = groupBuyingService.getById(orderSettleVO.getGroupbuyingId());
+        GroupBuyingGroup groupBuyingGroup = groupBuyingGroupService.getById(orderSettleVO.getGroupId());
+        //不锁次数
+        userGroupOrderService.checkGroup(orderSettleVO,groupBuying,groupBuyingGroup,false);
+        // 缓存订单结算对象
+        redisCache.setCacheObject(RedisKey.build(RedisKey.USER_GROUP_ORDER_KEY, userId), orderSettleVO, 10,
+                TimeUnit.MINUTES);
+        return AjaxResult.success(orderSettleVO);
+    }
+
+
+    /**
+     * 校验活动是否过期
+     * @return
+     */
+    private boolean checkBuying(GroupBuying groupBuying){
+        Date now = new Date();
+        Date start = groupBuying.getStartTime();
+        Date end = groupBuying.getEndTime();
+        if(now.compareTo(start) >= 0 || now.compareTo(end) <= 0){
+            return true;
+        }else {
+            return false;
+        }
+    }
+
+    /**
+     * 提交订单
+     */
+    @PostMapping("/order/submit")
+    @ApiOperation(value = "提交订单", notes = "在订单确认页面提交")
+    public AjaxResult submit(@RequestBody UserShareVO userShareVO) {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        GroupOrderSettleVO orderSettleVO = redisCache.getCacheObject(
+            RedisKey.build(RedisKey.USER_GROUP_ORDER_KEY, userId));
+        if (null == orderSettleVO) {
+            return AjaxResult.error("订单已过期,请重新下单");
+        }
+
+        String orderId = userGroupOrderService.submitOrder(userId, orderSettleVO, userShareVO);
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("orderId", orderId);
+        if (orderSettleVO.getOrderAmt() > 0) {
+            jsonObject.put("needPay", 1);
+        } else {
+            jsonObject.put("needPay", 0);
+        }
+        // 清除缓存的订单
+        redisCache.deleteObject(RedisKey.build(RedisKey.USER_GROUP_ORDER_KEY, userId));
+        if (orderSettleVO.getOrderAmt() > 0) {
+            if (AppSourceEnum.MP.getAppId().equals(orderSettleVO.getAppId())) {
+                return AjaxResult.error("支付系统升级中,请稍后再试");
+            }
+        }
+        return AjaxResult.success(jsonObject);
+    }
+
+
+    /**
+     * 订单支付
+     */
+    @PostMapping("/order/pay")
+    @ApiOperation(value = "订单支付", notes = "在拼团/开团页面支付")
+    public AjaxResult pay(@Valid @RequestBody OrderPayParam param) {
+        Long userId = SecurityUtils.getLoginUser().getUserId();
+        SysUser sysUser = sysUserService.selectUserById(userId);
+        String openId = "";
+        if (param.getPayType() == 1) {
+            if (StringUtils.isBlank(sysUser.getAliuserId())) {
+                return AjaxResult.error(ErrorCodeEnum.ERROR_CODE_1026);
+            }
+        } else {
+            AppSourceEnum appSourceEnum = AppSourceEnum.getByValue(param.getAppSource());
+            if (AppSourceEnum.MSDQ.equals(appSourceEnum)) {
+                if (StringUtils.isBlank(sysUser.getMsdqOpenId())) {
+                    return AjaxResult.error(ErrorCodeEnum.ERROR_CODE_1005);
+                }
+                openId = sysUser.getMsdqOpenId();
+            } else {
+                if (StringUtils.isBlank(sysUser.getOpenId())) {
+                    return AjaxResult.error(ErrorCodeEnum.ERROR_CODE_1005);
+                }
+                openId = sysUser.getOpenId();
+            }
+        }
+
+        GroupBuyingOrder buyingOrder = groupBuyingOrderService.getById(param.getOrderId());
+        JSONObject jsonObject;
+        try {
+            if (param.getPayType() == 1) {
+                // 支付宝支付
+                jsonObject = walletService.directAliPay(BizTypeEnum.GROUP_ORDER, param.getOrderId(), sysUser.getAliuserId(),
+                        buyingOrder.getPayAmt(), buyingOrder.getGroupbuyingTitle());
+            } else {
+                // 微信支付
+                jsonObject = walletService.pay(BizTypeEnum.GROUP_ORDER, param.getOrderId(), openId,
+                        buyingOrder.getPayAmt(), buyingOrder.getGroupbuyingTitle(), param.getAppSource());
+            }
+        } catch (ServiceException e) {
+            LogUtil.error(logger, e, "根据拼团购买订单创建支付单失败。userId:{0},orderId:{1}",
+                new Object[]{userId, param.getOrderId()});
+            return AjaxResult.error(e.getMessage());
+        }
+        return AjaxResult.success(jsonObject);
+    }
+
+}

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

@@ -261,5 +261,14 @@ public class RedisCache
      */
     public boolean hasKey(String key) {
         return null != redisTemplate.opsForValue().get(key);
+
+    }
+
+    public Long decrement(String key, int dv){
+       return redisTemplate.opsForValue().decrement(key,dv);
+    }
+
+    public Long increment(String key, int dv){
+        return redisTemplate.opsForValue().increment(key,dv);
     }
 }

+ 58 - 0
mp-common/src/main/java/com/qs/mp/common/core/redis/redissondelay/PTDelayQueue.java

@@ -0,0 +1,58 @@
+package com.qs.mp.common.core.redis.redissondelay;
+
+import cn.hutool.json.JSONObject;
+import com.qs.mp.common.utils.StringUtils;
+import org.redisson.api.RBlockingDeque;
+import org.redisson.api.RBlockingQueue;
+import org.redisson.api.RDelayedQueue;
+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 javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class PTDelayQueue {
+    private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
+    @Resource
+    private RedissonClient redissonClient;
+
+    private RDelayedQueue<String> delayQueue;
+    private RBlockingQueue<String> blockingQueue;
+
+    @PostConstruct
+    public void init() {
+        initDelayQueue();
+        startDelayQueueConsumer();
+    }
+
+    private void initDelayQueue() {
+        blockingQueue = redissonClient.getBlockingQueue("SANYOU");
+        delayQueue = redissonClient.getDelayedQueue(blockingQueue);
+    }
+
+    private void startDelayQueueConsumer() {
+        new Thread(() -> {
+            while (true) {
+                try {
+                    String task = blockingQueue.take();
+                    logger.info("接收到延迟任务:{}", task);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }, "SANYOU-Consumer").start();
+    }
+
+    public void offerTask(String task, long seconds) {
+        logger.info("添加延迟任务:{} 延迟时间:{}s", task, seconds);
+        delayQueue.offer(task, seconds, TimeUnit.SECONDS);
+    }
+
+
+
+}

+ 1 - 0
mp-common/src/main/java/com/qs/mp/common/enums/BizTypeEnum.java

@@ -15,6 +15,7 @@ public enum BizTypeEnum implements IEnum<Integer> {
   DELIVER_ORDER(3, "用户提货订单"),
   CHANNEL_GOODS_ORDER(4, "门店商品采购订单"),
   CHANNEL_GOODS__SETTLE_ORDER(5, "门店商品结算订单"),
+  GROUP_ORDER(6, "用户拼团订单"),
   ;
 
 

+ 62 - 0
mp-common/src/main/java/com/qs/mp/common/enums/GroupingStatusEnum.java

@@ -0,0 +1,62 @@
+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 GroupingStatusEnum implements IEnum<Integer> {
+    //0:进行中,1:已完成,2:已结束"
+    CLOSE(2, "已结束"),
+
+    START(0,"进行中"),
+
+    END(1,"已结束");
+
+    private Integer value;
+    private String desc;
+
+    GroupingStatusEnum(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 GroupingStatusEnum getByValue(Integer value) {
+        for (GroupingStatusEnum statusEnum : GroupingStatusEnum.values()) {
+            if (statusEnum.getValue().equals(value)) {
+                return statusEnum;
+            }
+        }
+        return null;
+    }
+}

+ 3 - 1
mp-common/src/main/java/com/qs/mp/common/enums/MqTopicType.java

@@ -10,7 +10,9 @@ import com.baomidou.mybatisplus.annotation.IEnum;
 public enum MqTopicType implements IEnum<String> {
 
   ticket_generate("1", "盲票生成"),
-  ticket_pay("2", "盲票付款"),;
+  ticket_pay("2", "盲票付款"),
+  group_effective("3", "拼团有效期"),
+  ;
 
 
   private final String value;

+ 51 - 0
mp-common/src/main/java/com/qs/mp/common/pulsar/PulsarClientService.java

@@ -159,6 +159,57 @@ public class PulsarClientService {
 
   }
 
+
+  /**
+   * 发送延迟消息
+   * @param mqTopicType
+   * @param data
+   * @param deliver
+   * @throws PulsarClientException
+   */
+  public void producerDeliver(MqTopicType mqTopicType, String data,Long deliver) throws PulsarClientException {
+    logger.info("start producer mq data:" + data);
+    String topic = "";
+    if (mqTopicType.getValue() == MqTopicType.ticket_generate.getValue()) {
+      topic = topicTicketGenerate; // 盲票生成
+    } else if (mqTopicType.getValue() == MqTopicType.ticket_pay.getValue()) {
+      topic = topicTicketPay; // 盲票付款成功
+    }
+    Producer<byte[]> producer = null;
+    if (Producers.containsKey(mqTopicType.getValue())) {
+      producer = Producers.get(mqTopicType.getValue());
+    } else {
+      producer = client.newProducer()
+              .topic(topic)//topic完整路径,格式为persistent://集群(租户)ID/命名空间/Topic名称
+              .enableBatching(true)
+              .create();
+      Producers.put(mqTopicType.getValue(), producer);
+    }
+    CompletableFuture<MessageId> messageIdFuture = producer.newMessage()
+            .key(mqTopicType.getValue())
+            .value(data.getBytes())
+            .deliverAfter(deliver,TimeUnit.SECONDS)
+            .sendAsync();
+    Producer<byte[]> finalProducer = producer;
+    messageIdFuture.whenComplete(((messageId, throwable) -> {
+      if (null != throwable) {
+        logger.error("【消息投递异常,开始重试】mq data:" + data, throwable);
+        //todo 失败重试策略
+        try {
+          finalProducer.newMessage().deliverAfter(deliver+5, TimeUnit.SECONDS
+          ).key(mqTopicType.getValue()).value(data.getBytes()).send();
+        } catch (PulsarClientException e) {
+          logger.error("重试投递失败", e);
+          //todo 保存数据库,增加失败重试,通过体系化重试框架保证消息投递完整性
+        }
+      } else {
+        logger.info("【消息成功投递】");
+      }
+    }));
+
+  }
+
+
   public void consumer() throws PulsarClientException {
 
   }

+ 14 - 0
mp-common/src/main/java/com/qs/mp/common/utils/DateUtils.java

@@ -4,6 +4,10 @@ import java.lang.management.ManagementFactory;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
@@ -571,4 +575,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
 //		
 //		System.out.println(start_time.indexOf("2022-01"));
   }
+
+  public static Date localDateTime2Date(LocalDateTime localDateTime){
+      // 将 LocalDateTime 转换为 ZonedDateTime
+      ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
+      // 将 ZonedDateTime 转换为 Instant
+      Instant instant = zonedDateTime.toInstant();
+      // 将 Instant 转换为 Date
+      Date date = Date.from(instant);
+      return date;
+  }
 }

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/param/GroupBuyingGroupParam.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 yang.zhao
+ */
+@ApiModel("我的拼团数据查询入参类")
+@Data
+public class GroupBuyingGroupParam {
+
+    @ApiModelProperty("成团状态,0:进行中,1:以开奖")
+    private Integer status;
+
+
+
+}

+ 2 - 2
mp-service/src/main/java/com/qs/mp/admin/domain/vo/GroupBuying.java

@@ -51,11 +51,11 @@ public class GroupBuying implements Serializable {
     @TableField("group_size")
     private Integer groupSize;
 
-    @ApiModelProperty("每个用户发起拼团的最大次数")
+    @ApiModelProperty("每个用户发起拼团的最大次数,活动的维度")
     @TableField("start_group_times")
     private Integer startGroupTimes;
 
-    @ApiModelProperty("每个用户参与拼团的最大次数")
+    @ApiModelProperty("每个用户参与拼团的最大次数,活动的维度,未设置事,可以无限制拼团直至成团")
     @TableField("partin_group_times")
     private Integer partinGroupTimes;
 

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

@@ -37,7 +37,7 @@ public class GroupBuyingGroup implements Serializable {
 
     @ApiModelProperty("参团用户ID")
     @TableField("partin_ids")
-    private Integer partinIds;
+    private String partinIds;
 
     @ApiModelProperty("成团状态,0:进行中,1:已完成,2:已结束")
     @TableField("group_status")

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

@@ -29,7 +29,7 @@ public class GroupBuyingOrder implements Serializable {
 
     @ApiModelProperty("拼团活动名称")
     @TableId(value = "groupbuying_title")
-    private Long groupbuyingTitle;
+    private String groupbuyingTitle;
 
     @ApiModelProperty("拼团ID")
     @TableId(value = "group_id")

+ 41 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/vo/MyGroupBuyingGroupEndVO.java

@@ -0,0 +1,41 @@
+package com.qs.mp.admin.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 我的拼团-已开奖出参类
+ */
+@ApiModel("我的拼团-已开奖")
+@Data
+public class MyGroupBuyingGroupEndVO {
+
+    @ApiModelProperty("拼团ID")
+    private Long groupId;
+
+    @ApiModelProperty("拼团活动ID")
+    private Long groupBuyingId;
+
+    @ApiModelProperty("开奖时间")
+    private Date groupEndTime;
+
+    @ApiModelProperty(value = "活动主图")
+    private String picUrl;
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @ApiModelProperty("是否中奖")
+    private boolean Winning;
+
+
+
+
+
+
+
+
+}

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

@@ -0,0 +1,44 @@
+package com.qs.mp.admin.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 我的拼团-进行中出参类
+ */
+@ApiModel("我的拼团-进行中")
+@Data
+public class MyGroupBuyingGroupStartVO {
+
+    @ApiModelProperty("拼团ID")
+    private Long groupId;
+
+    @ApiModelProperty("拼团活动ID")
+    private Long groupBuyingId;
+
+    @ApiModelProperty("参团人数")
+    private Integer partinNum;
+
+    @ApiModelProperty("成团人数")
+    private Integer groupSize;
+
+    @ApiModelProperty("开奖时间")
+    private Date groupEndTime;
+
+    @ApiModelProperty("拼团活动结束时间")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动主图")
+    private String picUrl;
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+
+
+
+}

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

@@ -0,0 +1,10 @@
+package com.qs.mp.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qs.mp.admin.domain.vo.GroupBuyingGroup;
+import com.qs.mp.user.domain.GroupBuyingHitPrize;
+
+
+public interface GroupBuyingHitPrizeMapper extends BaseMapper<GroupBuyingHitPrize> {
+
+}

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

@@ -0,0 +1,16 @@
+package com.qs.mp.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.admin.domain.GroupBuyingAwards;
+import com.qs.mp.user.domain.GroupBuyingHitPrize;
+
+/**
+ * <p>
+ * 营销活动奖项设置 服务类
+ * </p>
+ *
+ * @author yang.zhao
+ */
+public interface IGroupBuyingHitPrizeService extends IService<GroupBuyingHitPrize> {
+
+}

+ 19 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/GroupBuyingHitPrizeServiceImpl.java

@@ -0,0 +1,19 @@
+package com.qs.mp.admin.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.admin.domain.vo.GroupBuyingGroup;
+import com.qs.mp.admin.mapper.GroupBuyingGroupMapper;
+import com.qs.mp.admin.mapper.GroupBuyingHitPrizeMapper;
+import com.qs.mp.admin.service.IGroupBuyingGroupService;
+import com.qs.mp.admin.service.IGroupBuyingHitPrizeService;
+import com.qs.mp.user.domain.GroupBuyingHitPrize;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Description 拼团活动中奖记录表服务实现类
+ */
+@Service
+public class GroupBuyingHitPrizeServiceImpl extends ServiceImpl<GroupBuyingHitPrizeMapper, GroupBuyingHitPrize> implements IGroupBuyingHitPrizeService {
+
+
+}

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

@@ -19,6 +19,7 @@ public enum RedisKey {
     OFFLINE_TICKET_ID_KEY("offline_ticket_id_key_{0}", "线下盲票ID"),
     USER_PRIZE_RECOVERY_KEY("user_prize_recovery_key_{0}", "用户奖品回收"),
     CHANNEL_GOODS_ORDER_KEY("channel_goods_order_key_{0}", "经销商下的商品采购订单"),
+    USER_GROUP_ORDER_KEY("user_group_order_key{0}", "用户拼团订单"),
     ;
 
     public String keyTemplate;

+ 8 - 1
mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java

@@ -23,7 +23,14 @@ public enum RedisLockKey {
 
     USER_CD_KEY_EXCHANGE_LOCK("user_cd_key_exchange_lock_{0}", "兑换码兑换锁"),
 
-    USER_RED_PKG_DRAW_LOCK("user_red_pkg_draw_lock_{0}","红包提现锁")
+    USER_RED_PKG_DRAW_LOCK("user_red_pkg_draw_lock_{0}","红包提现锁"),
+
+    //0:活动ID,1:用户ID
+    USER_INITIATE_GROUP_LOCK("user_initiate_group_lock_{0}_{1}","用户发起拼团锁"),
+    //0:团Id,1:用户ID
+    USER_JOIN_GROUP_LOCK("user_join_group_lock_{0}_{1}","用户参与拼团锁"),
+    JOIN_GROUP_LOCK("join_group_lock_{0}","参与拼团锁"),
+    USER_GROUP_ORDER_LOCK("user_group_order_lock_{0}","拼团订单锁"),
     ;
 
 

+ 10 - 0
mp-service/src/main/java/com/qs/mp/mq/impl/PulsarConsumerImpl.java

@@ -63,6 +63,8 @@ public class PulsarConsumerImpl implements PulsarConsumer {
       processTicketGenerateMsg(mqData);
     } else if (MqTopicType.ticket_pay.getValue().equals(topicType)) {
       processTicketPayMsg(mqData);
+    }else if(MqTopicType.group_effective.getValue().equals(topicType)){
+
     }
   }
 
@@ -129,4 +131,12 @@ public class PulsarConsumerImpl implements PulsarConsumer {
     }
     ticketBoxService.generateTicket(boxId);
   }
+
+  /**
+   * 拼团到期逻辑处理
+   */
+  private void processGroupExpire(String groupId){
+    //todo 校验团状态,进行中则退款
+  }
+
 }

+ 113 - 0
mp-service/src/main/java/com/qs/mp/user/domain/GroupBuyingHitPrize.java

@@ -0,0 +1,113 @@
+package com.qs.mp.user.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @describe 拼团活动中奖信息表实体类
+ */
+@TableName("mp_groupbuying_hit_prize")
+@Data
+@ApiModel("拼团活动中奖信息表实体类")
+public class GroupBuyingHitPrize implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("中奖记录id")
+    @TableId(value = "id", type = IdType.INPUT)
+    private String id;
+
+    /**
+     * 拼团活动id
+     */
+    @ApiModelProperty("拼团活动id")
+    @TableField("groupbuying_id")
+    private Long groupbuyingId;
+
+    /**
+     * 拼团id
+     */
+    @ApiModelProperty("拼团id")
+    @TableField("group_id")
+    private Long groupId;
+
+    /**
+     * 用户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("prize_quantity")
+    private Long prizeQuantity;
+
+    /**
+     * 奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包
+     */
+    @ApiModelProperty("奖品数")
+    @TableField("prize_type")
+    private String prizeType;
+
+    /**
+     * 发货状态
+     */
+    @ApiModelProperty("发货状态")
+    @TableField("deliver_goods_status")
+    private int deliverGoodsStatus;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @ApiModelProperty("逻辑删除标识")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 180 - 0
mp-service/src/main/java/com/qs/mp/user/domain/UserGroupOrder.java

@@ -0,0 +1,180 @@
+package com.qs.mp.user.domain;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.annotation.*;
+import com.qs.mp.common.enums.CommStatusEnum;
+import com.qs.mp.common.enums.TicketTypeEnum;
+import com.qs.mp.common.enums.UserTicketOrderStatusEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @describe 用户盲票订单实体类
+ * @auther quanshu
+ * @create 2022-03-07 20:45:43
+ */
+@TableName("mp_groupbuying_order")
+@Data
+@ApiModel("用户拼团订单实体类")
+public class UserGroupOrder implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * 主键
+   */
+  @ApiModelProperty("订单id")
+  @TableId(value = "order_id", type = IdType.INPUT)
+  private String orderId;
+
+
+  /**
+   * 订单金额,单位:分
+   */
+  @ApiModelProperty("订单金额,单位:分")
+  @TableField("order_amt")
+  private int orderAmt;
+
+  /**
+   * 拼团活动id
+   */
+  @ApiModelProperty("拼团活动id")
+  @TableField("groupbuying_id")
+  private Long groupbuyingId;
+
+
+  /**
+   * 拼团id
+   */
+  @ApiModelProperty("拼团id")
+  @TableField("group_id")
+  private Long groupId;
+
+
+  /**
+   * 用户ID
+   */
+  @ApiModelProperty("用户id")
+  @TableField("user_id")
+  private Long userId;
+
+  /**
+   * 拼团价格,单位:分
+   */
+  @ApiModelProperty("拼团价格,单位:分")
+  @TableField("group_price")
+  private int groupPrice;
+
+  /**
+   * 优惠金额(暂无)
+   */
+  @ApiModelProperty("优惠金额,单位:分(暂无)")
+  @TableField("discount_amt")
+  private int discountAmt;
+
+  /**
+   * 实付金额,单位:分
+   */
+  @ApiModelProperty("实付金额,单位:分")
+  @TableField("pay_amt")
+  private int payAmt;
+
+  /**
+   * 分佣的销售额百分比
+   */
+  @ApiModelProperty("分佣的销售额百分比")
+  @TableField("sale_comm_rate")
+  private BigDecimal saleCommRate;
+
+
+  /**
+   * 订单备注
+   */
+  @ApiModelProperty("订单备注")
+  @TableField("memo")
+  private String memo;
+
+  /**
+   * 订单来源
+   */
+  @ApiModelProperty("订单来源")
+  @TableField("resource")
+  private String resource;
+
+  /**
+   * 使用优惠信息
+   */
+  @ApiModelProperty("使用优惠信息")
+  @TableField("coupon_info")
+  private String couponInfo;
+
+  /**
+   * 分享信息
+   */
+  @ApiModelProperty("分享信息")
+  @TableField("share_info")
+  private String shareInfo;
+
+  /**
+   * 订单状态;-1 已取消 0:待付款 1:已完成
+   */
+  @ApiModelProperty("订单状态;-1 已取消 0:待付款 1:已完成")
+  @TableField("status")
+  @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+  private UserTicketOrderStatusEnum status;
+
+  /**
+   * 结佣标识,1代表已结佣 0代表未结佣
+   */
+  @ApiModelProperty("结佣标识,1代表已结佣 0代表未结佣")
+  @TableField("comm_status")
+  @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+  private CommStatusEnum commStatus;
+
+  /**
+   * 结佣渠道ID
+   */
+  @ApiModelProperty("结佣渠道ID")
+  @TableField("channel_id")
+  private Long channelId;
+
+  @ApiModelProperty("推广员id")
+  @TableField("promoter_id")
+  private Long promoterId;
+
+
+  @ApiModelProperty("来源小程序id")
+  @TableField("app_id")
+  private String appId;
+
+  @ApiModelProperty("支付类型1支付宝,2微信,10支付宝直连")
+  @TableField("pay_type")
+  private String payType;
+
+  @ApiModelProperty("支付时间")
+  @TableField("pay_time")
+  private Date payTime;
+
+  /**
+   * 创建时间
+   */
+  @ApiModelProperty("创建时间")
+  @TableField("created_time")
+  private Date createdTime;
+
+  /**
+   * 更新时间
+   */
+  @ApiModelProperty("更新时间")
+  @TableField("updated_time")
+  @Version
+  private Date updatedTime;
+
+
+}

+ 124 - 0
mp-service/src/main/java/com/qs/mp/user/domain/param/GroupOrderParam.java

@@ -0,0 +1,124 @@
+package com.qs.mp.user.domain.param;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@ApiModel(value= "拼团订单参数")
+@Data
+public class GroupOrderParam {
+
+	@ApiModelProperty("拼团活动ID")
+	@TableId(value = "groupbuying_id")
+	private Long groupbuyingId;
+
+	@ApiModelProperty("拼团活动名称")
+	@TableId(value = "groupbuying_title")
+	private Long groupbuyingTitle;
+
+	@ApiModelProperty("拼团ID")
+	@TableId(value = "group_id")
+	private Long groupId;
+
+//	@ApiModelProperty("用户ID")
+//	@TableId(value = "user_id")
+//	private Long userId;
+
+	@ApiModelProperty("订单金额")
+	@TableId(value = "order_amt")
+	private Integer orderAmt;
+
+	@ApiModelProperty("团类型 1: 发起拼团,2: 参与拼团")
+	private int groupType;
+
+	@ApiModelProperty("小程序,1盲票,2码上兑券,3支付宝盲票")
+	private Integer appSource;
+
+//	@ApiModelProperty("用户ID")
+//	@TableId(value = "group_price")
+//	private Integer groupPrice;
+
+//	@ApiModelProperty("实付金额")
+//	@TableId(value = "pay_amt")
+//	private Integer payAmt;
+//
+//	@ApiModelProperty("分佣的销售额百分比")
+//	@TableId(value = "sale_comm_rate")
+//	private BigDecimal saleCommRate;
+//
+//	@ApiModelProperty("订单备注")
+//	@TableId(value = "memo")
+//	private String memo;
+//
+//	@ApiModelProperty("订单来源")
+//	@TableId(value = "resource")
+//	private String resource;
+//
+//
+//	@ApiModelProperty("线下购买渠道经销商")
+//	@TableId(value = "channel_id")
+//	private Long channelId;
+//
+//	@ApiModelProperty("推广员ID")
+//	@TableId(value = "promoter_id")
+//	private Long promoterId;
+//
+//	@ApiModelProperty("用户分享信息")
+//	@TableId(value = "share_info")
+//	private String shareInfo;
+//
+//	@ApiModelProperty("来源小程序id")
+//	@TableId(value = "app_id")
+//	private String appId;
+//
+//	@ApiModelProperty("支付类型1支付宝,2微信,10支付宝直连")
+//	@TableId(value = "pay_type")
+//	private String payType;
+//
+//	@ApiModelProperty("奖品名称")
+//	@TableId(value = "prize_title")
+//	private String prizeTitle;
+//
+//	@ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+//	@TableId(value = "prize_type")
+//	private String prizeType;
+//
+//	@ApiModelProperty("奖品数")
+//	@TableId(value = "prize_quantity")
+//	private Integer prizeQuantity;
+//
+//	@ApiModelProperty("收货地址-省")
+//	@TableId(value = "province")
+//	private String province;
+//
+//	@ApiModelProperty("收货地址-市")
+//	@TableId(value = "city")
+//	private String city;
+//
+//	@ApiModelProperty("收货地址-区")
+//	@TableId(value = "area")
+//	private String area;
+//
+//	@ApiModelProperty("收货地址-详细")
+//	@TableId(value = "address")
+//	private String address;
+//
+//	@ApiModelProperty("门店名称")
+//	@TableId(value = "channel_name")
+//	private String channelName;
+//
+//	@ApiModelProperty("收货人名称")
+//	@TableId(value = "user_name")
+//	private String userName;
+//
+//	@ApiModelProperty("收货人手机号")
+//	@TableId(value = "phonenumber")
+//	private String phonenumber;
+}

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

@@ -0,0 +1,102 @@
+package com.qs.mp.user.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.qs.mp.channel.domain.vo.PromoterVO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author zhongcp
+ * @Date 2022/3/3
+ */
+@Data
+@ApiModel("拼团订单结算出参类")
+public class GroupOrderSettleVO {
+
+    /**
+     * 订单金额
+     */
+    @ApiModelProperty("订单金额")
+    private Integer orderAmt = 0;
+
+    /**
+     * 优惠金额
+     */
+    @ApiModelProperty("优惠金额")
+    private Integer discountAmt = 0;
+
+    /**
+     * 拼团活动id
+     */
+    @ApiModelProperty("拼团活动id")
+    @TableField("groupbuying_id")
+    private Long groupbuyingId;
+
+
+    /**
+     * 拼团id
+     */
+    @ApiModelProperty("拼团id")
+    @TableField("group_id")
+    private Long groupId;
+
+
+    /**
+     * 用户ID
+     */
+    @ApiModelProperty("用户id")
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 拼团价格,单位:分
+     */
+    @ApiModelProperty("拼团价格,单位:分")
+    @TableField("group_price")
+    private int groupPrice;
+
+
+    @ApiModelProperty("小程序appId")
+    private String appId;
+
+    @ApiModelProperty("支付类型1支付宝,2微信,10支付宝直连")
+    @TableId(value = "pay_type")
+    private String payType;
+
+    @ApiModelProperty("用户关联的推广员")
+    private PromoterVO promoter;
+
+    @ApiModelProperty("关联的门店")
+    private Long channelId;
+
+    @ApiModelProperty("团类型 1: 发起拼团,2: 参与拼团")
+    private int groupType;
+
+    @ApiModelProperty("用户选择的地址")
+    private Long userAddrId;
+
+
+    @ApiModelProperty("奖品名称")
+    @TableId(value = "prize_title")
+    private String prizeTitle;
+
+    @ApiModelProperty("奖品类型 goods 实物商品 coupon优惠券 coin平台代币 coupon_pkg券包")
+    @TableId(value = "prize_type")
+    private String prizeType;
+
+    @ApiModelProperty("奖品数")
+    @TableId(value = "prize_quantity")
+    private Integer prizeQuantity;
+
+    /**
+     * 使用优惠券列表
+     */
+    @ApiModelProperty("使用优惠券列表")
+    List<UserCoupon4OrderVO> couponList = new ArrayList<>();
+
+}

+ 75 - 0
mp-service/src/main/java/com/qs/mp/user/service/IUserGroupOrderService.java

@@ -0,0 +1,75 @@
+package com.qs.mp.user.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.pay.domain.PayOrder;
+import com.qs.mp.user.domain.vo.GroupOrderSettleVO;
+import com.qs.mp.user.domain.vo.UserShareVO;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 用户盲票订单 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-03-07
+ */
+public interface IUserGroupOrderService extends IService<GroupBuyingOrder> {
+
+  /**
+   * 提交拼团购买订单
+   * @param userId
+   * @param orderSettleVO
+   * @param userShareVO
+   * @return
+   */
+  String submitOrder(Long userId, GroupOrderSettleVO orderSettleVO, UserShareVO userShareVO);
+
+  /**
+   * 交易当前是否能正常拼团
+   * @param orderSettleVO
+   * @param groupBuying
+   * @return
+   */
+  boolean checkGroup(GroupOrderSettleVO orderSettleVO,GroupBuying groupBuying,GroupBuyingGroup groupBuyingGroup,boolean updateGroupNumber);
+
+  boolean updateGroupNumber(GroupOrderSettleVO orderSettleVO,GroupBuying groupBuying,GroupBuyingGroup groupBuyingGroup);
+
+  /**
+   * 批量取消一个票组(线上)的订单(恢复优惠券、票组销量)
+   * @param boxId
+   * @param orderIds
+   * @return
+   */
+  boolean batchCancelOrder(String boxId, List<String> orderIds);
+
+  /**
+   * 取消订单(恢复优惠券、票组销量)
+   * @param orderId
+   * @return
+   */
+  boolean cancelOrder(String orderId);
+
+  /**
+   * 支付成功回调
+   * @param payOrder
+   * @return
+   */
+  boolean paySuccess(PayOrder payOrder);
+
+  /**
+   * 给渠道分润
+   * @param orderId
+   * @param resource 订单来源1用户,2经销商
+   * @return
+   */
+  boolean commToChannel(String orderId, Integer resource);
+
+
+
+
+
+
+}

+ 324 - 0
mp-service/src/main/java/com/qs/mp/user/service/impl/UserGroupOrderServiceImpl.java

@@ -0,0 +1,324 @@
+package com.qs.mp.user.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qs.mp.admin.domain.Goods;
+import com.qs.mp.admin.domain.Ticket;
+import com.qs.mp.admin.domain.TicketBox;
+import com.qs.mp.admin.domain.TicketBoxGoods;
+import com.qs.mp.admin.domain.param.IndexTicketBoxTopQueryParam;
+import com.qs.mp.admin.domain.param.IndexTicketSiteTopQueryParam;
+import com.qs.mp.admin.domain.param.TicketBoxGrossProfitParam;
+import com.qs.mp.admin.domain.vo.*;
+import com.qs.mp.admin.service.*;
+import com.qs.mp.channel.domain.*;
+import com.qs.mp.channel.domain.param.ChannelCommParam;
+import com.qs.mp.channel.service.*;
+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.RSAUtil;
+import com.qs.mp.common.utils.StringUtils;
+import com.qs.mp.framework.redis.RedisKey;
+import com.qs.mp.framework.redis.RedisLockKey;
+import com.qs.mp.framework.service.IAsyncTaskService;
+import com.qs.mp.pay.domain.PayOrder;
+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.*;
+import com.qs.mp.user.domain.vo.*;
+import com.qs.mp.user.mapper.GroupBuyingOrderMapper;
+import com.qs.mp.user.mapper.UserTicketOrderMapper;
+import com.qs.mp.user.service.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+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.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * 用户盲票订单 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-03-07
+ */
+@Service
+public class UserGroupOrderServiceImpl extends
+    ServiceImpl<GroupBuyingOrderMapper, GroupBuyingOrder> implements IUserGroupOrderService{
+
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+
+    @Value("${bind.channelId}")
+    private Long bindChannelId;
+
+    @Autowired
+    private IChannelUserRelService channelUserRelService;
+
+    @Autowired
+    private IChannelOrderDetailService channelOrderDetailService;
+
+    @Autowired
+    private IUserTicketOrderItemService userTicketOrderItemService;
+
+    @Autowired
+    private ITicketService ticketService;
+
+    @Autowired
+    private IChannelService channelService;
+
+
+    @Autowired
+    private BizIdGenerator bizIdGenerator;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private IAsyncTaskService asyncTaskService;
+
+    @Autowired
+    private DistributedLocker distributedLocker;
+
+    @Autowired
+    private ITicketBoxGoodsService ticketBoxGoodsService;
+
+    @Autowired
+    private IUserPrizeStorageService userPrizeStorageService;
+
+    @Autowired
+    private IGoodsService goodsService;
+
+    @Autowired
+    private ICouponService couponService;
+
+    @Autowired
+    private ICouponPkgService couponPkgService;
+
+
+    @Autowired
+    private IPromoterUserService promoterUserService;
+
+    @Autowired
+    private IGroupBuyingService groupBuyingService;
+
+    @Autowired
+    private IGroupBuyingGroupService groupBuyingGroupService;
+    @Autowired
+    private IUserAddrService userAddrService;
+    @Autowired
+    private ISysUserService userService;
+
+
+    @Override
+    public String submitOrder(Long userId, GroupOrderSettleVO orderSettleVO,
+                              UserShareVO userShareVO) {
+        UserGroupOrderServiceImpl proxy = (UserGroupOrderServiceImpl) AopContext.currentProxy();
+        GroupBuying groupBuying = groupBuyingService.getById(orderSettleVO.getGroupbuyingId());
+        GroupBuyingGroup groupBuyingGroup = groupBuyingGroupService.getById(orderSettleVO.getGroupId());
+        //再次校验并锁次数
+        checkGroup(orderSettleVO,groupBuying,groupBuyingGroup,true);
+        return proxy.createOnlineOrder(userId, orderSettleVO, userShareVO,groupBuying,groupBuyingGroup);
+    }
+
+    @Override
+    public boolean checkGroup(GroupOrderSettleVO orderSettleVO,GroupBuying groupBuying,GroupBuyingGroup groupBuyingGroup,boolean updateGroupNumber) {
+        boolean flag =false;
+        //发起拼团再次校验
+        String key = RedisLockKey.build(RedisLockKey.USER_INITIATE_GROUP_LOCK,orderSettleVO.getGroupbuyingId(),orderSettleVO.getUserId());
+        String groupUserKey = RedisLockKey.build(RedisLockKey.USER_JOIN_GROUP_LOCK,orderSettleVO.getGroupbuyingId(),orderSettleVO.getUserId());
+        if (!distributedLocker.tryLock(key, 3, -1, TimeUnit.SECONDS)) {
+            throw new ServiceException("拼团活动太火爆了,请稍后重试!");
+        }
+        if (!distributedLocker.tryLock(groupUserKey, 3, -1, TimeUnit.SECONDS)) {
+            throw new ServiceException("拼团活动太火爆了,请稍后重试!");
+        }
+        if(orderSettleVO.getGroupType() == 1){
+            if(ObjectUtil.isNotEmpty(redisCache.getCacheObject(key))){
+                int count = redisCache.getCacheObject(key);
+                if (count <= 0){
+                    throw new ServiceException("您的拼团次数已经达到上限啦!");
+                }
+            }else {
+                //设置当前用户发起拼团的最大次数
+                redisCache.setCacheObject(key,groupBuying.getStartGroupTimes());
+            }
+            //如果用户参团次数缓存为空,并且系统设置了每个用户的允许参团的次数
+            if(ObjectUtil.isEmpty(redisCache.getCacheList(groupUserKey)) && groupBuying.getPartinGroupTimes() != null){
+                redisCache.setCacheObject(groupUserKey,groupBuying.getPartinGroupTimes());
+            }
+        }
+        if(orderSettleVO.getGroupType() == 2){
+            //当前团参团人数小于开团人数
+            if(groupBuyingGroup.getPartinNum() < groupBuying.getGroupSize()){
+                //判断用户是否还有参团名额
+                int groupCount = -1;
+                //设置了用户参团次数
+                if(Objects.nonNull(redisCache.getCacheObject(groupUserKey))){
+                    groupCount = redisCache.getCacheObject(groupUserKey);
+                }
+                if (groupCount == 0){
+                    throw new ServiceException("您的参团次数已经达到上限啦!");
+                }
+            }
+        }
+        try {
+            //锁定名额
+            if(updateGroupNumber){
+                updateGroupNumber(orderSettleVO,groupBuying,groupBuyingGroup);
+            }
+        }finally {
+            distributedLocker.unlock(key);
+            distributedLocker.unlock(groupUserKey);
+        }
+        flag = true;
+        return flag;
+    }
+
+    @Override
+    public boolean updateGroupNumber(GroupOrderSettleVO orderSettleVO, GroupBuying groupBuying, GroupBuyingGroup groupBuyingGroup) {
+        String key = RedisLockKey.build(RedisLockKey.USER_INITIATE_GROUP_LOCK,orderSettleVO.getGroupbuyingId(),orderSettleVO.getUserId());
+        String groupUserKey = RedisLockKey.build(RedisLockKey.USER_JOIN_GROUP_LOCK,orderSettleVO.getGroupbuyingId(),orderSettleVO.getUserId());
+        //发起拼团,更新用户剩余拼团次数
+        if(orderSettleVO.getGroupType() == 1){
+            redisCache.decrement(key,1);
+            redisCache.decrement(groupUserKey,1);
+        }
+        //参与拼团,更新用户参团次数
+        if(orderSettleVO.getGroupType() == 2){
+            redisCache.decrement(groupUserKey,1);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean batchCancelOrder(String boxId, List<String> orderIds) {
+        return false;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public String createOnlineOrder(Long userId, GroupOrderSettleVO orderSettleVO,
+                                    UserShareVO userShareVO,GroupBuying groupBuying,GroupBuyingGroup groupBuyingGroup) {
+
+
+        // 创建订单
+        SysUser sysUser = userService.getById(userId);
+        UserAddr userAddr =  userAddrService.getDefaultChannelAddr(userId);
+        if(null != orderSettleVO.getUserAddrId()){
+            userAddr = userAddrService.getById(orderSettleVO.getUserAddrId());
+        }
+        GroupBuyingOrder groupBuyingOrder = new GroupBuyingOrder();
+        groupBuyingOrder.setOrderId(bizIdGenerator.newIdWithUidSharding(String.valueOf(userId)));
+        groupBuyingOrder.setUserId(userId);
+        groupBuyingOrder.setGroupbuyingTitle(groupBuying.getTitle());
+        groupBuyingOrder.setUserId(userId);
+        groupBuyingOrder.setAddress(userAddr.getAddr());
+        groupBuyingOrder.setProvince(userAddr.getProvince());
+        groupBuyingOrder.setCity(userAddr.getCity());
+        groupBuyingOrder.setArea(userAddr.getArea());
+        groupBuyingOrder.setCommStatus(CommStatusEnum.NO.getValue());
+        groupBuyingOrder.setGroupbuyingId(groupBuying.getId());
+        groupBuyingOrder.setGroupId(groupBuyingGroup.getId());
+        groupBuyingOrder.setOrderAmt(orderSettleVO.getOrderAmt());
+        groupBuyingOrder.setPayType(orderSettleVO.getPayType());
+        groupBuyingOrder.setAppId(orderSettleVO.getAppId());
+        groupBuyingOrder.setPhonenumber(sysUser.getPhonenumber());
+        groupBuyingOrder.setPrizeTitle(orderSettleVO.getPrizeTitle());
+        groupBuyingOrder.setPrizeQuantity(orderSettleVO.getPrizeQuantity());
+        groupBuyingOrder.setPrizeType(orderSettleVO.getPrizeType());
+
+        if (orderSettleVO.getOrderAmt() == 0) {
+            groupBuyingOrder.setCommStatus(CommStatusEnum.YES.getValue()); // 无需结佣,直接置为已结佣
+        }
+
+        // 查询用户的所属经销商,线上票直接查询
+        ChannelUserRel channelUserRel = channelUserRelService.getOne(
+                new LambdaQueryWrapper<ChannelUserRel>()
+                        .eq(ChannelUserRel::getUserId, userId));
+        if (null != channelUserRel) {
+            groupBuyingOrder.setChannelId(channelUserRel.getChannelId());
+        } else {
+            // 没被绑定,看有没有分享者
+            if (null != userShareVO.getSuid() && 0 != userShareVO.getSuid()) {
+                if (UserShareVO.SHARE_TYPE_USER.equals(userShareVO.getType())) {
+                    ChannelUserRel channelShareUserRel = channelUserRelService.getOne(
+                            new LambdaQueryWrapper<ChannelUserRel>()
+                                    .eq(ChannelUserRel::getUserId, userShareVO.getSuid()));
+                    if (null != channelShareUserRel) {
+                        groupBuyingOrder.setChannelId(channelShareUserRel.getChannelId());
+                        groupBuyingOrder.setShareInfo(JSONObject.toJSONString(userShareVO));
+                    }
+                } else if (UserShareVO.SHARE_TYPE_SITE.equals(userShareVO.getType())) {
+                    Channel channel = channelService.getOne(
+                            new LambdaQueryWrapper<Channel>()
+                                    .eq(Channel::getUserId, userShareVO.getSuid()));
+                    if (null != channel) {
+                        groupBuyingOrder.setChannelId(channel.getChannelId());
+                        groupBuyingOrder.setShareInfo(JSONObject.toJSONString(userShareVO));
+                    }
+                }
+            }
+        }
+        // 野生用户绑定固定的经销商
+        if (groupBuyingOrder.getChannelId() == null || groupBuyingOrder.getChannelId() == 0) {
+            UserShareVO tempUserShareVO = new UserShareVO();
+            tempUserShareVO.setSuid(bindChannelId);
+            tempUserShareVO.setType(UserShareVO.SHARE_TYPE_SITE);
+            groupBuyingOrder.setChannelId(bindChannelId);
+            groupBuyingOrder.setShareInfo(JSONObject.toJSONString(tempUserShareVO));
+        }
+        save(groupBuyingOrder);
+
+
+//        if (orderSettleVO.getPayAmt() == 0) {
+//            // 无需支付的,直接置为成功
+//            processTicketOrder(userTicketOrder);
+//            // 插入付款异步任务
+//            Assert.isTrue(asyncTaskService.insertAsyncTask(AsyncTaskTypeEnum.TICKET_PAY, userTicketOrder.getOrderId()),
+//                    "盲票支付,创建异步任务失败:" + userTicketOrder.getOrderId());
+//        }
+
+        //锁拼团次数
+        return groupBuyingOrder.getOrderId();
+    }
+
+    @Override
+    public boolean cancelOrder(String orderId) {
+        return false;
+    }
+
+    @Override
+    public boolean paySuccess(PayOrder payOrder) {
+        return false;
+    }
+
+    @Override
+    public boolean commToChannel(String orderId, Integer resource) {
+        return false;
+    }
+}