需求说明

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


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


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

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

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


<https://img2018.cnblogs.com/blog/533598/201907/533598-20190707093346731-417956439.png>

消费后红包的初始化

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

比如:

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


<https://img2018.cnblogs.com/blog/533598/201907/533598-20190707093357890-233895406.png>

那么我们需要设计一个新的实体,以表示分享出去的红包及其生成的随机红包:
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: });
测试

测试效果图如下:


<https://img2018.cnblogs.com/blog/533598/201907/533598-20190707093409385-620192797.png>

部分代码如下,
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:}

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信