連續登陸活動,或許大家都不會陌生,簡單了解就是使用者連續登陸了多少天之後,系統就會送一些禮品給相應的使用者。最常見的
莫過于遊戲和商城這些。遊戲就送遊戲币之類的東西,商城就送一些禮券。正值國慶,應該也有不少類似的活動。
下面就對這個的實作提供兩個思路,并提供解決方案。
思路1(以使用者為次元):
連續登陸活動,必然是要求連續登陸,不能有間隔。用1表示登陸,0表示沒有登陸,這樣我們可以為每個使用者建立一個key去存儲
他的登陸情況,就可以得到類似這樣的一個二進制序列:1110111,如果是7個1,就表示連續7天,如果不是7個1就表示沒有連續登
陸7天。是以就能實作這個登陸活動的要求了。
思路2(以天數為次元):
一天之内,使用者要麼是登陸過,要麼是沒有登陸過。同樣的用1來表示登陸過,用0表示沒有登陸過。假設我們連續登陸的活動是2天,
同時有3個使用者,那麼就要有2個key去存儲這3個使用者的登陸資訊,這樣就會得到類似這樣的兩個二進制序列:101(key1),111(key2)。
此時,對這兩個key的每一位都進行邏輯與運算,就會得到101,就表明,使用者1和使用者3連續登陸了兩天。進而達到活動的要求。
之前在string的基礎教程中曾經說過關于二進制的相關操作會用一個簡單的案例來給大家講解,現在是兌現這個諾言的時候了。
下面就簡單模拟一下國慶7天假期連續登陸七天的活動。
方案1 :以使用者為次元
先為每個使用者建立一個key(holiday:使用者辨別),對于我們的例子來說,每個key就會有7位二進制位。這時key會有這樣的結構

這時我們就會得到每個使用者對應的二進制序列,然後就可以用bitcount指令去得到key含有的1的個數。如果等于7,就是連續登陸了
七天。這樣就可以在第七天使用者登陸的時間去處理了是否發送禮品了。處理的邏輯是十分簡單的。控制器簡單邏輯如下:
1 [HttpPost]
2 public IActionResult LoginForEveryone()
3 {
4 Random rd = new Random();
5 var tran = _redis.GetTransaction();
6 for (int i = 0; i < 7; i++)
7 {
8 for (int j = 0; j < 1000; j++)
9 {
10 string activity_key = string.Format("holiday:{0}", j.ToString());
11 // login 1(true) other 0(false)
12 if (rd.Next(0,10) > 6)
13 {
14 tran.StringSetBitAsync(activity_key, i, true);
15 }
16 }
17 }
18 tran.ExecuteAsync();
19
20 List<int> res = new List<int>();
21 for (int i = 0; i < 1000; i++)
22 {
23 string activity_key = string.Format("holiday:{0}", i.ToString());
24 //7 days
25 if (_redis.BitCount(activity_key) == 7)
26 {
27 res.Add(i);
28 }
29 }
30 return Json(new { code = "000", data = res, count = res.Count });
31 }
在這裡還是用随機數的方法來模拟資料。主要操作有兩個,一個是模拟登陸後把當天對應的偏移設定為1(true),另一個是取出使用者
登陸的天數。這是一次性模拟操作,與正常情況的登陸操作還是有些許不同的。大緻如下:
1 [HttpPost]
2 public IActionResult LoginForEveryone()
3 {
4 //1.login and get the identify of user
5 //2.get the Current day and write to redis
6 string activity_key = string.Format("holiday:{0}", "identify of user");
7 _redis.SetBit(activity_key, currend day, true);
8 //3.send gift
9 if(currend day==7&& _redis.BitCount(activity_key)==7)
10 {
11 send gift
12 }
13 return ...;
14 }
回到我們模拟的情況,在界面展示時,模拟登陸後會顯示累計登陸使用者的id。
1 <script id="everyoneTpl" type="text/html">
2 <span>total:{{count}}</span>
3 <ul>
4 {{each data as item}}
5 <li>
6 {{item}}
7 </li>
8 {{/each}}
9 </ul>
10 </script>
11 <script>
12 $(function () {
13 $("#btn_everyone").click(function () {
14 $.ajax({
15 url: "/Holiday/LoginForEveryone",
16 dataType: "json",
17 method:"post",
18 success: function (res) {
19 if (res.code == "000") {
20 var html = template('everyoneTpl', res);
21 $("#div_everyone").html(html);
22 }
23 }
24 })
25 });
26 })
27 </script>
下面來看看效果:
示範中:38、103、234、264、412、529這6位使用者将得到連續登陸7天的禮品。
方案2 :以天數為次元
既然是以天數為次元,那麼就要定義7個redis的key用來當作每天的登陸記錄,類似:
這樣的話就要讓我們的使用者辨別是數字才行,如果是用guid做的使用者辨別就要做一定的處理将其轉化成數字,這樣友善我們
在給使用者設定是否登陸。現在假設我們的使用者辨別是從1~1000。使用者辨別對應的就是在key中的偏移量。這時我們就會得到每天
對應的二進制序列,然後就可以用bitop指令去得到邏輯與運算之後的key/value。如果這個key對應偏移量(使用者辨別)是1,就是
連續登陸了七天,處理的邏輯是十分簡單的。控制器簡單邏輯如下:
1 [HttpPost]
2 public IActionResult LoginForEveryday()
3 {
4 var tran = _redis.GetTransaction();
5
6 for (int i = 0; i < 7; i++)
7 {
8 for (int j = 0; j < 1000; j++)
9 {
10 //i day,j userId
11 SetBit(i, j, tran);
12 }
13 }
14 tran.Execute();
15 //get the result
16 _redis.BitOP(_bitWise, _res, _redisKeys.ToArray());
17 IList<int> res = new List<int>();
18 for (int i = 0; i < 1000; i++)
19 {
20 if (_redis.GetBit(_res, i) == true)
21 {
22 res.Add(i);
23 }
24 }
25 return Json(new { code = "000", data = res, count = res.Count });
26 }
27
28
29 private void SetBit(int day, int userId, StackExchange.Redis.ITransaction tran)
30 {
31 switch (day)
32 {
33 case 0:
34 if (_rd.Next(0, 10) > 3)
35 {
36 tran.StringSetBitAsync(_first, userId, true);
37 }
38 else
39 {
40 tran.StringSetBitAsync(_first, userId, false);
41 }
42 break;
43 case 1:
44 if (_rd.Next(0, 10) > 3)
45 {
46 tran.StringSetBitAsync(_second, userId, true);
47 }
48 else
49 {
50 tran.StringSetBitAsync(_second, userId, false);
51 }
52 break;
53 case 2:
54 if (_rd.Next(0, 10) > 3)
55 {
56 tran.StringSetBitAsync(_thrid, userId, true);
57 }
58 else
59 {
60 tran.StringSetBitAsync(_thrid, userId, false);
61 }
62 break;
63 case 3:
64 if (_rd.Next(0, 10) > 3)
65 {
66 tran.StringSetBitAsync(_fourth, userId, true);
67 }
68 else
69 {
70 tran.StringSetBitAsync(_fourth, userId, false);
71 }
72 break;
73 case 4:
74 if (_rd.Next(0, 10) > 3)
75 {
76 tran.StringSetBitAsync(_fifth, userId, true);
77 }
78 else
79 {
80 tran.StringSetBitAsync(_fifth, userId, false);
81 }
82 break;
83 case 5:
84 if (_rd.Next(0, 10) > 3)
85 {
86 tran.StringSetBitAsync(_sixth, userId, true);
87 }
88 else
89 {
90 tran.StringSetBitAsync(_sixth, userId, false);
91 }
92 break;
93 case 6:
94 if (_rd.Next(0, 10) >3)
95 {
96 tran.StringSetBitAsync(_seventh, userId, true);
97 }
98 else
99 {
100 tran.StringSetBitAsync(_seventh, userId, false);
101 }
102 break;
103 default:
104 break;
105 }
106 }
前台的處理與方案一的一模一樣,是以就不貼代碼了。下面來看看效果圖。
可能光看效果圖沒太大意義,還是要看一下redis中的資料來驗證一下的。圖中取了76和991這兩個使用者辨別(偏移量)來驗證。
可以看到76和991這兩個偏移量(使用者辨別)對應的二進制位是1,也驗證了其連續登陸了7天。當然,更多的明細資料也貼出來了
一大堆16進制的東西,有興趣可以去轉換成二進制試試。
對這兩種方案簡單的總結一下:
方案 | 優點 | 缺點 |
以使用者為次元 | 1.可以無縫對接,無論使用者辨別是數字還是其他 2.key對應的資料較少便于觀察 | 随着使用者數量的增長,要管理的key會越來越多 |
以天數為次元 | 1.有确定數量的key,友善管理 2.key對應的基數大 | 1.偏移量可能需要根據實際情況處理 2.資料檢視不是很清晰 |
可至于實際中用那種方案更合适,要根據情況來選擇。使用者量少的時候,可以用第一種方案,也可以用第二種方案,當使用者量很大的時候,
建議采用第二種方案,畢竟隻要使用者數量沒有超過43億,就不會超出其二進制位數的限制。是可以比較放心使用的。