diff --git a/DMS.Application/DTOs/CreateDeviceDto.cs b/DMS.Application/DTOs/CreateDeviceDto.cs index 5aa666c..ad59561 100644 --- a/DMS.Application/DTOs/CreateDeviceDto.cs +++ b/DMS.Application/DTOs/CreateDeviceDto.cs @@ -8,7 +8,15 @@ namespace DMS.Application.DTOs; public class CreateDeviceDto { public string Name { get; set; } + public string Description { get; set; } public ProtocolType Protocol { get; set; } public string IpAddress { get; set; } public int Port { get; set; } + public int Rack { get; set; } + public int Slot { get; set; } + public string CpuType { get; set; } + + public DeviceType DeviceType { get; set; } + public string OpcUaServerUrl { get; set; } + public bool IsActive { get; set; } } \ No newline at end of file diff --git a/DMS.Application/DTOs/CreateDeviceWithDetailsDto.cs b/DMS.Application/DTOs/CreateDeviceWithDetailsDto.cs index cb90442..434ed9c 100644 --- a/DMS.Application/DTOs/CreateDeviceWithDetailsDto.cs +++ b/DMS.Application/DTOs/CreateDeviceWithDetailsDto.cs @@ -7,5 +7,7 @@ public class CreateDeviceWithDetailsDto { public CreateDeviceDto Device { get; set; } public VariableTableDto VariableTable { get; set; } - // public MenuBeanDto Menu { get; set; } // 如果需要包含菜单信息 + + public MenuBeanDto DeviceMenu { get; set; } // 如果需要包含菜单信息 + public MenuBeanDto VariableTableMenu { get; set; } // 如果需要包含菜单信息 } \ No newline at end of file diff --git a/DMS.Application/DTOs/DeviceDto.cs b/DMS.Application/DTOs/DeviceDto.cs index 8caf6d4..2b1aa0a 100644 --- a/DMS.Application/DTOs/DeviceDto.cs +++ b/DMS.Application/DTOs/DeviceDto.cs @@ -7,7 +7,7 @@ public class DeviceDto { public int Id { get; set; } public string Name { get; set; } - public string Protocol { get; set; } + public ProtocolType Protocol { get; set; } public string IpAddress { get; set; } public int Port { get; set; } public int Rack { get; set; } diff --git a/DMS.Application/DTOs/MenuBeanDto.cs b/DMS.Application/DTOs/MenuBeanDto.cs index b00164f..6198319 100644 --- a/DMS.Application/DTOs/MenuBeanDto.cs +++ b/DMS.Application/DTOs/MenuBeanDto.cs @@ -1,3 +1,5 @@ +using DMS.Core.Enums; + namespace DMS.Application.DTOs; /// @@ -9,7 +11,15 @@ public class MenuBeanDto public int? ParentId { get; set; } public string Header { get; set; } public string Icon { get; set; } - public string TargetViewKey { get; set; } + /// + /// 菜单的类型,例如菜单关联的是设备,还是变量表,或者是MQTT + /// + public MenuType MenuType { get; set; } + + /// + /// 菜单关联的数据ID,例如设备Id,变量表Id + /// + public int TargetId { get; set; } public string NavigationParameter { get; set; } public int DisplayOrder { get; set; } } \ No newline at end of file diff --git a/DMS.Application/Profiles/MappingProfile.cs b/DMS.Application/Profiles/MappingProfile.cs index ed75a41..33ee62f 100644 --- a/DMS.Application/Profiles/MappingProfile.cs +++ b/DMS.Application/Profiles/MappingProfile.cs @@ -1,7 +1,6 @@ using AutoMapper; using DMS.Core.Models; using DMS.Application.DTOs; -using DMS.Core.Enums; namespace DMS.Application.Profiles; @@ -13,10 +12,19 @@ public class MappingProfile : Profile public MappingProfile() { // Device 映射 - CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.VariableTables, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Description, opt => opt.Ignore()) + .ForMember(dest => dest.VariableTables, opt => opt.Ignore()) + .ForMember(dest => dest.CpuType, opt => opt.Ignore()) + .ForMember(dest => dest.DeviceType, opt => opt.Ignore()); + CreateMap() - .ForMember(dest => dest.Protocol, opt => opt.MapFrom(src => src.Protocol.ToString())); + .ForMember(dest => dest.Protocol, opt => opt.MapFrom(src => src.Protocol.ToString())) + .ForMember(dest => dest.Status, opt => opt.Ignore()); // VariableTable 映射 CreateMap().ReverseMap(); @@ -24,16 +32,16 @@ public class MappingProfile : Profile // Variable 映射 CreateMap() .ForMember(dest => dest.DataType, opt => opt.MapFrom(src => src.DataType.ToString())) - .ForMember(dest => dest.CSharpDataType, opt => opt.MapFrom(src => src.CSharpDataType)); + .ForMember(dest => dest.CSharpDataType, opt => opt.MapFrom(src => src.CSharpDataType)) + .ForMember(dest => dest.Address, opt => opt.Ignore()); // MqttServer 映射 CreateMap().ReverseMap(); // VariableMqttAlias 映射 - CreateMap().ReverseMap(); - - // VariableTable 映射 - CreateMap().ReverseMap(); + CreateMap() + .ForMember(dest => dest.MqttServerName, opt => opt.Ignore()) + .ReverseMap(); // VariableHistory 映射 CreateMap().ReverseMap(); @@ -44,4 +52,4 @@ public class MappingProfile : Profile // User 映射 CreateMap().ReverseMap(); } -} \ No newline at end of file +} diff --git a/DMS.Application/Services/DeviceService.cs b/DMS.Application/Services/DeviceService.cs index 0fa01db..31cdd8b 100644 --- a/DMS.Application/Services/DeviceService.cs +++ b/DMS.Application/Services/DeviceService.cs @@ -49,37 +49,69 @@ public class DeviceService : IDeviceAppService { try { - _repoManager.BeginTranAsync(); + await _repoManager.BeginTranAsync(); var device = _mapper.Map(dto.Device); device.IsActive = true; // 默认激活 - await _repoManager.Devices.AddAsync(device); + var addDevice = await _repoManager.Devices.AddAsync(device); + if (addDevice == null || addDevice.Id == 0) + { + throw new InvalidOperationException($"添加设备失败:{addDevice}"); + } + + MenuBean addDeviceMenu = null; + + // 假设有设备菜单 + if (dto.DeviceMenu != null) + { + var deviceMenu = _mapper.Map(dto.DeviceMenu); + deviceMenu.ParentId = 2; + deviceMenu.MenuType = MenuType.DeviceMenu; + deviceMenu.TargetId = addDevice.Id; + addDeviceMenu=await _repoManager.Menus.AddAsync(deviceMenu); + if (addDeviceMenu == null || addDeviceMenu.Id == 0) + { + throw new InvalidOperationException($"添加设备菜单失败:{addDeviceMenu}"); + } + } + // 假设 CreateDeviceWithDetailsDto 包含了变量表和菜单信息 if (dto.VariableTable != null) { var variableTable = _mapper.Map(dto.VariableTable); variableTable.DeviceId = device.Id; // 关联新设备ID - await _repoManager.VariableTables.AddAsync(variableTable); + variableTable.Protocol=device.Protocol; + var addVariableTable = await _repoManager.VariableTables.AddAsync(variableTable); + if (addVariableTable == null || addVariableTable.Id == 0) + { + throw new InvalidOperationException($"添加设备变量表失败,设备:{device.Name},变量表:{variableTable.Name}"); + } + + // 假设有设备菜单 + if (dto.VariableTableMenu != null) + { + var menu = _mapper.Map(dto.VariableTableMenu); + menu.ParentId = addDeviceMenu.Id; + menu.MenuType = MenuType.VariableTableMenu; + menu.TargetId = addVariableTable.Id; + var addVariableTableMenu= await _repoManager.Menus.AddAsync(menu); + if (addVariableTableMenu == null || addVariableTableMenu.Id == 0) + { + throw new InvalidOperationException($"添加设备变量表菜单失败,变量表:{variableTable.Name},变量表菜单:{menu.Header}"); + } + } } - - // 假设有菜单服务或仓储 - // if (dto.Menu != null) - // { - // var menu = _mapper.Map(dto.Menu); - // menu.TargetId = device.Id; - // await _repoManager.Menus.AddAsync(menu); - // } - + await _repoManager.CommitAsync(); - return device.Id; + return addDevice.Id; } catch (Exception ex) { await _repoManager.RollbackAsync(); // 可以在此记录日志 - throw new ApplicationException("创建设备时发生错误,操作已回滚。", ex); + throw new ApplicationException($"创建设备时发生错误,操作已回滚,错误信息:{ex.Message}", ex); } } @@ -93,6 +125,7 @@ public class DeviceService : IDeviceAppService { throw new ApplicationException($"Device with ID {deviceDto.Id} not found."); } + _mapper.Map(deviceDto, device); await _repoManager.Devices.UpdateAsync(device); await _repoManager.CommitAsync(); @@ -117,6 +150,7 @@ public class DeviceService : IDeviceAppService { throw new ApplicationException($"Device with ID {id} not found."); } + device.IsActive = !device.IsActive; await _repoManager.Devices.UpdateAsync(device); await _repoManager.CommitAsync(); diff --git a/DMS.Application/Services/InitializeService.cs b/DMS.Application/Services/InitializeService.cs new file mode 100644 index 0000000..cd37d7c --- /dev/null +++ b/DMS.Application/Services/InitializeService.cs @@ -0,0 +1,29 @@ +using DMS.Core.Interfaces.Repositories; + +namespace DMS.Application.Services; + +public class InitializeService +{ + private readonly IInitializeRepository _repository; + + public InitializeService(IInitializeRepository repository ) + { + _repository = repository; + } + + public void InitializeTables() + { + _repository.InitializeTables(); + } + + public void InitializeTableIndex() + { + _repository.InitializeTableIndex(); + } + + public void InitializeMenus() + { + _repository.InitializeMenus(); + } + +} \ No newline at end of file diff --git a/DMS.Core/Enums/MenuType.cs b/DMS.Core/Enums/MenuType.cs index 39da8c6..69cf7a8 100644 --- a/DMS.Core/Enums/MenuType.cs +++ b/DMS.Core/Enums/MenuType.cs @@ -5,6 +5,5 @@ public enum MenuType MainMenu, DeviceMenu, VariableTableMenu, - AddVariableTableMenu, MqttMenu } \ No newline at end of file diff --git a/DMS.Core/Interfaces/IRepositoryManager.cs b/DMS.Core/Interfaces/IRepositoryManager.cs index f676902..6398bb5 100644 --- a/DMS.Core/Interfaces/IRepositoryManager.cs +++ b/DMS.Core/Interfaces/IRepositoryManager.cs @@ -49,6 +49,11 @@ public interface IRepositoryManager : IDisposable /// IUserRepository Users { get; set; } + /// + /// 初始化数据库 + /// + IInitializeRepository InitializeRepository { get; set; } + /// /// 开始一个新的数据库事务。 /// diff --git a/DMS.Core/Interfaces/Repositories/IInitializeRepository.cs b/DMS.Core/Interfaces/Repositories/IInitializeRepository.cs new file mode 100644 index 0000000..1e910bb --- /dev/null +++ b/DMS.Core/Interfaces/Repositories/IInitializeRepository.cs @@ -0,0 +1,10 @@ +namespace DMS.Core.Interfaces.Repositories; + +public interface IInitializeRepository +{ + void InitializeTables(); + void InitializeTableIndex(); + bool IsAnyTable(string tableName); + bool IsAnyIndex(string indexName); + void InitializeMenus(); +} \ No newline at end of file diff --git a/DMS.Core/Models/Device.cs b/DMS.Core/Models/Device.cs index c82a68e..03b9bb8 100644 --- a/DMS.Core/Models/Device.cs +++ b/DMS.Core/Models/Device.cs @@ -62,4 +62,7 @@ public class Device /// 此设备包含的变量表集合。 /// public List VariableTables { get; set; } = new(); + + public string CpuType { get; set; } + public DeviceType DeviceType { get; set; } } \ No newline at end of file diff --git a/DMS.Core/Models/MenuBean.cs b/DMS.Core/Models/MenuBean.cs index e00d1af..35ded1b 100644 --- a/DMS.Core/Models/MenuBean.cs +++ b/DMS.Core/Models/MenuBean.cs @@ -1,3 +1,5 @@ +using DMS.Core.Enums; + namespace DMS.Core.Models; /// @@ -9,7 +11,15 @@ public class MenuBean public int? ParentId { get; set; } public string Header { get; set; } public string Icon { get; set; } - public string TargetViewKey { get; set; } + /// + /// 菜单的类型,例如菜单关联的是设备,还是变量表,或者是MQTT + /// + public MenuType MenuType { get; set; } + + /// + /// 菜单关联的数据ID,例如设备Id,变量表Id + /// + public int TargetId { get; set; } public string NavigationParameter { get; set; } public int DisplayOrder { get; set; } } \ No newline at end of file diff --git a/DMS.Core/Models/Variable.cs b/DMS.Core/Models/Variable.cs index ed5ee62..dd739f2 100644 --- a/DMS.Core/Models/Variable.cs +++ b/DMS.Core/Models/Variable.cs @@ -27,7 +27,7 @@ public class Variable /// /// 在设备中的地址 (例如: DB1.DBD0, M100.0)。 /// - public string Address { get; set; } + public string S7Address { get; set; } /// /// 变量的信号类型,例如启动信号、停止信号。 diff --git a/DMS.Infrastructure.UnitTests/DMS.Infrastructure.UnitTests.csproj b/DMS.Infrastructure.UnitTests/DMS.Infrastructure.UnitTests.csproj index e5ab291..f5a0273 100644 --- a/DMS.Infrastructure.UnitTests/DMS.Infrastructure.UnitTests.csproj +++ b/DMS.Infrastructure.UnitTests/DMS.Infrastructure.UnitTests.csproj @@ -10,9 +10,11 @@ + + @@ -24,6 +26,7 @@ + diff --git a/DMS.Infrastructure.UnitTests/FakerHelper.cs b/DMS.Infrastructure.UnitTests/FakerHelper.cs index 86ea66d..987d601 100644 --- a/DMS.Infrastructure.UnitTests/FakerHelper.cs +++ b/DMS.Infrastructure.UnitTests/FakerHelper.cs @@ -5,55 +5,142 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using DMS.Application.DTOs; +using DMS.Core.Enums; +using DMS.Core.Models; namespace DMS.Infrastructure.UnitTests { public static class FakerHelper { - public static DbDevice FakeDbDevice() + public static Device FakeDevice() { - var dbDevice = new Faker() - .RuleFor(d => d.Name, f => f.Commerce.ProductName()) - .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) - .RuleFor(d => d.IpAddress, f => f.Internet.Ip()) - .RuleFor(d => d.OpcUaServerUrl, f => f.Internet.Url()) - .Generate(); + var dbDevice = new Faker() + .RuleFor(d => d.Name, f => f.Commerce.ProductName()) + .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) + .RuleFor(d => d.IpAddress, f => f.Internet.Ip()) + .RuleFor(d => d.OpcUaServerUrl, f => f.Internet.Url()) + .Generate(); dbDevice.Port = 102; dbDevice.Protocol = ProtocolType.S7; dbDevice.Slot = 1; dbDevice.Rack = 0; dbDevice.CpuType = "S7-1200"; dbDevice.DeviceType = Core.Enums.DeviceType.SiemensPLC; - + return dbDevice; } - public static DbVariableTable FakeDbVariableTable() + public static VariableTable FakeVariableTable() { - var dbVarTable = new Faker() - .RuleFor(d => d.Name, f => f.Commerce.ProductName()) - .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) - .Generate(); - dbVarTable.Protocol = ProtocolType.S7; - dbVarTable.IsActive=true; - return dbVarTable; + var varTable = new Faker() + .RuleFor(d => d.Name, f => f.Commerce.ProductName()) + .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) + .Generate(); + varTable.IsActive = true; + return varTable; } - // public static DbVariable FakeDbVariable() - // { - // var dbVariable = new Faker() - // .RuleFor(d => d.Name, f => f.Commerce.ProductName()) - // .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) - // .RuleFor(d => d.S7Address, f => f.Internet.DomainWord()) - // .RuleFor(d => d.DataValue, f => f.Commerce.Price()) - // .Generate(); - // dbVariable.ProtocolType = Core.Enums.ProtocolType.S7; - // dbVariable.IsActive = true; - // dbVariable.SignalType=Core.Enums.SignalType.RunSignal; - // dbVariable.UpdateTime=DateTime.Now; - // dbVariable.DataType = "String"; - // return dbVariable; - // } + + public static VariableTableDto FakeVariableTableDto() + { + var varTable = new Faker() + .RuleFor(d => d.Name, f => f.Commerce.ProductName()) + .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) + .Generate(); + varTable.IsActive = true; + return varTable; + } + + public static Variable FakeVariable() + { + var dbVariable = new Faker() + .RuleFor(d => d.Name, f => f.Commerce.ProductName()) + .RuleFor(d => d.S7Address, f => $"DB1.DBD{f.Random.Int(0, 1000)}") + .RuleFor(d => d.OpcUaNodeId, f => $"ns=2;s=My.DbDevice.Variable{f.Random.Int(1, 100)}") + .RuleFor(d => d.ConversionFormula, f => "x * 1.0") + .RuleFor(d => d.UpdatedBy, f => f.Name.FullName()) + .Generate(); + + // dbVariable.DataType = 1; + // dbVariable.PollLevel = 1; + dbVariable.IsActive = true; + dbVariable.VariableTableId = 1; + dbVariable.IsHistoryEnabled = true; + dbVariable.HistoryDeadband = 0.1; + dbVariable.IsAlarmEnabled = false; + dbVariable.AlarmMinValue = 0; + dbVariable.AlarmMaxValue = 100; + dbVariable.AlarmDeadband = 1; + dbVariable.Protocol = 0; + dbVariable.CSharpDataType = 0; + dbVariable.CreatedAt = DateTime.Now; + dbVariable.UpdatedAt = DateTime.Now; + dbVariable.IsModified = false; + + return dbVariable; + } + + public static DbMenu FakeDbMenu() + { + var dbMenu = new Faker() + .RuleFor(d => d.Header, f => f.Commerce.Department()) + .RuleFor(d => d.Icon, f => f.Random.Word()) + .RuleFor(d => d.DisplayOrder, f => f.Random.Number(1, 10)) + .Generate(); + dbMenu.ParentId = 0; + dbMenu.Childrens = new List(); + return dbMenu; + } + + public static DbMqttServer FakeDbMqttServer() + { + var dbMqttServer = new Faker() + .RuleFor(d => d.ServerName, f => f.Company.CompanyName()) + .RuleFor(d => d.BrokerAddress, f => f.Internet.Ip()) + .RuleFor(d => d.Username, f => f.Internet.UserName()) + .RuleFor(d => d.Password, f => f.Internet.Password()) + .RuleFor(d => d.SubscribeTopic, f => "/topic/sub") + .RuleFor(d => d.PublishTopic, f => "/topic/pub") + .RuleFor(d => d.ClientId, f => Guid.NewGuid() + .ToString()) + .RuleFor(d => d.MessageFormat, f => "JSON") + .Generate(); + dbMqttServer.Port = 1883; + dbMqttServer.IsActive = true; + dbMqttServer.CreatedAt = DateTime.Now; + return dbMqttServer; + } + + public static CreateDeviceDto FakeCreateDeviceDto() + { + var deviceDto = new Faker() + .RuleFor(d => d.Name, f => f.Commerce.ProductName()) + .RuleFor(d => d.Description, f => f.Commerce.ProductDescription()) + .RuleFor(d => d.IpAddress, f => f.Internet.Ip()) + .RuleFor(d => d.OpcUaServerUrl, f => f.Internet.Url()) + .Generate(); + deviceDto.Port = 102; + deviceDto.Protocol = ProtocolType.S7; + deviceDto.Slot = 1; + deviceDto.Rack = 0; + deviceDto.CpuType = "S7-1200"; + deviceDto.DeviceType = Core.Enums.DeviceType.SiemensPLC; + + return deviceDto; + } + + public static MenuBeanDto FakeCreateMenuDto() + { + var menuDto = new Faker() + .RuleFor(m => m.Header, f => f.Commerce.ProductName()) + .RuleFor(m => m.Icon, f => f.Random.Word()) + .RuleFor(m => m.DisplayOrder, f => f.Random.Number(1, 100)) + .RuleFor(m => m.ParentId, f => f.Random.Number(0, 10)) // 假设可以有父菜单 + .RuleFor(m => m.MenuType, f => f.PickRandom()) // 假设 MenuType 是一个枚举 + .Generate(); + return menuDto; + } } } diff --git a/DMS.Infrastructure.UnitTests/Services/BaseServiceTest.cs b/DMS.Infrastructure.UnitTests/Services/BaseServiceTest.cs new file mode 100644 index 0000000..e1e2b46 --- /dev/null +++ b/DMS.Infrastructure.UnitTests/Services/BaseServiceTest.cs @@ -0,0 +1,64 @@ +// DMS.Infrastructure.UnitTests/Services/BaseServiceTest.cs + +using AutoMapper; +using AutoMapper.Internal; +using DMS.Application.Interfaces; +using DMS.Application.Services; +using DMS.Core.Interfaces; +using DMS.Core.Interfaces.Repositories; +using DMS.Infrastructure.Data; +using DMS.Infrastructure.Repositories; +using Microsoft.Extensions.DependencyInjection; +using DMS.Infrastructure.Configurations; + + +namespace DMS.Infrastructure.UnitTests.Services; + +public class BaseServiceTest +{ + // ServiceProvider 将是所有测试的服务容器 + protected readonly IServiceProvider ServiceProvider; + + public BaseServiceTest() + { + var services = new ServiceCollection(); + + // --- 核心配置 --- + services.AddAutoMapper(cfg => + { + // 最终解决方案:根据异常信息的建议,设置此标记以忽略重复的Profile加载。 + // 注意:此属性位于 Internal() 方法下。 + cfg.Internal().AllowAdditiveTypeMapCreation = true; + + cfg.AddProfile(new DMS.Application.Profiles.MappingProfile()); + cfg.AddProfile(new DMS.Infrastructure.Profiles.MappingProfile()); + }); + + // 2. 配置数据库上下文 (在测试中通常使用单例) + services.AddSingleton(_ => + { + var appSettings = new AppSettings { Database = { Database = "dms_test" } }; + return new SqlSugarDbContext(appSettings); + }); + + // --- 注册服务和仓储 --- + // 使用 Transient 或 Scoped 取决于服务的生命周期需求,对于测试,Transient 通常更安全。 + + // 注册仓储管理器 + services.AddTransient(); + services.AddTransient(); + + // 注册应用服务 + services.AddTransient(); + // services.AddTransient(); // 如果需要测试 VariableService,取消此行注释 + // ... 在这里注册所有其他的应用服务 ... + + + // --- 构建服务提供程序 --- + ServiceProvider = services.BuildServiceProvider(); + + // 验证 AutoMapper 配置 (可选,但强烈推荐) + var mapper = ServiceProvider.GetService(); + mapper?.ConfigurationProvider.AssertConfigurationIsValid(); + } +} diff --git a/DMS.Infrastructure.UnitTests/Services/DatabaseServiceTest.cs b/DMS.Infrastructure.UnitTests/Services/DatabaseServiceTest.cs deleted file mode 100644 index 843aa81..0000000 --- a/DMS.Infrastructure.UnitTests/Services/DatabaseServiceTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using DMS.Core.Interfaces; -using DMS.Infrastructure.Configurations; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Services; - -namespace DMS.Infrastructure.UnitTests.Services; - -public class DatabaseServiceTest -{ - - private IDatabaseService _databaseService; - public DatabaseServiceTest() - { - AppSettings appSettings = new AppSettings(); - appSettings.Database.Database = "dms_test"; - SqlSugarDbContext dbContext=new SqlSugarDbContext(appSettings); - _databaseService = new DatabaseService(dbContext); - } - [Fact] - public void InitializeTables_Test() - { - _databaseService.InitializeTables(); - Assert.True(_databaseService.IsAnyTable("dbdevice")); - } - - [Fact] - public void InitializeTableIndex_Test() - { - _databaseService.InitializeTableIndex(); - } -} \ No newline at end of file diff --git a/DMS.Infrastructure.UnitTests/Services/DeviceServiceTest.cs b/DMS.Infrastructure.UnitTests/Services/DeviceServiceTest.cs index 3f8cceb..aa85a10 100644 --- a/DMS.Infrastructure.UnitTests/Services/DeviceServiceTest.cs +++ b/DMS.Infrastructure.UnitTests/Services/DeviceServiceTest.cs @@ -1,73 +1,41 @@ -using AutoMapper; -using DMS.Core.Models; -using DMS.Infrastructure.Configurations; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Profiles; -using DMS.Infrastructure.Repositories; -using DMS.Infrastructure.Services; +using DMS.Application.DTOs; +using DMS.Application.Interfaces; +using DMS.Application.Services; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; namespace DMS.Infrastructure.UnitTests.Services; [TestSubject(typeof(DeviceService))] -public class DeviceServiceTest +public class DeviceServiceTest : BaseServiceTest // 继承基类 { - private readonly DeviceRepository _deviceRepository; - private readonly DeviceService _deviceService; - private readonly IMapper _mapper; + private readonly IDeviceAppService _deviceService; - public DeviceServiceTest() + public DeviceServiceTest() : base() { - // 1. 创建 MapperConfiguration - var mappingConfig = new MapperConfiguration(mc => + // 从 IoC 容器中解析出需要测试的服务 + // 使用 GetRequiredService 可以确保如果服务未注册,测试会立即失败,这通常是我们想要的。 + _deviceService = ServiceProvider.GetRequiredService(); + } + [Fact] + public async Task CreateDeviceWithDetailsAsyncTest() + { + // Arrange + var dto = new CreateDeviceWithDetailsDto { - // 添加你的所有 Profile - mc.AddProfile(new MappingProfile()); - // 如果有其他 Profile,也可以在这里添加 - // mc.AddProfile(new AnotherProfile()); - }); + Device = FakerHelper.FakeCreateDeviceDto(), + VariableTable = FakerHelper.FakeVariableTableDto(), + DeviceMenu = FakerHelper.FakeCreateMenuDto(), + VariableTableMenu = FakerHelper.FakeCreateMenuDto() + + // ... 填充其他需要的数据 + }; - // 2. 验证映射配置是否有效 (可选,但在开发环境中推荐) - mappingConfig.AssertConfigurationIsValid(); - // 3. 创建 IMapper 实例 - _mapper = mappingConfig.CreateMapper(); + // Act + var addedDeviceId = await _deviceService.CreateDeviceWithDetailsAsync(dto); - - AppSettings appSettings = new AppSettings(); - appSettings.Database.Database = "dms_test"; - SqlSugarDbContext dbContext = new SqlSugarDbContext(appSettings); - _deviceRepository= new DeviceRepository(_mapper,dbContext); - _deviceService = new DeviceService(_deviceRepository); - } - - [Fact] - public async Task AddAsync_Test() - { - var dbDevice = FakerHelper.FakeDbDevice(); - var addDevice= await _deviceService.AddAsync(_mapper.Map(dbDevice)); - Assert.NotEqual(0, addDevice.Id); - } - - [Fact] - public async Task TakeAsync_Test() - { - var device= await _deviceService.TakeAsync(2); - Assert.Equal(2,device.Count); - } - [Fact] - public async Task UpdateAsync_Test() - { - var devices= await _deviceService.TakeAsync(1); - devices[0].IpAddress = "127.0.0.1"; - var res= await _deviceService.UpdateAsync(devices[0]); - Assert.Equal(1,res); - } - [Fact] - public async Task DeleteAsync_Test() - { - var devices= await _deviceService.TakeAsync(1); - var res= await _deviceService.DeleteAsync(devices[0]); - Assert.Equal(1,res); + // Assert + Assert.NotEqual(0, addedDeviceId); } } \ No newline at end of file diff --git a/DMS.Infrastructure.UnitTests/Services/InitializeServiceTest.cs b/DMS.Infrastructure.UnitTests/Services/InitializeServiceTest.cs new file mode 100644 index 0000000..b0f837c --- /dev/null +++ b/DMS.Infrastructure.UnitTests/Services/InitializeServiceTest.cs @@ -0,0 +1,24 @@ +using DMS.Application.Interfaces; +using DMS.Application.Services; +using DMS.Core.Interfaces.Repositories; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace DMS.Infrastructure.UnitTests.Services; + +[TestSubject(typeof(InitializeService))] +public class InitializeServiceTest:BaseServiceTest +{ + private readonly IInitializeRepository _initializeRepository; + + public InitializeServiceTest() + { + _initializeRepository = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void InitializeTablesTest() + { + _initializeRepository.InitializeTables(); + } +} \ No newline at end of file diff --git a/DMS.Infrastructure.UnitTests/Services/VariableTableServieTest.cs b/DMS.Infrastructure.UnitTests/Services/VariableTableServieTest.cs deleted file mode 100644 index a499a9f..0000000 --- a/DMS.Infrastructure.UnitTests/Services/VariableTableServieTest.cs +++ /dev/null @@ -1,71 +0,0 @@ -using AutoMapper; -using DMS.Core.Models; -using DMS.Infrastructure.Configurations; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Profiles; -using DMS.Infrastructure.Repositories; -using DMS.Infrastructure.Services; - -namespace DMS.Infrastructure.UnitTests.Services; - -public class VariableTableServieTest -{ - private readonly VariableTableRepository _variableTableRepository; - private readonly VariableTableService _variableTableService; - private readonly IMapper _mapper; - - public VariableTableServieTest() - { - // 1. 创建 MapperConfiguration - var mappingConfig = new MapperConfiguration(mc => - - { - // 添加你的所有 Profile - mc.AddProfile(new MappingProfile()); - // 如果有其他 Profile,也可以在这里添加 - // mc.AddProfile(new AnotherProfile()); - }); - - // 2. 验证映射配置是否有效 (可选,但在开发环境中推荐) - mappingConfig.AssertConfigurationIsValid(); - // 3. 创建 IMapper 实例 - _mapper = mappingConfig.CreateMapper(); - - - AppSettings appSettings = new AppSettings(); - appSettings.Database.Database = "dms_test"; - SqlSugarDbContext dbContext = new SqlSugarDbContext(appSettings); - _variableTableRepository= new VariableTableRepository(_mapper,dbContext); - _variableTableService = new VariableTableService(_variableTableRepository); - } - - [Fact] - public async Task AddAsync_Test() - { - // var dbDevice = FakerHelper - // var addDevice= await _variableTableService.AddAsync(_mapper.Map(dbDevice)); - // Assert.NotEqual(0, addDevice.Id); - } - - [Fact] - public async Task TakeAsync_Test() - { - var device= await _variableTableService.TakeAsync(2); - Assert.Equal(2,device.Count); - } - [Fact] - public async Task UpdateAsync_Test() - { - var devices= await _variableTableService.TakeAsync(1); - // devices[0].IpAddress = "127.0.0.1"; - var res= await _variableTableService.UpdateAsync(devices[0]); - Assert.Equal(1,res); - } - [Fact] - public async Task DeleteAsync_Test() - { - var devices= await _variableTableService.TakeAsync(1); - var res= await _variableTableService.DeleteAsync(devices[0]); - Assert.Equal(1,res); - } -} \ No newline at end of file diff --git a/DMS.Infrastructure/Entities/DbMenu.cs b/DMS.Infrastructure/Entities/DbMenu.cs index 8cc4197..e8f2abb 100644 --- a/DMS.Infrastructure/Entities/DbMenu.cs +++ b/DMS.Infrastructure/Entities/DbMenu.cs @@ -1,24 +1,61 @@ +using DMS.Core.Enums; using SqlSugar; +using SqlSugar.DbConvert; namespace DMS.Infrastructure.Entities; +/// +/// 数据库中的菜单项实体 +/// public class DbMenu { + /// + /// 菜单的唯一标识符 + /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } + /// + /// 父菜单的标识符。如果是根菜单,则为 null。 + /// [SugarColumn(IsNullable = true)] public int? ParentId { get; set; } + /// + /// 菜单项显示的文本 + /// public string Header { get; set; } + /// + /// 与菜单项关联的图标 + /// public string Icon { get; set; } - public string TargetViewKey { get; set; } + /// + /// 菜单的类型 + /// + [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] + public MenuType MenuType { get; set; } + + /// + /// 菜单关联的数据ID,例如设备Id,变量表Id + /// + public int TargetId { get; set; } + /// + /// 导航的可选参数 + /// [SugarColumn(IsNullable = true)] public string NavigationParameter { get; set; } + + /// + /// 子菜单项 + /// + [SugarColumn(IsIgnore = true)] public List Childrens { get; set; } + /// + /// 菜单项的显示顺序 + /// public int DisplayOrder { get; set; } } \ No newline at end of file diff --git a/DMS.Infrastructure/Entities/DbMqttServer.cs b/DMS.Infrastructure/Entities/DbMqttServer.cs index 186f28b..3b8f813 100644 --- a/DMS.Infrastructure/Entities/DbMqttServer.cs +++ b/DMS.Infrastructure/Entities/DbMqttServer.cs @@ -2,21 +2,79 @@ using SqlSugar; namespace DMS.Infrastructure.Entities; +/// +/// MQTT服务器配置实体 +/// public class DbMqttServer { + /// + /// 唯一标识符 + /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } + + /// + /// 服务器名称 + /// public string ServerName { get; set; } + + /// + /// MQTT代理地址 + /// public string BrokerAddress { get; set; } + + /// + /// 端口号 + /// public int Port { get; set; } + + /// + /// 用户名 + /// public string Username { get; set; } + + /// + /// 密码 + /// public string Password { get; set; } + + /// + /// 是否激活 + /// public bool IsActive { get; set; } + + /// + /// 订阅的主题 + /// public string SubscribeTopic { get; set; } + + /// + /// 发布的主题 + /// public string PublishTopic { get; set; } + + /// + /// 客户端ID + /// public string ClientId { get; set; } + + /// + /// 创建时间 + /// public DateTime CreatedAt { get; set; } + + /// + /// 连接时间 + /// public DateTime? ConnectedAt { get; set; } + + /// + /// 连接持续时间(秒) + /// public long ConnectionDuration { get; set; } + + /// + /// 消息格式 + /// public string MessageFormat { get; set; } } \ No newline at end of file diff --git a/DMS.Infrastructure/Entities/DbVariableTable.cs b/DMS.Infrastructure/Entities/DbVariableTable.cs index 9745786..a691c4b 100644 --- a/DMS.Infrastructure/Entities/DbVariableTable.cs +++ b/DMS.Infrastructure/Entities/DbVariableTable.cs @@ -35,7 +35,7 @@ public class DbVariableTable /// 关联的设备 /// [SugarColumn(IsIgnore = true)] - public DbDevice Device { get; set; } + public DbDevice DbDevice { get; set; } /// /// 协议类型 diff --git a/DMS.Infrastructure/Profiles/MappingProfile.cs b/DMS.Infrastructure/Profiles/MappingProfile.cs index 9c33a0a..0133905 100644 --- a/DMS.Infrastructure/Profiles/MappingProfile.cs +++ b/DMS.Infrastructure/Profiles/MappingProfile.cs @@ -17,7 +17,7 @@ public class MappingProfile : Profile // --- 设备映射 (包含List的父对象) --- // AutoMapper 会自动使用上面的规则来处理 VariableTables 属性 - CreateMap() + CreateMap() .ReverseMap(); // --- 变量表映射 (List中的元素) --- diff --git a/DMS.Infrastructure/Repositories/DeviceRepository.cs b/DMS.Infrastructure/Repositories/DeviceRepository.cs index a5550ba..b9eb0f7 100644 --- a/DMS.Infrastructure/Repositories/DeviceRepository.cs +++ b/DMS.Infrastructure/Repositories/DeviceRepository.cs @@ -18,28 +18,28 @@ public class DeviceRepository : BaseRepository, IDeviceRepository _mapper = mapper; } - public async Task> GetAllAsync() + public async Task> GetAllAsync() { var dbList = await base.GetAllAsync(); - return _mapper.Map>(dbList); + return _mapper.Map>(dbList); } - public async Task GetByIdAsync(int id) + public async Task GetByIdAsync(int id) { var dbDevice = await base.GetByIdAsync(id); - return _mapper.Map(dbDevice); + return _mapper.Map(dbDevice); } - public async Task AddAsync(Device model) + public async Task AddAsync(Core.Models.Device model) { var dbDevice = await base.AddAsync(_mapper.Map(model)); return _mapper.Map(dbDevice, model); } - public async Task UpdateAsync(Device model) => await base.UpdateAsync(_mapper.Map(model)); + public async Task UpdateAsync(Core.Models.Device model) => await base.UpdateAsync(_mapper.Map(model)); - public async Task DeleteAsync(Device model) => await base.DeleteAsync(_mapper.Map(model)); + public async Task DeleteAsync(Core.Models.Device model) => await base.DeleteAsync(_mapper.Map(model)); public async Task DeleteAsync(int id) { @@ -52,10 +52,10 @@ public class DeviceRepository : BaseRepository, IDeviceRepository return result; } - public new async Task> TakeAsync(int number) + public new async Task> TakeAsync(int number) { var dbList = await base.TakeAsync(number); - return _mapper.Map>(dbList); + return _mapper.Map>(dbList); } } \ No newline at end of file diff --git a/DMS.Infrastructure/Services/DatabaseService.cs b/DMS.Infrastructure/Repositories/InitializeRepository.cs similarity index 93% rename from DMS.Infrastructure/Services/DatabaseService.cs rename to DMS.Infrastructure/Repositories/InitializeRepository.cs index 0769d1b..10b0026 100644 --- a/DMS.Infrastructure/Services/DatabaseService.cs +++ b/DMS.Infrastructure/Repositories/InitializeRepository.cs @@ -1,20 +1,18 @@ -using DMS.Core.Interfaces; +using DMS.Core.Interfaces.Repositories; using DMS.Core.Models; using DMS.Infrastructure.Configurations; using DMS.Infrastructure.Data; using DMS.Infrastructure.Entities; using SqlSugar; -namespace DMS.Infrastructure.Services; +namespace DMS.Infrastructure.Repositories; - - -public class DatabaseService : IDatabaseService +public class InitializeRepository : IInitializeRepository { private readonly SqlSugarDbContext _dbContext; private readonly SqlSugarClient _db; - public DatabaseService(SqlSugarDbContext dbContext) + public InitializeRepository(SqlSugarDbContext dbContext) { _dbContext = dbContext; _db = _dbContext.GetInstance(); diff --git a/DMS.Infrastructure/Repositories/RepositoryManager.cs b/DMS.Infrastructure/Repositories/RepositoryManager.cs index 9ad2412..612cea6 100644 --- a/DMS.Infrastructure/Repositories/RepositoryManager.cs +++ b/DMS.Infrastructure/Repositories/RepositoryManager.cs @@ -18,6 +18,7 @@ public class RepositoryManager : IRepositoryManager _dbContext = dbContext; _db = dbContext.GetInstance(); + InitializeRepository=new InitializeRepository(dbContext); Devices = new DeviceRepository(mapper, dbContext); VariableTables = new VariableTableRepository(mapper, dbContext); Variables = new VariableRepository(mapper, dbContext); @@ -41,6 +42,7 @@ public class RepositoryManager : IRepositoryManager public IMenuRepository Menus { get; set; } public IVariableHistoryRepository VariableHistories { get; set; } public IUserRepository Users { get; set; } + public IInitializeRepository InitializeRepository { get; set; } public async Task BeginTranAsync() => await _db.BeginTranAsync(); public async Task CommitAsync() => await _db.CommitTranAsync(); diff --git a/DMS.Infrastructure/Services/BaseService.cs b/DMS.Infrastructure/Services/BaseService.cs deleted file mode 100644 index dac6c17..0000000 --- a/DMS.Infrastructure/Services/BaseService.cs +++ /dev/null @@ -1,74 +0,0 @@ -using AutoMapper; -using DMS.Infrastructure.Repositories; -using System.Threading.Tasks; -using DMS.Core.Interfaces.Repositories; - -namespace DMS.Infrastructure.Services -{ - /// - /// 通用服务基类,封装了常见的增、删、改操作。 - /// - /// 业务逻辑模型类型。 - /// 数据库实体类型。 - /// 与实体对应的仓储类型。 - public abstract class BaseService - where TModel : class, new() - where TRepository : IBaseRepository - { - protected readonly TRepository ServerRepository; - - /// - /// 初始化 BaseService 的新实例。 - /// - /// AutoMapper 实例,用于对象映射。 - /// 仓储实例,用于数据访问。 - protected BaseService( TRepository serverRepository) - { - ServerRepository = serverRepository; - } - - /// - /// 异步添加一个新的业务模型对象。 - /// - /// 要添加的业务模型对象。 - /// 返回添加后的数据库实体。 - public virtual async Task AddAsync(TModel model) - { - return await ServerRepository.AddAsync(model); - } - - /// - /// 异步更新一个现有的业务模型对象。 - /// - /// 要更新的业务模型对象。 - /// 返回受影响的行数。 - public virtual async Task UpdateAsync(TModel model) - { - return await ServerRepository.UpdateAsync(model); - } - - /// - /// 异步删除一个业务模型对象。 - /// - /// 要删除的业务模型对象。 - /// 返回受影响的行数。 - public virtual async Task DeleteAsync(TModel model) - { - return await ServerRepository.DeleteAsync(model); - } - - public virtual async Task> GetAllAsync() - { - return await ServerRepository.GetAllAsync(); - } - - public virtual async Task GetByIdAsync(int id) - { - return await ServerRepository.GetByIdAsync(id); - } - public virtual async Task> TakeAsync(int number) - { - return await ServerRepository.TakeAsync(number); - } - } -} diff --git a/DMS.Infrastructure/Services/DeviceService.cs b/DMS.Infrastructure/Services/DeviceService.cs deleted file mode 100644 index 6dacd2f..0000000 --- a/DMS.Infrastructure/Services/DeviceService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AutoMapper; -using DMS.Core.Helper; -using DMS.Core.Models; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Entities; -using DMS.Infrastructure.Repositories; -using SqlSugar; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using System.Collections.Concurrent; -using DMS.Core.Interfaces; -using DMS.Core.Interfaces.Repositories; - -namespace DMS.Infrastructure.Services -{ - public class DeviceService : BaseService - { - private readonly IDeviceRepository _deviceRepository; - - public DeviceService(DeviceRepository repository) : base(repository) - { - _deviceRepository = repository; - } - - } -} \ No newline at end of file diff --git a/DMS.Infrastructure/Services/MenuService.cs b/DMS.Infrastructure/Services/MenuService.cs deleted file mode 100644 index f392302..0000000 --- a/DMS.Infrastructure/Services/MenuService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using DMS.Core.Models; -using DMS.Infrastructure.Entities; -using DMS.Infrastructure.Repositories; - -namespace DMS.Infrastructure.Services -{ - public class MenuService : BaseService - { - public MenuService(MenuRepository repository) : base(repository) - { - } - } -} diff --git a/DMS.Infrastructure/Services/MqttService.cs b/DMS.Infrastructure/Services/MqttService.cs deleted file mode 100644 index 87ab217..0000000 --- a/DMS.Infrastructure/Services/MqttService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AutoMapper; -using DMS.Core.Enums; -using DMS.Core.Helper; -using DMS.Core.Models; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Entities; -using DMS.Infrastructure.Repositories; -using SqlSugar; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace DMS.Infrastructure.Services -{ - public class MqttService:BaseService - { - public MqttService( MqttServerRepository serverRepository) : base( serverRepository) - { - } - } -} diff --git a/DMS.Infrastructure/Services/VariableService.cs b/DMS.Infrastructure/Services/VariableService.cs deleted file mode 100644 index 7951ad3..0000000 --- a/DMS.Infrastructure/Services/VariableService.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AutoMapper; -using DMS.Core.Helper; -using DMS.Core.Models; -using DMS.Infrastructure.Data; -using DMS.Infrastructure.Entities; -using SqlSugar; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using DMS.Infrastructure.Repositories; - -namespace DMS.Infrastructure.Services -{ - public class VariableService : BaseService - { - - public VariableService(VariableRepository repository) : base(repository) - { - } - - } -} diff --git a/DMS.Infrastructure/Services/VariableTableService.cs b/DMS.Infrastructure/Services/VariableTableService.cs deleted file mode 100644 index 61a26f4..0000000 --- a/DMS.Infrastructure/Services/VariableTableService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using DMS.Core.Models; -using DMS.Infrastructure.Entities; -using DMS.Infrastructure.Repositories; - -namespace DMS.Infrastructure.Services; - -public class VariableTableService : BaseService -{ - public VariableTableService(VariableTableRepository repository) : base(repository) - { - } -} \ No newline at end of file