From 42c637153a95ffd27cf8acd8486166ec73299eec Mon Sep 17 00:00:00 2001 From: test Date: Wed, 9 Jul 2025 18:09:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84TestCase=E7=94=A8?= =?UTF-8?q?=E4=BE=8BIMSI=E4=BF=9D=E6=8A=A4=E4=B8=8E=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateTestCase/CreateTestCaseCommand.cs | 19 +- .../CreateTestCaseCommandHandler.cs | 55 +++ .../UpdateTestCase/UpdateTestCaseCommand.cs | 19 +- .../UpdateTestCaseCommandHandler.cs | 55 +++ .../Attributes/IMSIValidationAttribute.cs | 449 ++++++++++++++++++ .../Common/Attributes/IMSIValidationTests.cs | 150 ++++++ src/X1.Domain/Entities/Device/TestCase.cs | 23 +- src/X1.Domain/Models/UserInfo.cs | 30 ++ .../Services/IIMSIValidationService.cs | 42 ++ src/X1.Domain/ValueObjects/IMSI.cs | 172 +++++++ .../Device/TestCaseConfiguration.cs | 2 +- src/X1.Infrastructure/DependencyInjection.cs | 1 + .../Security/IMSIValidationService.cs | 364 ++++++++++++++ 13 files changed, 1370 insertions(+), 11 deletions(-) create mode 100644 src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs create mode 100644 src/X1.Domain/Common/Attributes/IMSIValidationTests.cs create mode 100644 src/X1.Domain/Models/UserInfo.cs create mode 100644 src/X1.Domain/Services/IIMSIValidationService.cs create mode 100644 src/X1.Domain/ValueObjects/IMSI.cs create mode 100644 src/X1.Infrastructure/Services/Security/IMSIValidationService.cs diff --git a/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs b/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs index 858e3ab..ef08632 100644 --- a/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs +++ b/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs @@ -24,13 +24,26 @@ public class CreateTestCaseCommand : IRequest - /// 用户数 + /// 用户数(1-5,作为父级参数) /// - [Range(0, int.MaxValue, ErrorMessage = "用户数必须大于等于0")] - public int UserCount { get; set; } = 0; + [Range(1, 5, ErrorMessage = "用户数必须在1到5之间")] + public int UserCount { get; set; } = 1; /// /// 用户信息列表(JSON格式) + /// 格式示例: + /// [ + /// { + /// "UserId": 1, + /// "IMSI1": "460001234567890", + /// "IMSI2": "460001234567891" + /// }, + /// { + /// "UserId": 2, + /// "IMSI1": "460001234567892", + /// "IMSI2": "460001234567893" + /// } + /// ] /// [MaxLength(4000, ErrorMessage = "用户信息列表长度不能超过4000个字符")] public string? UserInfoList { get; set; } diff --git a/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommandHandler.cs b/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommandHandler.cs index 4774956..1a8883d 100644 --- a/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommandHandler.cs +++ b/src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommandHandler.cs @@ -5,6 +5,9 @@ using CellularManagement.Domain.Entities.Device; using CellularManagement.Domain.Repositories.Device; using CellularManagement.Domain.Repositories.Base; using CellularManagement.Domain.Services; +using CellularManagement.Domain.Models; +using CellularManagement.Domain.ValueObjects; +using System.Text.Json; namespace CellularManagement.Application.Features.TestCases.Commands.CreateTestCase; @@ -57,6 +60,58 @@ public class CreateTestCaseCommandHandler : IRequestHandler.CreateFailure($"用例版本 {request.Version} 已存在"); } + // 验证用户信息列表 + if (!string.IsNullOrEmpty(request.UserInfoList)) + { + try + { + var userInfos = JsonSerializer.Deserialize>(request.UserInfoList); + if (userInfos == null || userInfos.Count != request.UserCount) + { + _logger.LogWarning("用户信息列表数量与用户数不匹配,用户数: {UserCount}, 用户信息数量: {UserInfoCount}", + request.UserCount, userInfos?.Count ?? 0); + return OperationResult.CreateFailure($"用户信息列表数量必须与用户数({request.UserCount})匹配"); + } + + // 验证用户ID是否连续且从1开始,并验证IMSI格式 + for (int i = 0; i < userInfos.Count; i++) + { + if (userInfos[i].UserId != i + 1) + { + _logger.LogWarning("用户ID不连续,期望: {Expected}, 实际: {Actual}", i + 1, userInfos[i].UserId); + return OperationResult.CreateFailure($"用户ID必须连续,从1开始,当前用户ID: {userInfos[i].UserId}"); + } + + // 验证IMSI1格式 + try + { + var imsi1 = IMSI.Create(userInfos[i].IMSI1); + } + catch (ArgumentException ex) + { + _logger.LogWarning("用户 {UserId} 的IMSI1格式错误: {Error}", userInfos[i].UserId, ex.Message); + return OperationResult.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI1格式错误: {ex.Message}"); + } + + // 验证IMSI2格式 + try + { + var imsi2 = IMSI.Create(userInfos[i].IMSI2); + } + catch (ArgumentException ex) + { + _logger.LogWarning("用户 {UserId} 的IMSI2格式错误: {Error}", userInfos[i].UserId, ex.Message); + return OperationResult.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI2格式错误: {ex.Message}"); + } + } + } + catch (JsonException ex) + { + _logger.LogWarning("用户信息列表JSON格式错误: {Error}", ex.Message); + return OperationResult.CreateFailure($"用户信息列表JSON格式错误: {ex.Message}"); + } + } + // 获取当前用户ID var currentUserId = _currentUserService.GetCurrentUserId(); if (string.IsNullOrEmpty(currentUserId)) diff --git a/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs b/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs index df0d27a..e3d1cad 100644 --- a/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs +++ b/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs @@ -31,13 +31,26 @@ public class UpdateTestCaseCommand : IRequest - /// 用户数 + /// 用户数(1-5,作为父级参数) /// - [Range(0, int.MaxValue, ErrorMessage = "用户数必须大于等于0")] - public int UserCount { get; set; } = 0; + [Range(1, 5, ErrorMessage = "用户数必须在1到5之间")] + public int UserCount { get; set; } = 1; /// /// 用户信息列表(JSON格式) + /// 格式示例: + /// [ + /// { + /// "UserId": 1, + /// "IMSI1": "460001234567890", + /// "IMSI2": "460001234567891" + /// }, + /// { + /// "UserId": 2, + /// "IMSI1": "460001234567892", + /// "IMSI2": "460001234567893" + /// } + /// ] /// [MaxLength(4000, ErrorMessage = "用户信息列表长度不能超过4000个字符")] public string? UserInfoList { get; set; } diff --git a/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommandHandler.cs b/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommandHandler.cs index 707ebe1..74045d4 100644 --- a/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommandHandler.cs +++ b/src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommandHandler.cs @@ -5,6 +5,9 @@ using CellularManagement.Domain.Entities.Device; using CellularManagement.Domain.Repositories.Device; using CellularManagement.Domain.Repositories.Base; using CellularManagement.Domain.Services; +using CellularManagement.Domain.Models; +using CellularManagement.Domain.ValueObjects; +using System.Text.Json; namespace CellularManagement.Application.Features.TestCases.Commands.UpdateTestCase; @@ -67,6 +70,58 @@ public class UpdateTestCaseCommandHandler : IRequestHandler.CreateFailure($"用例版本 {request.Version} 已被其他用例使用"); } + // 验证用户信息列表 + if (!string.IsNullOrEmpty(request.UserInfoList)) + { + try + { + var userInfos = JsonSerializer.Deserialize>(request.UserInfoList); + if (userInfos == null || userInfos.Count != request.UserCount) + { + _logger.LogWarning("用户信息列表数量与用户数不匹配,用户数: {UserCount}, 用户信息数量: {UserInfoCount}", + request.UserCount, userInfos?.Count ?? 0); + return OperationResult.CreateFailure($"用户信息列表数量必须与用户数({request.UserCount})匹配"); + } + + // 验证用户ID是否连续且从1开始,并验证IMSI格式 + for (int i = 0; i < userInfos.Count; i++) + { + if (userInfos[i].UserId != i + 1) + { + _logger.LogWarning("用户ID不连续,期望: {Expected}, 实际: {Actual}", i + 1, userInfos[i].UserId); + return OperationResult.CreateFailure($"用户ID必须连续,从1开始,当前用户ID: {userInfos[i].UserId}"); + } + + // 验证IMSI1格式 + try + { + IMSI.Create(userInfos[i].IMSI1); + } + catch (ArgumentException ex) + { + _logger.LogWarning("用户 {UserId} 的IMSI1格式错误: {Error}", userInfos[i].UserId, ex.Message); + return OperationResult.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI1格式错误: {ex.Message}"); + } + + // 验证IMSI2格式 + try + { + IMSI.Create(userInfos[i].IMSI2); + } + catch (ArgumentException ex) + { + _logger.LogWarning("用户 {UserId} 的IMSI2格式错误: {Error}", userInfos[i].UserId, ex.Message); + return OperationResult.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI2格式错误: {ex.Message}"); + } + } + } + catch (JsonException ex) + { + _logger.LogWarning("用户信息列表JSON格式错误: {Error}", ex.Message); + return OperationResult.CreateFailure($"用户信息列表JSON格式错误: {ex.Message}"); + } + } + // 获取当前用户ID var currentUserId = _currentUserService.GetCurrentUserId(); if (string.IsNullOrEmpty(currentUserId)) diff --git a/src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs b/src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs new file mode 100644 index 0000000..140f6d8 --- /dev/null +++ b/src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs @@ -0,0 +1,449 @@ +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Domain.Common.Attributes; + +/// +/// IMSI验证特性 +/// 确保IMSI是15位数字,并且符合IMSI格式要求 +/// +public class IMSIValidationAttribute : ValidationAttribute +{ + /// + /// 验证IMSI格式 + /// + /// 要验证的值 + /// 验证结果 + public override bool IsValid(object? value) + { + if (value == null) + { + return false; + } + + var imsi = value.ToString(); + if (string.IsNullOrWhiteSpace(imsi)) + { + return false; + } + + // 检查长度是否为15位 + if (imsi.Length != 15) + { + return false; + } + + // 检查是否全部为数字 + if (!imsi.All(char.IsDigit)) + { + return false; + } + + // 检查MCC(移动国家代码)是否有效(前3位) + var mcc = imsi.Substring(0, 3); + if (!IsValidMCC(mcc)) + { + return false; + } + + // 检查MNC(移动网络代码)是否有效(第4-5位或第4-6位) + var mnc = GetMNC(imsi); + if (!IsValidMNC(mnc)) + { + return false; + } + + return true; + } + + /// + /// 获取验证错误消息 + /// + /// 属性名称 + /// 错误消息 + public override string FormatErrorMessage(string name) + { + return $"{name}必须是15位数字,且符合IMSI格式要求(MCC+MNC+MSIN)"; + } + + /// + /// 验证MCC(移动国家代码)是否有效 + /// + /// MCC代码 + /// 是否有效 + private bool IsValidMCC(string mcc) + { + // 常见的MCC代码验证 + var validMCCs = new[] + { + "460", // 中国 + "466", // 台湾 + "455", // 澳门 + "454", // 香港 + "310", // 美国 + "311", // 美国 + "312", // 美国 + "313", // 美国 + "314", // 美国 + "315", // 美国 + "316", // 美国 + "234", // 英国 + "235", // 英国 + "238", // 丹麦 + "240", // 瑞典 + "242", // 挪威 + "244", // 芬兰 + "246", // 立陶宛 + "247", // 拉脱维亚 + "248", // 爱沙尼亚 + "250", // 俄罗斯 + "255", // 乌克兰 + "257", // 白俄罗斯 + "259", // 摩尔多瓦 + "260", // 波兰 + "262", // 德国 + "268", // 葡萄牙 + "270", // 卢森堡 + "272", // 爱尔兰 + "274", // 冰岛 + "276", // 阿尔巴尼亚 + "278", // 马耳他 + "280", // 塞浦路斯 + "282", // 格鲁吉亚 + "283", // 亚美尼亚 + "284", // 保加利亚 + "286", // 土耳其 + "288", // 法罗群岛 + "290", // 格陵兰 + "292", // 安道尔 + "293", // 斯洛文尼亚 + "294", // 北马其顿 + "295", // 列支敦士登 + "297", // 黑山 + "302", // 加拿大 + "334", // 墨西哥 + "338", // 牙买加 + "340", // 瓜德罗普 + "342", // 巴巴多斯 + "344", // 安提瓜和巴布达 + "346", // 开曼群岛 + "348", // 英属维尔京群岛 + "350", // 百慕大 + "352", // 格林纳达 + "354", // 蒙特塞拉特 + "356", // 圣基茨和尼维斯 + "358", // 圣卢西亚 + "360", // 圣文森特和格林纳丁斯 + "362", // 荷属安的列斯 + "363", // 阿鲁巴 + "364", // 巴哈马 + "365", // 安圭拉 + "366", // 多米尼克 + "368", // 古巴 + "370", // 多米尼加共和国 + "372", // 海地 + "374", // 特立尼达和多巴哥 + "376", // 特克斯和凯科斯群岛 + "400", // 阿塞拜疆 + "401", // 哈萨克斯坦 + "402", // 不丹 + "404", // 印度 + "405", // 印度 + "406", // 印度 + "410", // 巴基斯坦 + "412", // 阿富汗 + "413", // 斯里兰卡 + "414", // 缅甸 + "415", // 黎巴嫩 + "416", // 约旦 + "417", // 叙利亚 + "418", // 伊拉克 + "419", // 科威特 + "420", // 沙特阿拉伯 + "421", // 也门 + "422", // 阿曼 + "424", // 阿联酋 + "425", // 以色列 + "426", // 巴林 + "427", // 卡塔尔 + "428", // 蒙古 + "429", // 尼泊尔 + "430", // 阿联酋 + "431", // 阿联酋 + "432", // 伊朗 + "434", // 乌兹别克斯坦 + "436", // 塔吉克斯坦 + "437", // 吉尔吉斯斯坦 + "438", // 土库曼斯坦 + "440", // 日本 + "441", // 日本 + "450", // 韩国 + "452", // 越南 + "454", // 香港 + "455", // 澳门 + "456", // 柬埔寨 + "457", // 老挝 + "460", // 中国 + "466", // 台湾 + "467", // 朝鲜 + "470", // 孟加拉国 + "472", // 马尔代夫 + "502", // 马来西亚 + "505", // 澳大利亚 + "510", // 印度尼西亚 + "514", // 东帝汶 + "515", // 菲律宾 + "520", // 泰国 + "525", // 新加坡 + "528", // 文莱 + "530", // 新西兰 + "534", // 北马里亚纳群岛 + "535", // 关岛 + "536", // 瑙鲁 + "537", // 巴布亚新几内亚 + "539", // 汤加 + "540", // 所罗门群岛 + "541", // 瓦努阿图 + "542", // 斐济 + "543", // 瓦利斯和富图纳 + "544", // 美属萨摩亚 + "545", // 基里巴斯 + "546", // 新喀里多尼亚 + "547", // 法属波利尼西亚 + "548", // 库克群岛 + "549", // 萨摩亚 + "550", // 密克罗尼西亚联邦 + "552", // 帕劳 + "553", // 图瓦卢 + "554", // 托克劳 + "555", // 纽埃 + "602", // 埃及 + "603", // 阿尔及利亚 + "604", // 摩洛哥 + "605", // 突尼斯 + "606", // 利比亚 + "607", // 冈比亚 + "608", // 塞内加尔 + "609", // 毛里塔尼亚 + "610", // 马里 + "611", // 几内亚 + "612", // 科特迪瓦 + "613", // 布基纳法索 + "614", // 尼日尔 + "615", // 多哥 + "616", // 贝宁 + "617", // 毛里求斯 + "618", // 利比里亚 + "619", // 塞拉利昂 + "620", // 加纳 + "621", // 尼日利亚 + "622", // 乍得 + "623", // 中非共和国 + "624", // 喀麦隆 + "625", // 佛得角 + "626", // 圣多美和普林西比 + "627", // 赤道几内亚 + "628", // 加蓬 + "629", // 刚果共和国 + "630", // 刚果民主共和国 + "631", // 安哥拉 + "632", // 几内亚比绍 + "633", // 塞舌尔 + "634", // 苏丹 + "635", // 卢旺达 + "636", // 埃塞俄比亚 + "637", // 索马里 + "638", // 吉布提 + "639", // 肯尼亚 + "640", // 坦桑尼亚 + "641", // 乌干达 + "642", // 布隆迪 + "643", // 莫桑比克 + "645", // 赞比亚 + "646", // 马达加斯加 + "647", // 留尼汪 + "648", // 津巴布韦 + "649", // 纳米比亚 + "650", // 马拉维 + "651", // 莱索托 + "652", // 博茨瓦纳 + "653", // 斯威士兰 + "654", // 科摩罗 + "655", // 南非 + "657", // 厄立特里亚 + "659", // 南苏丹 + "702", // 伯利兹 + "704", // 危地马拉 + "706", // 萨尔瓦多 + "708", // 洪都拉斯 + "710", // 尼加拉瓜 + "712", // 哥斯达黎加 + "714", // 巴拿马 + "716", // 秘鲁 + "722", // 阿根廷 + "724", // 巴西 + "730", // 智利 + "732", // 哥伦比亚 + "734", // 委内瑞拉 + "736", // 玻利维亚 + "738", // 圭亚那 + "740", // 厄瓜多尔 + "742", // 法属圭亚那 + "744", // 巴拉圭 + "746", // 苏里南 + "748", // 乌拉圭 + "750", // 福克兰群岛 + "901", // 国际海事卫星组织 + "902", // 国际海事卫星组织 + "903", // 国际海事卫星组织 + "904", // 国际海事卫星组织 + "905", // 国际海事卫星组织 + "906", // 国际海事卫星组织 + "907", // 国际海事卫星组织 + "908", // 国际海事卫星组织 + "909", // 国际海事卫星组织 + "910", // 国际海事卫星组织 + "911", // 国际海事卫星组织 + "912", // 国际海事卫星组织 + "913", // 国际海事卫星组织 + "914", // 国际海事卫星组织 + "915", // 国际海事卫星组织 + "916", // 国际海事卫星组织 + "917", // 国际海事卫星组织 + "918", // 国际海事卫星组织 + "919", // 国际海事卫星组织 + "920", // 国际海事卫星组织 + "921", // 国际海事卫星组织 + "922", // 国际海事卫星组织 + "923", // 国际海事卫星组织 + "924", // 国际海事卫星组织 + "925", // 国际海事卫星组织 + "926", // 国际海事卫星组织 + "927", // 国际海事卫星组织 + "928", // 国际海事卫星组织 + "929", // 国际海事卫星组织 + "930", // 国际海事卫星组织 + "931", // 国际海事卫星组织 + "932", // 国际海事卫星组织 + "933", // 国际海事卫星组织 + "934", // 国际海事卫星组织 + "935", // 国际海事卫星组织 + "936", // 国际海事卫星组织 + "937", // 国际海事卫星组织 + "938", // 国际海事卫星组织 + "939", // 国际海事卫星组织 + "940", // 国际海事卫星组织 + "941", // 国际海事卫星组织 + "942", // 国际海事卫星组织 + "943", // 国际海事卫星组织 + "944", // 国际海事卫星组织 + "945", // 国际海事卫星组织 + "946", // 国际海事卫星组织 + "947", // 国际海事卫星组织 + "948", // 国际海事卫星组织 + "949", // 国际海事卫星组织 + "950", // 国际海事卫星组织 + "951", // 国际海事卫星组织 + "952", // 国际海事卫星组织 + "953", // 国际海事卫星组织 + "954", // 国际海事卫星组织 + "955", // 国际海事卫星组织 + "956", // 国际海事卫星组织 + "957", // 国际海事卫星组织 + "958", // 国际海事卫星组织 + "959", // 国际海事卫星组织 + "960", // 国际海事卫星组织 + "961", // 国际海事卫星组织 + "962", // 国际海事卫星组织 + "963", // 国际海事卫星组织 + "964", // 国际海事卫星组织 + "965", // 国际海事卫星组织 + "966", // 国际海事卫星组织 + "967", // 国际海事卫星组织 + "968", // 国际海事卫星组织 + "969", // 国际海事卫星组织 + "970", // 国际海事卫星组织 + "971", // 国际海事卫星组织 + "972", // 国际海事卫星组织 + "973", // 国际海事卫星组织 + "974", // 国际海事卫星组织 + "975", // 国际海事卫星组织 + "976", // 国际海事卫星组织 + "977", // 国际海事卫星组织 + "978", // 国际海事卫星组织 + "979", // 国际海事卫星组织 + "980", // 国际海事卫星组织 + "981", // 国际海事卫星组织 + "982", // 国际海事卫星组织 + "983", // 国际海事卫星组织 + "984", // 国际海事卫星组织 + "985", // 国际海事卫星组织 + "986", // 国际海事卫星组织 + "987", // 国际海事卫星组织 + "988", // 国际海事卫星组织 + "989", // 国际海事卫星组织 + "990", // 国际海事卫星组织 + "991", // 国际海事卫星组织 + "992", // 国际海事卫星组织 + "993", // 国际海事卫星组织 + "994", // 国际海事卫星组织 + "995", // 国际海事卫星组织 + "996", // 国际海事卫星组织 + "997", // 国际海事卫星组织 + "998", // 国际海事卫星组织 + "999", // 国际海事卫星组织 + }; + + return validMCCs.Contains(mcc); + } + + /// + /// 获取MNC(移动网络代码) + /// + /// IMSI字符串 + /// MNC代码 + private string GetMNC(string imsi) + { + // 根据MCC确定MNC长度 + var mcc = imsi.Substring(0, 3); + + // 某些国家的MNC是3位,其他是2位 + var mncLength = GetMNCLength(mcc); + return imsi.Substring(3, mncLength); + } + + /// + /// 获取MNC长度 + /// + /// MCC代码 + /// MNC长度 + private int GetMNCLength(string mcc) + { + // 3位MNC的国家 + var threeDigitMNC = new[] + { + "302", // 加拿大 + "310", // 美国 + "311", // 美国 + "312", // 美国 + "313", // 美国 + "314", // 美国 + "315", // 美国 + "316", // 美国 + "460", // 中国 + "466", // 台湾 + }; + + return threeDigitMNC.Contains(mcc) ? 3 : 2; + } + + /// + /// 验证MNC(移动网络代码)是否有效 + /// + /// MNC代码 + /// 是否有效 + private bool IsValidMNC(string mnc) + { + // MNC必须是2-3位数字 + return mnc.Length >= 2 && mnc.Length <= 3 && mnc.All(char.IsDigit); + } +} \ No newline at end of file diff --git a/src/X1.Domain/Common/Attributes/IMSIValidationTests.cs b/src/X1.Domain/Common/Attributes/IMSIValidationTests.cs new file mode 100644 index 0000000..e3367ba --- /dev/null +++ b/src/X1.Domain/Common/Attributes/IMSIValidationTests.cs @@ -0,0 +1,150 @@ +using CellularManagement.Domain.Common.Attributes; +using CellularManagement.Domain.ValueObjects; +using CellularManagement.Domain.Models; + +namespace CellularManagement.Domain.Common.Attributes; + +/// +/// IMSI验证测试类 +/// +public static class IMSIValidationTests +{ + /// + /// 测试IMSI验证特性 + /// + public static void TestIMSIValidationAttribute() + { + var validationAttribute = new IMSIValidationAttribute(); + + // 测试有效的IMSI + var validIMSIs = new[] + { + "460001234567890", // 中国移动 + "460011234567890", // 中国联通 + "460021234567890", // 中国电信 + "466001234567890", // 台湾 + "310001234567890", // 美国 + "234001234567890", // 英国 + "262001234567890", // 德国 + }; + + foreach (var imsi in validIMSIs) + { + var isValid = validationAttribute.IsValid(imsi); + Console.WriteLine($"IMSI {imsi}: {(isValid ? "有效" : "无效")}"); + } + + // 测试无效的IMSI + var invalidIMSIs = new[] + { + "", // 空字符串 + "123", // 太短 + "1234567890123456", // 太长 + "12345678901234a", // 包含字母 + "12345678901234!", // 包含特殊字符 + "999001234567890", // 无效MCC + }; + + foreach (var imsi in invalidIMSIs) + { + var isValid = validationAttribute.IsValid(imsi); + Console.WriteLine($"IMSI {imsi}: {(isValid ? "有效" : "无效")}"); + } + } + + /// + /// 测试IMSI值对象 + /// + public static void TestIMSIValueObject() + { + // 测试有效的IMSI + try + { + var imsi = IMSI.Create("460001234567890"); + Console.WriteLine($"成功创建IMSI: {imsi}"); + Console.WriteLine($"MCC: {imsi.GetMCC()}"); + Console.WriteLine($"MNC: {imsi.GetMNC()}"); + Console.WriteLine($"MSIN: {imsi.GetMSIN()}"); + } + catch (Exception ex) + { + Console.WriteLine($"创建IMSI失败: {ex.Message}"); + } + + // 测试无效的IMSI + var invalidIMSIs = new[] + { + "", + "123", + "1234567890123456", + "12345678901234a", + "999001234567890", + }; + + foreach (var imsiString in invalidIMSIs) + { + try + { + var imsi = IMSI.Create(imsiString); + Console.WriteLine($"意外成功创建IMSI: {imsi}"); + } + catch (ArgumentException ex) + { + Console.WriteLine($"预期失败创建IMSI {imsiString}: {ex.Message}"); + } + } + } + + /// + /// 测试UserInfo模型 + /// + public static void TestUserInfoModel() + { + // 测试有效的UserInfo + try + { + var userInfo = new UserInfo + { + UserId = 1, + IMSI1 = "460001234567890", + IMSI2 = "460001234567891" + }; + Console.WriteLine($"成功创建UserInfo: UserId={userInfo.UserId}, IMSI1={userInfo.IMSI1}, IMSI2={userInfo.IMSI2}"); + } + catch (Exception ex) + { + Console.WriteLine($"创建UserInfo失败: {ex.Message}"); + } + + // 测试无效的UserInfo + try + { + var userInfo = new UserInfo + { + UserId = 1, + IMSI1 = "123", // 无效IMSI + IMSI2 = "460001234567891" + }; + Console.WriteLine($"意外成功创建UserInfo: {userInfo}"); + } + catch (ArgumentException ex) + { + Console.WriteLine($"预期失败创建UserInfo: {ex.Message}"); + } + } + + /// + /// 运行所有测试 + /// + public static void RunAllTests() + { + Console.WriteLine("=== IMSI验证特性测试 ==="); + TestIMSIValidationAttribute(); + + Console.WriteLine("\n=== IMSI值对象测试 ==="); + TestIMSIValueObject(); + + Console.WriteLine("\n=== UserInfo模型测试 ==="); + TestUserInfoModel(); + } +} \ No newline at end of file diff --git a/src/X1.Domain/Entities/Device/TestCase.cs b/src/X1.Domain/Entities/Device/TestCase.cs index 6b7adc0..807d176 100644 --- a/src/X1.Domain/Entities/Device/TestCase.cs +++ b/src/X1.Domain/Entities/Device/TestCase.cs @@ -1,5 +1,7 @@ using System.ComponentModel.DataAnnotations; using CellularManagement.Domain.Entities.Common; +using CellularManagement.Domain.Models; +using CellularManagement.Domain.ValueObjects; namespace CellularManagement.Domain.Entities.Device; @@ -25,12 +27,25 @@ public class TestCase : AuditableEntity public string Name { get; private set; } = null!; /// - /// 用户数 + /// 用户数(1-5,作为父级参数) /// - public int UserCount { get; private set; } = 0; + public int UserCount { get; private set; } = 1; /// /// 用户信息列表(JSON格式存储) + /// 格式示例: + /// [ + /// { + /// "UserId": 1, + /// "IMSI1": "460001234567890", + /// "IMSI2": "460001234567891" + /// }, + /// { + /// "UserId": 2, + /// "IMSI1": "460001234567892", + /// "IMSI2": "460001234567893" + /// } + /// ] /// [MaxLength(4000)] public string? UserInfoList { get; private set; } @@ -95,7 +110,7 @@ public class TestCase : AuditableEntity string name, string version, string createdBy, - int userCount = 0, + int userCount = 1, string? userInfoList = null, int latencyThresholdCount = 0, string? latencyThresholdList = null, @@ -139,7 +154,7 @@ public class TestCase : AuditableEntity string name, string version, string updatedBy, - int userCount = 0, + int userCount = 1, string? userInfoList = null, int latencyThresholdCount = 0, string? latencyThresholdList = null, diff --git a/src/X1.Domain/Models/UserInfo.cs b/src/X1.Domain/Models/UserInfo.cs new file mode 100644 index 0000000..48a439b --- /dev/null +++ b/src/X1.Domain/Models/UserInfo.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using CellularManagement.Domain.Common.Attributes; +using CellularManagement.Domain.ValueObjects; + +namespace CellularManagement.Domain.Models; + +/// +/// 用户信息模型 +/// +public class UserInfo +{ + /// + /// 用户ID(1-5) + /// + [Required(ErrorMessage = "用户ID不能为空")] + [Range(1, 5, ErrorMessage = "用户ID必须在1到5之间")] + public int UserId { get; set; } + + /// + /// IMSI1(国际移动用户识别码1) + /// + [Required(ErrorMessage = "IMSI1不能为空")] + public string IMSI1 { get; set; } = null!; + + /// + /// IMSI2(国际移动用户识别码2) + /// + [Required(ErrorMessage = "IMSI2不能为空")] + public string IMSI2 { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Domain/Services/IIMSIValidationService.cs b/src/X1.Domain/Services/IIMSIValidationService.cs new file mode 100644 index 0000000..7f3e115 --- /dev/null +++ b/src/X1.Domain/Services/IIMSIValidationService.cs @@ -0,0 +1,42 @@ +namespace CellularManagement.Domain.Services; + +/// +/// IMSI验证服务接口 +/// +public interface IIMSIValidationService +{ + /// + /// 验证IMSI格式 + /// + /// IMSI字符串 + /// 验证结果 + (bool IsValid, string ErrorMessage) ValidateIMSI(string imsi); + + /// + /// 验证IMSI列表 + /// + /// IMSI列表 + /// 验证结果 + (bool IsValid, List ErrorMessages) ValidateIMSIs(IEnumerable imsiList); + + /// + /// 检查IMSI是否重复 + /// + /// IMSI列表 + /// 是否有重复 + (bool HasDuplicates, List DuplicateIMSIs) CheckDuplicateIMSIs(IEnumerable imsiList); + + /// + /// 获取IMSI的国家信息 + /// + /// IMSI字符串 + /// 国家信息 + string GetCountryInfo(string imsi); + + /// + /// 获取IMSI的运营商信息 + /// + /// IMSI字符串 + /// 运营商信息 + string GetOperatorInfo(string imsi); +} \ No newline at end of file diff --git a/src/X1.Domain/ValueObjects/IMSI.cs b/src/X1.Domain/ValueObjects/IMSI.cs new file mode 100644 index 0000000..d216299 --- /dev/null +++ b/src/X1.Domain/ValueObjects/IMSI.cs @@ -0,0 +1,172 @@ +using System.ComponentModel.DataAnnotations; +using CellularManagement.Domain.Common.Attributes; +using System.Collections.Generic; +using System.Linq; + +namespace CellularManagement.Domain.ValueObjects; + +/// +/// IMSI(国际移动用户识别码)值对象 +/// +public class IMSI +{ + private IMSI() { } + + /// + /// IMSI值 + /// + [Required(ErrorMessage = "IMSI不能为空")] + [IMSIValidation(ErrorMessage = "IMSI必须是15位数字,且符合IMSI格式要求")] + public string Value { get; private set; } = null!; + + /// + /// 创建IMSI + /// + /// IMSI值 + /// IMSI实例 + public static IMSI Create(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("IMSI不能为空", nameof(value)); + } + + if (value.Length != 15) + { + throw new ArgumentException("IMSI必须是15位数字", nameof(value)); + } + + if (!value.All(char.IsDigit)) + { + throw new ArgumentException("IMSI只能包含数字", nameof(value)); + } + + // 使用验证特性进行验证 + var validationAttribute = new IMSIValidationAttribute(); + if (!validationAttribute.IsValid(value)) + { + throw new ArgumentException(validationAttribute.FormatErrorMessage("IMSI"), nameof(value)); + } + + return new IMSI { Value = value }; + } + + /// + /// 获取MCC(移动国家代码) + /// + /// MCC代码 + public string GetMCC() + { + return Value.Substring(0, 3); + } + + /// + /// 获取MNC(移动网络代码) + /// + /// MNC代码 + public string GetMNC() + { + var mcc = GetMCC(); + var mncLength = GetMNCLength(mcc); + return Value.Substring(3, mncLength); + } + + /// + /// 获取MSIN(移动用户识别号码) + /// + /// MSIN代码 + public string GetMSIN() + { + var mcc = GetMCC(); + var mncLength = GetMNCLength(mcc); + return Value.Substring(3 + mncLength); + } + + /// + /// 获取MNC长度 + /// + /// MCC代码 + /// MNC长度 + private int GetMNCLength(string mcc) + { + // 3位MNC的国家 + var threeDigitMNC = new[] + { + "302", // 加拿大 + "310", // 美国 + "311", // 美国 + "312", // 美国 + "313", // 美国 + "314", // 美国 + "315", // 美国 + "316", // 美国 + "460", // 中国 + "466", // 台湾 + }; + + return threeDigitMNC.Contains(mcc) ? 3 : 2; + } + + /// + /// 转换为字符串 + /// + /// IMSI字符串 + public override string ToString() + { + return Value; + } + + /// + /// 相等性比较 + /// + /// 比较对象 + /// 是否相等 + public override bool Equals(object? obj) + { + if (obj is IMSI other) + { + return Value == other.Value; + } + return false; + } + + /// + /// 获取哈希码 + /// + /// 哈希码 + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + /// + /// 相等运算符 + /// + /// 左操作数 + /// 右操作数 + /// 是否相等 + public static bool operator ==(IMSI? left, IMSI? right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + /// 不等运算符 + /// + /// 左操作数 + /// 右操作数 + /// 是否不等 + public static bool operator !=(IMSI? left, IMSI? right) + { + return !(left == right); + } + + /// + /// 隐式转换为字符串 + /// + /// IMSI实例 + public static implicit operator string(IMSI imsi) + { + return imsi.Value; + } +} \ No newline at end of file diff --git a/src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs index 221803b..966deb7 100644 --- a/src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs @@ -35,7 +35,7 @@ public class TestCaseConfiguration : IEntityTypeConfiguration builder.Property(x => x.UserCount) .IsRequired() - .HasDefaultValue(0); + .HasDefaultValue(1); builder.Property(x => x.UserInfoList) .HasMaxLength(4000); diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs index 369dd5e..ffa7bac 100644 --- a/src/X1.Infrastructure/DependencyInjection.cs +++ b/src/X1.Infrastructure/DependencyInjection.cs @@ -153,6 +153,7 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/X1.Infrastructure/Services/Security/IMSIValidationService.cs b/src/X1.Infrastructure/Services/Security/IMSIValidationService.cs new file mode 100644 index 0000000..0713002 --- /dev/null +++ b/src/X1.Infrastructure/Services/Security/IMSIValidationService.cs @@ -0,0 +1,364 @@ +using CellularManagement.Domain.Services; +using CellularManagement.Domain.Common.Attributes; + +namespace CellularManagement.Infrastructure.Services.Security; + +/// +/// IMSI验证服务实现 +/// +public class IMSIValidationService : IIMSIValidationService +{ + private readonly IMSIValidationAttribute _validationAttribute; + + public IMSIValidationService() + { + _validationAttribute = new IMSIValidationAttribute(); + } + + /// + /// 验证IMSI格式 + /// + /// IMSI字符串 + /// 验证结果 + public (bool IsValid, string ErrorMessage) ValidateIMSI(string imsi) + { + if (string.IsNullOrWhiteSpace(imsi)) + { + return (false, "IMSI不能为空"); + } + + if (imsi.Length != 15) + { + return (false, "IMSI必须是15位数字"); + } + + if (!imsi.All(char.IsDigit)) + { + return (false, "IMSI只能包含数字"); + } + + if (!_validationAttribute.IsValid(imsi)) + { + return (false, _validationAttribute.FormatErrorMessage("IMSI")); + } + + return (true, string.Empty); + } + + /// + /// 验证IMSI列表 + /// + /// IMSI列表 + /// 验证结果 + public (bool IsValid, List ErrorMessages) ValidateIMSIs(IEnumerable imsiList) + { + var errorMessages = new List(); + var isValid = true; + + foreach (var imsi in imsiList) + { + var (isValidIMSI, errorMessage) = ValidateIMSI(imsi); + if (!isValidIMSI) + { + isValid = false; + errorMessages.Add($"IMSI {imsi}: {errorMessage}"); + } + } + + return (isValid, errorMessages); + } + + /// + /// 检查IMSI是否重复 + /// + /// IMSI列表 + /// 是否有重复 + public (bool HasDuplicates, List DuplicateIMSIs) CheckDuplicateIMSIs(IEnumerable imsiList) + { + var duplicateIMSIs = imsiList + .GroupBy(x => x) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + return (duplicateIMSIs.Any(), duplicateIMSIs); + } + + /// + /// 获取IMSI的国家信息 + /// + /// IMSI字符串 + /// 国家信息 + public string GetCountryInfo(string imsi) + { + if (string.IsNullOrWhiteSpace(imsi) || imsi.Length < 3) + { + return "未知"; + } + + var mcc = imsi.Substring(0, 3); + return mcc switch + { + "460" => "中国", + "466" => "台湾", + "455" => "澳门", + "454" => "香港", + "310" or "311" or "312" or "313" or "314" or "315" or "316" => "美国", + "234" or "235" => "英国", + "238" => "丹麦", + "240" => "瑞典", + "242" => "挪威", + "244" => "芬兰", + "246" => "立陶宛", + "247" => "拉脱维亚", + "248" => "爱沙尼亚", + "250" => "俄罗斯", + "255" => "乌克兰", + "257" => "白俄罗斯", + "259" => "摩尔多瓦", + "260" => "波兰", + "262" => "德国", + "268" => "葡萄牙", + "270" => "卢森堡", + "272" => "爱尔兰", + "274" => "冰岛", + "276" => "阿尔巴尼亚", + "278" => "马耳他", + "280" => "塞浦路斯", + "282" => "格鲁吉亚", + "283" => "亚美尼亚", + "284" => "保加利亚", + "286" => "土耳其", + "288" => "法罗群岛", + "290" => "格陵兰", + "292" => "安道尔", + "293" => "斯洛文尼亚", + "294" => "北马其顿", + "295" => "列支敦士登", + "297" => "黑山", + "302" => "加拿大", + "334" => "墨西哥", + "338" => "牙买加", + "340" => "瓜德罗普", + "342" => "巴巴多斯", + "344" => "安提瓜和巴布达", + "346" => "开曼群岛", + "348" => "英属维尔京群岛", + "350" => "百慕大", + "352" => "格林纳达", + "354" => "蒙特塞拉特", + "356" => "圣基茨和尼维斯", + "358" => "圣卢西亚", + "360" => "圣文森特和格林纳丁斯", + "362" => "荷属安的列斯", + "363" => "阿鲁巴", + "364" => "巴哈马", + "365" => "安圭拉", + "366" => "多米尼克", + "368" => "古巴", + "370" => "多米尼加共和国", + "372" => "海地", + "374" => "特立尼达和多巴哥", + "376" => "特克斯和凯科斯群岛", + "400" => "阿塞拜疆", + "401" => "哈萨克斯坦", + "402" => "不丹", + "404" or "405" or "406" => "印度", + "410" => "巴基斯坦", + "412" => "阿富汗", + "413" => "斯里兰卡", + "414" => "缅甸", + "415" => "黎巴嫩", + "416" => "约旦", + "417" => "叙利亚", + "418" => "伊拉克", + "419" => "科威特", + "420" => "沙特阿拉伯", + "421" => "也门", + "422" => "阿曼", + "424" => "阿联酋", + "425" => "以色列", + "426" => "巴林", + "427" => "卡塔尔", + "428" => "蒙古", + "429" => "尼泊尔", + "430" or "431" => "阿联酋", + "432" => "伊朗", + "434" => "乌兹别克斯坦", + "436" => "塔吉克斯坦", + "437" => "吉尔吉斯斯坦", + "438" => "土库曼斯坦", + "440" or "441" => "日本", + "450" => "韩国", + "452" => "越南", + "456" => "柬埔寨", + "457" => "老挝", + "467" => "朝鲜", + "470" => "孟加拉国", + "472" => "马尔代夫", + "502" => "马来西亚", + "505" => "澳大利亚", + "510" => "印度尼西亚", + "514" => "东帝汶", + "515" => "菲律宾", + "520" => "泰国", + "525" => "新加坡", + "528" => "文莱", + "530" => "新西兰", + "534" => "北马里亚纳群岛", + "535" => "关岛", + "536" => "瑙鲁", + "537" => "巴布亚新几内亚", + "539" => "汤加", + "540" => "所罗门群岛", + "541" => "瓦努阿图", + "542" => "斐济", + "543" => "瓦利斯和富图纳", + "544" => "美属萨摩亚", + "545" => "基里巴斯", + "546" => "新喀里多尼亚", + "547" => "法属波利尼西亚", + "548" => "库克群岛", + "549" => "萨摩亚", + "550" => "密克罗尼西亚联邦", + "552" => "帕劳", + "553" => "图瓦卢", + "554" => "托克劳", + "555" => "纽埃", + "602" => "埃及", + "603" => "阿尔及利亚", + "604" => "摩洛哥", + "605" => "突尼斯", + "606" => "利比亚", + "607" => "冈比亚", + "608" => "塞内加尔", + "609" => "毛里塔尼亚", + "610" => "马里", + "611" => "几内亚", + "612" => "科特迪瓦", + "613" => "布基纳法索", + "614" => "尼日尔", + "615" => "多哥", + "616" => "贝宁", + "617" => "毛里求斯", + "618" => "利比里亚", + "619" => "塞拉利昂", + "620" => "加纳", + "621" => "尼日利亚", + "622" => "乍得", + "623" => "中非共和国", + "624" => "喀麦隆", + "625" => "佛得角", + "626" => "圣多美和普林西比", + "627" => "赤道几内亚", + "628" => "加蓬", + "629" => "刚果共和国", + "630" => "刚果民主共和国", + "631" => "安哥拉", + "632" => "几内亚比绍", + "633" => "塞舌尔", + "634" => "苏丹", + "635" => "卢旺达", + "636" => "埃塞俄比亚", + "637" => "索马里", + "638" => "吉布提", + "639" => "肯尼亚", + "640" => "坦桑尼亚", + "641" => "乌干达", + "642" => "布隆迪", + "643" => "莫桑比克", + "645" => "赞比亚", + "646" => "马达加斯加", + "647" => "留尼汪", + "648" => "津巴布韦", + "649" => "纳米比亚", + "650" => "马拉维", + "651" => "莱索托", + "652" => "博茨瓦纳", + "653" => "斯威士兰", + "654" => "科摩罗", + "655" => "南非", + "657" => "厄立特里亚", + "659" => "南苏丹", + "702" => "伯利兹", + "704" => "危地马拉", + "706" => "萨尔瓦多", + "708" => "洪都拉斯", + "710" => "尼加拉瓜", + "712" => "哥斯达黎加", + "714" => "巴拿马", + "716" => "秘鲁", + "722" => "阿根廷", + "724" => "巴西", + "730" => "智利", + "732" => "哥伦比亚", + "734" => "委内瑞拉", + "736" => "玻利维亚", + "738" => "圭亚那", + "740" => "厄瓜多尔", + "742" => "法属圭亚那", + "744" => "巴拉圭", + "746" => "苏里南", + "748" => "乌拉圭", + "750" => "福克兰群岛", + _ => "未知" + }; + } + + /// + /// 获取IMSI的运营商信息 + /// + /// IMSI字符串 + /// 运营商信息 + public string GetOperatorInfo(string imsi) + { + if (string.IsNullOrWhiteSpace(imsi) || imsi.Length < 5) + { + return "未知"; + } + + var mcc = imsi.Substring(0, 3); + var mnc = GetMNC(imsi); + + // 这里可以根据MCC和MNC的组合来识别具体的运营商 + // 由于运营商信息较多,这里只提供一些常见的示例 + return $"{GetCountryInfo(imsi)} - MCC:{mcc}, MNC:{mnc}"; + } + + /// + /// 获取MNC(移动网络代码) + /// + /// IMSI字符串 + /// MNC代码 + private string GetMNC(string imsi) + { + var mcc = imsi.Substring(0, 3); + var mncLength = GetMNCLength(mcc); + return imsi.Substring(3, mncLength); + } + + /// + /// 获取MNC长度 + /// + /// MCC代码 + /// MNC长度 + private int GetMNCLength(string mcc) + { + // 3位MNC的国家 + var threeDigitMNC = new[] + { + "302", // 加拿大 + "310", // 美国 + "311", // 美国 + "312", // 美国 + "313", // 美国 + "314", // 美国 + "315", // 美国 + "316", // 美国 + "460", // 中国 + "466", // 台湾 + }; + + return threeDigitMNC.Contains(mcc) ? 3 : 2; + } +} \ No newline at end of file