企业网站推广论述,seo问答,华东网站建设,属于免费推广的方式是《魔兽世界》的老玩家都知道#xff0c;密保卡曾经被用于登录验证#xff0c;以保证账号安全。今天我用.NET Core模拟了一把密保卡#xff08;也叫矩阵卡#xff09;的实现#xff0c;分享给大家。密保卡的原理这是一张典型的魔兽世界密保卡。序列号用于绑定游戏账号… 《魔兽世界》的老玩家都知道密保卡曾经被用于登录验证以保证账号安全。今天我用.NET Core模拟了一把密保卡也叫矩阵卡的实现分享给大家。密保卡的原理这是一张典型的魔兽世界密保卡。序列号用于绑定游戏账号而下面表格中的数字用于登录验证。图片来源于网络假设黑客已经知道了你的账号和密码但是由于你绑定了一张密保卡。因此在登录游戏时游戏会随机挑选其中一定数量一般是3个格子要求输入对应的数字如A1928C8985B10640。而因为黑客没有拿到你的密保卡因此他不知道矩阵中的数字无法登录你的账号。即使抓取了几次你的输入但由于每次登录账号被随机选中的单元格组合都不同因此对于一张7X12的密保卡黑客需要抓对不起我数学40分这个算不出来次才能完全掌握你的密保卡信息。然而账号主人可以随时更换密保卡让黑客前功尽弃。.NET Core 实现关注我博客的朋友可能知道8年前我写过这个话题两篇文章分别是《C#仿魔兽世界密保卡简单实现》与《C#仿魔兽世界密保卡OOP重构版》。但是时代变了兽人永不为奴而.NET必将为王。8年了当年文章里用的ASP.NET WebForm和巫妖王一起死在了冰封王座.NET踏上了跨平台的远征C# 的语法也突飞猛进的发展。荣耀属于.NET Core因此我把这盘冷饭拿出来炒一下用现代化的手段重写当年的老代码刷刷声望。最终效果如下实现生成、序列号数据、重新加载数据以及验证输入源代码传送门https://go.edi.wang/fw/5d12778dCell 类Cell用于描述矩阵卡中的单元格。对于一个Cell它拥有行标、列标和值三个属性。我分别用RowIndexColIndexValue来表示。为了方便显示我加入了ColumnName属性用于把列标显示为英文字母此处稍微和官方密保卡设计不一样。为了约束Cell类型的使用以上属性设计为只读并只能从构造函数赋值。public class Cell{ public int RowIndex { get; } public int ColIndex { get; } public ColumnCode ColumnName (ColumnCode)ColIndex; public int Value { get; set; } public Cell(int rowIndex, int colIndex, int val 0) { RowIndex rowIndex; ColIndex colIndex; Value val; }}public enum ColumnCode{ A 0, B 1, C 2, D 3, E 4}ColumnCode 可以根据自己需要拓展目前我只写了5个值。Card 类Card用于描述一张密保卡。因此除了包含一堆Cell以外还得有卡号Id以及行数、列数等信息。起初的Card类型长这样public class Card{ public Guid Id { get; set; } public int Rows { get; set; } public int Cols { get; set; } public ListCell Cells { get; set; } public Card(int rows 5, int cols 5) { Id Guid.NewGuid(); Rows rows; Cols cols; Cells new ListCell(); }}但是考虑到序列化数据时候不希望字符串有太多冗余信息因此加入CellData属性用于简化Cells的数据表示。将Cells中的数据拼成一个以逗号分隔的字符串中。以便于持久化的时候和Card类型的属性一起包在一个Json字符串中看起来不会太长。[JsonIgnore]public ListCell Cells { get; set; }public string CellData{ get { var vals Cells.Select(c c.Value); return string.Join(,, vals); }}生成密保卡数据首先根据行、列数量生成一个二位数组使用0-100的随机值填充。值范围可以根据自己需要改。private static int[,] GenerateRandomMatrix(int rows, int cols){ var r new Random(); var arr new int[rows, cols]; for (var row 0; row rows; row) { for (var col 0; col cols; col) { arr[row, col] r.Next(0, 100); } } return arr;}然后将生成的值按行、列分配给Cells属性private void FillCellData(int[,] array){ for (var row 0; row Rows; row) { for (var col 0; col Cols; col) { var c new Cell(row, col, array[row, col]); Cells.Add(c); } }}在Console上打印密保卡信息也很简单用两个循环分别控制行、列的输出即可。当然这只是demo意图真实使用场景用不着consoleprivate static void PrintCard(Card card){ Console.WriteLine( |\tA\tB\tC\tD\tE\t); Console.WriteLine(----------------------------------------------); var i 0; for (var k 0; k card.Rows; k) { Console.Write(k |\t); for (var l 0; l card.Cols; l) { Console.Write(card.Cells[i].Value \t); i; } Console.WriteLine(); }}加载Cells数据除了生成数据我们还要支持加载既有数据到Cells中。因为之前被简化过的Cells数据是个以逗号分割的string字符串因此我们需要把它拆成数组并转换类型回int然后利用之前写的FillCellData()方法填充到Cells属性里。public Card LoadCellData(string strMatrix){ var tempArrStr strMatrix.Split(,); if (tempArrStr.Length ! Rows * Cols) { throw new ArgumentException( The number of elements in the matrix does not match the current card cell numbers., nameof(strMatrix)); } var arr new int[Rows, Cols]; var index 0; for (var row 0; row Rows; row) { for (var col 0; col Cols; col) { arr[row, col] int.Parse(tempArrStr[index]); index; } } FillCellData(arr); return this;}随机选择与验证同样使用Random类型在给定的行列范围内随机选择给定数量的单元格但不从Cells中取因为我们无需返回单元格的值。在服务器/客户端场景下验证始终应该放在服务器上做不要在客户端验证值因此不要返回值。public IEnumerableCell PickRandomCells(int howMany){ var r new Random(); for (var i 0; i howMany; i) { var randomCol r.Next(0, Cols); var randomRow r.Next(0, Rows); var c new Cell(randomRow, randomCol); yield return c; }}由于返回的Cell信息包含了行、列因此当用户输入值之后我们可以与Cells中已存在的信息进行对比。对于每一个需要验证的单元格在Cells中查找具有同样行列的单元格。对比这两者的值是否相等一旦遇到不相等直接返回false无需再验证下一个单元格。通常这样的操作某些语言就得写好几个循环不仅麻烦还容易下标搞错数组越界然后996。好在C#的LINQ一行就写完了换行只是代码格式public bool Validate(IEnumerableCell cellsToValidate){ return ( from cell in cellsToValidate let thisCell Cells.Find(p p.ColIndex cell.ColIndex p.RowIndex cell.RowIndex) select thisCell.Value cell.Value) .All(matches matches);}完整代码传送门https://go.edi.wang/fw/5d12778d