营销活动抽奖算法(1)

3/10/2023 1:45:21 PM
798
0

做秒杀的深有体会

做抽奖的深有体会

体会什么?

奖品不一会儿就被抽光了??纳尼(一脸懵逼)

说好的这些奖品要维持一天呢!!

去数据库查查去~

这货怎么能有两个订单??不是说好的一个用户只能抽奖一次么!!(沮丧脸)

这货又是谁?他们的名字怎么那么相似??(挖日)

下面言归正传

在做营销活动时候会经常遇到上面这些情况,咋办。

一、对于同一个用户能违规提交多个订单的情况

    在用户抽奖时候都要检测一下用户上次抽奖产生的标识符,比如在缓存中存储的一个值。比如这个样子

 Util.CacheManager.InsertCache(KeyName + UserModel.ID,"1", DateTime.Now.AddSeconds(10));  

  如果下次抽奖的时候这个值还在,说明两次抽奖间隔小于10s,那么给与相关提示,比如:抽奖关于频繁拉

  验证通过后进入下一步。

  为用户生成一个标志,在缓存中,对缓存的插入操作一定要是线程安全的,(线程安全圈起来这是重点,要考)。比如这样:
 

object timeobje =  Util.CacheManager.GetCache(KeyName + UserModel.ID); 

  当然你也可以使用redis,memcached 等缓存服务来实现这种方式。

  引申一下你可以使用乐观锁来实现:用户请求进入时候为用户创建一个标识值,插入订单前判断这个值是否变化,变化了,放弃本次操作,未变化,继续本次操作。

  redis,memcached 都提供有相关功能。

  使用了上述手段就能完全避免恶意的提交么? 否  ,但是能解决绝大部分恶意的提交

  有没有完全的解决方案? 我这里暂时没有

  你也可以参考他们的文章:

  http://blog.csdn.net/zlhzhj/article/details/51245807

  http://blog.csdn.net/lvqingyao520/article/details/52974217

二、多个相类似的用户,通过正常的流程进行活动

  通过实名认证来解决?   优点:可以98%的规避机器人注册账号和提高用户的真实性 。 缺点:认证成本高,流程麻烦,用户粘性小,不适合营销活动

  封IP?有点:能屏蔽部分非法注册和非法参与活动的人。  缺点:使用代理的用户可以轻松绕过,会误伤同一个局域网的用户,比如同一个办公室的

  通过算法分析用户行为?优点:可靠程度比较高,针对性比较强,误伤率低       缺点:得有个会写相关算法分析的

  以上为个人看法,通常我们的营销活动对于这种情况的选择都是:忽略(滑稽脸,不要扔砖)

 

三、那我们是怎样确保奖品不会很快被人抽光呢?重点来了,开始集中活力,哦不是注意力~~

  在活动中将整个活动时间换算成秒,然后拿来除以奖品总数量 。比如一天有  (24*60*60s)/(3000个奖品)得到平均两个奖品发放时间间隔(得到值A),当然平均时间发放容易让人诟病,做营销活动的知道,总是能遇到

  死缠烂打的这样的一些爱占便宜的和钻牛角尖的用户。我们总归有办法解决这个问题(废话)。

  根据活动结束时间和最后一次发奖时间 之差 (得到值B)作为随机数种子

  根据已经发放的奖品数量得到完美的情况下理论上需要使用的时间(A*发放的奖品数量,得到值C)

  用活动开始时间+C+随机时间(使用种子B得到,这样每个Random第一次Next()得到的值是固定的)得到下个奖品的发放时间 (得到时间D)

  如果当前时间>=时间D   则说明下个奖品应该发放了。即:发放给时间D之后第一个进入抽奖的用户

  罗哩罗嗦的,说的有点蒙。~~~~~~~~来让我们看看代码吧

/// <summary>
    /// 奖项
    /// </summary>
    public interface IAward
    {
        /// <summary>
        /// 奖品编号
        /// </summary>
          int ID { get; set; }
        /// <summary>
        /// 奖品名称
        /// </summary>
          string  Name { get; set; }
        /// <summary>
        /// 奖品剩余数量
        /// </summary>
          int SurplusCount { get; set; }
        /// <summary>
        /// 产品总数
        /// </summary>
          int TotalCount { get; set; }
        /// <summary>
        /// 奖品位置
        /// </summary>
          int TurnLocation { get; set; }
        /// <summary>
        /// 商品价格
        /// </summary>
          decimal TurnPrice { get; set; }
        /// <summary>
        /// 最后一次被抽中时间
        /// </summary>
          DateTime LastWinningTime { get; set; }
             
 
    }

        上边的代码是奖品项的接口,用于传递给算法和脱离实际的奖品实体。下边的才是硬菜

public class DrawPro
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        DateTime? LastUpdateTime;
        public List<IAward> AwardsCollection { get; set; }


        public DrawPro(List<IAward> awardsCollection, DateTime startTime, DateTime endTime ,DateTime lastTime)
        {this.AwardsCollection = awardsCollection;   //奖项集合
            this.StartTime = startTime;  //活动的开始时间
            this.EndTime = endTime;   //活动的结束时间
            this.LastUpdateTime = lastTime;
        }


        /// <summary>
        /// 从奖项集合中抽取奖品
        /// </summary>
        /// <returns>奖品项</returns>
        public IAward Draw(bool VaryAward=false)
        {
            if (this.AwardsCollection != null && this.AwardsCollection.Count > 0)
            {
                return GetWinningAwards(VaryAward);
            }
            //抽奖
            return null;
        }

       /// <summary>
        /// 从集合中抽取奖品项
        /// </summary>
        protected IAward GetWinningAwards(bool varyAward)
        {

            if (AwardsCollection == null)
            {
                return null; //没有传递奖品时候,直接返回null
            }

            //总是能够出奖,从所有的奖类中选出一个奖类,奖品余量越多的,越容易命中
            IAward awards = RandomGetAwardBath(this.AwardsCollection); //

            //奖品发放完毕
            if (awards == null)
            {
                return null;
            }

            if (varyAward)
            {
                return awards;
            }

        
            TimeSpan lstspan = StartTime.Subtract(LastUpdateTime.Value);
            int currentBalance = 0; // awards.SurplusCount; //剩余奖品数量
            int currentAmount = 0; // awards.TotalCount;  //奖品总数

            foreach (var item in this.AwardsCollection)
            {
                currentBalance += item.SurplusCount;
                currentAmount += item.TotalCount;
            }

            TimeSpan tsanSs = EndTime.Subtract(StartTime);
            long dataTime = Math.Abs((long)tsanSs.TotalMilliseconds / currentAmount);   //两个奖品出奖时间间隔(单位毫秒)

            if (dataTime == 0)
            {
                dataTime = 100;//当时间无法分配的时候,设置默认出奖
            }

            //使用lastUpdateTime  作为随机因子保证下一个奖品的出奖时间对每个抽奖者一样
            Random rand = new Random(lstspan.Milliseconds);


            //计算下一个奖品的释放时间点
            //已经出奖所用时间+  小于间隔的时间(<dataTime),就是下个奖品的出奖时间  ,
            //用一个随机数%dataTime的到结果永远小于dataTime
            long releaseTime = (currentAmount - currentBalance) * dataTime + rand.Next() % dataTime;

            DateTime eqTime = this.StartTime.AddMilliseconds(releaseTime);
            if (DateTime.Now < eqTime)
            {
                /*当前时间未达到下一个奖品的释放时间点*/
                return null;
            }

            return awards;
        }


        /// <summary>
        /// 随机获取一个奖品(总是出奖)
        /// </summary>
        /// <param name="awardBatchs"></param>
        /// <returns></returns>
        protected IAward RandomGetAwardBath(List<IAward> awardBatchs)
        {
            if (awardBatchs == null || awardBatchs.Count <= 0)
            {
                return null;
            }

            int weight = 0;
            for (int i = 0; i < awardBatchs.Count; i++)
            {
                weight += awardBatchs[i].SurplusCount;
            }

            if (weight == 0)   //剩余奖品数量
            {
                return null;
            }

            /*这里:如果剩余的奖品数量不变,那么此处随机出的结果是不会变化的,即:当奖品数量不变时候,这里能指定下次出奖的奖品,任何情况下不会改变,除非
               奖品数量发生了变化
            */
            Random random = new Random(weight);
            int num = random.Next(weight);    
            for (int i = 0; i < awardBatchs.Count; i++)
            {
                num -= awardBatchs[i].SurplusCount;   //确保num-每个奖品的数量,当小于0时候就可以出奖, 循环内确保总是能够出奖
                if (num < 0)
                {
                    return awardBatchs[i];
                }
            }

            return null;
        }

    }

 

 

 

 

 

全部评论



提问