近日畸冲,北京知識產(chǎn)權法院在官方微信發(fā)文稱,掌上遠景公司開發(fā)并運營了一款名為“微信自動搶紅包”軟件观腊,卓易訊暢公司運營的豌豆莢應用開發(fā)平臺提供下載該軟件邑闲。騰訊科技公司、騰訊計算機公司以不正當競爭為由梧油,將掌上遠景公司和卓易訊暢公司訴至北京知識產(chǎn)權法院苫耸。
最終,法院認定掌上遠景公司構成不正當競爭儡陨,判決掌上遠景公司賠償二原告經(jīng)濟損失450萬元及合理支出約25.4萬元褪子。雙方當事人均未提出上訴,目前該案已生效骗村。
那我們的搶紅包是怎么實現(xiàn)的呢嫌褪?我來一步一步的教你,用個實例
需求:用戶分享紅包到微信群中胚股。
每一個用戶只能領取一個紅包笼痛。
比如餓了么的紅包分享:
在設計之前,先了解一下redis的list的數(shù)據(jù)結構:
1琅拌、lpush+lpop=Stack(棧)
2缨伊、lpush+rpop=Queue(隊列)
3、lpsh+ltrim=Capped Collection(有限集合)
4进宝、lpush+brpop=Message Queue(消息隊列)
我們基于lpush+rpop進行紅包的設計刻坊。如何設計?
1党晋、當用戶點擊分享按鈕谭胚,首先會給該訂單生成若干個紅包徐块,將該紅包push到redis中。
key的設計: hb:pool:{orderId}
value : n個紅包的List隊列灾而,用來表示n個紅包的池子
2胡控、記錄哪些用戶已搶過紅包,防止重復搶:從紅包池中rpop(彈出)一個紅包绰疤,就需要記錄下來該用戶領取了紅包铜犬,這里用了hash結構舞终。
key:hb:rd:{orderId} 例如訂單號為12345轻庆,key為hb:rd:12345, 有4個用戶id分別為 11111,11112,11113,11114
hset hb:rd:12345 11111 1
hset hb:rd:12345 11112 1
hset hb:rd:12345 11113 1
hset hb:rd:12345 11114 1
如下圖:
判斷用戶11111是否領過紅包,可以根據(jù) hexists hb:rd:12345 11111 進行判斷敛劝,如果領過返回1余爆,否則返回0;
3夸盟、記錄用戶搶了多少錢:這里采用list結構進行存儲蛾方。
key:hb:detailList:{orderId}
value : 用戶搶到的紅包列表。
搶紅包流程:
保證用戶搶紅包整個流程的原子操作就必然要引入lua腳本上陕,redis+Lua可以保證多條命令組合的原子性桩砰。
lua腳本:
/**
* 腳本調(diào)用方式
* Object object = jedisUtils.eval(LuaScript.getHbLua,//lua腳本
4,//參數(shù)個數(shù) RedisKeys.getHbPoolKey(orderId),//對應腳本里的KEYS[1]
RedisKeys.getDetailListKey(orderId),//對應腳本里的KEYS[2]
RedisKeys.getHbRdKey(orderId),//對應腳本里的KEYS[3]
String.valueOf(userId));//對應腳本里的KEYS[4]
*
*
*/
public static String getHbLua =
//查詢用戶是否已搶過紅包,如果用戶已搶過紅包释簿,則直接返回
"if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then\n" +
//如果搶過紅包 返回“1”
"return '1';\n" + "else\n" +
//從紅包池取出一個小紅包
"local hb = redis.call('rpop', KEYS[1]);\n" +
//判斷紅包池的紅包不為空
"if hb then\n" +
"local x = cjson.decode(hb);\n" +
//將紅包信息與用戶ID信息綁定亚隅,表示該用戶已搶到紅包
"x['userId'] = KEYS[4];\n" +
"local re = cjson.encode(x);\n" +
//記錄用戶已搶過 比如 hset hb:rd:{orderId} {userId} 1
"redis.call('hset', KEYS[3], KEYS[4], '1');\n" +
//將搶紅包的結果詳情存入hb:detailList:{orderId}
"redis.call('lpush', KEYS[2], re);\n" +
"return re;\n" + "else\n" +
//如果紅包已被搶完 返回“0”
"return '0';" +
"end\n" +
"end\n" +
"return nil";
生成紅包:
/**
* 生成紅包
* @param orderId
*/ public void genRedpack(long orderId,int redPackCount){
Boolean exists = jedisUtils.exists(RedisKeys.getHbPoolKey(orderId));
if (!exists){
//根據(jù)業(yè)務規(guī)則生成紅包
int totalAmount = 2000;//總的紅包金額20元 也就是2000分
int[] redpacks = doPartitionRedpack(totalAmount,redPackCount);
String[] list = new String[redpacks.length];
//將生成的紅包push到redis中
for (int i = 0;i < redpacks.length; i++){
JSONObject object = new JSONObject();
object.put("hbId", i); //紅包ID
object.put("amount", redpacks[i]); //紅包金額,存的是分
list[i] = object.toJSONString();
}
jedisUtils.lpush(RedisKeys.getHbPoolKey(orderId),list);
}
}
/**
* 劃分紅包 * @param totalAmount 紅包總額 單位:分
* @param redPackCount 紅包數(shù)量
* @return
*/
private int[] doPartitionRedpack(int totalAmount,int redPackCount) {
Random random = new Random();
int randomMax= totalAmount - redPackCount;//每個人至少分1分錢,2000 - 6 = 1994元 也就是要隨機分的錢庶溶。
//要把1994 隨機分成6份煮纵,我們需要向1994 這個數(shù)字中插入5個點
// 比如 6 100 500 500 1600 這5個數(shù)字把1994分成了6份:6分 94分 400分 0分 1000分 394分
int[] posArray = new int[redPackCount-1];
for (int i = 0;i < posArray.length; i++){
int pos = random.nextInt(randomMax);
posArray[i] = pos;
}
Arrays.sort(posArray);//對數(shù)組進行排序
//生成紅包
int[] redpacks = new int[redPackCount];
for (int i = 0;i <= posArray.length; i++){
if (i == 0){
redpacks[i] = posArray[i] + 1;//第一份
}else if(i == posArray.length){//如果循環(huán)到posArray.length,此時數(shù)組已越界1位偏螺,randomMax - 該值 + 1分錢=最后一份
redpacks[i] = randomMax - posArray[i-1] + 1;
}else {
redpacks[i] = posArray[i] - posArray[i-1] + 1;
}
}
return redpacks;
}
上面首先給每個紅包分1分錢行疏,然后把剩下的錢通過插入(redPackCount-1)個板子,就將剩余的錢分為redPackCount份套像。每份錢加上1分錢酿联,就是每個紅包的大小。分完紅包后夺巩,將紅包push到redis中货葬。
搶紅包:
/**
* 搶紅包
* @param userId
* @param orderId
*/
public String snatchRedpack(long userId,long orderId){
Object object = jedisUtils.eval(LuaScript.getHbLua,4,
RedisKeys.getHbPoolKey(orderId),//
RedisKeys.getDetailListKey(orderId),//
RedisKeys.getHbRdKey(orderId),String.valueOf(userId));
return (String) object;
}
搶紅包只需要執(zhí)行一下上面的lua腳本。
測試:
運行生成紅包:這里生成5個紅包劲够,orderId為111111.
Test
public void genRedpack(){
JedisUtils jedisUtils = new JedisUtils("127.0.0.1", 6379, "123456");
RedpackService redpackService = new RedpackService(jedisUtils);
redpackService.genRedpack(111111,5);
}
運行后震桶,redis中紅包池就生成好了
運行搶紅包:這里模擬了100個人,也就是100個線程征绎。這里用了CyclicBarrier蹲姐,等到所有的線程都準備好磨取,同時開搶。
@Test
public void snatchRedpack() throws InterruptedException {
JedisUtils jedisUtils = new JedisUtils("118.89.196.99", 6379, "123456");
RedpackService redpackService = new RedpackService(jedisUtils);
IdWorker idWorker = new IdWorker();
int N = 100;
CyclicBarrier barrier = new CyclicBarrier(N);
for (int i = 0;i<N;i++){
new Thread(()->{
long userId = idWorker.nextId();
try {
System.out.println("用戶"+userId+"準備搶紅包");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
String result = redpackService.snatchRedpack(userId, 111111);
if ("0".equals(result)){
System.out.println("用戶" + userId + "未搶到紅包柴墩,原因:紅包已領完");
}else if ("1".equals(result)){
System.out.println("用戶" + userId + "未搶到紅包忙厌,原因:紅包已領過");
}else{
System.out.println("用戶" + userId + "搶到紅包:" + result);
}
},"thread"+i).start();
}
Thread.sleep(Integer.MAX_VALUE);
}
運行結果:
......
用戶1078994397959344128準備搶紅包
用戶1078994397959344129準備搶紅包
用戶1078994397959344130準備搶紅包
用戶1078994397959344131準備搶紅包
用戶1078994397896429573搶到紅包:{"userId":"1078994397896429573","hbId":2,"amount":126}
用戶1078994397888040960搶到紅包:{"userId":"1078994397888040960","hbId":1,"amount":526}
用戶1078994397896429572搶到紅包:{"userId":"1078994397896429572","hbId":5,"amount":666}
用戶1078994397950955528未搶到紅包,原因:紅包已領完
用戶1078994397925789696搶到紅包:{"userId":"1078994397925789696","hbId":4,"amount":490}
用戶1078994397929984004未搶到紅包江咳,原因:紅包已領完
用戶1078994397959344128搶到紅包:{"userId":"1078994397959344128","hbId":3,"amount":192}
用戶1078994397955149824未搶到紅包逢净,原因:紅包已領完
用戶1078994397900623875未搶到紅包,原因:紅包已領完
用戶1078994397929984006未搶到紅包歼指,原因:紅包已領完
用戶1078994397892235264未搶到紅包爹土,原因:紅包已領完
.......
結果看出100個線程同時搶迫筑,只有5個人搶成功了饶囚。
查看redis里的數(shù)據(jù):紅包池子里的紅包已被領完顺又,對應紅包用戶信息也相應的生成逛钻。 ?????這樣就好了扁瓢。