很多年前就給前公司的年會做過年會抽獎,基本要求就是年會入場時簽到,簽到的員工都參與抽獎(也可以設置公司高管過濾,不參與抽獎),獎品是預設好的,到時候就是給所有簽到員工編號,然后抽獎過程中不斷生成一組隨機數,這些隨機數對應的編號的員工姓名和照片就顯示出來,這是很容易想到的算法。
但是還要一種情況就是互聯網模式的抽獎,有點像雙十一之前,阿里派發紅包一樣,大家都可以在開始抽獎的時候去抽,獎品也是預設好的,比如1000W的獎金池,派發完畢就抽獎完畢,每個用戶可以抽取多次。這種抽獎方式主要是應對抽獎人數不確定的情況,誰也不需要提前簽到報名,到了抽獎時間只要注冊用戶都可以抽獎。
因為抽獎人數不確定,所以采用一人多次抽獎的方案是很好的,對用戶來說也是,如果第一次沒有抽中,還可以嘗試第二次,第三次。具體算法上,其實更簡單,因為用戶點擊抽獎的順序是隨機的,所以我們連隨機數都不用用,直接給用戶的一次抽獎請求編個自增的號,如果這個號滿足中獎規則,那么就分配禮品,返回該抽獎請求中獎結果,如果不滿足中獎規則,那么我們就返回未中獎。
為了避免用戶頻繁的點擊,造成服務器過高的負擔,我們可以在客戶端設置一個動畫過程,比如轉盤抽獎,可以轉幾秒以后才請求服務器,看是否中獎,對用戶來說也增加了趣味性。為了避免用戶不通過客戶端,直接發起頻繁的HTTP請求來刷獎,我們甚至可以在服務器設置同一個用戶的請求時間間隔。
下面貼出我寫的一個示例代碼部分,我設置了一個自增的整數Sequence ,每個正常的抽獎請求,則Sequence ++,另外設置默認的抽獎基數baseNumber=100,如果能夠Sequence能夠被baseNumber整除,那么就中獎,否則不中獎:
[RoutePrefix("api/Lottery")] public class LotteryController : AbpApiController { private static volatile int Sequence = 1; private static IList<int> winnerList=new List<int>(); /// <summary> /// 抽獎開始標記,請通過StartNewLotteryRound打開 /// </summary> private static bool start = false; /// <summary> /// 所有產品都被抽完了的標記 /// </summary> private static bool allPrizeOut = false; /// <summary> /// 當前輪次ID /// </summary> private static int currentRoundId = 0; public ILotteryAppService LotteryAppService { get; set; } /// <summary> /// 抽獎基數,只要被該數整除就中獎 /// </summary> private static int baseNumber =100; private static IDictionary<int,DateTime> userDrawTime=new Dictionary<int, DateTime>(); private bool CheckUserDrawTime(int userId) { if (userDrawTime.ContainsKey(userId)) { return userDrawTime[userId].AddSeconds(8) < DateTime.Now;//8s后可以抽獎 } else { return true; } } /// <summary> /// 抽獎一次 /// </summary> /// <param name="userId"></param> /// <returns></returns> [HttpGet] [Route("Draw/{userId}")] public DrawResult Draw(int userId) { if (!start) { return new DrawResult(400,0, "抽獎未開始"); } if (allPrizeOut) { return new DrawResult(400, 0, "所有獎品已抽完"); } if (!CheckUserDrawTime(userId)) { return new DrawResult(400, 0, "請求過於頻繁,請稍后再試"); } int myNumber = Sequence++; userDrawTime[userId] = DateTime.Now;//記錄用戶這次抽獎的時間 if (myNumber%baseNumber == 0) //中獎啦! { if (winnerList.Contains(userId)) { //用戶已經中獎,不用再抽 return new DrawResult(200, 0, "您已經中過獎了"); } var result = LotteryAppService.WriteAWinner(userId, currentRoundId); switch (result.ExceptionType) { case LotteryExceptionType.NoException: { winnerList.Add(userId); return new DrawResult(200, result.PrizeId, ""); } case LotteryExceptionType.AllPrizeOut: { allPrizeOut = true; return new DrawResult(400, 0, "所有獎品已抽完"); } case LotteryExceptionType.InvalidLotteryRound: { return new DrawResult(400, 0, "抽獎輪次無效"); } default: { return new DrawResult(400, 0, "當前用戶無效"); } } } return new DrawResult(200, 0, ""); } /// <summary> /// 獲得我的獎品對象 /// </summary> /// <param name="userId"></param> /// <returns></returns> [HttpGet] [Route("MyPrize/{userId}")] public IList<LotteryDto> GetMyPrize(int userId) { return LotteryAppService.GetMyPrize(userId); } /// <summary> /// 開始新一輪的抽獎 /// </summary> /// <param name="roundId"></param> [HttpPost] [Route("StartNewLotteryRound")] [AbpApiAuthorize(PermissionNames.Admin)] public bool StartNewLotteryRound(int roundId) { start = true; allPrizeOut = false; currentRoundId = roundId; return true; } /// <summary> /// 獲得當前輪次的獎品和獲獎者 /// </summary> /// <returns></returns> [HttpGet] [Route("")] public IList<LotteryDto> GetLotteries() { return LotteryAppService.GetLotteries(currentRoundId); } /// <summary> /// 獲得所有的獎品和獲獎者 /// </summary> /// <returns></returns> [HttpGet] [Route("All")] public IList<LotteryDto> GetAllLotteries() { return LotteryAppService.GetLotteries(0); } /// <summary> /// 清空中獎結果,各種緩存 /// </summary> /// <returns></returns> [HttpPost] [Route("Clean")] [AbpApiAuthorize(PermissionNames.Admin)] public bool Clean() { Sequence = 1; start = false; winnerList.Clear(); LotteryAppService.CleanLotteries(); return true; } /// <summary> /// 獲取是否顯示抽獎圖標 /// </summary> /// <returns></returns> [HttpGet] [Route("ShowLotteryIcon")] public bool GetShowLotteryIcon() { return LotteryAppService.ShowLotteryIcon; } /// <summary> /// 設置是否顯示抽獎圖標 /// </summary> /// <param name="show"></param> /// <returns></returns> [HttpPut] [Route("ShowLotteryIcon/{show}")] public HttpResponseMessage SetShowLotteryIcon(bool show) { try { LotteryAppService.ShowLotteryIcon = show; return Request.CreateResponse(HttpStatusCode.OK, true); } catch (Exception ex) { var resp = new HttpResponseMessage(HttpStatusCode.BadGateway) { Content = new StringContent("設置ShowLotteryIcon失敗:" + ex.Message), ReasonPhrase = "Gateway failed" }; throw new HttpResponseException(resp); } } /// <summary> /// 設置Base Number /// </summary> /// <param name="number"></param> /// <returns></returns> [HttpPut] [AbpApiAuthorize(PermissionNames.Admin)] [Route("SetBaseNumber/{number}")] public bool SetBaseNumber(int number) { baseNumber = number; return true; } }