设备类添加产品字段、添加物模型类,新增产品CRUD方法
This commit is contained in:
@@ -15,23 +15,26 @@ namespace WebAPI.Controllers
|
|||||||
public class IotDeviceController : ControllerBase
|
public class IotDeviceController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IDeviceService _deviceService;
|
private readonly IDeviceService _deviceService;
|
||||||
|
private readonly IDeviceCommandService _deviceCommandService;
|
||||||
|
|
||||||
public IotDeviceController(IDeviceService deviceService)
|
public IotDeviceController(IDeviceService deviceService, IDeviceCommandService deviceCommandService)
|
||||||
{
|
{
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
|
_deviceCommandService = deviceCommandService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备列表(分页查询,支持关键字搜索)
|
/// 设备列表(分页查询,支持关键字搜索,可按所属产品筛选)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pageIndex">页码(从1开始,默认1)</param>
|
/// <param name="pageIndex">页码(从1开始,默认1)</param>
|
||||||
/// <param name="pageSize">每页数量(默认10)</param>
|
/// <param name="pageSize">每页数量(默认10)</param>
|
||||||
/// <param name="keyword">关键字(模糊匹配设备编号/名称/类型)</param>
|
/// <param name="keyword">关键字(模糊匹配设备编号/名称/类型)</param>
|
||||||
|
/// <param name="productId">所属产品Id(0 表示不过滤)</param>
|
||||||
[HttpGet("list")]
|
[HttpGet("list")]
|
||||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null)
|
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, long productId = 0)
|
||||||
{
|
{
|
||||||
RefAsync<int> total = 0;
|
RefAsync<int> total = 0;
|
||||||
var result = await _deviceService.GetPagedAsync(pageIndex, pageSize, total, keyword);
|
var result = await _deviceService.GetPagedAsync(pageIndex, pageSize, total, keyword, productId > 0 ? productId : null);
|
||||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||||
return result.IsSuccess
|
return result.IsSuccess
|
||||||
? Ok(Result<List<IotDeviceDto>>.Success(result.Data))
|
? Ok(Result<List<IotDeviceDto>>.Success(result.Data))
|
||||||
@@ -92,5 +95,19 @@ namespace WebAPI.Controllers
|
|||||||
? Ok(Result<List<DeviceLogDto>>.Success(result.Data))
|
? Ok(Result<List<DeviceLogDto>>.Success(result.Data))
|
||||||
: Ok(Result<List<DeviceLogDto>>.Error(result.Msg));
|
: Ok(Result<List<DeviceLogDto>>.Error(result.Msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按物模型可写点向设备下发指令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备主键 Id</param>
|
||||||
|
/// <param name="dto">指令请求(PointId 点Id + Value 工程值 + IsSimulated 是否模拟)</param>
|
||||||
|
[HttpPost("{id}/command")]
|
||||||
|
public async Task<IActionResult> SendCommand(long id, [FromBody] DeviceCommandDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
return Ok(Result.Error("请求体不能为空"));
|
||||||
|
dto.Id = id.ToString();
|
||||||
|
return Ok(await _deviceCommandService.SendCommandAsync(dto));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,128 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using Service.Interface.Config;
|
||||||
|
using SqlSugar;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace WebAPI.Controllers
|
namespace WebAPI.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品管理
|
/// 产品管理(含产品分类)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/config/product")]
|
[Route("api/config/product")]
|
||||||
public class ProductController : ControllerBase
|
public class ProductController : ControllerBase
|
||||||
{
|
{
|
||||||
// TODO: 实现 产品管理 相关接口
|
private readonly IProductService _productService;
|
||||||
|
|
||||||
|
public ProductController(IProductService productService)
|
||||||
|
{
|
||||||
|
_productService = productService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 产品分类 =====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品分类列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("category/list")]
|
||||||
|
public async Task<IActionResult> GetCategories()
|
||||||
|
{
|
||||||
|
return Ok(await _productService.GetCategoriesAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增产品分类
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("category/add")]
|
||||||
|
public async Task<IActionResult> AddCategory([FromBody] ProductCategoryDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.AddCategoryAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改产品分类
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("category/update")]
|
||||||
|
public async Task<IActionResult> UpdateCategory([FromBody] ProductCategoryDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.UpdateCategoryAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除产品分类(软删除)
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("category/{id}")]
|
||||||
|
public async Task<IActionResult> DeleteCategory(long id)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.DeleteCategoryAsync(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 产品 =====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品下拉选项(设备选择所属产品)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("options")]
|
||||||
|
public async Task<IActionResult> GetOptions()
|
||||||
|
{
|
||||||
|
return Ok(await _productService.GetOptionsAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品列表(分页)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageIndex">页码(从1开始,默认1)</param>
|
||||||
|
/// <param name="pageSize">每页数量(默认10)</param>
|
||||||
|
/// <param name="keyword">关键字(模糊匹配型号/名称)</param>
|
||||||
|
/// <param name="categoryId">分类Id(0 表示不过滤)</param>
|
||||||
|
[HttpGet("list")]
|
||||||
|
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, long categoryId = 0)
|
||||||
|
{
|
||||||
|
RefAsync<int> total = 0;
|
||||||
|
var result = await _productService.GetPagedAsync(pageIndex, pageSize, total, keyword, categoryId);
|
||||||
|
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||||
|
return result.IsSuccess
|
||||||
|
? Ok(Result<List<ProductDto>>.Success(result.Data))
|
||||||
|
: Ok(Result<List<ProductDto>>.Error(result.Msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品详情
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetById(long id)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.GetByIdAsync(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增产品
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Add([FromBody] ProductDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.AddAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改产品
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut]
|
||||||
|
public async Task<IActionResult> Update([FromBody] ProductDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.UpdateAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除产品(软删除)
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Delete(long id)
|
||||||
|
{
|
||||||
|
return Ok(await _productService.DeleteAsync(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using Service.Interface.Config;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebAPI.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点(产品/设备通用,按 ownerType+ownerId 归属)
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/config/thing-point")]
|
||||||
|
public class ThingPointController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IThingPointService _thingPointService;
|
||||||
|
|
||||||
|
public ThingPointController(IThingPointService thingPointService)
|
||||||
|
{
|
||||||
|
_thingPointService = thingPointService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 点列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ownerType">归属类型(1=产品 2=设备)</param>
|
||||||
|
/// <param name="ownerId">归属对象Id</param>
|
||||||
|
/// <param name="keyword">关键字(点编码/名称)</param>
|
||||||
|
[HttpGet("list")]
|
||||||
|
public async Task<IActionResult> GetList(int ownerType, long ownerId, string? keyword = null)
|
||||||
|
{
|
||||||
|
return Ok(await _thingPointService.GetListAsync(ownerType, ownerId, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 点详情
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetById(long id)
|
||||||
|
{
|
||||||
|
return Ok(await _thingPointService.GetByIdAsync(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增点
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Add([FromBody] ThingModelPointDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _thingPointService.AddAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改点
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut]
|
||||||
|
public async Task<IActionResult> Update([FromBody] ThingModelPointDto dto)
|
||||||
|
{
|
||||||
|
return Ok(await _thingPointService.UpdateAsync(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除点(软删除)
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Delete(long id)
|
||||||
|
{
|
||||||
|
return Ok(await _thingPointService.DeleteAsync(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,18 @@ namespace WebAPI.Controllers
|
|||||||
return Ok(await _alertRuleService.GetListAsync());
|
return Ok(await _alertRuleService.GetListAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某归属对象(产品/设备)下的规则列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ownerType">归属类型(1=产品 2=设备)</param>
|
||||||
|
/// <param name="ownerId">归属对象Id</param>
|
||||||
|
/// <param name="pointCode">可选:绑定点编码过滤</param>
|
||||||
|
[HttpGet("owner")]
|
||||||
|
public async Task<IActionResult> GetOwnerRules(int ownerType, long ownerId, string? pointCode = null)
|
||||||
|
{
|
||||||
|
return Ok(await _alertRuleService.GetOwnerRulesAsync(ownerType, ownerId, pointCode));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 新增告警规则(前端传 DTO)
|
/// 新增告警规则(前端传 DTO)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Model.Dto.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 设备指令下发请求 DTO
|
||||||
|
/// </summary>
|
||||||
|
public class DeviceCommandDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 设备主键 Id(string)
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点 Id(string,决定写哪个寄存器/线圈)
|
||||||
|
/// </summary>
|
||||||
|
public string PointId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下发的工程值
|
||||||
|
/// </summary>
|
||||||
|
public double Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否仅记录(模拟/离线调试:不实际发网络报文)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSimulated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,12 @@ namespace Model.Dto.Config
|
|||||||
/// <summary>设备类型(型号,对应 DeviceCommand 驱动类)</summary>
|
/// <summary>设备类型(型号,对应 DeviceCommand 驱动类)</summary>
|
||||||
public string DeviceType { get; set; }
|
public string DeviceType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>所属产品Id(string,0=未分类)</summary>
|
||||||
|
public string ProductId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>所属产品型号/名称(只读展示)</summary>
|
||||||
|
public string? ProductName { get; set; }
|
||||||
|
|
||||||
/// <summary>所属网关Code</summary>
|
/// <summary>所属网关Code</summary>
|
||||||
public string GatewayCode { get; set; }
|
public string GatewayCode { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Model.Dto.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品分类 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||||
|
/// </summary>
|
||||||
|
public class ProductCategoryDto
|
||||||
|
{
|
||||||
|
/// <summary>主键 Id(long → string)</summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>分类编码</summary>
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>分类名称</summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>排序号</summary>
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>备注</summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>创建时间</summary>
|
||||||
|
public DateTime? CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Model.Entity.Config;
|
||||||
|
|
||||||
|
namespace Model.Dto.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||||
|
/// </summary>
|
||||||
|
public class ProductDto
|
||||||
|
{
|
||||||
|
/// <summary>主键 Id(long → string)</summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>型号(唯一)</summary>
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
/// <summary>产品名称</summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>产品分类Id</summary>
|
||||||
|
public string CategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>分类名称(只读展示)</summary>
|
||||||
|
public string? CategoryName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>制造商</summary>
|
||||||
|
public string? Manufacturer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>产品描述</summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>通讯协议</summary>
|
||||||
|
public IotDeviceProtocolEnum ProtocolType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>消息模型</summary>
|
||||||
|
public string? MessageModel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否启用</summary>
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>备注</summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>创建时间</summary>
|
||||||
|
public DateTime? CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Model.Dto.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品下拉选项 DTO(设备选择"所属产品"、筛选下拉用)
|
||||||
|
/// </summary>
|
||||||
|
public class ProductOptionDto
|
||||||
|
{
|
||||||
|
/// <summary>主键 Id(string)</summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>型号</summary>
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
/// <summary>产品名称</summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using Model.Entity.Config;
|
||||||
|
|
||||||
|
namespace Model.Dto.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||||
|
/// </summary>
|
||||||
|
public class ThingModelPointDto
|
||||||
|
{
|
||||||
|
/// <summary>主键 Id(long → string)</summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>归属类型(1=产品 2=设备)</summary>
|
||||||
|
public ThingOwnerTypeEnum OwnerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>归属对象Id(string)</summary>
|
||||||
|
public string OwnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>点编码</summary>
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>点名称</summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>寄存器类型</summary>
|
||||||
|
public ThingRegisterTypeEnum RegisterType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>读写权限</summary>
|
||||||
|
public ThingRwEnum Rw { get; set; }
|
||||||
|
|
||||||
|
/// <summary>数据类型</summary>
|
||||||
|
public ThingDataTypeEnum DataType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>寄存器起始地址</summary>
|
||||||
|
public ushort Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>换算系数</summary>
|
||||||
|
public double Scale { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>偏移量</summary>
|
||||||
|
public double Offset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>单位</summary>
|
||||||
|
public string? Unit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否启用</summary>
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>排序号</summary>
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>备注</summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>创建时间</summary>
|
||||||
|
public DateTime? CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,15 @@ namespace Model.Dto.Inspection
|
|||||||
/// <summary>主键 Id(long → string)</summary>
|
/// <summary>主键 Id(long → string)</summary>
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>归属类型(0=全局;1=产品 2=设备)</summary>
|
||||||
|
public byte OwnerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>归属对象Id(0 表示全局)</summary>
|
||||||
|
public long OwnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定点编码(空表示整产品/设备级规则)</summary>
|
||||||
|
public string PointCode { get; set; }
|
||||||
|
|
||||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||||
public byte IsDel { get; set; }
|
public byte IsDel { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ namespace Model.Entity.Config
|
|||||||
[SugarColumn(ColumnDescription = "设备类型", Length = 64)]
|
[SugarColumn(ColumnDescription = "设备类型", Length = 64)]
|
||||||
public string DeviceType { get; set; }
|
public string DeviceType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所属产品Id(关联 ProductEntity.Id,仅用于归类,0=未分类)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "所属产品Id", DefaultValue = "0")]
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 所属网关Code(对应网关实体/通道的标识,连接时据此刻查找 IP/端口/串口)
|
/// 所属网关Code(对应网关实体/通道的标识,连接时据此刻查找 IP/端口/串口)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Model.Entity.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品分类(字典:维护产品分类,新增产品时可设置分类)
|
||||||
|
/// </summary>
|
||||||
|
public class ProductCategoryEntity : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 分类编码
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "分类编码", Length = 50)]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分类名称
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "分类名称", Length = 100, IsNullable = true)]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "排序号")]
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Model.Entity.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品(按型号区分;产品给设备归类,产品的物模型/告警等配置与设备相互独立)
|
||||||
|
/// </summary>
|
||||||
|
public class ProductEntity : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 型号(唯一,如 GT-100)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "型号", Length = 100)]
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品名称
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "产品名称", Length = 100, IsNullable = true)]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品分类Id(关联 ProductCategoryEntity.Id,0 表示未分类)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "产品分类Id")]
|
||||||
|
public long CategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 制造商
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "制造商", Length = 100, IsNullable = true)]
|
||||||
|
public string? Manufacturer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 产品描述
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "产品描述", Length = 500, IsNullable = true)]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通讯协议(复用 IotDeviceProtocolEnum:决定 DeviceCommand 驱动分支)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "通讯协议")]
|
||||||
|
public IotDeviceProtocolEnum ProtocolType { get; set; } = IotDeviceProtocolEnum.ModbusTcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息模型(消息协议/报文格式描述,如 JSON / 透传)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "消息模型", Length = 64, IsNullable = true)]
|
||||||
|
public string? MessageModel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "是否启用")]
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
namespace Model.Entity.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型归属类型(物模型点表 OwnerType:点挂在哪一类对象下)
|
||||||
|
/// </summary>
|
||||||
|
public enum ThingOwnerTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 产品(型号)
|
||||||
|
/// </summary>
|
||||||
|
Product = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备(实例)
|
||||||
|
/// </summary>
|
||||||
|
Device = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 点读写权限(决定该点是否可用于下发指令)
|
||||||
|
/// </summary>
|
||||||
|
public enum ThingRwEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 只读
|
||||||
|
/// </summary>
|
||||||
|
ReadOnly = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 只写
|
||||||
|
/// </summary>
|
||||||
|
WriteOnly = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读写
|
||||||
|
/// </summary>
|
||||||
|
ReadWrite = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 寄存器类型
|
||||||
|
/// </summary>
|
||||||
|
public enum ThingRegisterTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 线圈(读/写 bool)
|
||||||
|
/// </summary>
|
||||||
|
Coil = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保持寄存器(读/写)
|
||||||
|
/// </summary>
|
||||||
|
HoldingRegister = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输入寄存器(只读,如 UMC1300 的温度 PV)
|
||||||
|
/// </summary>
|
||||||
|
InputRegister = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据类型
|
||||||
|
/// </summary>
|
||||||
|
public enum ThingDataTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 布尔
|
||||||
|
/// </summary>
|
||||||
|
Bool = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 16位有符号整数
|
||||||
|
/// </summary>
|
||||||
|
Int16 = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 16位无符号整数
|
||||||
|
/// </summary>
|
||||||
|
UInt16 = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 32位整数(占2寄存器,暂不支持下发)
|
||||||
|
/// </summary>
|
||||||
|
Int32 = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 32位浮点(占2寄存器,暂不支持下发)
|
||||||
|
/// </summary>
|
||||||
|
Float = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Model.Entity.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点(属性/指令通用表;通过 OwnerType+OwnerId 归属产品或设备)
|
||||||
|
/// 一条点 = 一个寄存器/线圈映射;可写点即"下发指令"的入口
|
||||||
|
/// </summary>
|
||||||
|
public class ThingModelPointEntity : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 归属类型(1=产品 2=设备)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "归属类型")]
|
||||||
|
public ThingOwnerTypeEnum OwnerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 归属对象Id(ProductEntity.Id / IotDeviceEntity.Id)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "归属对象Id")]
|
||||||
|
public long OwnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 点编码(同一归属内唯一,如 TempPV)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "点编码", Length = 64)]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 点名称
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "点名称", Length = 100, IsNullable = true)]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 寄存器类型(线圈/保持寄存器/输入寄存器)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "寄存器类型")]
|
||||||
|
public ThingRegisterTypeEnum RegisterType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读写权限
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "读写权限")]
|
||||||
|
public ThingRwEnum Rw { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据类型
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "数据类型")]
|
||||||
|
public ThingDataTypeEnum DataType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 寄存器起始地址
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "寄存器起始地址")]
|
||||||
|
public ushort Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 换算系数(工程值 = 寄存器原始值 / Scale + Offset,对应驱动内 SCALE)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "换算系数", DecimalDigits = 4)]
|
||||||
|
public double Scale { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 偏移量
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "偏移量", DecimalDigits = 4)]
|
||||||
|
public double Offset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单位
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "单位", Length = 16, IsNullable = true)]
|
||||||
|
public string? Unit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "是否启用")]
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "排序号")]
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,24 @@ namespace Model.Entity.Inspection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AlertRuleEntity : BaseEntity
|
public class AlertRuleEntity : BaseEntity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 归属类型(0=全局/旧数据;1=产品 2=设备)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "归属类型", DefaultValue = "0")]
|
||||||
|
public byte OwnerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 归属对象Id(ProductEntity.Id / IotDeviceEntity.Id,0 表示全局)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "归属对象Id", DefaultValue = "0")]
|
||||||
|
public long OwnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 绑定点编码(关联物模型点 Code;为空表示整产品/设备级规则)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "绑定点编码", Length = 64, IsNullable = true)]
|
||||||
|
public string? PointCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 规则名称
|
/// 规则名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -255,6 +255,9 @@ namespace Model.Mapper
|
|||||||
return new AlertRuleDto
|
return new AlertRuleDto
|
||||||
{
|
{
|
||||||
Id = entity.Id.ToString(),
|
Id = entity.Id.ToString(),
|
||||||
|
OwnerType = entity.OwnerType,
|
||||||
|
OwnerId = entity.OwnerId,
|
||||||
|
PointCode = entity.PointCode,
|
||||||
IsDel = entity.IsDel,
|
IsDel = entity.IsDel,
|
||||||
CreateTime = entity.CreateTime,
|
CreateTime = entity.CreateTime,
|
||||||
RuleName = entity.RuleName,
|
RuleName = entity.RuleName,
|
||||||
@@ -352,6 +355,9 @@ namespace Model.Mapper
|
|||||||
return new AlertRuleEntity
|
return new AlertRuleEntity
|
||||||
{
|
{
|
||||||
Id = ParseId(dto.Id),
|
Id = ParseId(dto.Id),
|
||||||
|
OwnerType = dto.OwnerType,
|
||||||
|
OwnerId = dto.OwnerId,
|
||||||
|
PointCode = dto.PointCode,
|
||||||
IsDel = dto.IsDel,
|
IsDel = dto.IsDel,
|
||||||
CreateTime = dto.CreateTime,
|
CreateTime = dto.CreateTime,
|
||||||
RuleName = dto.RuleName,
|
RuleName = dto.RuleName,
|
||||||
@@ -403,6 +409,7 @@ namespace Model.Mapper
|
|||||||
Code = entity.Code,
|
Code = entity.Code,
|
||||||
Name = entity.Name,
|
Name = entity.Name,
|
||||||
DeviceType = entity.DeviceType,
|
DeviceType = entity.DeviceType,
|
||||||
|
ProductId = entity.ProductId.ToString(),
|
||||||
GatewayCode = entity.GatewayCode,
|
GatewayCode = entity.GatewayCode,
|
||||||
SlaveId = entity.SlaveId,
|
SlaveId = entity.SlaveId,
|
||||||
Manufacturer = entity.Manufacturer,
|
Manufacturer = entity.Manufacturer,
|
||||||
@@ -437,6 +444,7 @@ namespace Model.Mapper
|
|||||||
Code = dto.Code,
|
Code = dto.Code,
|
||||||
Name = dto.Name,
|
Name = dto.Name,
|
||||||
DeviceType = dto.DeviceType,
|
DeviceType = dto.DeviceType,
|
||||||
|
ProductId = ParseId(dto.ProductId),
|
||||||
GatewayCode = dto.GatewayCode,
|
GatewayCode = dto.GatewayCode,
|
||||||
SlaveId = dto.SlaveId,
|
SlaveId = dto.SlaveId,
|
||||||
Manufacturer = dto.Manufacturer,
|
Manufacturer = dto.Manufacturer,
|
||||||
@@ -476,5 +484,186 @@ namespace Model.Mapper
|
|||||||
return entities?.Select(e => e.ToDto()).ToList() ?? new List<DeviceLogDto>();
|
return entities?.Select(e => e.ToDto()).ToList() ?? new List<DeviceLogDto>();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 产品分类
|
||||||
|
/// <summary>
|
||||||
|
/// ProductCategoryEntity → ProductCategoryDto
|
||||||
|
/// </summary>
|
||||||
|
public static ProductCategoryDto ToDto(this ProductCategoryEntity entity)
|
||||||
|
{
|
||||||
|
if (entity == null) return null;
|
||||||
|
return new ProductCategoryDto
|
||||||
|
{
|
||||||
|
Id = entity.Id.ToString(),
|
||||||
|
Code = entity.Code,
|
||||||
|
Name = entity.Name,
|
||||||
|
Sort = entity.Sort,
|
||||||
|
Remark = entity.Remark,
|
||||||
|
CreateTime = entity.CreateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List<ProductCategoryEntity> → List<ProductCategoryDto>
|
||||||
|
/// </summary>
|
||||||
|
public static List<ProductCategoryDto> ToDtoList(this List<ProductCategoryEntity> entities)
|
||||||
|
{
|
||||||
|
return entities?.Select(e => e.ToDto()).ToList() ?? new List<ProductCategoryDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ProductCategoryDto → ProductCategoryEntity(入参映射)
|
||||||
|
/// </summary>
|
||||||
|
public static ProductCategoryEntity ToEntity(this ProductCategoryDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null) return null;
|
||||||
|
return new ProductCategoryEntity
|
||||||
|
{
|
||||||
|
Id = ParseId(dto.Id),
|
||||||
|
Code = dto.Code,
|
||||||
|
Name = dto.Name,
|
||||||
|
Sort = dto.Sort,
|
||||||
|
Remark = dto.Remark
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 产品
|
||||||
|
/// <summary>
|
||||||
|
/// ProductEntity → ProductDto
|
||||||
|
/// </summary>
|
||||||
|
public static ProductDto ToDto(this ProductEntity entity)
|
||||||
|
{
|
||||||
|
if (entity == null) return null;
|
||||||
|
return new ProductDto
|
||||||
|
{
|
||||||
|
Id = entity.Id.ToString(),
|
||||||
|
Model = entity.Model,
|
||||||
|
Name = entity.Name,
|
||||||
|
CategoryId = entity.CategoryId.ToString(),
|
||||||
|
Manufacturer = entity.Manufacturer,
|
||||||
|
Description = entity.Description,
|
||||||
|
ProtocolType = entity.ProtocolType,
|
||||||
|
MessageModel = entity.MessageModel,
|
||||||
|
IsEnabled = entity.IsEnabled,
|
||||||
|
Remark = entity.Remark,
|
||||||
|
CreateTime = entity.CreateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List<ProductEntity> → List<ProductDto>
|
||||||
|
/// </summary>
|
||||||
|
public static List<ProductDto> ToDtoList(this List<ProductEntity> entities)
|
||||||
|
{
|
||||||
|
return entities?.Select(e => e.ToDto()).ToList() ?? new List<ProductDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ProductDto → ProductEntity(入参映射,IsDel/CreateTime 等服务端管控字段不映射)
|
||||||
|
/// </summary>
|
||||||
|
public static ProductEntity ToEntity(this ProductDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null) return null;
|
||||||
|
return new ProductEntity
|
||||||
|
{
|
||||||
|
Id = ParseId(dto.Id),
|
||||||
|
Model = dto.Model,
|
||||||
|
Name = dto.Name,
|
||||||
|
CategoryId = ParseId(dto.CategoryId),
|
||||||
|
Manufacturer = dto.Manufacturer,
|
||||||
|
Description = dto.Description,
|
||||||
|
ProtocolType = dto.ProtocolType,
|
||||||
|
MessageModel = dto.MessageModel,
|
||||||
|
IsEnabled = dto.IsEnabled,
|
||||||
|
Remark = dto.Remark
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ProductEntity → ProductOptionDto(下拉选项)
|
||||||
|
/// </summary>
|
||||||
|
public static ProductOptionDto ToOptionDto(this ProductEntity entity)
|
||||||
|
{
|
||||||
|
if (entity == null) return null;
|
||||||
|
return new ProductOptionDto
|
||||||
|
{
|
||||||
|
Id = entity.Id.ToString(),
|
||||||
|
Model = entity.Model,
|
||||||
|
Name = entity.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List<ProductEntity> → List<ProductOptionDto>
|
||||||
|
/// </summary>
|
||||||
|
public static List<ProductOptionDto> ToOptionDtoList(this List<ProductEntity> entities)
|
||||||
|
{
|
||||||
|
return entities?.Select(e => e.ToOptionDto()).ToList() ?? new List<ProductOptionDto>();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 物模型点
|
||||||
|
/// <summary>
|
||||||
|
/// ThingModelPointEntity → ThingModelPointDto
|
||||||
|
/// </summary>
|
||||||
|
public static ThingModelPointDto ToDto(this ThingModelPointEntity entity)
|
||||||
|
{
|
||||||
|
if (entity == null) return null;
|
||||||
|
return new ThingModelPointDto
|
||||||
|
{
|
||||||
|
Id = entity.Id.ToString(),
|
||||||
|
OwnerType = entity.OwnerType,
|
||||||
|
OwnerId = entity.OwnerId.ToString(),
|
||||||
|
Code = entity.Code,
|
||||||
|
Name = entity.Name,
|
||||||
|
RegisterType = entity.RegisterType,
|
||||||
|
Rw = entity.Rw,
|
||||||
|
DataType = entity.DataType,
|
||||||
|
Address = entity.Address,
|
||||||
|
Scale = entity.Scale,
|
||||||
|
Offset = entity.Offset,
|
||||||
|
Unit = entity.Unit,
|
||||||
|
Enabled = entity.Enabled,
|
||||||
|
Sort = entity.Sort,
|
||||||
|
Remark = entity.Remark,
|
||||||
|
CreateTime = entity.CreateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List<ThingModelPointEntity> → List<ThingModelPointDto>
|
||||||
|
/// </summary>
|
||||||
|
public static List<ThingModelPointDto> ToDtoList(this List<ThingModelPointEntity> entities)
|
||||||
|
{
|
||||||
|
return entities?.Select(e => e.ToDto()).ToList() ?? new List<ThingModelPointDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ThingModelPointDto → ThingModelPointEntity(入参映射)
|
||||||
|
/// </summary>
|
||||||
|
public static ThingModelPointEntity ToEntity(this ThingModelPointDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null) return null;
|
||||||
|
return new ThingModelPointEntity
|
||||||
|
{
|
||||||
|
Id = ParseId(dto.Id),
|
||||||
|
OwnerType = dto.OwnerType,
|
||||||
|
OwnerId = ParseId(dto.OwnerId),
|
||||||
|
Code = dto.Code,
|
||||||
|
Name = dto.Name,
|
||||||
|
RegisterType = dto.RegisterType,
|
||||||
|
Rw = dto.Rw,
|
||||||
|
DataType = dto.DataType,
|
||||||
|
Address = dto.Address,
|
||||||
|
Scale = dto.Scale,
|
||||||
|
Offset = dto.Offset,
|
||||||
|
Unit = dto.Unit,
|
||||||
|
Enabled = dto.Enabled,
|
||||||
|
Sort = dto.Sort,
|
||||||
|
Remark = dto.Remark
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using Model.Entity.Config;
|
||||||
|
using ORM;
|
||||||
|
using Service.Interface.Config;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Implement.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 设备指令下发 服务实现
|
||||||
|
/// 目前无网关运行时/采集引擎:每次调用先落一条设备日志(始终可验证),
|
||||||
|
/// 设备在线时才实际尝试 Modbus 写(GatewayCode 需为 "ip:port",默认 127.0.0.1:502)。
|
||||||
|
/// </summary>
|
||||||
|
public class DeviceCommandService : IDeviceCommandService
|
||||||
|
{
|
||||||
|
public async Task<Result> SendCommandAsync(DeviceCommandDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null || !long.TryParse(dto.Id, out var deviceId) || deviceId <= 0)
|
||||||
|
return Result.Error("设备Id无效");
|
||||||
|
if (string.IsNullOrWhiteSpace(dto.PointId) || !long.TryParse(dto.PointId, out var pointId) || pointId <= 0)
|
||||||
|
return Result.Error("物模型点Id无效");
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
|
||||||
|
// 载入设备与点
|
||||||
|
var device = await SqlSugarContext.DbContext.Queryable<IotDeviceEntity>()
|
||||||
|
.Where(x => x.Id == deviceId && x.IsDel == 0).FirstAsync();
|
||||||
|
if (device == null)
|
||||||
|
return Result.Error("设备不存在或已被删除");
|
||||||
|
|
||||||
|
var point = await SqlSugarContext.DbContext.Queryable<ThingModelPointEntity>()
|
||||||
|
.Where(x => x.Id == pointId && x.OwnerType == ThingOwnerTypeEnum.Device && x.OwnerId == deviceId && x.IsDel == 0).FirstAsync();
|
||||||
|
if (point == null)
|
||||||
|
return Result.Error("物模型点不存在或不属于该设备");
|
||||||
|
|
||||||
|
// 校验可写
|
||||||
|
if (point.Rw == ThingRwEnum.ReadOnly)
|
||||||
|
return Result.Error($"点【{point.Name ?? point.Code}】为只读,无法下发指令");
|
||||||
|
if (device.ProtocolType != IotDeviceProtocolEnum.ModbusTcp)
|
||||||
|
return Result.Error("当前仅支持 Modbus TCP 协议设备下发指令");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 模拟模式 / 设备离线 → 只记录、不发网络报文
|
||||||
|
if (dto.IsSimulated || device.OnlineStatus != IotDeviceOnlineStatusEnum.Online)
|
||||||
|
{
|
||||||
|
await WriteLogAsync(device, "Warn", "指令", dto.IsSimulated
|
||||||
|
? $"【模拟】点 {point.Name ?? point.Code} 下发值 {dto.Value}(未发送网络报文)"
|
||||||
|
: $"设备离线,指令已记录但未发送(点 {point.Name ?? point.Code} 值 {dto.Value})");
|
||||||
|
return dto.IsSimulated
|
||||||
|
? Result.Success()
|
||||||
|
: Result.Error("设备离线,指令已记录但未发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际 Modbus 写
|
||||||
|
var (host, port) = ParseGateway(device.GatewayCode);
|
||||||
|
using var modbus = new ModbusTcp();
|
||||||
|
modbus.ConfigureDevice(host, port, 3000, 3000);
|
||||||
|
bool connected = await modbus.ConnectAsync();
|
||||||
|
if (!connected)
|
||||||
|
{
|
||||||
|
await WriteLogAsync(device, "Error", "指令",
|
||||||
|
$"网关 {device.GatewayCode} 连接失败,指令未发送(点 {point.Name ?? point.Code} 值 {dto.Value})");
|
||||||
|
return Result.Error($"网关 {device.GatewayCode} 连接失败,指令未发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
string desc;
|
||||||
|
if (point.RegisterType == ThingRegisterTypeEnum.Coil)
|
||||||
|
{
|
||||||
|
bool coil = dto.Value != 0;
|
||||||
|
await modbus.WriteSingleCoilAsync(device.SlaveId, point.Address, coil);
|
||||||
|
desc = $"写线圈 {point.Name ?? point.Code} 地址 {point.Address} = {coil}";
|
||||||
|
}
|
||||||
|
else if (point.DataType == ThingDataTypeEnum.Int32 || point.DataType == ThingDataTypeEnum.Float)
|
||||||
|
{
|
||||||
|
// 32 位类型占连续 2 个寄存器,走 FC16 连写(Int32 按有符号整数编码,Float 按 IEEE754 编码)
|
||||||
|
ushort[] words = ToWords(dto.Value, point);
|
||||||
|
await modbus.WriteMultipleRegistersAsync(device.SlaveId, point.Address, words);
|
||||||
|
desc = $"写双寄存器 {point.Name ?? point.Code} 地址 {point.Address}~{point.Address + 1} = [{words[0]}, {words[1]}](工程值 {dto.Value})";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ushort raw = ToRaw(dto.Value, point);
|
||||||
|
await modbus.WriteSingleRegisterAsync(device.SlaveId, point.Address, raw);
|
||||||
|
desc = $"写寄存器 {point.Name ?? point.Code} 地址 {point.Address} = {raw}(工程值 {dto.Value})";
|
||||||
|
}
|
||||||
|
|
||||||
|
await SqlSugarContext.DbContext.Updateable<IotDeviceEntity>()
|
||||||
|
.SetColumns(x => new IotDeviceEntity { LastCollectTime = now, LastError = null })
|
||||||
|
.Where(x => x.Id == device.Id).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
await WriteLogAsync(device, "Info", "指令", $"下发成功:{desc}");
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await WriteLogAsync(device, "Error", "指令", $"下发失败:{ex.Message}");
|
||||||
|
return Result.Error($"指令下发异常:{ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工程值 → 寄存器原始值:raw = (value - Offset) / Scale,取整并夹到寄存器范围
|
||||||
|
/// Int16 按有符号处理(补码),其余类型按 0..65535
|
||||||
|
/// </summary>
|
||||||
|
private static ushort ToRaw(double value, ThingModelPointEntity point)
|
||||||
|
{
|
||||||
|
double raw = (value - point.Offset) / point.Scale;
|
||||||
|
if (point.DataType == ThingDataTypeEnum.Int16)
|
||||||
|
{
|
||||||
|
if (raw <= short.MinValue) return unchecked((ushort)short.MinValue);
|
||||||
|
if (raw >= short.MaxValue) return (ushort)short.MaxValue;
|
||||||
|
return unchecked((ushort)(short)Math.Round(raw));
|
||||||
|
}
|
||||||
|
if (raw <= 0) return 0;
|
||||||
|
if (raw >= 65535) return 65535;
|
||||||
|
return (ushort)Math.Round(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工程值 → 32 位双寄存器字数组(raw32 = (value - Offset) / Scale)
|
||||||
|
/// Int32 按有符号整数编码,Float 按 IEEE754 编码;字序按 Modbus 常规高字在前(AB CD)
|
||||||
|
/// </summary>
|
||||||
|
private static ushort[] ToWords(double value, ThingModelPointEntity point)
|
||||||
|
{
|
||||||
|
uint bits;
|
||||||
|
if (point.DataType == ThingDataTypeEnum.Float)
|
||||||
|
{
|
||||||
|
bits = BitConverter.SingleToUInt32Bits((float)((value - point.Offset) / point.Scale));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double raw = Math.Round((value - point.Offset) / point.Scale);
|
||||||
|
if (raw < int.MinValue) raw = int.MinValue;
|
||||||
|
if (raw > int.MaxValue) raw = int.MaxValue;
|
||||||
|
bits = unchecked((uint)(int)raw);
|
||||||
|
}
|
||||||
|
return new[] { (ushort)(bits >> 16), (ushort)(bits & 0xFFFF) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 GatewayCode 为 host:port;非法/缺失回退 127.0.0.1:502
|
||||||
|
/// </summary>
|
||||||
|
private static (string host, int port) ParseGateway(string? gatewayCode)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(gatewayCode))
|
||||||
|
{
|
||||||
|
var idx = gatewayCode.LastIndexOf(':');
|
||||||
|
if (idx > 0 && IPAddress.TryParse(gatewayCode[..idx], out _) && int.TryParse(gatewayCode[(idx + 1)..], out var p) && p > 0)
|
||||||
|
return (gatewayCode[..idx], p);
|
||||||
|
}
|
||||||
|
return ("127.0.0.1", 502);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写一条设备日志(设备独享日志,前端设备日志抽屉可见)
|
||||||
|
/// </summary>
|
||||||
|
private static async Task WriteLogAsync(IotDeviceEntity device, string level, string logType, string message)
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
await SqlSugarContext.DbContext.Insertable(new DeviceLogEntity
|
||||||
|
{
|
||||||
|
DeviceId = device.Id,
|
||||||
|
DeviceCode = device.Code,
|
||||||
|
Level = level,
|
||||||
|
LogType = logType,
|
||||||
|
Message = message,
|
||||||
|
LogTime = now,
|
||||||
|
CreateTime = now
|
||||||
|
}).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ using Service.Interface.Config;
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Service.Implement.Config
|
namespace Service.Implement.Config
|
||||||
@@ -17,9 +18,9 @@ namespace Service.Implement.Config
|
|||||||
public class DeviceService : IDeviceService
|
public class DeviceService : IDeviceService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询设备列表(支持关键字搜索编号/名称/类型)
|
/// 分页查询设备列表(支持关键字搜索编号/名称/类型,可按所属产品筛选)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<Result<List<IotDeviceDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword)
|
public async Task<Result<List<IotDeviceDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword, long? productId = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -27,10 +28,13 @@ namespace Service.Implement.Config
|
|||||||
.Where(x => x.IsDel == 0)
|
.Where(x => x.IsDel == 0)
|
||||||
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
|
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
|
||||||
x => x.Code.Contains(keyword!) || x.Name.Contains(keyword!) || x.DeviceType.Contains(keyword!))
|
x => x.Code.Contains(keyword!) || x.Name.Contains(keyword!) || x.DeviceType.Contains(keyword!))
|
||||||
|
.WhereIF(productId.HasValue && productId.Value > 0, x => x.ProductId == productId!.Value)
|
||||||
.OrderBy(x => x.CreateTime, OrderByType.Desc)
|
.OrderBy(x => x.CreateTime, OrderByType.Desc)
|
||||||
.ToPageListAsync(pageIndex, pageSize, total);
|
.ToPageListAsync(pageIndex, pageSize, total);
|
||||||
|
|
||||||
return Result<List<IotDeviceDto>>.Success(list.ToDtoList());
|
var dtos = list.ToDtoList();
|
||||||
|
await FillProductNames(dtos);
|
||||||
|
return Result<List<IotDeviceDto>>.Success(dtos);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -50,7 +54,10 @@ namespace Service.Implement.Config
|
|||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
return Result<IotDeviceDto>.Error("设备不存在或已被删除");
|
return Result<IotDeviceDto>.Error("设备不存在或已被删除");
|
||||||
return Result<IotDeviceDto>.Success(entity.ToDto());
|
|
||||||
|
var dto = entity.ToDto();
|
||||||
|
await FillProductNames(new List<IotDeviceDto> { dto });
|
||||||
|
return Result<IotDeviceDto>.Success(dto);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -157,5 +164,30 @@ namespace Service.Implement.Config
|
|||||||
return Result<List<DeviceLogDto>>.Error("查询设备日志失败", ex);
|
return Result<List<DeviceLogDto>>.Error("查询设备日志失败", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 给设备 DTO 填充所属产品的型号/名称(ProductName 展示用)
|
||||||
|
/// </summary>
|
||||||
|
private async Task FillProductNames(List<IotDeviceDto> dtos)
|
||||||
|
{
|
||||||
|
if (dtos == null || dtos.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ids = dtos.Where(x => long.TryParse(x.ProductId, out var pid) && pid > 0)
|
||||||
|
.Select(x => long.Parse(x.ProductId!)).Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var products = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.Where(x => ids.Contains(x.Id) && x.IsDel == 0)
|
||||||
|
.ToListAsync();
|
||||||
|
var map = products.ToDictionary(p => p.Id, p => p.Name ?? p.Model);
|
||||||
|
|
||||||
|
foreach (var d in dtos)
|
||||||
|
{
|
||||||
|
if (long.TryParse(d.ProductId, out var pid) && map.TryGetValue(pid, out var name))
|
||||||
|
d.ProductName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,255 @@
|
|||||||
using Service.Interface;
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using Model.Entity.Config;
|
||||||
|
using Model.Mapper;
|
||||||
|
using ORM;
|
||||||
|
using Service.Interface.Config;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Service.Implement
|
namespace Service.Implement.Config
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品管理 服务实现
|
/// 产品管理(含产品分类)服务实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProductService : IProductService
|
public class ProductService : IProductService
|
||||||
{
|
{
|
||||||
// TODO: 实现 产品管理 相关方法
|
// ===================== 产品分类 =====================
|
||||||
|
|
||||||
|
public async Task<Result<List<ProductCategoryDto>>> GetCategoriesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<ProductCategoryEntity>()
|
||||||
|
.Where(x => x.IsDel == 0)
|
||||||
|
.OrderBy(x => x.Sort)
|
||||||
|
.ToListAsync();
|
||||||
|
return Result<List<ProductCategoryDto>>.Success(list.ToDtoList());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<ProductCategoryDto>>.Error("查询产品分类失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> AddCategoryAsync(ProductCategoryDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null || string.IsNullOrWhiteSpace(dto.Code))
|
||||||
|
return Result.Error("分类编码不能为空");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ProductCategoryEntity>()
|
||||||
|
.AnyAsync(x => x.Code == dto.Code && x.IsDel == 0);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"分类编码【{dto.Code}】已存在");
|
||||||
|
|
||||||
|
var entity = dto.ToEntity();
|
||||||
|
entity.CreateTime = DateTime.Now;
|
||||||
|
await SqlSugarContext.DbContext.Insertable(entity).ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("新增产品分类失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> UpdateCategoryAsync(ProductCategoryDto dto)
|
||||||
|
{
|
||||||
|
var entity = dto?.ToEntity();
|
||||||
|
if (entity == null || entity.Id <= 0)
|
||||||
|
return Result.Error("分类Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ProductCategoryEntity>()
|
||||||
|
.AnyAsync(x => x.Code == entity.Code && x.IsDel == 0 && x.Id != entity.Id);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"分类编码【{entity.Code}】已存在");
|
||||||
|
|
||||||
|
await SqlSugarContext.DbContext.Updateable(entity)
|
||||||
|
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("修改产品分类失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteCategoryAsync(long id)
|
||||||
|
{
|
||||||
|
if (id <= 0)
|
||||||
|
return Result.Error("分类Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SqlSugarContext.DbContext.Updateable<ProductCategoryEntity>()
|
||||||
|
.SetColumns(x => x.IsDel == 1)
|
||||||
|
.Where(x => x.Id == id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("删除产品分类失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 产品 =====================
|
||||||
|
|
||||||
|
public async Task<Result<List<ProductDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword, long categoryId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.Where(x => x.IsDel == 0)
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
|
||||||
|
x => x.Model.Contains(keyword!) || (x.Name != null && x.Name.Contains(keyword!)))
|
||||||
|
.WhereIF(categoryId > 0, x => x.CategoryId == categoryId)
|
||||||
|
.OrderBy(x => x.CreateTime, OrderByType.Desc)
|
||||||
|
.ToPageListAsync(pageIndex, pageSize, total);
|
||||||
|
|
||||||
|
var dtos = list.ToDtoList();
|
||||||
|
await FillCategoryNames(dtos);
|
||||||
|
return Result<List<ProductDto>>.Success(dtos);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<ProductDto>>.Error("查询产品列表失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<List<ProductOptionDto>>> GetOptionsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.Where(x => x.IsDel == 0 && x.IsEnabled)
|
||||||
|
.OrderBy(x => x.Model)
|
||||||
|
.ToListAsync();
|
||||||
|
return Result<List<ProductOptionDto>>.Success(list.ToOptionDtoList());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<ProductOptionDto>>.Error("查询产品选项失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<ProductDto>> GetByIdAsync(long id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var entity = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.Where(x => x.Id == id && x.IsDel == 0)
|
||||||
|
.FirstAsync();
|
||||||
|
if (entity == null)
|
||||||
|
return Result<ProductDto>.Error("产品不存在或已被删除");
|
||||||
|
|
||||||
|
var dto = entity.ToDto();
|
||||||
|
await FillCategoryNames(new List<ProductDto> { dto });
|
||||||
|
return Result<ProductDto>.Success(dto);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<ProductDto>.Error("查询产品详情失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> AddAsync(ProductDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null || string.IsNullOrWhiteSpace(dto.Model))
|
||||||
|
return Result.Error("产品型号不能为空");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.AnyAsync(x => x.Model == dto.Model && x.IsDel == 0);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"产品型号【{dto.Model}】已存在");
|
||||||
|
|
||||||
|
var entity = dto.ToEntity();
|
||||||
|
entity.CreateTime = DateTime.Now;
|
||||||
|
await SqlSugarContext.DbContext.Insertable(entity).ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("新增产品失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> UpdateAsync(ProductDto dto)
|
||||||
|
{
|
||||||
|
var entity = dto?.ToEntity();
|
||||||
|
if (entity == null || entity.Id <= 0)
|
||||||
|
return Result.Error("产品Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ProductEntity>()
|
||||||
|
.AnyAsync(x => x.Model == entity.Model && x.IsDel == 0 && x.Id != entity.Id);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"产品型号【{entity.Model}】已存在");
|
||||||
|
|
||||||
|
await SqlSugarContext.DbContext.Updateable(entity)
|
||||||
|
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("修改产品失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteAsync(long id)
|
||||||
|
{
|
||||||
|
if (id <= 0)
|
||||||
|
return Result.Error("产品Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SqlSugarContext.DbContext.Updateable<ProductEntity>()
|
||||||
|
.SetColumns(x => x.IsDel == 1)
|
||||||
|
.Where(x => x.Id == id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("删除产品失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 给产品 DTO 填充分类名称(供列表/详情展示)
|
||||||
|
/// </summary>
|
||||||
|
private async Task FillCategoryNames(List<ProductDto> dtos)
|
||||||
|
{
|
||||||
|
if (dtos == null || dtos.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ids = dtos.Where(x => x.CategoryId != null && long.TryParse(x.CategoryId, out _))
|
||||||
|
.Select(x => long.Parse(x.CategoryId!)).Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var categories = await SqlSugarContext.DbContext.Queryable<ProductCategoryEntity>()
|
||||||
|
.Where(x => ids.Contains(x.Id) && x.IsDel == 0)
|
||||||
|
.ToListAsync();
|
||||||
|
var map = categories.ToDictionary(c => c.Id, c => c.Name);
|
||||||
|
|
||||||
|
foreach (var d in dtos)
|
||||||
|
{
|
||||||
|
if (long.TryParse(d.CategoryId, out var cid) && map.TryGetValue(cid, out var name))
|
||||||
|
d.CategoryName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using Model.Entity.Config;
|
||||||
|
using Model.Mapper;
|
||||||
|
using ORM;
|
||||||
|
using Service.Interface.Config;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Implement.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点 服务实现(产品/设备通用,按 OwnerType+OwnerId 归属)
|
||||||
|
/// </summary>
|
||||||
|
public class ThingPointService : IThingPointService
|
||||||
|
{
|
||||||
|
public async Task<Result<List<ThingModelPointDto>>> GetListAsync(int ownerType, long ownerId, string? keyword)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<ThingModelPointEntity>()
|
||||||
|
.Where(x => x.IsDel == 0)
|
||||||
|
.Where(x => x.OwnerType == (ThingOwnerTypeEnum)ownerType && x.OwnerId == ownerId)
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
|
||||||
|
x => x.Code.Contains(keyword!) || (x.Name != null && x.Name.Contains(keyword!)))
|
||||||
|
.OrderBy(x => x.Sort)
|
||||||
|
.ToListAsync();
|
||||||
|
return Result<List<ThingModelPointDto>>.Success(list.ToDtoList());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<ThingModelPointDto>>.Error("查询物模型点失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<ThingModelPointDto>> GetByIdAsync(long id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var entity = await SqlSugarContext.DbContext.Queryable<ThingModelPointEntity>()
|
||||||
|
.Where(x => x.Id == id && x.IsDel == 0)
|
||||||
|
.FirstAsync();
|
||||||
|
if (entity == null)
|
||||||
|
return Result<ThingModelPointDto>.Error("物模型点不存在或已被删除");
|
||||||
|
return Result<ThingModelPointDto>.Success(entity.ToDto());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<ThingModelPointDto>.Error("查询物模型点失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> AddAsync(ThingModelPointDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null || string.IsNullOrWhiteSpace(dto.Code))
|
||||||
|
return Result.Error("点编码不能为空");
|
||||||
|
if (dto.OwnerId == null || !long.TryParse(dto.OwnerId, out var ownerId) || ownerId <= 0)
|
||||||
|
return Result.Error("归属对象Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ThingModelPointEntity>()
|
||||||
|
.AnyAsync(x => x.OwnerType == dto.OwnerType && x.OwnerId == ownerId
|
||||||
|
&& x.Code == dto.Code && x.IsDel == 0);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"点编码【{dto.Code}】在该归属下已存在");
|
||||||
|
|
||||||
|
var entity = dto.ToEntity();
|
||||||
|
entity.CreateTime = DateTime.Now;
|
||||||
|
await SqlSugarContext.DbContext.Insertable(entity).ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("新增物模型点失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> UpdateAsync(ThingModelPointDto dto)
|
||||||
|
{
|
||||||
|
var entity = dto?.ToEntity();
|
||||||
|
if (entity == null || entity.Id <= 0)
|
||||||
|
return Result.Error("物模型点Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool exists = await SqlSugarContext.DbContext.Queryable<ThingModelPointEntity>()
|
||||||
|
.AnyAsync(x => x.OwnerType == entity.OwnerType && x.OwnerId == entity.OwnerId
|
||||||
|
&& x.Code == entity.Code && x.IsDel == 0 && x.Id != entity.Id);
|
||||||
|
if (exists)
|
||||||
|
return Result.Error($"点编码【{entity.Code}】在该归属下已存在");
|
||||||
|
|
||||||
|
await SqlSugarContext.DbContext.Updateable(entity)
|
||||||
|
.IgnoreColumns(x => new { x.CreateTime, x.IsDel, x.OwnerType, x.OwnerId })
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("修改物模型点失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteAsync(long id)
|
||||||
|
{
|
||||||
|
if (id <= 0)
|
||||||
|
return Result.Error("物模型点Id无效");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SqlSugarContext.DbContext.Updateable<ThingModelPointEntity>()
|
||||||
|
.SetColumns(x => x.IsDel == 1)
|
||||||
|
.Where(x => x.Id == id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Error("删除物模型点失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,14 +16,14 @@ namespace Service.Implement
|
|||||||
public class AlertRuleService : IAlertRuleService
|
public class AlertRuleService : IAlertRuleService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取全部告警规则列表
|
/// 获取全局告警规则列表(OwnerType=0,不含产品/设备归属规则)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<Result<List<AlertRuleDto>>> GetListAsync()
|
public async Task<Result<List<AlertRuleDto>>> GetListAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var list = await SqlSugarContext.DbContext.Queryable<AlertRuleEntity>()
|
var list = await SqlSugarContext.DbContext.Queryable<AlertRuleEntity>()
|
||||||
.Where(x => x.IsDel == 0)
|
.Where(x => x.IsDel == 0 && x.OwnerType == 0)
|
||||||
.OrderBy(x => x.CreateTime, SqlSugar.OrderByType.Desc)
|
.OrderBy(x => x.CreateTime, SqlSugar.OrderByType.Desc)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
@@ -36,6 +36,27 @@ namespace Service.Implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某归属对象下的规则列表(OwnerType=产品/设备,可选按点过滤)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<AlertRuleDto>>> GetOwnerRulesAsync(int ownerType, long ownerId, string? pointCode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<AlertRuleEntity>()
|
||||||
|
.Where(x => x.IsDel == 0 && x.OwnerType == (byte)ownerType && x.OwnerId == ownerId)
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(pointCode), x => x.PointCode == pointCode)
|
||||||
|
.OrderBy(x => x.CreateTime, SqlSugar.OrderByType.Desc)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<List<AlertRuleDto>>.Success(list.ToDtoList());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<AlertRuleDto>>.Error("查询归属告警规则失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 新增告警规则(DTO → Entity,string Id 转 long)
|
/// 新增告警规则(DTO → Entity,string Id 转 long)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Interface.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 设备指令下发 服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IDeviceCommandService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 按物模型可写点向设备下发指令
|
||||||
|
/// 每次调用都会写入设备日志;网关不可达时优雅降级(记录未连通)。
|
||||||
|
/// </summary>
|
||||||
|
Task<Result> SendCommandAsync(DeviceCommandDto dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,9 @@ namespace Service.Interface.Config
|
|||||||
public interface IDeviceService
|
public interface IDeviceService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询设备列表(支持关键字搜索编号/名称/类型)
|
/// 分页查询设备列表(支持关键字搜索编号/名称/类型,可按所属产品筛选)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Result<List<IotDeviceDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword);
|
Task<Result<List<IotDeviceDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword, long? productId = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据 Id 获取设备详情
|
/// 根据 Id 获取设备详情
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
namespace Service.Interface
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Interface.Config
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品管理 服务接口
|
/// 产品管理(含产品分类)服务接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IProductService
|
public interface IProductService
|
||||||
{
|
{
|
||||||
// TODO: 定义 产品管理 相关方法
|
// ===== 产品分类 =====
|
||||||
|
/// <summary>产品分类列表</summary>
|
||||||
|
Task<Result<List<ProductCategoryDto>>> GetCategoriesAsync();
|
||||||
|
|
||||||
|
/// <summary>新增产品分类</summary>
|
||||||
|
Task<Result> AddCategoryAsync(ProductCategoryDto dto);
|
||||||
|
|
||||||
|
/// <summary>修改产品分类</summary>
|
||||||
|
Task<Result> UpdateCategoryAsync(ProductCategoryDto dto);
|
||||||
|
|
||||||
|
/// <summary>删除产品分类(软删除)</summary>
|
||||||
|
Task<Result> DeleteCategoryAsync(long id);
|
||||||
|
|
||||||
|
// ===== 产品 =====
|
||||||
|
/// <summary>分页查询产品列表(关键字搜型号/名称;可按分类筛选)</summary>
|
||||||
|
Task<Result<List<ProductDto>>> GetPagedAsync(int pageIndex, int pageSize, SqlSugar.RefAsync<int> total, string? keyword, long categoryId);
|
||||||
|
|
||||||
|
/// <summary>下拉选项(不分页,设备选择所属产品用)</summary>
|
||||||
|
Task<Result<List<ProductOptionDto>>> GetOptionsAsync();
|
||||||
|
|
||||||
|
/// <summary>产品详情</summary>
|
||||||
|
Task<Result<ProductDto>> GetByIdAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>新增产品(型号唯一校验)</summary>
|
||||||
|
Task<Result> AddAsync(ProductDto dto);
|
||||||
|
|
||||||
|
/// <summary>修改产品</summary>
|
||||||
|
Task<Result> UpdateAsync(ProductDto dto);
|
||||||
|
|
||||||
|
/// <summary>删除产品(软删除)</summary>
|
||||||
|
Task<Result> DeleteAsync(long id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Dto.Config;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Interface.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物模型点 服务接口(产品/设备通用,按 OwnerType+OwnerId 归属)
|
||||||
|
/// </summary>
|
||||||
|
public interface IThingPointService
|
||||||
|
{
|
||||||
|
/// <summary>查询某归属下的点列表</summary>
|
||||||
|
Task<Result<List<ThingModelPointDto>>> GetListAsync(int ownerType, long ownerId, string? keyword);
|
||||||
|
|
||||||
|
/// <summary>查询单点详情</summary>
|
||||||
|
Task<Result<ThingModelPointDto>> GetByIdAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>新增点(同一归属内 Code 唯一)</summary>
|
||||||
|
Task<Result> AddAsync(ThingModelPointDto dto);
|
||||||
|
|
||||||
|
/// <summary>修改点</summary>
|
||||||
|
Task<Result> UpdateAsync(ThingModelPointDto dto);
|
||||||
|
|
||||||
|
/// <summary>删除点(软删除)</summary>
|
||||||
|
Task<Result> DeleteAsync(long id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using Model;
|
using Model;
|
||||||
using Model.Dto.Inspection;
|
using Model.Dto.Inspection;
|
||||||
using Model.Entity.Inspection;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -12,10 +11,15 @@ namespace Service.Interface
|
|||||||
public interface IAlertRuleService
|
public interface IAlertRuleService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取全部告警规则列表
|
/// 获取全局告警规则列表(OwnerType=0,不含产品/设备归属规则)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Result<List<AlertRuleDto>>> GetListAsync();
|
Task<Result<List<AlertRuleDto>>> GetListAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某归属对象下的规则列表(OwnerType=产品/设备,可选按点过滤)
|
||||||
|
/// </summary>
|
||||||
|
Task<Result<List<AlertRuleDto>>> GetOwnerRulesAsync(int ownerType, long ownerId, string? pointCode);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 新增告警规则(前端传 DTO,Id 为 string)
|
/// 新增告警规则(前端传 DTO,Id 为 string)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||||
<ProjectReference Include="..\Model\Model.csproj" />
|
<ProjectReference Include="..\Model\Model.csproj" />
|
||||||
<ProjectReference Include="..\ORM\ORM.csproj" />
|
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||||
|
|||||||
Reference in New Issue
Block a user