数据库建表
(1)red_send_record
 记录用户发送了若干总金额的若干个红包。
CREATE TABLE `red_send_record`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `user_id` int(0) NOT NULL COMMENT '用户id',
  `red_packet` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '红包全局唯一标识串',
  `total` int(0) NOT NULL COMMENT '人数',
  `amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '总金额(单位分)',
  `enable_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否有效',
  `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) 
(2)red_detail
 记录用户发送的红包被分成的小红包金额。
CREATE TABLE `red_detail`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `red_packet` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `amount` decimal(8, 2) NULL DEFAULT NULL,
  `enable_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1',
  `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
)
(3)red_rob_record
 记录用户抢到的红包金额。
CREATE TABLE `red_rob_record`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `user_id` int(0) NOT NULL,
  `red_packet` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `amount` decimal(8, 2) NOT NULL,
  `create_time` timestamp(0) NULL DEFAULT NULL,
  `enable_flag` char(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1',
  PRIMARY KEY (`id`) USING BTREE
)
随机生成红包金额
红包金额的最小单位是分,将红包金额放大100倍到int类型(为了方便生成随机数),保证红包金额至少是1。
第一种分红包的方式是:红包金额先按照红包数均分,再拿2份数量的金额生成随机数,这种方式生成的金额方差小。
 第二种分红包的方式是:直接拿红包总金额生成随机数,这种方式生成的金额方差大。
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class RedPacketUtil {
    public static Random random = new Random();
    public static int min = 1;
    /**
     * 随机生成红包金额
     * @param totalAmount 总金额
     * @param totalNum 红包个数
     * @return
     */
    public static List<Double> devideRedPacket(int totalAmount, int totalNum) {
        List<Double> result = new ArrayList();
        while(totalNum > 1) {
            int max = totalAmount / totalNum * 2;
            int randomAmount = min + random.nextInt(max);
            double amountResult = (double)randomAmount / 100;
            result.add(amountResult);
            totalAmount -= randomAmount;
            totalNum--;
        }
        result.add((double)totalAmount / 100);
        return result;
    }
    public static List<Double> devideRedPacket2(int totalAmount, int totalNum) {
        List<Double> result = new ArrayList();
        int splitAmount = totalAmount - totalNum * min;
        while(totalNum > 1) {
            int randomAmount = random.nextInt(splitAmount);
            double amountResult = (double)(randomAmount + min) / 100;
            result.add(amountResult);
            splitAmount -= randomAmount;
            totalNum--;
        }
        result.add((double)(splitAmount + min) / 100);
        return result;
    }   
}
单元测试:
@Test
public void testRedPacket() {
    List<Double> result = RedPacketUtil.devideRedPacket(2000, 10);
    AtomicReference<Double> total = new AtomicReference<>((double) 0);
    result.stream().forEach(data -> {
        System.out.println(data);
        total.updateAndGet(v -> new Double((double) (v + data)));
    });
    System.out.println("total = " + total);
}
发红包
发红包的请求参数是用户唯一标识、红包总金额和红包个数。
import lombok.Data;
import java.io.Serializable;
@Data
public class RedSendRecordDTO implements Serializable {
    private int userId;
    private int total;
    private double amount;
}
将分好的随机红包金额放入redis的List集合中,设置24小时失效。异步将用户发红包记录和随机红包金额写入数据库。
数据库操作直接引入Mybatis-plus
public String sendRedPacket(RedSendRecordDTO redSendRecordDTO) {
    String redPacket = UUID.randomUUID().toString();
    List<Double> amountPerRedPacket = RedPacketUtil.devideRedPacket((int)redSendRecordDTO.getAmount() * 100, redSendRecordDTO.getTotal());
    amountPerRedPacket.stream().forEach(amount -> {
        redisTemplate.opsForList().rightPush(redPacket, amount);
    });
    redisTemplate.expire(redPacket, 24, TimeUnit.HOURS);
    CompletableFuture.runAsync(new Runnable() {
        @Override
        public void run() {
            RedSendRecord redSendRecord = new RedSendRecord();
            redSendRecord.setRedPacket(redPacket);
            redSendRecord.setAmount(redSendRecordDTO.getAmount());
            redSendRecord.setUserId(redSendRecordDTO.getUserId());
            redSendRecord.setTotal(redSendRecordDTO.getTotal());
            save(redSendRecord);
            amountPerRedPacket.stream().forEach(amount -> {
                RedDetail redDetail = new RedDetail();
                redDetail.setRedPacket(redPacket);
                redDetail.setAmount(amount);
                redDetailService.save(redDetail);
            });
        }
    });
    return redPacket;
}

 
抢红包
抢红包入参是用户唯一标识、红包唯一标识。
import lombok.Data;
import java.io.Serializable;
@Data
public class RedRobRecordDTO implements Serializable {
    private int userId;
    private String redPacket;
}
抢红包要防止用户重复抢红包和一个红包被多个用户抢到。
(1)为了方便jmeter测试,在没有传userId参数时,生成一个随机用户标识。
(2)用redis的map存放用户标识和抢到的红包金额。
(3)先判断该用户之前有没有抢过红包,抢过直接返回抢到的红包金额。如果没有抢过,向redis添加一个key-value,如果添加成功,标识该用户可以抢红包,如果添加失败,标识该用户重复抢红包了。
(4)从redis存放随机红包金额的集合弹出一个红包给可以抢红包的用户,并保存到数据库。如果随机红包金额集合弹出的元素为空,表示红包抢完了。
@Override
public String robRedPacket(RedRobRecordDTO redRobRecordDTO) {
    if(0 == redRobRecordDTO.getUserId()) {
        int userId = RedPacketUtil.random.nextInt(1000);
        redRobRecordDTO.setUserId(userId);
    }
    log.info("robRedPacket redRobRecordDTO is:{}", JSONUtil.toJsonStr(redRobRecordDTO));
    //标识用户正在抢红包
    String userRobKey = redRobRecordDTO.getRedPacket() + ":" + redRobRecordDTO.getUserId();
    //用户抢到的红包金额
    String userAmountKey = redRobRecordDTO.getRedPacket() + ":amount";
    //判断用户是否抢过
    boolean isRobed = redisTemplate.opsForHash().hasKey(userAmountKey, redRobRecordDTO.getUserId());
    if(isRobed) {
        double amountPerUserId = (double) redisTemplate.opsForHash().get(userAmountKey, redRobRecordDTO.getUserId());
        return String.valueOf(amountPerUserId);
    }
    boolean isSet = redisTemplate.opsForValue().setIfAbsent(userRobKey, redRobRecordDTO.getUserId(), 1, TimeUnit.MINUTES);
    if(!isSet) {
        return "抢过了";
    }
    //抢到红包
    Double value = (Double) redisTemplate.opsForList().rightPop(redRobRecordDTO.getRedPacket());
    if(value == null) {
        return "红包被抢完了";
    }
    redisTemplate.opsForHash().put(userAmountKey, redRobRecordDTO.getUserId(), value);
    RedRobRecord redRobRecord = new RedRobRecord();
    redRobRecord.setUserId(redRobRecordDTO.getUserId());
    redRobRecord.setAmount(value);
    redRobRecord.setRedPocket(redRobRecordDTO.getRedPacket());
    redRobRecordService.save(redRobRecord);
   return String.valueOf(value);
}

 



















