用.NET Core實現一個類似於餓了嗎的簡易拆紅包功能


 

需求說明

以前很討厭點外賣的我,最近中午經常點外賣,因為確實很方便,提前點好餐,算准時間,就可以在下班的時候吃上飯,然后省下的那些時間就可以在中午的時候多休息一下了。

點餐結束后,會有一個好友分享紅包功能,雖說這個紅包不能提現,但卻可以抵扣點餐費用,對於經常點餐的人來說,直接用於抵扣現金確實是很大的誘惑,在點餐之后所獲得的那個紅包,必須要分享出去才能拆。

那么如果自己也想實現以下搶紅包功能,需要說明的是,本文所描述的紅包功能更多的關注與隨機紅包的生成,至於高並發、數據一致性等問題,本文暫未涉及,以下是本文所討論的兩個技術點:

  • 不同的消費金額獲取的紅包總額不同,消費金額越大,紅包總額就越大,紅包總數也就越多;
  • 假設有一天,有一種需求是,需要保證參與搶紅包的人獲得的紅包金額在平均數附近波動,也就是盡量的服從正態分布;

功能實現

本文描述的場景,所涉及到的金額以分為單位,目的是為了更好的處理隨機數。總體的示意圖如下:

totalrobbed

消費后紅包的初始化

需求重點,用戶分享出去的紅包總額跟消費總額成正比,可以分拆的子紅包個數也與消費總額成正比。

比如:

  • 10-20元的消費金額,可以分享的單個紅包金額為10元,可以供5個人搶
  • 20-40元的消費金額,可以分享的單個紅包金額為20元,可以供8個人搶
  • 40-60元的消費金額,可以分享的單個紅包金額為30元,可以供10個人搶
  • 60-100元的消費金額,可以分享的單個紅包金額為40元,可以供10個人搶
  • 100元以上的消費金額,可以分享的單個紅包金額為50元,可以供10個人搶

那么我們設計出來一個實體,用於表示紅包信息,以方便的配置及調整紅包規則

   1:  public class RedPacketsInfo
   2:  {
   3:      /// <summary>
   4:      /// 最大消費金額
   5:      /// </summary>
   6:      public int MaxAmount { get; set; }
   7:   
   8:      /// <summary>
   9:      /// 最小消費金額
  10:      /// </summary>
  11:      public int MinAmount { get; set; }
  12:   
  13:      /// <summary>
  14:      /// 紅包金額
  15:      /// </summary>
  16:      public int TotalAmount { get; set; }
  17:   
  18:      /// <summary>
  19:      /// 紅包可被分割的數量
  20:      /// </summary>
  21:      public int RedPacketQuantity { get; set; }
  22:  }

紅包初始化信息

   1:  private static List<RedPacketsInfo> GetRedPackets()
   2:  {
   3:      return new List<RedPacketsInfo>()
   4:      {
   5:          new RedPacketsInfo
   6:          {
   7:              MinAmount = 1000,
   8:              MaxAmount = 2000,
   9:              RedPacketQuantity = 5,
  10:              TotalAmount=1000
  11:          },
  12:          new RedPacketsInfo
  13:          {
  14:              MinAmount = 2000,
  15:              MaxAmount = 3000,
  16:              RedPacketQuantity = 5,
  17:              TotalAmount=1000
  18:          },
  19:          new RedPacketsInfo
  20:          {
  21:              MinAmount = 4000,
  22:              MaxAmount = 6000,
  23:              RedPacketQuantity = 5,
  24:              TotalAmount=1000
  25:          },
  26:          new RedPacketsInfo
  27:          {
  28:              MinAmount = 6000,
  29:              MaxAmount = 8000,
  30:              RedPacketQuantity = 5,
  31:              TotalAmount=1000
  32:          },
  33:          new RedPacketsInfo
  34:          {
  35:              MinAmount = 10000,
  36:              MaxAmount = int.MaxValue,
  37:              RedPacketQuantity = 5,
  38:              TotalAmount=1000
  39:          }
  40:      };
  41:  }

接下來我們就可以通過消費金額獲取相應的紅包信息了。

隨機紅包的生成時機及處理

隨機紅包的生成可以在搶之前生成也可以在搶的過程中確定,一般而言,很多時候紅包會在搶的過程中動態的實際分配,不過在本文中,紅包在用戶分享成功后會預先生成,主要原因是為了更好地處理處理數據,以使得數據能夠服從正態分布。

以下是其流程圖,其中有一段邏輯是回調功能,可能會有圈友會問,如何保證有回調以及回調是成功的,這個地方有很多種處理,比如MQ、任務調度等,此處也不做討論

robbed

那么我們需要設計一個新的實體,以表示分享出去的紅包及其生成的隨機紅包:

   1:  public class SharedRedPacket
   2:  {
   3:      /// <summary>
   4:      /// 分享人UserId
   5:      /// </summary>
   6:      public int SenderUserId { get; set; }
   7:   
   8:      /// <summary>
   9:      /// 分享時間
  10:      /// </summary>
  11:      public DateTime SendTime { get; set; }
  12:   
  13:      public List<RobbedRedPacket> RobbedRedPackets { get; set; }
  14:  }
  15:   
  16:  public class RobbedRedPacket
  17:  {
  18:      /// <summary>
  19:      /// 搶到紅包的人的UserId
  20:      /// </summary>
  21:      public int UserId { get; set; }
  22:   
  23:      /// <summary>
  24:      /// 搶到的紅包金額
  25:      /// </summary>
  26:      public int Amount { get; set; }
  27:   
  28:      /// <summary>
  29:      /// 搶到時間
  30:      /// </summary>
  31:      public DateTime RobbedTime { get; set; }
  32:  }

在實現過程中,根據用戶消費金額獲取相應紅包,然后通過隨機數,生成n-1個原始的隨機數據,最后一個數據用總和減去n-1個數據的和獲取到。

   1:  //紅包隨機拆分
   2:  Random ran = new Random();
   3:  List<double> randoms = new List<double>(redPacketsList.Count);
   4:  for (int i = 0; i < redPacketsInfo.RedPacketQuantity - 1; i++)
   5:  {
   6:      int max = (totalAmount - (redPacketsInfo.RedPacketQuantity - i)) * 1;
   7:      int result = ran.Next(1, max);
   8:      randoms.Add(result);
   9:      totalAmount -= result;
  10:  }
  11:  randoms.Add(totalAmount);

然后通過設置好系數,以處理數據達到服從正太分布的目的:

   1:  //正太分布處理
   2:  for (int i = 0; i < redPacketsInfo.RedPacketQuantity; i++)
   3:  {
   4:      double a = Math.Sqrt(Math.Abs(2 * Math.Log(randoms[i], Math.E)));
   5:      double b = Math.Cos(2 * Math.PI * randoms[i]);
   6:      randoms[i] = a * b * 0.3 + 1;
   7:  }

經過第二次處理后,得到的數據與原始數據有偏差,那么我們通過等比例方式再次處理,以確保拆分后的紅包總額等於紅包原始總額:

   1:  //生成最終的紅包數據
   2:  double d = originalTotal / randoms.Sum();
   3:  SharedRedPacket sharedRedPacket = new SharedRedPacket();
   4:  sharedRedPacket.RobbedRedPackets = new List<RobbedRedPacket>(redPacketsList.Count);
   5:  for (int i = 0; i < redPacketsInfo.RedPacketQuantity - 1; i++)
   6:  {
   7:      sharedRedPacket.RobbedRedPackets.Add(new RobbedRedPacket
   8:      {
   9:          Amount = (int)Math.Round(randoms[i] * d, 0)
  10:      });
  11:  }
  12:  sharedRedPacket.RobbedRedPackets.Add(new RobbedRedPacket
  13:  {
  14:      Amount = originalTotal - sharedRedPacket.RobbedRedPackets.Sum(p => p.Amount)
  15:  });

測試

測試效果圖如下:

image

部分代碼如下,

   1:  Console.WriteLine("是否分享輸入Y分享成功,輸入N退出");
   2:  string result = Console.ReadLine();
   3:  if (result == "Y")
   4:  {
   5:      var leftRedPacket = sharedRedPacket.RobbedRedPackets.Where(p => p.UserId <= 0).ToList();
   6:      var robbedRedPacket = leftRedPacket[new Random().Next(1, leftRedPacket.Count + 1)];
   7:      Console.WriteLine("搶到的到紅包金額是:" + robbedRedPacket.Amount);
   8:      Console.WriteLine("-------------------------------------------------------");
   9:  }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM