springboot实现高并发红包系统(java 全网最全包括语音口令 文字口令 普通 拼手气)

一起探讨学习

欢迎大家进群,一起讨论学习

每天给大家提供技术干货

在这里插入图片描述

博主技术笔记 https://notes.xiyankt.com


博主开源微服架构前后端分离技术博客项目源码地址,欢迎各位star https://gitee.com/bright-boy/xiyan-blog


springboot实现高并发红包系统(全网最全)

下面的业务处理请根据你们实际的场景进行处理

1.sql设计

CREATE TABLE `red_packet_info`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `packet_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包id',
  `type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包类型(0=拼手气红包,1=普通红包,2=文字口令红包,3=语音口令红包)',
  `watchword_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '口令内容',
  `user_id` int(0) NOT NULL COMMENT '用户id,哪个用户发的红包',
  `total_amount` decimal(8, 2) NOT NULL COMMENT '红包总金额',
  `total_packet` int(0) NOT NULL COMMENT '红包个数',
  `amount_one` decimal(8, 2) NULL DEFAULT NULL COMMENT '单个红包金额',
  `blessing` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包祝福语',
  `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包封面',
  `left_amount` decimal(8, 2) NOT NULL COMMENT '剩余红包金额',
  `left_packet` int(0) NOT NULL COMMENT '剩余红包个数',
  `expire_time` datetime(0) NOT NULL COMMENT '红包过期时间',
  `send_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '发送红包类型(0=私发,1=群发)',
  `status` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包状态(1=已创建,-1=已失效,2=已抢完)',
  `create_time` datetime(0) NOT NULL COMMENT '创建时间',
  `update_time` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '是否删除',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '更新者',
  `tenant_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '租户编号',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `packet_id`(`packet_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 775 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '红包表' ROW_FORMAT = Dynamic;
CREATE TABLE `red_packet_records`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `user_id` int(0) NOT NULL COMMENT '用户id',
  `amount` decimal(8, 2) NOT NULL COMMENT '抢到的金额',
  `watchword_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '口令内容',
  `red_packet_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '红包id',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  `create_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  `update_time` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '1' COMMENT '是否删除',
  `tenant_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '租户编号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 223 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '红包记录表' ROW_FORMAT = Dynamic;

2.实体类

RedPacketInfoDO
package cn.inno.pala.module.packet.dal.dataobject;

import cn.inno.pala.framework.tenant.core.db.TenantBaseDO;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.math.BigDecimal;
import java.util.Date;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description: 红包表
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("red_packet_info")
public class RedPacketInfoDO extends TenantBaseDO {

    private static final long serialVersionUID = 1L;

    /**
     * 自增id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 红包id
     */
    private String packetId;

    /**
     * 红包类型(0=拼手气红包,1=普通红包,2=文字口令红包,3=语音口令红包)
     */
    private String type;

    /**
     * 口令内容
     */
    private String watchwordContent;

    /**
     * 用户id,哪个用户发的红包
     */
    private Integer userId;

    /**
     * 红包总金额
     */
    private BigDecimal totalAmount;

    /**
     * 红包个数
     */
    private Integer totalPacket;

    /**
     * 单个红包金额
     */
    private BigDecimal amountOne;

    /**
     * 红包祝福语
     */
    private String blessing;

    /**
     * 红包封面
     */
    private String cover;

    /**
     * 剩余红包金额
     */
    private BigDecimal leftAmount;

    /**
     * 剩余红包个数
     */
    private Integer leftPacket;

    /**
     * 发送红包类型(0=私发,1=群发)
     */
    private String sendType;

    /**
     * 红包过期时间
     */
    private Date expireTime;

    /**
     * 红包状态(1=已创建,-1=已失效,2=已抢完)
     */
    private String status;

    /**
     * 用户昵称
     */
    @TableField(exist = false)
    private String nickname;

    /**
     * 用户头像
     */
    @TableField(exist = false)
    private String avatar;

}

RedPacketRecordsDO
package cn.inno.pala.module.packet.dal.dataobject;

import cn.inno.pala.framework.tenant.core.db.TenantBaseDO;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.math.BigDecimal;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description: 红包记录表
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("red_packet_records")
public class RedPacketRecordsDO extends TenantBaseDO {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 用户id
     */
    private Integer userId;

    /**
     * 抢到的金额
     */
    private BigDecimal amount;

    /**
     * 口令内容
     */
    private String watchwordContent;
    /**
     * 红包id
     */
    private String redPacketId;


    /**
     * 用户昵称
     */
    @TableField(exist = false)
    private String nickname;

    /**
     * 用户头像
     */
    @TableField(exist = false)
    private String avatar;
}

3.Mapper

RedPacketInfoMapper
package cn.inno.pala.module.packet.dal.mysql;

import cn.inno.pala.framework.mybatis.core.mapper.BaseMapperX;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketInfoDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description:
 */
@Mapper
public interface RedPacketInfoMapper extends BaseMapperX<RedPacketInfoDO> {
    /**
     * 红包查询
     *
     * @param packetId
     * @return
     */
    RedPacketInfoDO getRedPacket(@Param("packetId") String packetId);
}

RedPacketRecordsMapper
package cn.inno.pala.module.packet.dal.mysql;

import cn.inno.pala.framework.mybatis.core.mapper.BaseMapperX;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketRecordsDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description:
 */
@Mapper
public interface RedPacketRecordsMapper extends BaseMapperX<RedPacketRecordsDO> {
    /**
     * 根据用户id和红包id查询红包记录
     *
     * @param packetId
     * @param userId
     * @return
     */
    RedPacketRecordsDO getRedPacketRecord(@Param("packetId") String packetId, @Param("userId") Long userId);

    /**
     * 根据红包id查询红包记录列表
     *
     * @param packetId
     * @return
     */
    List<RedPacketRecordsDO> getRedPacketRecordList(@Param("packetId") String packetId);
}

4.xml

RedPacketInfoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.inno.pala.module.packet.dal.mysql.RedPacketInfoMapper">
    <resultMap id="redPacketResultMap" type="RedPacketInfoDO">
        <id column="id" property="id"></id>
        <result column="packet_id" property="packetId"></result>
        <result column="type" property="type"></result>
        <result column="watchword_content" property="watchwordContent"></result>
        <result column="user_id" property="userId"></result>
        <result column="total_amount" property="totalAmount"></result>
        <result column="total_packet" property="totalPacket"></result>
        <result column="amount_one" property="amountOne"></result>
        <result column="blessing" property="blessing"></result>
        <result column="cover" property="cover"></result>
        <result column="left_amount" property="leftAmount"></result>
        <result column="left_packet" property="leftPacket"></result>
        <result column="expire_time" property="expireTime"></result>
        <result column="send_type" property="sendType"></result>
        <result column="status" property="status"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>
        <result column="deleted" property="deleted"></result>
        <result column="creator" property="creator"></result>
        <result column="updater" property="updater"></result>
        <result column="tenant_id" property="tenantId"></result>
    </resultMap>

    <select id="getRedPacket" resultMap="redPacketResultMap">
        SELECT r.*, m.nickname, m.avatar
        FROM `red_packet_info` r
                 INNER JOIN member_user m ON r.user_id = m.id
        WHERE r.packet_id = #{packetId}
    </select>
</mapper>
RedPacketRecordsMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.inno.pala.module.packet.dal.mysql.RedPacketRecordsMapper">
    <resultMap id="redPacketRecordResultMap" type="RedPacketRecordsDO">
        <id column="id" property="id"></id>
        <result column="user_id" property="userId"></result>
        <result column="amount" property="amount"></result>
        <result column="watchword_content" property="watchwordContent"></result>
        <result column="red_packet_id" property="redPacketId"></result>
        <result column="create_time" property="createTime"></result>
        <result column="nickname" property="nickname"></result>
        <result column="avatar" property="avatar"></result>
    </resultMap>

    <select id="getRedPacketRecord" resultMap="redPacketRecordResultMap">
        SELECT r.id, r.user_id,r.watchword_content, r.amount, r.red_packet_id, r.create_time, m.nickname, m.avatar
        FROM `red_packet_records` r
                 INNER JOIN member_user m ON r.user_id = m.id
        WHERE r.red_packet_id = #{packetId}
          AND r.user_id = #{userId}
        ORDER BY r.create_time ASC
    </select>

    <select id="getRedPacketRecordList" resultMap="redPacketRecordResultMap">
        SELECT r.id, r.user_id,r.watchword_content, r.amount, r.red_packet_id, r.create_time, m.nickname, m.avatar
        FROM `red_packet_records` r
                 INNER JOIN member_user m ON r.user_id = m.id
        WHERE r.red_packet_id = #{packetId}
        ORDER BY r.create_time ASC
    </select>
</mapper>

5.Service

RedPacketInfoService
package cn.inno.pala.module.packet.service;

import cn.inno.pala.module.packet.controller.app.dto.DismantleRedPacketDTO;
import cn.inno.pala.module.packet.controller.app.dto.RedPacketsDTO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketInfoVO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketQualificationsVO;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketInfoDO;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description:
 */
public interface RedPacketInfoService extends IService<RedPacketInfoDO> {
    /**
     * 发红包
     *
     * @param packetsDTO
     * @return
     */
    String sendRedPacket(RedPacketsDTO packetsDTO);


    /**
     * 抢红包
     *
     * @param packetId
     * @return
     */
    RedPacketQualificationsVO grabPacket(String packetId);


    /**
     * 拆红包
     *
     * @param dismantleRedPacketDTO
     * @return
     */
    RedPacketInfoVO getPacket(DismantleRedPacketDTO dismantleRedPacketDTO);


    /**
     * 红包记录
     *
     * @param packetId
     * @return
     */
    RedPacketInfoVO getPacketRecord(String packetId);
}

6.impl

RedPacketInfoServiceImpl
package cn.inno.pala.module.packet.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.inno.pala.framework.common.util.collection.ObjectConvertUtil;
import cn.inno.pala.framework.common.util.md5.MD5Util;
import cn.inno.pala.framework.common.util.validation.AssertUtils;
import cn.inno.pala.framework.security.core.LoginUser;
import cn.inno.pala.framework.security.core.util.SecurityFrameworkUtils;
import cn.inno.pala.framework.tenant.core.context.TenantContextHolder;
import cn.inno.pala.module.packet.constants.Constant;
import cn.inno.pala.module.packet.controller.app.bo.RedPacketRecordsBO;
import cn.inno.pala.module.packet.controller.app.dto.DismantleRedPacketDTO;
import cn.inno.pala.module.packet.controller.app.dto.RedPacketsDTO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketInfoVO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketQualificationsVO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketRecordsVO;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketInfoDO;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketRecordsDO;
import cn.inno.pala.module.packet.dal.mysql.RedPacketInfoMapper;
import cn.inno.pala.module.packet.dal.mysql.RedPacketRecordsMapper;
import cn.inno.pala.module.packet.dal.redis.packet.RedPacketQualificationDAO;
import cn.inno.pala.module.packet.dal.redis.packet.RedPacketRedisDAO;
import cn.inno.pala.module.packet.dal.redis.records.RedPacketRecordsRedisDAO;
import cn.inno.pala.module.packet.enums.RedPacketSendTypeEnum;
import cn.inno.pala.module.packet.enums.RedPacketStatusEnum;
import cn.inno.pala.module.packet.enums.RedPacketTypeEnum;
import cn.inno.pala.module.packet.service.RedPacketInfoService;
import cn.inno.pala.module.pay.enums.wallet.PalaWalletEnum;
import cn.inno.pala.module.pay.wallet.ApiWalletService;
import cn.inno.pala.module.pay.wallet.vo.ApiJudgeAmplePalaCoinReqVO;
import cn.inno.pala.module.pay.wallet.vo.ApiReducePalaCoinReqVO;
import cn.inno.pala.module.pay.wallet.vo.ApiReducePalaCoinRespVO;
import cn.inno.pala.module.system.api.dict.DictDataApi;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

import static cn.inno.pala.module.packet.enums.ErrorCodeConstants.*;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description:
 */
@Service
@SuppressWarnings("rawtypes")
@Slf4j
public class RedPacketInfoServiceImpl extends ServiceImpl<RedPacketInfoMapper, RedPacketInfoDO> implements RedPacketInfoService {
    @Value("${red-packet.backtrack.delayed-exchange-name}")
    public String delayedExchangeName;
    @Value("${red-packet.backtrack.delayed-routing-key}")
    public String delayedRoutingKey;
    @Value("${red-packet.warehousing.entry-exchange-name}")
    public String entryExchangeName;
    @Value("${red-packet.warehousing.entry-routing-key}")
    public String entryRoutingKey;
    @Resource
    private RedPacketRedisDAO redPacketRedisDAO;
    @Resource
    private DictDataApi dictDataApi;
    @Resource
    private RedPacketInfoMapper redPacketInfoMapper;
    @Resource
    private RedPacketRecordsMapper redPacketRecordsMapper;
    @Resource
    private RedPacketRecordsRedisDAO redPacketRecordsRedisDAO;
    @Resource
    private RedPacketQualificationDAO redPacketQualificationDAO;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private ApiWalletService apiWalletService;
    @Resource
    private RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String sendRedPacket(RedPacketsDTO packetsDTO) {
        log.info("request param 【{}】", packetsDTO);
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        Long loginUserId = loginUser.getId();
        RedPacketInfoDO redPackets = new RedPacketInfoDO();
        RLock lock = redissonClient.getLock(Constant.RED_PACKET_LOCK_KEY + loginUserId);
        lock.lock();
        try {
            //设置默认的祝福语
            if (StrUtil.isBlank(packetsDTO.getBlessing())) {
                packetsDTO.setBlessing(Constant.DEFAULT_BLESSING_MESSAGE);
            }

            if (StrUtil.isBlank(packetsDTO.getCover())) {
                packetsDTO.setCover(Constant.DEFAULT_RED_PACKET_COVER);
            }
            //红包的总个数
            int totalPacket = Integer.valueOf(packetsDTO.getTotalPacket());
            //红包总金额
            BigDecimal totalAmount = packetsDTO.getTotalAmount();
            //单个红包金额
            BigDecimal amountOne = packetsDTO.getAmountOne();
            AssertUtils.isTrue(totalPacket <= 0, NUMBER_NOT_ZERO);
            //拼手气红包或者口令红包
            if (packetsDTO.getType().equals(RedPacketTypeEnum.LUCKY.getType()) || packetsDTO.getType().equals(RedPacketTypeEnum.TEXT_WATCHWORD.getType()) || packetsDTO.getType().equals(RedPacketTypeEnum.VOICE_WATCHWORD.getType())) {
                AssertUtils.isTrue(null == totalAmount, TOTAL_AMOUNT_NOT_NULL);
                amountOne = totalAmount.divide(BigDecimal.valueOf(totalPacket), 2, RoundingMode.DOWN);
            }
            //普通红包
            if (packetsDTO.getType().equals(RedPacketTypeEnum.ORDINARY.getType())) {
                AssertUtils.isTrue(null == amountOne, ONE_AMOUNT_NOT_NULL);
                totalAmount = amountOne.multiply(BigDecimal.valueOf(totalPacket));
            }
            //从数据字段获取金额的最大值和最小值
            Integer minRedPacket = Integer.valueOf(dictDataApi.dictDatalist("min_red_packet").get(0).getValue());
            Integer maxRedPacket = Integer.valueOf(dictDataApi.dictDatalist("max_red_packet").get(0).getValue());

            Integer minTotalPacket = Integer.valueOf(dictDataApi.dictDatalist("min_total_packet").get(0).getValue());
            Integer maxTotalPacket = Integer.valueOf(dictDataApi.dictDatalist("max_total_packet").get(0).getValue());

            //判断用户发的红包是否在限制内
            AssertUtils.isTrue(amountOne.compareTo(BigDecimal.valueOf(minRedPacket)) == -1, MIN_AMOUNT_ERROR, minRedPacket);
            AssertUtils.isTrue(amountOne.compareTo(BigDecimal.valueOf(maxRedPacket)) == 1, MAX_AMOUNT_ERROR, maxRedPacket);
            AssertUtils.isTrue(Integer.valueOf(packetsDTO.getTotalPacket()) < minTotalPacket, TOTAL_PACKET);
            AssertUtils.isTrue(Integer.valueOf(packetsDTO.getTotalPacket()) > maxTotalPacket, TOTAL_PACKET);
            AssertUtils.isTrue(BigDecimal.valueOf(totalAmount.intValue()).compareTo(totalAmount) == -1, NOT_DECIMAL);
            //需要校验用户的账户余额
            Boolean result = apiWalletService.judgeAmplePalaCoin(new ApiJudgeAmplePalaCoinReqVO(loginUserId, totalAmount));
            AssertUtils.isTrue(!result, INSUFFICIENT_BALANCE);

            BeanUtils.copyProperties(packetsDTO, redPackets);
            redPackets.setUserId(loginUserId.intValue());
            redPackets.setCreateTime(new Date());
            redPackets.setTotalPacket(totalPacket);
            redPackets.setTotalAmount(totalAmount);
            redPackets.setWatchwordContent(packetsDTO.getWatchwordContent());
            redPackets.setLeftAmount(totalAmount);
            redPackets.setLeftPacket(totalPacket);
            redPackets.setStatus(RedPacketStatusEnum.CREATED.getStatus());
            redPackets.setSendType(packetsDTO.getSendType());
            redPackets.setNickname(loginUser.getNickname());
            redPackets.setAvatar(loginUser.getAvatar());
            //采用雪花算法生成红包id,保证唯一性
            redPackets.setPacketId(String.valueOf(IdUtil.getSnowflake((long) (0 + Math.random() * (30 - 0 + 1)), (long) (0 + Math.random() * (30 - 0 + 1))).nextId()));
            Date date = new Date();
            //一天过期
            redPackets.setExpireTime(DateUtil.offsetDay(date, 1));

            //1.扣减账户额度
            ApiReducePalaCoinReqVO palaCoinReq = new ApiReducePalaCoinReqVO().setUserId(loginUserId).setBusiness(PalaWalletEnum.REDUCE_HAND_OUT_RED_ENVELOPES.getStatus()).setTargetId(redPackets.getPacketId()).setOperationPalaCoin(totalAmount).setContent(RedPacketSendTypeEnum.PRIVATE.getStatus().equals(packetsDTO.getSendType()) ? "给昵称" + packetsDTO.getNickname() + "发红包" : "世界频道发出红包");
            ApiReducePalaCoinRespVO apiReducePalaCoinResp = apiWalletService.reducePalaCoin(palaCoinReq);
            AssertUtils.isTrue(!apiReducePalaCoinResp.getState(), DEDUCTION_ACCOUNT_ERROR);
            log.info("扣减账户额度成功状态【{}】", apiReducePalaCoinResp.getState());
            //2.保存红包信息
            redPacketInfoMapper.insert(redPackets);
            log.info("用户【{}】,插入红包成功记录【{}】", loginUserId, redPackets);
            //3.往mq发送一条延迟消息,方便做24小时红包到期退款的作用
            rabbitTemplate.convertAndSend(delayedExchangeName, delayedRoutingKey, redPackets.getPacketId(), correlationData -> {
                //设置24小时延迟时间
                correlationData.getMessageProperties().setDelay(24 * 60 * 60 * 1000);
                correlationData.getMessageProperties().setHeader("tenant-id", TenantContextHolder.getTenantId());
                correlationData.getMessageProperties().setHeader("user-id", loginUserId);
                return correlationData;
            });
            log.info("往mq发送延迟消息");
            //4.写入redis中  过期时间是3天
            redPacketRedisDAO.set(redPackets.getPacketId(), redPackets);
            log.info("红包记录写入redis");
            //5.返回红包id
            return redPackets.getPacketId();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RedPacketQualificationsVO grabPacket(String packetId) {
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        judge(loginUserId, packetId, new RedPacketInfoDO(), new RedPacketRecordsDO());
        String md5 = MD5Util.getMD5(loginUserId.toString());
        redPacketQualificationDAO.set(Constant.RED_PACKET_QUALIFICATIONS_KEY + loginUserId + "_" + packetId, md5);
        return RedPacketQualificationsVO.builder().build().setFlag(true).setSign(md5);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public RedPacketInfoVO getPacket(DismantleRedPacketDTO dismantleRedPacketDTO) {
        //登录用户
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        RedPacketInfoDO redPackets = new RedPacketInfoDO();
        RedPacketRecordsDO redPacketRecords = new RedPacketRecordsDO();
        RLock lock = redissonClient.getLock(Constant.RED_PACKET_LOCK_KEY + dismantleRedPacketDTO.getPacketId());
        lock.lock();
        try {
            //md5验签
            String value = redPacketQualificationDAO.get(Constant.RED_PACKET_QUALIFICATIONS_KEY + loginUserId + "_" + dismantleRedPacketDTO.getPacketId());
            AssertUtils.isTrue(ObjectUtil.notEqual(value, dismantleRedPacketDTO.getSign()), SIGN_ERROR);

            judge(loginUserId, dismantleRedPacketDTO.getPacketId(), redPackets, redPacketRecords);
            //如果是私发,自己不能领取自己的红包
            if (redPackets.getSendType().equals(RedPacketSendTypeEnum.PRIVATE.getStatus())) {
                AssertUtils.isTrue(loginUserId.intValue() == redPackets.getUserId(), NOT_AVAILABLE);
            }
            //抢到的红包金额
            BigDecimal amount = calculate(redPackets);
            //更新红包的剩余金额和数量
            redPackets.setLeftPacket(redPackets.getLeftPacket() - 1);
            redPackets.setLeftAmount(redPackets.getLeftAmount().subtract(amount));
            if (redPackets.getLeftPacket() == 0) {
                //更新红包的状态为已领完
                redPackets.setStatus(RedPacketStatusEnum.WITHOUT.getStatus());
            }
            //构建抢红包记录
            RedPacketRecordsDO packetRecords = new RedPacketRecordsDO();
            packetRecords.setAmount(amount);
            packetRecords.setUserId(loginUserId.intValue());
            packetRecords.setRedPacketId(redPackets.getPacketId());
            packetRecords.setAvatar(loginUser.getAvatar());
            packetRecords.setCreateTime(new Date());
            packetRecords.setNickname(loginUser.getNickname());
            packetRecords.setWatchwordContent(dismantleRedPacketDTO.getWatchwordContent());

            RedPacketRecordsBO redPacketRecordsBO = new RedPacketRecordsBO();
            BeanUtils.copyProperties(packetRecords, redPacketRecordsBO);
            redPacketRecordsBO.setNickname(redPackets.getNickname());

            Map<String, Object> map = new HashMap<>(3);
            map.put("redPackets", redPackets);
            map.put("redPacketRecords", redPacketRecordsBO);
            //异步入账
            rabbitTemplate.convertAndSend(entryExchangeName, entryRoutingKey, map, correlationData -> {
                correlationData.getMessageProperties().setHeader("tenant-id", TenantContextHolder.getTenantId());
                correlationData.getMessageProperties().setHeader("user-id", loginUserId);
                return correlationData;
            });
            log.info("mq异步入账");

            //更新redis状态
            redPacketRedisDAO.set(dismantleRedPacketDTO.getPacketId(), redPackets);
            log.info("红包记录状态更新【{}】,并写入redis", redPackets);

            //添加到redis中
            redPacketRecordsRedisDAO.set(dismantleRedPacketDTO.getPacketId(), loginUserId.toString(), packetRecords);
            log.info("用户【{}】抢到的红包【{}】", loginUser, packetRecords);
            //构建返回红包信息
            RedPacketInfoVO redPacketVO = buildRedPacketVO(redPackets, packetRecords);
            //删除签名
            redPacketQualificationDAO.delete(Constant.RED_PACKET_QUALIFICATIONS_KEY + loginUserId + "_" + dismantleRedPacketDTO.getPacketId());
            return redPacketVO;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RedPacketInfoVO getPacketRecord(String packetId) {
        //从缓存中读取红包信息
        RedPacketInfoDO redPackets = redPacketRedisDAO.get(packetId);
        //校验红包是否存在
        AssertUtils.isTrue(ObjectUtil.isEmpty(redPackets), RECORD_ERROR);
        //登录用户
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        //该用户用户领过红包记录
        RedPacketRecordsDO redPacketRecords = redPacketRecordsRedisDAO.get(packetId, loginUserId.toString());
        //构建返回红包信息
        RedPacketInfoVO redPacketVO = buildRedPacketVO(redPackets, redPacketRecords);
        log.info("红包记录【{}】", redPacketVO);
        return redPacketVO;
    }

    /**
     * 计算金额
     *
     * @param redPacketInfo
     * @return
     */
    private BigDecimal calculate(RedPacketInfoDO redPacketInfo) {
        BigDecimal amount = BigDecimal.valueOf(0);
        if (redPacketInfo.getType().equals(RedPacketTypeEnum.LUCKY.getType()) || redPacketInfo.getType().equals(RedPacketTypeEnum.TEXT_WATCHWORD.getType()) || redPacketInfo.getType().equals(RedPacketTypeEnum.VOICE_WATCHWORD.getType())) {
            //如果只剩下一个红包
            if (redPacketInfo.getLeftPacket() == 1) {
                //抢到的红包金额就是剩下的红包金额
                amount = redPacketInfo.getLeftAmount();
            } else {
                //如果还有多个红包通过二倍均值法计算
                //剩余红包金额/剩余红包个数
                BigDecimal result = redPacketInfo.getLeftAmount().divide(BigDecimal.valueOf(redPacketInfo.getLeftPacket()), 0, RoundingMode.UP);
                //二倍均值发 (M/N)*2 剩余红包金额/剩余红包个数*2
                result = result.multiply(BigDecimal.valueOf(2));
                //将金额单位元拆分为分
                int limit = (int) (result.doubleValue() * 100);
                amount = BigDecimal.valueOf(RandomUtil.randomInt(limit - 1) + 1).divide(BigDecimal.valueOf(100), 0, RoundingMode.UP);
            }
        } else if (redPacketInfo.getType().equals(RedPacketTypeEnum.ORDINARY.getType())) {
            //如果是普通红包,抢到的金额就是单个红包的金额
            amount = redPacketInfo.getAmountOne();
        }
        return amount;
    }


    /**
     * 数据构建
     *
     * @param redPackets
     * @param redPacketRecordsDO
     * @return
     */
    private RedPacketInfoVO buildRedPacketVO(RedPacketInfoDO redPackets, RedPacketRecordsDO redPacketRecordsDO) {
        RedPacketInfoVO redPacketVO = new RedPacketInfoVO();
        redPacketVO.setSendUserName(redPackets.getNickname());
        redPacketVO.setAvatar(redPackets.getAvatar());
        if (null != redPacketRecordsDO) {
            redPacketVO.setReceiveAmount(redPacketRecordsDO.getAmount());
        }
        redPacketVO.setTotalPacket(redPackets.getTotalPacket());
        //已领取红包的个数
        redPacketVO.setGetPacket(redPackets.getTotalPacket() - redPackets.getLeftPacket());
        redPacketVO.setTotalAmount(redPackets.getTotalAmount());
        redPacketVO.setLeftAmount(redPackets.getLeftAmount());
        redPacketVO.setBlessing(redPackets.getBlessing());
        redPacketVO.setWatchwordContent(redPackets.getWatchwordContent());
        //当前红包所有抢到的记录
        Map<String, RedPacketRecordsDO> entries = redPacketRecordsRedisDAO.entries(redPackets.getPacketId());
        List<RedPacketRecordsDO> redPacketRecordList = new ArrayList<>(entries.values());
        if (entries.size() == 0) {
            redPacketRecordList = redPacketRecordsMapper.getRedPacketRecordList(redPackets.getPacketId());
        }
        //类型转换
        List<RedPacketRecordsVO> list = ObjectConvertUtil.convertInstance().objectConvert(redPacketRecordList, RedPacketRecordsVO.class);
        redPacketVO.setRecordsList(list);
        return redPacketVO;
    }


    /**
     * 数据判断
     *
     * @param loginUserId
     * @param packetId
     * @param redPackets
     * @param redPacketRecords
     */
    private void judge(Long loginUserId, String packetId, RedPacketInfoDO redPackets, RedPacketRecordsDO redPacketRecords) {
        AssertUtils.isTrue(StrUtil.isBlank(packetId), PARAMETER_EXCEPTION);
        RedPacketInfoDO redPacketInfoDO = redPacketRedisDAO.get(packetId);
        if (ObjectUtil.isNotNull(redPacketInfoDO)) {
            BeanUtils.copyProperties(redPacketInfoDO, redPackets);
        }
        //校验红包是否存在
        AssertUtils.isTrue(ObjectUtil.isNull(redPacketInfoDO), RECORD_ERROR);
        RedPacketRecordsDO redPacketRecordsDO = redPacketRecordsRedisDAO.get(packetId, loginUserId.toString());
        if (ObjectUtil.isNotNull(redPacketRecordsDO)) {
            BeanUtils.copyProperties(redPacketRecordsDO, redPacketRecords);
        }
        //判断用户是否领过红包
        AssertUtils.isTrue(ObjectUtil.isNotNull(redPacketRecordsDO), RECEIVED);
        //判断红包是否过期
        AssertUtils.isTrue(ObjectUtil.equals(RedPacketStatusEnum.EXPIRED.getStatus(), redPacketInfoDO.getStatus()), EXPIRED);
        //判断红包是否已抢完
        AssertUtils.isTrue(RedPacketStatusEnum.WITHOUT.getStatus().equals(redPacketInfoDO.getStatus()) || redPacketInfoDO.getLeftPacket() < 1, ROBBED);
    }
}

7.application.yaml


数据库配置省略了,你自己加上

spring:
  # rabbitmq
  rabbitmq:
    host: 10.0.99.114
    port: 5672 # tcp端口
    username: admin # 用户名
    password: admin # 用户密码
    #确认消息已发送到交换机(Exchange)
    publisher-confirm-type: correlated
    #保证交换机能把消息推送到队列中
    publisher-returns: true
    virtual-host: / # 虚拟主机
    #这个配置是保证消费者会消费消息,手动确认
    listener:
      simple:
        acknowledge-mode: manual
    template:
      mandatory: true
    # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
  redis:
	 host: 10.0.99.191 # 地址
	 port: 6379 # 端口
	 database: 8 # 数据库索引
	 password: 'password'
#红包队列
red_packet:
  backtrack:
    # 24小时退回业务队列
    delayed-queue-name: red.packet.delayed.queue-test
    delayed-exchange-name: red.packet.delayed.exchange-test
    delayed-routing-key: red.packet.delayed.routing.key-test
    # 24小时退回死信队列
    dead-letter-queue-name: red.packet.dead.letter.delayed.queue-test
    dead-letter-exchange-name: red.packet.dead.letter.delayed.exchange-test
    dead-letter-routing-key: red.packet.dead.letter.delayed.routing.key-test

8.controller

package cn.inno.pala.module.packet.controller.app;

import cn.inno.pala.framework.common.pojo.CommonResult;
import cn.inno.pala.module.packet.controller.app.dto.DismantleRedPacketDTO;
import cn.inno.pala.module.packet.controller.app.dto.RedPacketsDTO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketInfoVO;
import cn.inno.pala.module.packet.controller.app.vo.RedPacketQualificationsVO;
import cn.inno.pala.module.packet.service.RedPacketInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;

import static cn.inno.pala.framework.common.pojo.CommonResult.success;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description:
 */
@RestController
@Api(tags = "用户 APP - 红包")
@RequestMapping("/redPacket")
public class RedPacketInfoController {
    @Resource
    private RedPacketInfoService redPacketInfoService;

    @PostMapping("/send")
    @ApiOperation("发红包接口")
    public CommonResult<String> sendPacket(@RequestBody @Valid RedPacketsDTO redPacketsDTO) {
        return success(redPacketInfoService.sendRedPacket(redPacketsDTO));
    }

    @GetMapping("/grab/{packetId}")
    @ApiOperation("抢红包接口")
    public CommonResult<RedPacketQualificationsVO> grabPacket(@PathVariable("packetId") String packetId) {
        return success(redPacketInfoService.grabPacket(packetId));
    }

    @PostMapping("/rob")
    @ApiOperation("拆红包接口")
    public CommonResult<RedPacketInfoVO> getPacket(@RequestBody DismantleRedPacketDTO dismantleRedPacketDTO) {
        return success(redPacketInfoService.getPacket(dismantleRedPacketDTO));
    }

    @GetMapping("/get/{packetId}")
    @ApiOperation("红包记录接口")
    public CommonResult<RedPacketInfoVO> getPacketRecord(@PathVariable("packetId") String packetId) {
        return success(redPacketInfoService.getPacketRecord(packetId));
    }
}
9.VO
RedPacketInfoVO
package cn.inno.pala.module.packet.controller.app.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

/**
 * @author: bright
 * @date:Created in 2022/6/4 16:42
 * @describe : 抢红包返回对象
 */
@Data
public class RedPacketInfoVO implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "发红包的用户名")
    private String sendUserName;

    @ApiModelProperty(value = "头像")
    private String avatar;

    @ApiModelProperty(value = "当前登陆用户领取的红包金额")
    private BigDecimal receiveAmount;

    @ApiModelProperty(value = "红包总数量")
    private Integer totalPacket;

    @ApiModelProperty(value = "已领取的红包数量")
    private Integer getPacket;

    @ApiModelProperty(value = "红包总金额")
    private BigDecimal totalAmount;

    @ApiModelProperty(value = "剩余红包金额")
    private BigDecimal leftAmount;

    @ApiModelProperty(value = "口令内容")
    private String watchwordContent;

    @ApiModelProperty(value = "祝福语")
    private String blessing;

    @ApiModelProperty(value = "红包领取记录集合")
    private List<RedPacketRecordsVO> recordsList;

}

RedPacketQualificationsVO
package cn.inno.pala.module.packet.controller.app.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
 * @author: bright
 * @description:
 * @time: 2022-07-25 19:04
 */
@Data
@Builder
public class RedPacketQualificationsVO implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "true 有资格抢")
    private Boolean flag;

    @ApiModelProperty(value = "拆红包签名")
    private String sign;
}

RedPacketRecordsVO
package cn.inno.pala.module.packet.controller.app.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * @author: bright
 * @date:Created in 2022/6/3 13:40
 * @describe :红包记录
 */
@Data
public class RedPacketRecordsVO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户名")
    private String nickname;

    @ApiModelProperty(value = "红包金额")
    private BigDecimal amount;

    @ApiModelProperty(value = "抢红包时间")
    private Date createTime;

    @ApiModelProperty(value = "头像")
    private String avatar;

}

10.DTO
DismantleRedPacketDTO
package cn.inno.pala.module.packet.controller.app.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

/**
 * @author: bright
 * @description:
 * @time: 2022-07-06 12:30
 */
@Data
public class DismantleRedPacketDTO implements Serializable {
    @ApiModelProperty(value = "红包id", required = true)
    @NotBlank(message = "红包id不能为空")
    private String packetId;

    @ApiModelProperty(value = "口令内容")
    private String watchwordContent;

    @ApiModelProperty(value = "签名")
    private String sign;
}

RedPacketsDTO
package cn.inno.pala.module.packet.controller.app.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * @author: bright
 * @date:Created in 2022/6/3 13:40
 * @describe :红包表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RedPacketsDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "红包类型(0=拼手气红包,1=普通红包,2=文字口令红包,3=语音口令红包)", required = true)
    @NotBlank(message = "红包类型不能为空")
    @Range(min = 0, max = 3, message = "红包类型必须在规定内")
    private String type;

    @ApiModelProperty(value = "红包总金额")
    private BigDecimal totalAmount;

    @ApiModelProperty(value = "口令内容")
    private String watchwordContent;

    @ApiModelProperty(value = "红包个数", required = true)
    @NotNull(message = "红包个数不能为空")
    @Pattern(regexp = "-?[1-9]\\d*", message = "红包个数不能为小数")
    private String totalPacket;

    @ApiModelProperty(value = "单个红包金额")
    private BigDecimal amountOne;

    @ApiModelProperty(value = "祝福语")
    private String blessing;

    @ApiModelProperty(value = "封面")
    private String cover;

    @ApiModelProperty(value = "0.私发,1.群发")
    @NotBlank(message = "类型不能为空")
    @Range(min = 0, max = 1, message = "类型必须在规定内")
    private String sendType;

    @ApiModelProperty(value = "私发的目标昵称,群发不需要传")
    private String nickname;
}

11.BO
RedPacketRecordsBO
package cn.inno.pala.module.packet.controller.app.bo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description: 红包记录表
 */
@Data
@Accessors(chain = true)
public class RedPacketRecordsBO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    private Integer userId;

    private BigDecimal amount;

    private String redPacketId;

    private String nickname;

    private String watchwordContent;
}

12.mp

RedPacketDelayMessage
package cn.inno.pala.module.packet.mp.message;


import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author: Bright
 * @time: 2022-06-07 13:58
 * @description: 红包过期退款延迟消息配置类
 */
@Configuration
public class RedPacketDelayMessage {

    /*=====================业务队列===================*/

    @Value("${red-packet.backtrack.delayed-queue-name}")
    public String delayedQueueName;

    @Value("${red-packet.backtrack.delayed-exchange-name}")
    public String delayedExchangeName;

    @Value("${red-packet.backtrack.delayed-routing-key}")
    public String delayedRoutingKey;


    /*=====================死信队列===================*/

    @Value("${red-packet.backtrack.dead-letter-queue-name}")
    public String deadLetterQueueName;

    @Value("${red-packet.backtrack.dead-letter-exchange-name}")
    public String deadLetterExchangeName;


    @Value("${red-packet.backtrack.dead-letter-routing-key}")
    public String deadLetterRoutingKey;

    /**
     * 业务队列
     *
     * @return
     */
    @Bean
    public Queue delayedQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", deadLetterExchangeName);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", deadLetterRoutingKey);
        return QueueBuilder.durable(delayedQueueName).withArguments(args).build();
    }


    /**
     * 声明死信队列
     *
     * @return
     */
    @Bean
    public Queue delayedDeadLetterQueue() {
        return new Queue(deadLetterQueueName);
    }

    /**
     * 自定义交换机 我们在这里定义的是一个延迟交换机
     *
     * @return
     */
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>(1);
        //自定义交换机的类型
        args.put("x-delayed-type", "direct");
        return new CustomExchange(delayedExchangeName, "x-delayed-message", true, false, args);
    }

    /**
     * 声明死信Exchange
     *
     * @return
     */
    @Bean
    public DirectExchange delayedDeadLetterExchange() {
        return new DirectExchange(deadLetterExchangeName);
    }

    /**
     * 业务队列绑定关系
     *
     * @param queue
     * @param delayedExchange
     * @return
     */
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,
                                       @Qualifier("delayedExchange") CustomExchange delayedExchange) {
        return BindingBuilder.bind(queue).to(delayedExchange).with(delayedRoutingKey).noargs();
    }


    /**
     * 声明死信队列绑定关系
     *
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding bindingDelayedDeadLetterQueue(@Qualifier("delayedDeadLetterQueue") Queue queue,
                                      @Qualifier("delayedDeadLetterExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(deadLetterRoutingKey);
    }

}

RedPacketEntryMessage
package cn.inno.pala.module.packet.mp.message;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author: bright
 * @description: 抢红包入账队列
 * @time: 2022-06-11 15:29
 */
@Configuration
public class RedPacketEntryMessage {

    /*=====================业务队列===================*/

    /**
     * durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
     * exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
     * autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
     *
     * @return
     */
    @Value("${red-packet.warehousing.entry-queue-name}")
    public String entryQueueName;

    @Value("${red-packet.warehousing.entry-exchange-name}")
    public String entryExchangeName;


    @Value("${red-packet.warehousing.entry-routing-key}")
    public String entryRoutingKey;


    /*=====================死信队列===================*/


    @Value("${red-packet.warehousing.dead-letter-queue-name}")
    public String deadLetterQueueName;

    @Value("${red-packet.warehousing.dead-letter-exchange-name}")
    public String deadLetterExchangeName;


    @Value("${red-packet.warehousing.dead-letter-routing-key}")
    public String deadLetterRoutingKey;

    /**
     * 业务队列
     *
     * @return
     */
    @Bean
    public Queue entryQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", deadLetterExchangeName);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", deadLetterRoutingKey);
        return QueueBuilder.durable(entryQueueName).withArguments(args).build();
    }

    /**
     * 声明死信队列
     *
     * @return
     */
    @Bean
    public Queue entryDeadLetterQueue() {
        return new Queue(deadLetterQueueName);
    }

    /**
     * 业务交换机
     *
     * @return
     */
    @Bean
    public DirectExchange entryExchange() {
        return new DirectExchange(entryExchangeName, true, false);
    }


    /**
     * 声明死信Exchange
     *
     * @return
     */
    @Bean
    public DirectExchange entryDeadLetterExchange() {
        return new DirectExchange(deadLetterExchangeName);
    }

    /**
     * 业务队列绑定关系
     *
     * @param queue
     * @param entryExchange
     * @return
     */
    @Bean
    public Binding bindingEntryQueue(@Qualifier("entryQueue") Queue queue,
                                     @Qualifier("entryExchange") DirectExchange entryExchange) {
        return BindingBuilder.bind(queue).to(entryExchange).with(entryRoutingKey);
    }

    /**
     * 声明死信队列绑定关系
     *
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding bindingEntryDeadLetterQueue(@Qualifier("entryDeadLetterQueue") Queue queue,
                                     @Qualifier("entryDeadLetterExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(deadLetterRoutingKey);
    }
}

RedPacketDelayConsumer
package cn.inno.pala.module.packet.mp.consumer;

import cn.hutool.core.util.ObjectUtil;
import cn.inno.pala.framework.common.exception.ServiceException;
import cn.inno.pala.framework.common.util.validation.AssertUtils;
import cn.inno.pala.framework.tenant.core.context.TenantContextHolder;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketInfoDO;
import cn.inno.pala.module.packet.dal.mysql.RedPacketInfoMapper;
import cn.inno.pala.module.packet.dal.redis.packet.RedPacketRedisDAO;
import cn.inno.pala.module.packet.enums.RedPacketStatusEnum;
import cn.inno.pala.module.pay.enums.wallet.PalaWalletEnum;
import cn.inno.pala.module.pay.wallet.ApiWalletService;
import cn.inno.pala.module.pay.wallet.vo.ApiAddPalaCoinReqVO;
import cn.inno.pala.module.pay.wallet.vo.ApiAddPalaCoinRespVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

import static cn.inno.pala.module.packet.enums.ErrorCodeConstants.ADD_ACCOUNT_ERROR;

/**
 * @author: bright
 * @description:
 * @time: 2022-06-11 10:17
 */
@Component
@Slf4j
public class RedPacketDelayConsumer {

    @Resource
    private RedPacketInfoMapper redPacketInfoMapper;

    @Resource
    private ApiWalletService apiWalletService;

    @Resource
    private RedPacketRedisDAO redPacketRedisDAO;

    /**
     * 普通消费
     *
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "#{delayedQueue.name}")
    @Transactional(rollbackFor = Exception.class)
    public void receiveDelayedQueue(Message message, Channel channel) throws Exception {
        try {
            business(message);
            //手动确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (e instanceof ServiceException) {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //丢到死信队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            e.printStackTrace();
        }
    }


    /**
     * 死信消费
     *
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "#{delayedDeadLetterQueue.name}")
    @Transactional(rollbackFor = Exception.class)
    public void receiveDelayedDeadLetterQueue(Message message, Channel channel) throws Exception {
        try {
            business(message);
            //手动确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (e instanceof ServiceException) {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //这里需要做插入数据库的操作防止消息丢失,在通过订单任务进行重新投递,我这里持久化就省略了
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            e.printStackTrace();
        }
    }

    private void business(Message message) {
        //获取tenantId
        String tenantId = message.getMessageProperties().getHeader("tenant-id").toString();
        //获取当前登录用户id
        String userId = message.getMessageProperties().getHeader("user-id").toString();
        //设置tenantId
        TenantContextHolder.setTenantId(Long.valueOf(tenantId));
        String redPacketId = new String(message.getBody());
        RedPacketInfoDO redPacketInfo = redPacketInfoMapper.selectOne("packet_id", redPacketId);
        //未抢完需求进行退款处理
        if (ObjectUtil.isNotNull(redPacketInfo) && redPacketInfo.getLeftPacket() != 0 && redPacketInfo.getLeftAmount().intValue() != 0) {
            ApiAddPalaCoinReqVO palaCoinReq = new ApiAddPalaCoinReqVO()
                    .setUserId(Long.valueOf(redPacketInfo.getUserId()))
                    .setContent("红包退款")
                    .setBusiness(PalaWalletEnum.ADD_RETURN_THE_RED_ENVELOPE.getStatus())
                    .setTargetId(redPacketInfo.getPacketId())
                    .setOperationPalaCoin(redPacketInfo.getLeftAmount());

            ApiAddPalaCoinRespVO apiAddPalaCoinResp = apiWalletService.addPalaCoin(palaCoinReq);
            log.info("退还给用户【】,金额【{}】,红包id【{}】", redPacketInfo.getUserId(), redPacketInfo.getLeftAmount(), redPacketInfo.getPacketId());
            AssertUtils.isTrue(!apiAddPalaCoinResp.getState(), ADD_ACCOUNT_ERROR);
            //修改状态为已过期
            RedPacketInfoDO redPacketInfoDO = new RedPacketInfoDO();
            redPacketInfoDO.setStatus(RedPacketStatusEnum.EXPIRED.getStatus());
            redPacketInfoDO.setUpdater(userId);
            redPacketInfoMapper.update(redPacketInfoDO, new QueryWrapper<RedPacketInfoDO>().eq("packet_id", redPacketInfo.getPacketId()));
            log.info("修改数据库状态为已过期");
            //修改redis状态
            redPacketInfo.setStatus(RedPacketStatusEnum.EXPIRED.getStatus());
            redPacketRedisDAO.set(redPacketInfo.getPacketId(), redPacketInfo);
            log.info("修改redis状态为已过期");
        }
    }
}

RedPacketEntryConsumer
package cn.inno.pala.module.packet.mp.consumer;

import cn.inno.pala.framework.common.exception.ServiceException;
import cn.inno.pala.framework.common.util.validation.AssertUtils;
import cn.inno.pala.framework.tenant.core.context.TenantContextHolder;
import cn.inno.pala.module.packet.controller.app.bo.RedPacketRecordsBO;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketInfoDO;
import cn.inno.pala.module.packet.dal.dataobject.RedPacketRecordsDO;
import cn.inno.pala.module.packet.dal.mysql.RedPacketInfoMapper;
import cn.inno.pala.module.packet.dal.mysql.RedPacketRecordsMapper;
import cn.inno.pala.module.pay.enums.wallet.ConversionTypeEnum;
import cn.inno.pala.module.pay.enums.wallet.PalaTypeEnum;
import cn.inno.pala.module.pay.enums.wallet.PalaWalletDiamondsnEnum;
import cn.inno.pala.module.pay.wallet.ApiWalletService;
import cn.inno.pala.module.pay.wallet.vo.ApiDiamondsnRespVO;
import cn.inno.pala.module.pay.wallet.vo.ApiSetDiamondsnReqVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;

import static cn.inno.pala.module.packet.enums.ErrorCodeConstants.ADD_ACCOUNT_ERROR;

/**
 * @author: bright
 * @description: 抢红包入账消费
 * @time: 2022-06-13 15:29
 */
@Component
@Slf4j
public class RedPacketEntryConsumer {
    @Resource
    private ApiWalletService apiWalletService;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private RedPacketInfoMapper redPacketInfoMapper;

    @Resource
    private RedPacketRecordsMapper redPacketRecordsMapper;

    /**
     * 普通消费
     *
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "#{entryQueue.name}")
    @Transactional(rollbackFor = Exception.class)
    public void receiveEntryQueue(Message message, Channel channel) throws IOException {
        try {
            business(message);
            //手动确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (e instanceof ServiceException) {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //丢到死信队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            e.printStackTrace();
        }
    }

    /**
     * 死信消费
     *
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "#{entryDeadLetterQueue.name}")
    @Transactional(rollbackFor = Exception.class)
    public void receiveEntryDeadLetterQueue(Message message, Channel channel) throws Exception {
        try {
            business(message);
            //手动确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (e instanceof ServiceException) {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //这里需要做插入数据库的操作防止消息丢失,在通过订单任务进行重新投递,我这里持久化就省略了
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            e.printStackTrace();
        }
    }

    private void business(Message message) {
        //获取tenantId
        String tenantId = message.getMessageProperties().getHeader("tenant-id").toString();
        //获取当前登录用户id
        String userId = message.getMessageProperties().getHeader("user-id").toString();
        //设置tenantId
        TenantContextHolder.setTenantId(Long.valueOf(tenantId));
        Map<String, Object> map = (Map<String, Object>) rabbitTemplate.getMessageConverter().fromMessage(message);
        //更新数据库剩余金额和个数,如果已抢完,也需要修改状态
        RedPacketInfoDO redPackets = (RedPacketInfoDO) map.get("redPackets");
        //更新数据库状态
        redPacketInfoMapper.update(redPackets, new QueryWrapper<RedPacketInfoDO>().eq("packet_id", redPackets.getPacketId()));
        log.info("修改数据库红包金额和个数【{}】", redPackets);
        RedPacketRecordsBO redPacketRecords = (RedPacketRecordsBO) map.get("redPacketRecords");
        ApiSetDiamondsnReqVO apiSetDiamondsnReq = new ApiSetDiamondsnReqVO();
        apiSetDiamondsnReq.setUserId(Long.valueOf(userId));
        apiSetDiamondsnReq.setContent("抢到昵称" + redPacketRecords.getNickname() + "红包");
        apiSetDiamondsnReq.setBusiness(PalaWalletDiamondsnEnum.ADD_RECEIVED_A_RED_ENVELOPE.getStatus());
        apiSetDiamondsnReq.setTargetId(redPacketRecords.getRedPacketId());
        apiSetDiamondsnReq.setConversionType(ConversionTypeEnum.PARRA_CURRENCY.getStatus());
        apiSetDiamondsnReq.setType(PalaTypeEnum.TYPE_ADD.getStatus());
        apiSetDiamondsnReq.setOperationQuota(redPacketRecords.getAmount());
        ApiDiamondsnRespVO apiDiamondsnResp = apiWalletService.setDiamondsn(apiSetDiamondsnReq);
        AssertUtils.isTrue(!apiDiamondsnResp.getState(), ADD_ACCOUNT_ERROR);
        log.info("抢到昵称【{}】 金额【{}】", redPacketRecords.getNickname(), redPacketRecords.getAmount());
        //添加抢到记录
        RedPacketRecordsDO redPacketRecordsDO = new RedPacketRecordsDO();
        redPacketRecordsDO.setAmount(redPacketRecords.getAmount());
        redPacketRecordsDO.setRedPacketId(redPacketRecords.getRedPacketId());
        redPacketRecordsDO.setUserId(redPacketRecords.getUserId());
        redPacketRecordsDO.setUpdater(userId);
        redPacketRecordsDO.setCreator(userId);
        redPacketRecordsDO.setWatchwordContent(redPacketRecords.getWatchwordContent());
        //插入数据库中
        redPacketRecordsMapper.insert(redPacketRecordsDO);
        log.info("插入红包记录【{}】", redPacketRecordsDO);
    }
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘明同学呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值