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;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("red_packet_info")
public class RedPacketInfoDO extends TenantBaseDO {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String packetId;
private String type;
private String watchwordContent;
private Integer userId;
private BigDecimal totalAmount;
private Integer totalPacket;
private BigDecimal amountOne;
private String blessing;
private String cover;
private BigDecimal leftAmount;
private Integer leftPacket;
private String sendType;
private Date expireTime;
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;
@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;
private Integer userId;
private BigDecimal amount;
private String watchwordContent;
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;
@Mapper
public interface RedPacketInfoMapper extends BaseMapperX<RedPacketInfoDO> {
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;
@Mapper
public interface RedPacketRecordsMapper extends BaseMapperX<RedPacketRecordsDO> {
RedPacketRecordsDO getRedPacketRecord(@Param("packetId") String packetId, @Param("userId") Long userId);
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;
public interface RedPacketInfoService extends IService<RedPacketInfoDO> {
String sendRedPacket(RedPacketsDTO packetsDTO);
RedPacketQualificationsVO grabPacket(String packetId);
RedPacketInfoVO getPacket(DismantleRedPacketDTO dismantleRedPacketDTO);
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.*;
@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());
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));
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());
redPacketInfoMapper.insert(redPackets);
log.info("用户【{}】,插入红包成功记录【{}】", loginUserId, redPackets);
rabbitTemplate.convertAndSend(delayedExchangeName, delayedRoutingKey, redPackets.getPacketId(), correlationData -> {
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发送延迟消息");
redPacketRedisDAO.set(redPackets.getPacketId(), redPackets);
log.info("红包记录写入redis");
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 {
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异步入账");
redPacketRedisDAO.set(dismantleRedPacketDTO.getPacketId(), redPackets);
log.info("红包记录状态更新【{}】,并写入redis", redPackets);
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;
}
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);
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;
}
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;
}
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:
host: 10.0.99.114
port: 5672
username: admin
password: admin
publisher-confirm-type: correlated
publisher-returns: true
virtual-host: /
listener:
simple:
acknowledge-mode: manual
template:
mandatory: true
redis:
host: 10.0.99.191
port: 6379
database: 8
password: 'password'
red_packet:
backtrack:
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
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;
@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;
@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;
@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;
@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;
@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;
@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;
@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;
@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;
@Bean
public Queue delayedQueue() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", deadLetterExchangeName);
args.put("x-dead-letter-routing-key", deadLetterRoutingKey);
return QueueBuilder.durable(delayedQueueName).withArguments(args).build();
}
@Bean
public Queue delayedDeadLetterQueue() {
return new Queue(deadLetterQueueName);
}
@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);
}
@Bean
public DirectExchange delayedDeadLetterExchange() {
return new DirectExchange(deadLetterExchangeName);
}
@Bean
public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,
@Qualifier("delayedExchange") CustomExchange delayedExchange) {
return BindingBuilder.bind(queue).to(delayedExchange).with(delayedRoutingKey).noargs();
}
@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;
@Configuration
public class RedPacketEntryMessage {
@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;
@Bean
public Queue entryQueue() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", deadLetterExchangeName);
args.put("x-dead-letter-routing-key", deadLetterRoutingKey);
return QueueBuilder.durable(entryQueueName).withArguments(args).build();
}
@Bean
public Queue entryDeadLetterQueue() {
return new Queue(deadLetterQueueName);
}
@Bean
public DirectExchange entryExchange() {
return new DirectExchange(entryExchangeName, true, false);
}
@Bean
public DirectExchange entryDeadLetterExchange() {
return new DirectExchange(deadLetterExchangeName);
}
@Bean
public Binding bindingEntryQueue(@Qualifier("entryQueue") Queue queue,
@Qualifier("entryExchange") DirectExchange entryExchange) {
return BindingBuilder.bind(queue).to(entryExchange).with(entryRoutingKey);
}
@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;
@Component
@Slf4j
public class RedPacketDelayConsumer {
@Resource
private RedPacketInfoMapper redPacketInfoMapper;
@Resource
private ApiWalletService apiWalletService;
@Resource
private RedPacketRedisDAO redPacketRedisDAO;
@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();
}
}
@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) {
String tenantId = message.getMessageProperties().getHeader("tenant-id").toString();
String userId = message.getMessageProperties().getHeader("user-id").toString();
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("修改数据库状态为已过期");
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;
@Component
@Slf4j
public class RedPacketEntryConsumer {
@Resource
private ApiWalletService apiWalletService;
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private RedPacketInfoMapper redPacketInfoMapper;
@Resource
private RedPacketRecordsMapper redPacketRecordsMapper;
@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();
}
}
@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) {
String tenantId = message.getMessageProperties().getHeader("tenant-id").toString();
String userId = message.getMessageProperties().getHeader("user-id").toString();
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);
}
}