需求分析
模拟微信发红包功能,发红包+抢红包,此功能为高并发业务要求,因此不能用mysql来做,只能用redis来实现
发红包
当用户发出一个红包,会设置该红包的总金额,以及领取人数。
一个总的大红包,会有可能拆分成多个小红包,即:总金额= 分金额1+分金额2+分金额3......分金额N;
当超过一天红包未领完则自动回退到发红包用户账户下
抢红包
每个用户每个红包只能抢一次,红包抢一个少一个,直到红包被抢完为止,要保证每个人至少抢到一分钱
代码实现
本次案例仅实现controller层
import com.google.common.primitives.Ints;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.hutool.core.util.IdUtil;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 模拟微信抢红包
*/
@Controller
public class RedPackageController {
@Autowired
private RedisTemplate redisTemplate;
public static final String RED_PACKAGE_KEY = "redpackage:";
public static final String RED_PACKAGE_CONSUME_KEY = "redpackage:consume:";
/**
* 模拟发红包功能
* @param totalMoney 红包总额
* @param redPackageNumber 红包领取个数
* @return
*/
@RequestMapping(value = "/send")
@ResponseBody
public String sendRedPackage(int totalMoney,int redPackageNumber){
//1、拆分红包,将红包钱数totalMoney拆分成redPackageNumber个小红包,记录每个小红包钱数
//说明:简化案例,钱数均为整数;利用二倍均值算法为每个小红包分配金额
Integer[] splitRedPackages=splitRedPackage(totalMoney,redPackageNumber);
//2、记录红包
//key
String key=RED_PACKAGE_KEY+IdUtil.simpleUUID();
//存储红包
redisTemplate.opsForList().leftPushAll(key,splitRedPackages);
//为红包设置过期时间:如果一天后未领取,则退回红包
redisTemplate.expire(key,1, TimeUnit.DAYS);
//3、 return key+"\t"+"\t"+ Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
return key+"\t"+ Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
}
/**
* 抢红包
* @param redPackageKey 红包key
* @param userId 用户id
* @return
*/
@RequestMapping(value = "/rob")
public String robRedPackage(String redPackageKey ,int userId){
//先判断用户是否已经抢过红包
Object redPackage = redisTemplate.opsForHash().get(RED_PACKAGE_CONSUME_KEY + redPackageKey,userId);
if(null==redPackage){
//用户没有抢过,则随机弹出一个红包
Object partRedPackage = redisTemplate.opsForList().leftPop(RED_PACKAGE_KEY + redPackageKey);
//红包里还有红包
if(partRedPackage!=null){
System.out.println("用户: "+userId+"\t 抢到多少钱红包: "+partRedPackage);
//TODO 后续异步进mysql或者RabbitMQ进一步处理
}else {
//红包里没有红包
return "errorCode:-1,红包抢完了";
}
}
//用户抢过该红包,不可重复抢
return "errorCode:-2, message: "+"\t"+userId+" 用户你已经抢过红包了";
}
//二倍均值算法
private Integer[] splitRedPackage(int totalMoney,int redPackageNumber){
int useMoney=0; //记录已经分出去红包
Integer[] redPackageNumbers=new Integer[redPackageNumber];
Random random=new Random();
for(int i=0;i<redPackageNumber;i++){
if(i==redPackageNumber-1){
//最后一个红包钱数不用二倍均值算法
redPackageNumbers[i]=totalMoney-useMoney;
}else{
//其余红包使用二倍均值算法:红包钱数=random(0,(剩余红包钱数M/剩余红包数N)*2)
int avgMoney=(totalMoney-useMoney)*2/(redPackageNumber-i);
redPackageNumbers[i]=1+random.nextInt(avgMoney-1);
}
useMoney+=redPackageNumbers[i]; //更新已使用钱数
}
return redPackageNumbers;
}
}