.NetCore+Redis模拟秒杀商品活动(分析)

主页

这里写图片描述

HomeController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SeckillPro.Com.Tool;
using SeckillPro.Com.Model.CrmModel;
using SeckillPro.Com.Model;
using SeckillPro.Com.Common;
using System.Net.Http;
using SeckillPro.Com.Model.ApiModel;
using Newtonsoft.Json;

namespace SeckillPro.Web.Controllers
{
    public class HomeController : Controller
    {
        //redis引用
        private StackRedis _redis = StackRedis.Current;

        /// <summary>
        /// 用户抢购商品列表
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> Index()
        {
            #region  使用ip模拟登录账号
            var token = "Sid_" + HttpContext.Connection.RemoteIpAddress.ToString();
            var sessionData = await _redis.Get<MoUserInfo>(token);
            if (sessionData == null || sessionData.UserId <= 0)
            {
                //用户基本信息
                var moUser = new MoUserInfo();
                moUser.UserId = await DataKeyHelper.Current.GetKeyId(EnumHelper.EmDataKey.UserId);
                moUser.NickName = token;
                //redis存储session,默认30分钟失效
                var isLogin = await _redis.Set<MoUserInfo>(token, moUser, 30);
                if (isLogin)
                {
                    ViewData["MoUser"] = moUser;
                    //加入cookie
                    Response.Cookies.Append(EnumHelper.EmDataKey.SessionKey.ToString(), token, new Microsoft.AspNetCore.Http.CookieOptions
                    {
                        Expires = DateTime.Now.AddMinutes(30),
                        HttpOnly = true
                    });
                }
            }
            else
            {
                ViewData["MoUser"] = sessionData;
                //已经是登陆状态,需要重新设置失效时间
                var isLogin = await _redis.Set<MoUserInfo>(token, sessionData, 30);
                Response.Cookies.Append(EnumHelper.EmDataKey.SessionKey.ToString(), token, new Microsoft.AspNetCore.Http.CookieOptions
                {
                    Expires = DateTime.Now.AddMinutes(30),
                    HttpOnly = true
                });
            }
            #endregion

            //商品列表
            var shoppings = await _redis.GetHashsToList<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString());
            Response.Headers.Add("PageCache-Time", $"{60 * 2}");  //2分钟
            return View(shoppings);
        }

        [HttpGet]
        public async Task<IActionResult> Qiang(int? id)
        {
            if (id == null) { return BadRequest(); }

            var shop = await _redis.GetHashField<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString(), id.ToString());
            if (shop == null) { return NotFound(); }
            return View(shop);
        }

        [HttpPost]
        public async Task<IActionResult> Qiang(MoKaiQiang qiang)
        {

            #region 基础验证
            if (qiang == null || qiang.ShopId <= 0) { return RedirectToAction("Error", new { msg = "操作太快,请稍后重试。" }); }

            if (!Request.Cookies.TryGetValue(EnumHelper.EmDataKey.SessionKey.ToString(), out string token))
            {
                return RedirectToAction("Error", new { msg = "请先去登录。" });
            }
            var sessionData = await _redis.Get<MoUserInfo>(token);
            if (sessionData == null || sessionData.UserId <= 0) { return RedirectToAction("Error", new { msg = "请先去登录!" }); }

            var shop = await _redis.GetHashField<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString(), qiang.ShopId.ToString());
            if (shop == null) { return NotFound(); }
            else if (shop.MaxNum <= 0)
            {
                return RedirectToAction("Error", new { msg = $"你太慢了,商品:{shop.Name},已经被抢完了!" });
            }
            else if (shop.MaxNum < qiang.Num)
            {
                return RedirectToAction("Error", new { msg = $"库存不足,商品:{shop.Name},只剩{shop.MaxNum}了!" });
            }
            else if (shop.MaxGouNum < qiang.Num)
            {
                return RedirectToAction("Error", new { msg = $"一个账号每次最多只能抢购【{shop.Name}】{shop.MaxGouNum}件。" });
            }

            #endregion

            #region 请求抢购商品的分布式接口
            var rq = new MoQiangGouRq();
            rq.Num = qiang.Num;
            rq.ShoppingId = qiang.ShopId;
            rq.MemberRq = new MoMemberRq
            {
                Ip = HttpContext.Connection.RemoteIpAddress.ToString(),  //用户Ip
                RqSource = (int)EnumHelper.EmRqSource.Web,
                Token = token
            };
            var strRq = JsonConvert.SerializeObject(rq);
            var content = new StringContent(strRq, System.Text.Encoding.UTF8, "application/json");

            //基础接口地址
            //string apiBaseUrl = $"http://{HttpContext.Connection.LocalIpAddress}:4545";
            string apiBaseUrl = $"http://localhost:4545";
            var qiangApiUrl = $"{apiBaseUrl}/api/order/SubmitQiangGouOrder";
            var strRp = await HttpTool.HttpPostAsync(qiangApiUrl, content, 30);
            if (string.IsNullOrWhiteSpace(strRp))
            {
                return RedirectToAction("Error", new { msg = $"抢单超时,请查看你的订单列表是否抢单成功。" });
            }
            var rp = JsonConvert.DeserializeObject<MoQiangGouRp>(strRp);
            if (rp == null)
            {
                return RedirectToAction("Error", new { msg = $"抢单超时,请查看你的订单列表是否抢单成功。" });
            }
            else if (rp.RpStatus != 1)
            {
                return Error(rp.RpMsg);
            }
            #endregion

            return RedirectToAction("QiangResult", new { id = rp.OrderId });
        }

        /// <summary>
        /// 抢购结果
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> QiangResult(long? id)
        {
            if (id == null) { return BadRequest(); }
            if (!Request.Cookies.TryGetValue(EnumHelper.EmDataKey.SessionKey.ToString(), out string token))
            {
                return RedirectToAction("Error", new { msg = $"请先去登录" });
            }

            var rq = new MoOrderDetailRq();
            rq.OrderId = Convert.ToInt64(id);
            rq.MemberRq = new MoMemberRq { Token = token };

            var strRq = JsonConvert.SerializeObject(rq);
            var content = new StringContent(strRq, System.Text.Encoding.UTF8, "application/json");

            //基础接口地址
            //string apiBaseUrl = $"http://{HttpContext.Connection.LocalIpAddress}:4545";
            string apiBaseUrl = $"http://localhost:4545";
            
            var qiangApiUrl = $"{apiBaseUrl}/api/order/GetOrderDetail";
            var strRp = await HttpTool.HttpPostAsync(qiangApiUrl, content, 30);
            if (string.IsNullOrWhiteSpace(strRp))
            {
                return RedirectToAction("Error", new { msg = "查询失败,请稍后重试。" });
            }
            var rp = JsonConvert.DeserializeObject<MoOrderDetailRp>(strRp);
            if (rp == null) { return RedirectToAction("Error", new { msg = $"查询失败,请稍后重试!" }); }
            else if (rp.RpStatus != 1)
            {
                return RedirectToAction("Error", new { msg = rp.RpMsg });
            }
            return View(rp);
        }

        /// <summary>
        /// 订单列表
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> OrderList()
        {
            if (!Request.Cookies.TryGetValue(EnumHelper.EmDataKey.SessionKey.ToString(), out string token))
            {
                return RedirectToAction("Error", new { msg = "请先去登录。" });
            }
            var sessionData = await _redis.Get<MoUserInfo>(token);
            if (sessionData == null || sessionData.UserId <= 0) { return RedirectToAction("Error", new { msg = "请先去登录!" }); }

            var orderList = await _redis.GetHashsToList<MoOrderInfo>($"User_{sessionData.UserId}");
            return View(orderList);
        }

        #region 商品后台管理
        /// <summary>
        /// 商品后台管理列表(后台人员设置)
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> ShoppingManage()
        {
            //获取原有商品
            var shoppings = await _redis.GetHashsToList<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString());
            return View(shoppings);
        }

        #region 添加商品
        [HttpGet]
        public IActionResult ShoppingAdd()
        {
            return View();
        }
        [HttpPost]
        public async Task<IActionResult> ShoppingAdd(MoShopping shopping)
        {

            //加入商品列表
            shopping.Id = await DataKeyHelper.Current.GetKeyId(EnumHelper.EmDataKey.ShoppingId);
            shopping.MaxNum = shopping.MaxNum < 0 ? 0 : shopping.MaxNum;
            shopping.MaxGouNum = shopping.MaxGouNum <= 0 ? 1 : shopping.MaxGouNum;
            shopping.Url = string.IsNullOrWhiteSpace(shopping.Url) ? "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=112998166,256272819&fm=117&gp=0.jpg" : shopping.Url;
            var result = await _redis.SetOrUpdateHashsField<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString(), shopping.Id.ToString(), shopping);
            if (result > 0) { return RedirectToAction("ShoppingManage"); }
            return View(shopping);
        }
        #endregion

        #endregion

        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Error(string msg = "怎么就迷路了呢。")
        {
            ViewData["msg"] = msg;
            return View();
        }
    }

    public class MoKaiQiang
    {
        public long ShopId { get; set; }
        public int Num { get; set; }
    }
}


redis里的商品
这里写图片描述


redis里的订单
这里写图片描述


redis里的用户
这里写图片描述


订单列表

这里写图片描述


请求API的响应
这里写图片描述

OrderController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using SeckillPro.Com.Model.ApiModel;
using SeckillPro.Com.Tool;
using SeckillPro.Com.Model.CrmModel;
using SeckillPro.Com.Model;
using SeckillPro.Com.Common;
using System.IO;

namespace SeckillPro.Api.Controllers
{
    [Route("api/[controller]/[action]")]
    public class OrderController : Controller
    {
        //redis引用
        private StackRedis _redis = StackRedis.Current;

        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        /// <summary>
        /// 抢单
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<MoQiangGouRp> SubmitQiangGouOrder()
        {
            var rp = new MoQiangGouRp { RpMsg = EnumHelper.EmOrderStatus.抢购失败.ToString() };
            try
            {
                var strRq = string.Empty;
                using (var stream = Request.Body)
                {
                    using (var reader = new StreamReader(stream))
                    {
                        strRq = await reader.ReadToEndAsync();
                    }
                }
                if (string.IsNullOrWhiteSpace(strRq)) { return rp; }

                var rq = JsonConvert.DeserializeObject<MoQiangGouRq>(strRq);
                if (rq.ShoppingId <= 0 || rq.MemberRq == null) { return rp; }

                #region 验证

                //登录验证
                if (string.IsNullOrWhiteSpace(rq.MemberRq.Token))
                {
                    rp.RpMsg = $"未登录,请登录后重试。";
                    return rp;
                }
                var sessionData = await _redis.Get<MoUserInfo>(rq.MemberRq.Token);
                if (sessionData == null)
                {
                    rp.RpMsg = $"登录失效,请重新登录!";
                    return rp;
                }

                //库存验证
                var shopsKey = EnumHelper.EmDataKey.ShoppingHash.ToString();
                var shop = await _redis.GetHashField<MoShopping>(shopsKey, rq.ShoppingId.ToString());
                if (shop == null) { return rp; }
                else if (shop.MaxNum <= 0)
                {
                    rp.RpMsg = $"你太慢了,商品:{shop.Name},已经被抢完了!";
                    return rp;
                }
                else if (shop.MaxNum < rq.Num)
                {
                    rp.RpMsg = $"库存不足,商品:{shop.Name},只剩{shop.MaxNum}了!";
                    return rp;
                }
                else if (shop.MaxGouNum < rq.Num)
                {
                    rp.RpMsg = $"一个账号每次最多只能抢购【{shop.Name}】{shop.MaxGouNum}件。";
                    return rp;
                }
                #endregion

                #region 加入订单list中

                var orderInfo = new MoOrderInfo();
                orderInfo.OrderId = await DataKeyHelper.Current.GetKeyId(EnumHelper.EmDataKey.OrderId);
                orderInfo.OrderStatus = (int)EnumHelper.EmOrderStatus.排队抢购中;
                orderInfo.CreatTime = DateTime.Now;
                orderInfo.Num = rq.Num;
                orderInfo.ShoppingId = rq.ShoppingId;
                orderInfo.UserId = sessionData.UserId;
                orderInfo.MoShopping = shop;
                //记录所有订单

                //记录会员名下的订单
                var isAddOrder = await _redis.SetOrUpdateHashsField<MoOrderInfo>($"User_{orderInfo.UserId}", orderInfo.OrderId.ToString(), orderInfo);
                if (isAddOrder <= 0)
                {
                    return rp;
                }
                rp.OrderId = orderInfo.OrderId;
                rp.OrderStatus = orderInfo.OrderStatus;
                rp.CreatTime = orderInfo.CreatTime;
                rp.RpStatus = 1;
                rp.RpMsg = EnumHelper.EmOrderStatus.排队抢购中.ToString();
                #endregion

                #region 各种验证无误后,加入抢购队列,并返回抢购中...

                var isAddQiangQueue = await _redis.SetList<MoOrderInfo>($"{EnumHelper.EmDataKey.QiangOrderEqueue.ToString()}_{orderInfo.ShoppingId}", orderInfo);

                #endregion
            }
            catch (Exception ex)
            {
                rp.RpMsg = "抢购活动正在高峰期,请稍后重试";
            }
            return rp;
        }

        [HttpPost]
        public async Task<MoOrderDetailRp> GetOrderDetail()
        {
            var rp = new MoOrderDetailRp();
            try
            {
                //获取请求参数
                var strRq = string.Empty;
                using (var stream = Request.Body)
                {
                    using (var reader = new StreamReader(stream))
                    {
                        strRq = await reader.ReadToEndAsync();
                    }
                }
                if (string.IsNullOrWhiteSpace(strRq)) { return rp; }

                var rq = JsonConvert.DeserializeObject<MoOrderDetailRq>(strRq);

                #region 验证

                #region 登录验证
                if (rq.MemberRq == null || string.IsNullOrWhiteSpace(rq.MemberRq.Token))
                {
                    rp.RpMsg = $"未登录,请登录后重试。";
                    return rp;
                }
                var sessionData = await _redis.Get<MoUserInfo>(rq.MemberRq.Token);
                if (sessionData == null)
                {
                    rp.RpMsg = $"登录失效,请重新登录!";
                    return rp;
                }
                #endregion

                if (rq.OrderId <= 0)
                {
                    rp.RpMsg = $"参数不正确!";
                    return rp;
                }
                #endregion

                var orderDetail = await _redis.GetHashField<MoOrderInfo>($"User_{sessionData.UserId}", rq.OrderId.ToString());
                if (orderDetail == null)
                {
                    rp.RpMsg = $"订单查询失败,无此订单!";
                    return rp;
                }

                rp.OrderId = orderDetail.OrderId;
                rp.Num = orderDetail.Num;
                rp.OrderStatus = orderDetail.OrderStatus;
                rp.PayOutTime = orderDetail.PayOutTime;
                rp.CreatTime = orderDetail.CreatTime;
                rp.ShoppingId = orderDetail.ShoppingId;

                var shop = await _redis.GetHashField<MoShopping>(EnumHelper.EmDataKey.ShoppingHash.ToString(), rp.ShoppingId.ToString());
                if (shop == null)
                {
                    rp.RpMsg = $"订单查询失败,请稍后重试!";
                    return rp;
                }
                rp.MoShopping = shop;
                rp.RpStatus = 1;
                rp.RpMsg = "订单查询成功";
            }
            catch (Exception ex)
            {
                rp.RpMsg = "查询超时,请稍后重试";
            }
            return rp;
        }
    }
}

控制台程序,监控订单队列
这里写图片描述

Program

using SeckillPro.Com.Model;
using SeckillPro.Com.Model.CrmModel;
using SeckillPro.Com.Tool;
using System;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;

namespace SeckillPro.Server
{
    class Program
    {
        //redis引用
        private static StackRedis _redis = StackRedis.Current;
        //控制重复任务
        private static Dictionary<long, long> _dicTask = new Dictionary<long, long>();
        static void Main(string[] args)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            Console.OutputEncoding = Encoding.GetEncoding("GB2312");

            try
            {
                Console.WriteLine("是否开启处理抢购队列(Y):");
                Console.ReadLine();
                Console.WriteLine($"开启抢购任务监控中...");
                var shopsKey = EnumHelper.EmDataKey.ShoppingHash.ToString();
                while (true)
                {
                    //匹配出QiangOrderEqueue_xxx格式的抢单队列keys
                    var matchKey = $"{EnumHelper.EmDataKey.QiangOrderEqueue.ToString()}_*";
                    var matches = _redis.MatchKeys(matchKey).Result;
                    var matchLen = matches.Count;
                    if (matchLen <= 0) { continue; }

                    //根据key获取对应的商品,并加载对应的商品抢单任务处理
                    foreach (var item in matches)
                    {
                        var itemArr = item.Split('_');
                        if (itemArr.Length <= 1) { continue; }
                        var shopId = itemArr[1];
                        var shop = _redis.GetHashField<MoShopping>(shopsKey, shopId).Result;
                        if (shop == null || _dicTask.ContainsKey(shop.Id)) { continue; }

                        //加入排重任务dic
                        _dicTask.Add(shop.Id, shop.Id);

                        Task.Factory.StartNew(async b =>
                        {
                            var equeueShop = b as MoShopping;
                            var equeueKey = $"{EnumHelper.EmDataKey.QiangOrderEqueue.ToString()}_{equeueShop.Id}";
                            var sbTaskLog = new StringBuilder(string.Empty);
                            try
                            {
                                sbTaskLog.AppendFormat("商品【{0}-{1}】,开启抢购队列处理;", equeueShop.Name, equeueShop.Id);
                                Console.WriteLine(sbTaskLog);

                                //监控队列key是否存在
                                while (await _redis.KeyExists(equeueKey))
                                {
                                    //获取队列
                                    var qiangOrder = await _redis.GetListAndPop<MoOrderInfo>(equeueKey);
                                    if (qiangOrder == null) { continue; }

                                    //获取真实剩余库存
                                    var equShop = await _redis.GetHashField<MoShopping>(shopsKey, equeueShop.Id.ToString());
                                    if (equShop == null) { continue; }

                                    var sbLog = new StringBuilder(string.Empty);
                                    Stopwatch watch = new Stopwatch();
                                    watch.Start();
                                    try
                                    {
                                        #region 逻辑处理库存

                                        sbLog.AppendFormat("用户:{0}抢购商品【{1}-{4}】当前库存:{2}件,抢购数:{3}件,",
                                                                    qiangOrder.UserId,
                                                                    equShop.Name,
                                                                    equShop.MaxNum,
                                                                    qiangOrder.Num,
                                                                    equShop.Id);
                                        if (equShop.MaxNum <= 0)
                                        {
                                            //无库存,直接抢购失败
                                            qiangOrder.OrderStatus = (int)EnumHelper.EmOrderStatus.抢购失败;
                                        }
                                        else if (equShop.MaxNum < qiangOrder.Num)
                                        {
                                            //剩余库存小于抢购数量
                                            qiangOrder.OrderStatus = (int)EnumHelper.EmOrderStatus.抢购失败;
                                        }
                                        else if (equShop.MaxGouNum < qiangOrder.Num)
                                        {
                                            //最大允许抢购数量小于抢购申请数量
                                            qiangOrder.OrderStatus = (int)EnumHelper.EmOrderStatus.抢购失败;
                                        }
                                        else
                                        {
                                            //库存充足
                                            equShop.MaxNum = equShop.MaxNum - qiangOrder.Num;
                                            //扣除当前抢购数量后,更新库存
                                            var isOk = await _redis.SetOrUpdateHashsField<MoShopping>(shopsKey, equShop.Id.ToString(), equShop, false) > 0;
                                            if (!isOk)
                                            {
                                                qiangOrder.OrderStatus = (int)EnumHelper.EmOrderStatus.抢购失败;
                                            }
                                            else
                                            {
                                                qiangOrder.OrderStatus = (int)EnumHelper.EmOrderStatus.抢购成功;
                                            }
                                        }
                                        #endregion
                                    }
                                    catch (Exception ex)
                                    {
                                        sbLog.AppendFormat("异常信息:{0},", ex.Message);
                                    }
                                    finally
                                    {
                                        sbLog.AppendFormat("库存剩余:{0}件,抢购订单状态:{1},",
                                            equeueShop.MaxNum,
                                            Enum.GetName(typeof(EnumHelper.EmOrderStatus), qiangOrder.OrderStatus));

                                        //更新当前订单抢购状态
                                        var isQiangOrder = await _redis.SetOrUpdateHashsField<MoOrderInfo>($"User_{qiangOrder.UserId}", qiangOrder.OrderId.ToString(), qiangOrder, false);
                                        watch.Stop();
                                        sbLog.AppendFormat("更新订单状态:{0};处理总耗时:{1}ms。", isQiangOrder > 0, watch.ElapsedMilliseconds);
                                        Console.WriteLine(sbLog);
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                sbTaskLog.AppendFormat("异常信息:{0};", ex.Message);
                            }
                            finally
                            {
                                //任务结束时去掉排重任务dic记录
                                if (_dicTask.ContainsKey(equeueShop.Id)) { _dicTask.Remove(equeueShop.Id); }

                                sbTaskLog.Append("处理抢购订单队列结束。");
                                Console.WriteLine(sbTaskLog);
                            }
                        }, shop);
                        //Console.WriteLine($"商品【{shop.Name}-{shop.Id}】开启处理订单队列任务;");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("全局异常信息:" + ex.Message);
            }
            Console.WriteLine("温馨提示:按住任意键即可退出!");
            Console.ReadLine();
        }
    }
}

抢购成功
这里写图片描述


下载示例

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值