专栏首页DotNet Core圈圈用.NET Core实现一个类似于饿了吗的简易拆红包功能

用.NET Core实现一个类似于饿了吗的简易拆红包功能

需求说明

以前很讨厌点外卖的我,最近中午经常点外卖,因为确实很方便,提前点好餐,算准时间,就可以在下班的时候吃上饭,然后省下的那些时间就可以在中午的时候多休息一下了。

点餐结束后,会有一个好友分享红包功能,虽说这个红包不能提现,但却可以抵扣点餐费用,对于经常点餐的人来说,直接用于抵扣现金确实是很大的诱惑,在点餐之后所获得的那个红包,必须要分享出去才能拆。

那么如果自己也想实现以下抢红包功能,需要说明的是,本文所描述的红包功能更多的关注与随机红包的生成,至于高并发、数据一致性等问题,本文暂未涉及,以下是本文所讨论的两个技术点:

  • 不同的消费金额获取的红包总额不同,消费金额越大,红包总额就越大,红包总数也就越多;
  • 假设有一天,有一种需求是,需要保证参与抢红包的人获得的红包金额在平均数附近波动,也就是尽量的服从正态分布;

功能实现

本文描述的场景,所涉及到的金额以分为单位,目的是为了更好的处理随机数。总体的示意图如下:

消费后红包的初始化

需求重点,用户分享出去的红包总额跟消费总额成正比,可以分拆的子红包个数也与消费总额成正比。

比如:

  • 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、任务调度等,此处也不做讨论

那么我们需要设计一个新的实体,以表示分享出去的红包及其生成的随机红包:

   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:  });

测试

测试效果图如下:

部分代码如下,

   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:  }

本文分享自微信公众号 - DotNet技术平台(DotNetCore_Mements),作者:艾心0626

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • .NET Core 3.0之深入源码理解HttpClientFactory(二)

    上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpC...

    Edison.Ma
  • 设计模式之中介者模式

    提供一个中介对象出来,用于封装一系列对象的交互,从而使各对象不需要直接交互,进一步降低了对象间的耦合度。这是一种行为型设计模式。

    Edison.Ma
  • 设计模式之工厂方法模式

    定义一个创建对象的接口,但让这个接口的实现类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。也就是说,工厂方法模式会定义一个单独的方法去创建或者管理对...

    Edison.Ma
  • 成为0.01%!利用TensorFlow.js和深度学习,轻松阅读古草体文字

    古文字识别能力是从事历史研究的学者的必备技能,对于日本的历史研究学者而言,他们的挑战则来自于“古草体”文字,这种文字是古日本使用频率最高的文字之一,也将是他们科...

    大数据文摘
  • 面试官,你再问我滑动窗口问题试试?我有解题模板,不怕!

    滑动窗口类问题是面试当中的 高频题 ,问题本身其实并不复杂,但是实现起来细节思考非常的多,想着想着可能因为变量变化,指针移动等等问题,导致程序反复删来改去,有思...

    五分钟学算法
  • 安卓Fragment和Activity之间的数据通讯

             Fragment是Android3.0之后才推出来的。可以用来做底部菜单,现在很多APP都有用到这个底部菜单。当然TabHost也可以用来做底...

    用户1208223
  • Android RecyclerView浅析(分类型)

    整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoratio...

    IT大飞说
  • 设计模式学习——代理模式(Proxy Pattern)

    代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与...

    饮水思源为名
  • loadrunner 运行脚本-Run-time Settings之Preferences设置

    开启图片和文本检查。允许用户在回放期间通过web_find(文本检测)或web_image_check(图片检测)验证函数执行验证检查。这个选项仅应用于HTM...

    授客
  • .Net使用HttpClient以multipart/form-data形式post上传文件及其相关参数

      本次要讲的是使用.Net HttpClient拼接multipark/form-data形式post上传文件和相关参数,并接收到上传文件成功后返回过来的结果...

    追逐时光

扫码关注云+社区

领取腾讯云代金券