.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();
}
}
}
搶購成功
相關文章
- .NetCore+Jexus代理+Redis模擬秒殺商品活動NetCoreRedis
- 大型PHP電商網站商品秒殺功能實現思路分析PHP網站
- 秒殺系統分析
- 圖靈官方淘寶店圖書秒殺活動預告圖靈
- 雲端計算秒殺電商秒殺
- PHP高併發商品秒殺問題的解決方案PHP
- 秒殺外掛的業務邏輯分析 秒殺外掛可以幫助您什麼?
- 直播帶貨app開發,制定商品秒殺倒數計時提示APP
- 高併發秒殺系統架構詳解,不是所有的秒殺都是秒殺!架構
- 秒殺系統架構分析與實戰架構
- 分享一個整合SSM框架的高併發和商品秒殺專案SSM框架
- 秒殺流程圖流程圖
- 【高併發】秒殺系統架構解密,不是所有的秒殺都是秒殺(升級版)!!架構解密
- PHP秒殺系統全方位設計分析(二)PHP
- PHP秒殺系統全方位設計分析(一)PHP
- 同步秒殺實現:Redis在秒殺功能的實踐Redis
- 01 整合IDEA+Maven+SSM框架的高併發的商品秒殺專案之業務分析與DAO層IdeaMavenSSM框架
- Web系統大規模併發——電商秒殺與搶購Web
- 【Python秒殺指令碼】淘寶或京東等秒殺搶購Python指令碼
- 秒殺系統如何保證資料庫不崩潰以及防止商品超賣資料庫
- python使用requests秒殺茅臺(適用某寶,也可搶購其他商品)Python
- PHP高併發 商品秒殺 問題的 2大種(MySQL or Redis) 解決方案PHPMySqlRedis
- 從京東618秒殺聊聊秒殺限流的多種實現
- 秒殺系統設計
- 秒殺架構實踐架構
- 秒殺多執行緒執行緒
- 秒殺最佳化-基於阻塞佇列實現秒殺最佳化佇列
- 視訊直播系統原始碼,倒數計時顯示,商品秒殺倒數計時原始碼
- 【一文秒懂】電商系統商品模組初建分析與設計
- 秒殺架構模型設計架構模型
- 電商秒殺系統設計
- 秒殺系統的設計
- 微服務電商秒殺系統微服務
- 秒殺系統前端優化前端優化
- 電商 秒殺 庫存問題
- PostgreSQL秒殺場景優化SQL優化
- 雙十一有很多一元秒殺的商品!網速不夠?Python指令碼來湊!Python指令碼
- Storm模擬分析電話日誌ORM