Browse Source

feat: 完善TestCase用例IMSI保护与验证逻辑,修复相关编译错误

feature/x1-owen-debug
test 4 weeks ago
parent
commit
42c637153a
  1. 19
      src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs
  2. 55
      src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommandHandler.cs
  3. 19
      src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs
  4. 55
      src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommandHandler.cs
  5. 449
      src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs
  6. 150
      src/X1.Domain/Common/Attributes/IMSIValidationTests.cs
  7. 23
      src/X1.Domain/Entities/Device/TestCase.cs
  8. 30
      src/X1.Domain/Models/UserInfo.cs
  9. 42
      src/X1.Domain/Services/IIMSIValidationService.cs
  10. 172
      src/X1.Domain/ValueObjects/IMSI.cs
  11. 2
      src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs
  12. 1
      src/X1.Infrastructure/DependencyInjection.cs
  13. 364
      src/X1.Infrastructure/Services/Security/IMSIValidationService.cs

19
src/X1.Application/Features/TestCases/Commands/CreateTestCase/CreateTestCaseCommand.cs

@ -24,13 +24,26 @@ public class CreateTestCaseCommand : IRequest<OperationResult<CreateTestCaseResp
public string Name { get; set; } = null!;
/// <summary>
/// 用户数
/// 用户数(1-5,作为父级参数)
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "用户数必须大于等于0")]
public int UserCount { get; set; } = 0;
[Range(1, 5, ErrorMessage = "用户数必须在1到5之间")]
public int UserCount { get; set; } = 1;
/// <summary>
/// 用户信息列表(JSON格式)
/// 格式示例:
/// [
/// {
/// "UserId": 1,
/// "IMSI1": "460001234567890",
/// "IMSI2": "460001234567891"
/// },
/// {
/// "UserId": 2,
/// "IMSI1": "460001234567892",
/// "IMSI2": "460001234567893"
/// }
/// ]
/// </summary>
[MaxLength(4000, ErrorMessage = "用户信息列表长度不能超过4000个字符")]
public string? UserInfoList { get; set; }

55
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<CreateTestCaseComman
return OperationResult<CreateTestCaseResponse>.CreateFailure($"用例版本 {request.Version} 已存在");
}
// 验证用户信息列表
if (!string.IsNullOrEmpty(request.UserInfoList))
{
try
{
var userInfos = JsonSerializer.Deserialize<List<UserInfo>>(request.UserInfoList);
if (userInfos == null || userInfos.Count != request.UserCount)
{
_logger.LogWarning("用户信息列表数量与用户数不匹配,用户数: {UserCount}, 用户信息数量: {UserInfoCount}",
request.UserCount, userInfos?.Count ?? 0);
return OperationResult<CreateTestCaseResponse>.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<CreateTestCaseResponse>.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<CreateTestCaseResponse>.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<CreateTestCaseResponse>.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI2格式错误: {ex.Message}");
}
}
}
catch (JsonException ex)
{
_logger.LogWarning("用户信息列表JSON格式错误: {Error}", ex.Message);
return OperationResult<CreateTestCaseResponse>.CreateFailure($"用户信息列表JSON格式错误: {ex.Message}");
}
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))

19
src/X1.Application/Features/TestCases/Commands/UpdateTestCase/UpdateTestCaseCommand.cs

@ -31,13 +31,26 @@ public class UpdateTestCaseCommand : IRequest<OperationResult<UpdateTestCaseResp
public string Name { get; set; } = null!;
/// <summary>
/// 用户数
/// 用户数(1-5,作为父级参数)
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "用户数必须大于等于0")]
public int UserCount { get; set; } = 0;
[Range(1, 5, ErrorMessage = "用户数必须在1到5之间")]
public int UserCount { get; set; } = 1;
/// <summary>
/// 用户信息列表(JSON格式)
/// 格式示例:
/// [
/// {
/// "UserId": 1,
/// "IMSI1": "460001234567890",
/// "IMSI2": "460001234567891"
/// },
/// {
/// "UserId": 2,
/// "IMSI1": "460001234567892",
/// "IMSI2": "460001234567893"
/// }
/// ]
/// </summary>
[MaxLength(4000, ErrorMessage = "用户信息列表长度不能超过4000个字符")]
public string? UserInfoList { get; set; }

55
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<UpdateTestCaseComman
return OperationResult<UpdateTestCaseResponse>.CreateFailure($"用例版本 {request.Version} 已被其他用例使用");
}
// 验证用户信息列表
if (!string.IsNullOrEmpty(request.UserInfoList))
{
try
{
var userInfos = JsonSerializer.Deserialize<List<UserInfo>>(request.UserInfoList);
if (userInfos == null || userInfos.Count != request.UserCount)
{
_logger.LogWarning("用户信息列表数量与用户数不匹配,用户数: {UserCount}, 用户信息数量: {UserInfoCount}",
request.UserCount, userInfos?.Count ?? 0);
return OperationResult<UpdateTestCaseResponse>.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<UpdateTestCaseResponse>.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<UpdateTestCaseResponse>.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<UpdateTestCaseResponse>.CreateFailure($"用户 {userInfos[i].UserId} 的IMSI2格式错误: {ex.Message}");
}
}
}
catch (JsonException ex)
{
_logger.LogWarning("用户信息列表JSON格式错误: {Error}", ex.Message);
return OperationResult<UpdateTestCaseResponse>.CreateFailure($"用户信息列表JSON格式错误: {ex.Message}");
}
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))

449
src/X1.Domain/Common/Attributes/IMSIValidationAttribute.cs

@ -0,0 +1,449 @@
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Domain.Common.Attributes;
/// <summary>
/// IMSI验证特性
/// 确保IMSI是15位数字,并且符合IMSI格式要求
/// </summary>
public class IMSIValidationAttribute : ValidationAttribute
{
/// <summary>
/// 验证IMSI格式
/// </summary>
/// <param name="value">要验证的值</param>
/// <returns>验证结果</returns>
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;
}
/// <summary>
/// 获取验证错误消息
/// </summary>
/// <param name="name">属性名称</param>
/// <returns>错误消息</returns>
public override string FormatErrorMessage(string name)
{
return $"{name}必须是15位数字,且符合IMSI格式要求(MCC+MNC+MSIN)";
}
/// <summary>
/// 验证MCC(移动国家代码)是否有效
/// </summary>
/// <param name="mcc">MCC代码</param>
/// <returns>是否有效</returns>
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);
}
/// <summary>
/// 获取MNC(移动网络代码)
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>MNC代码</returns>
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);
}
/// <summary>
/// 获取MNC长度
/// </summary>
/// <param name="mcc">MCC代码</param>
/// <returns>MNC长度</returns>
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;
}
/// <summary>
/// 验证MNC(移动网络代码)是否有效
/// </summary>
/// <param name="mnc">MNC代码</param>
/// <returns>是否有效</returns>
private bool IsValidMNC(string mnc)
{
// MNC必须是2-3位数字
return mnc.Length >= 2 && mnc.Length <= 3 && mnc.All(char.IsDigit);
}
}

150
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;
/// <summary>
/// IMSI验证测试类
/// </summary>
public static class IMSIValidationTests
{
/// <summary>
/// 测试IMSI验证特性
/// </summary>
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 ? "" : "")}");
}
}
/// <summary>
/// 测试IMSI值对象
/// </summary>
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}");
}
}
}
/// <summary>
/// 测试UserInfo模型
/// </summary>
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}");
}
}
/// <summary>
/// 运行所有测试
/// </summary>
public static void RunAllTests()
{
Console.WriteLine("=== IMSI验证特性测试 ===");
TestIMSIValidationAttribute();
Console.WriteLine("\n=== IMSI值对象测试 ===");
TestIMSIValueObject();
Console.WriteLine("\n=== UserInfo模型测试 ===");
TestUserInfoModel();
}
}

23
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!;
/// <summary>
/// 用户数
/// 用户数(1-5,作为父级参数)
/// </summary>
public int UserCount { get; private set; } = 0;
public int UserCount { get; private set; } = 1;
/// <summary>
/// 用户信息列表(JSON格式存储)
/// 格式示例:
/// [
/// {
/// "UserId": 1,
/// "IMSI1": "460001234567890",
/// "IMSI2": "460001234567891"
/// },
/// {
/// "UserId": 2,
/// "IMSI1": "460001234567892",
/// "IMSI2": "460001234567893"
/// }
/// ]
/// </summary>
[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,

30
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;
/// <summary>
/// 用户信息模型
/// </summary>
public class UserInfo
{
/// <summary>
/// 用户ID(1-5)
/// </summary>
[Required(ErrorMessage = "用户ID不能为空")]
[Range(1, 5, ErrorMessage = "用户ID必须在1到5之间")]
public int UserId { get; set; }
/// <summary>
/// IMSI1(国际移动用户识别码1)
/// </summary>
[Required(ErrorMessage = "IMSI1不能为空")]
public string IMSI1 { get; set; } = null!;
/// <summary>
/// IMSI2(国际移动用户识别码2)
/// </summary>
[Required(ErrorMessage = "IMSI2不能为空")]
public string IMSI2 { get; set; } = null!;
}

42
src/X1.Domain/Services/IIMSIValidationService.cs

@ -0,0 +1,42 @@
namespace CellularManagement.Domain.Services;
/// <summary>
/// IMSI验证服务接口
/// </summary>
public interface IIMSIValidationService
{
/// <summary>
/// 验证IMSI格式
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>验证结果</returns>
(bool IsValid, string ErrorMessage) ValidateIMSI(string imsi);
/// <summary>
/// 验证IMSI列表
/// </summary>
/// <param name="imsiList">IMSI列表</param>
/// <returns>验证结果</returns>
(bool IsValid, List<string> ErrorMessages) ValidateIMSIs(IEnumerable<string> imsiList);
/// <summary>
/// 检查IMSI是否重复
/// </summary>
/// <param name="imsiList">IMSI列表</param>
/// <returns>是否有重复</returns>
(bool HasDuplicates, List<string> DuplicateIMSIs) CheckDuplicateIMSIs(IEnumerable<string> imsiList);
/// <summary>
/// 获取IMSI的国家信息
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>国家信息</returns>
string GetCountryInfo(string imsi);
/// <summary>
/// 获取IMSI的运营商信息
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>运营商信息</returns>
string GetOperatorInfo(string imsi);
}

172
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;
/// <summary>
/// IMSI(国际移动用户识别码)值对象
/// </summary>
public class IMSI
{
private IMSI() { }
/// <summary>
/// IMSI值
/// </summary>
[Required(ErrorMessage = "IMSI不能为空")]
[IMSIValidation(ErrorMessage = "IMSI必须是15位数字,且符合IMSI格式要求")]
public string Value { get; private set; } = null!;
/// <summary>
/// 创建IMSI
/// </summary>
/// <param name="value">IMSI值</param>
/// <returns>IMSI实例</returns>
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 };
}
/// <summary>
/// 获取MCC(移动国家代码)
/// </summary>
/// <returns>MCC代码</returns>
public string GetMCC()
{
return Value.Substring(0, 3);
}
/// <summary>
/// 获取MNC(移动网络代码)
/// </summary>
/// <returns>MNC代码</returns>
public string GetMNC()
{
var mcc = GetMCC();
var mncLength = GetMNCLength(mcc);
return Value.Substring(3, mncLength);
}
/// <summary>
/// 获取MSIN(移动用户识别号码)
/// </summary>
/// <returns>MSIN代码</returns>
public string GetMSIN()
{
var mcc = GetMCC();
var mncLength = GetMNCLength(mcc);
return Value.Substring(3 + mncLength);
}
/// <summary>
/// 获取MNC长度
/// </summary>
/// <param name="mcc">MCC代码</param>
/// <returns>MNC长度</returns>
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;
}
/// <summary>
/// 转换为字符串
/// </summary>
/// <returns>IMSI字符串</returns>
public override string ToString()
{
return Value;
}
/// <summary>
/// 相等性比较
/// </summary>
/// <param name="obj">比较对象</param>
/// <returns>是否相等</returns>
public override bool Equals(object? obj)
{
if (obj is IMSI other)
{
return Value == other.Value;
}
return false;
}
/// <summary>
/// 获取哈希码
/// </summary>
/// <returns>哈希码</returns>
public override int GetHashCode()
{
return Value.GetHashCode();
}
/// <summary>
/// 相等运算符
/// </summary>
/// <param name="left">左操作数</param>
/// <param name="right">右操作数</param>
/// <returns>是否相等</returns>
public static bool operator ==(IMSI? left, IMSI? right)
{
return EqualityComparer<IMSI>.Default.Equals(left, right);
}
/// <summary>
/// 不等运算符
/// </summary>
/// <param name="left">左操作数</param>
/// <param name="right">右操作数</param>
/// <returns>是否不等</returns>
public static bool operator !=(IMSI? left, IMSI? right)
{
return !(left == right);
}
/// <summary>
/// 隐式转换为字符串
/// </summary>
/// <param name="imsi">IMSI实例</param>
public static implicit operator string(IMSI imsi)
{
return imsi.Value;
}
}

2
src/X1.Infrastructure/Configurations/Device/TestCaseConfiguration.cs

@ -35,7 +35,7 @@ public class TestCaseConfiguration : IEntityTypeConfiguration<TestCase>
builder.Property(x => x.UserCount)
.IsRequired()
.HasDefaultValue(0);
.HasDefaultValue(1);
builder.Property(x => x.UserInfoList)
.HasMaxLength(4000);

1
src/X1.Infrastructure/DependencyInjection.cs

@ -153,6 +153,7 @@ public static class DependencyInjection
services.AddScoped<ICacheService, CacheService>();
services.AddScoped<ICaptchaService, CaptchaService>();
services.AddScoped<ICaptchaVerificationService, CaptchaVerificationService>();
services.AddScoped<IIMSIValidationService, IMSIValidationService>();
return services;
}

364
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;
/// <summary>
/// IMSI验证服务实现
/// </summary>
public class IMSIValidationService : IIMSIValidationService
{
private readonly IMSIValidationAttribute _validationAttribute;
public IMSIValidationService()
{
_validationAttribute = new IMSIValidationAttribute();
}
/// <summary>
/// 验证IMSI格式
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>验证结果</returns>
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);
}
/// <summary>
/// 验证IMSI列表
/// </summary>
/// <param name="imsiList">IMSI列表</param>
/// <returns>验证结果</returns>
public (bool IsValid, List<string> ErrorMessages) ValidateIMSIs(IEnumerable<string> imsiList)
{
var errorMessages = new List<string>();
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);
}
/// <summary>
/// 检查IMSI是否重复
/// </summary>
/// <param name="imsiList">IMSI列表</param>
/// <returns>是否有重复</returns>
public (bool HasDuplicates, List<string> DuplicateIMSIs) CheckDuplicateIMSIs(IEnumerable<string> imsiList)
{
var duplicateIMSIs = imsiList
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
return (duplicateIMSIs.Any(), duplicateIMSIs);
}
/// <summary>
/// 获取IMSI的国家信息
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>国家信息</returns>
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" => "福克兰群岛",
_ => "未知"
};
}
/// <summary>
/// 获取IMSI的运营商信息
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>运营商信息</returns>
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}";
}
/// <summary>
/// 获取MNC(移动网络代码)
/// </summary>
/// <param name="imsi">IMSI字符串</param>
/// <returns>MNC代码</returns>
private string GetMNC(string imsi)
{
var mcc = imsi.Substring(0, 3);
var mncLength = GetMNCLength(mcc);
return imsi.Substring(3, mncLength);
}
/// <summary>
/// 获取MNC长度
/// </summary>
/// <param name="mcc">MCC代码</param>
/// <returns>MNC长度</returns>
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;
}
}
Loading…
Cancel
Save