Files
IOT_API/Service/Implement/Config/GatewayService.cs
T
dengbolingandClaude Sonnet 4.6 6aca0297bb feat: 网关管理模块 + RBAC权限认证 + 组织架构 + 审计日志
- 网关管理:GatewayEntity/Service/Controller CRUD + 测试连接;设备 GatewayId 外键关联
- JWT 认证:登录签发 Token(权限编码写入 Claims)、RequirePermission 权限过滤器、CurrentUser 上下文
- RBAC:用户/角色/权限实体与 CRUD,预定义 4 角色 + 6 权限种子(admin/admin123)
- 组织架构:sys_org 固定层级树(公司/实验室/部门/班组白夜班),层级校验,用户挂 OrgId/岗位/技能标签
- 数据权限:角色 DataScope(全部/本组织及下级),用户列表按组织子树过滤
- 防锁死保护:禁止删/禁自己,保证至少一名活跃管理员,角色摘除 user:manage 前校验
- 审计日志:AuditRecorder 接入设备增删改/指令下发/登录登出/组织变更,AuditController 查询
- 设备指令下发按网关表取连接参数;设备列表支持 gatewayId/productId 筛选

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-10 17:31:39 +08:00

263 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Model;
using Model.Dto.Config;
using Model.Entity.Config;
using Model.Mapper;
using ORM;
using Service.Interface.Config;
using SqlSugar;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
namespace Service.Implement.Config
{
/// <summary>
/// 网关管理 服务实现
/// </summary>
public class GatewayService : IGatewayService
{
public async Task<Result<List<GatewayDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword = null, int? protocolType = null)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.IsDel == 0)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.Code.Contains(keyword!) || x.Name.Contains(keyword!))
.WhereIF(protocolType.HasValue, x => x.ProtocolType == (IotDeviceProtocolEnum)protocolType!.Value)
.OrderBy(x => x.CreateTime, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize, total);
return Result<List<GatewayDto>>.Success(list.ToDtoList());
}
catch (Exception ex)
{
return Result<List<GatewayDto>>.Error("查询网关列表失败", ex);
}
}
public async Task<Result<GatewayDto>> GetByIdAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result<GatewayDto>.Error("网关不存在");
return Result<GatewayDto>.Success(entity.ToDto());
}
catch (Exception ex)
{
return Result<GatewayDto>.Error("查询网关详情失败", ex);
}
}
public async Task<Result<GatewayDto>> AddAsync(GatewayDto dto)
{
try
{
// 编码唯一校验
var exists = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Code == dto.Code && x.IsDel == 0).AnyAsync();
if (exists) return Result<GatewayDto>.Error($"网关编码「{dto.Code}」已存在");
var entity = dto.ToEntity();
entity.Id = 0; // 雪花ID自动生成
var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync();
entity.Id = id;
return Result<GatewayDto>.Success(entity.ToDto());
}
catch (Exception ex)
{
return Result<GatewayDto>.Error("新增网关失败", ex);
}
}
public async Task<Result<GatewayDto>> UpdateAsync(GatewayDto dto)
{
try
{
if (!long.TryParse(dto.Id, out var id) || id <= 0)
return Result<GatewayDto>.Error("网关Id无效");
var entity = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result<GatewayDto>.Error("网关不存在");
// 编码唯一校验(排除自身)
var codeExists = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
if (codeExists) return Result<GatewayDto>.Error($"网关编码「{dto.Code}」已存在");
var newEntity = dto.ToEntity();
newEntity.Id = id;
await SqlSugarContext.DbContext.Updateable(newEntity)
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
.ExecuteCommandAsync();
return Result<GatewayDto>.Success(newEntity.ToDto());
}
catch (Exception ex)
{
return Result<GatewayDto>.Error("修改网关失败", ex);
}
}
public async Task<Result> DeleteAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("网关不存在");
// 软删除网关
await SqlSugarContext.DbContext.Updateable<GatewayEntity>()
.SetColumns(x => x.IsDel == 1)
.Where(x => x.Id == id).ExecuteCommandAsync();
// 该网关下设备的 GatewayId 置 0(不级联删除设备)
await SqlSugarContext.DbContext.Updateable<IotDeviceEntity>()
.SetColumns(x => x.GatewayId == 0)
.Where(x => x.GatewayId == id).ExecuteCommandAsync();
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("删除网关失败", ex);
}
}
/// <summary>
/// 测试网关连接:TCP 尝试 TcpClient.ConnectAsync 3s 超时,串口尝试 SerialPort.Open
/// 成功则 OnlineStatus=Online+更新 LastConnectedTime,失败则 OnlineStatus=Fault+记录 LastError
/// </summary>
public async Task<Result<GatewayTestResultDto>> TestConnectionAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result<GatewayTestResultDto>.Error("网关不存在");
bool success = false;
string message = "";
var now = DateTime.Now;
switch (entity.ProtocolType)
{
case IotDeviceProtocolEnum.ModbusTcp:
case IotDeviceProtocolEnum.Tcp:
case IotDeviceProtocolEnum.S7:
// TCP 类:尝试连接 IP:Port
if (string.IsNullOrWhiteSpace(entity.Host) || !entity.Port.HasValue)
{
message = "未配置 IP 地址或端口号";
}
else
{
(success, message) = await TestTcpAsync(entity.Host, entity.Port!.Value, 3000);
}
break;
case IotDeviceProtocolEnum.ModbusRtu:
case IotDeviceProtocolEnum.Serial:
// 串口类:尝试打开串口
if (string.IsNullOrWhiteSpace(entity.ComPort))
{
message = "未配置串口号";
}
else
{
(success, message) = TestSerial(entity.ComPort, entity.BaudRate ?? 9600);
}
break;
default:
message = $"暂不支持 {entity.ProtocolType} 协议的连接测试";
break;
}
// 更新网关状态
await SqlSugarContext.DbContext.Updateable<GatewayEntity>()
.SetColumns(x => new GatewayEntity
{
OnlineStatus = success ? (byte)1 : (byte)3,
LastConnectedTime = success ? now : x.LastConnectedTime,
LastError = success ? null : message
})
.Where(x => x.Id == id).ExecuteCommandAsync();
return Result<GatewayTestResultDto>.Success(new GatewayTestResultDto
{
Success = success,
Message = success ? $"连接成功({entity.Host}:{entity.Port ?? 0}" : message
});
}
catch (Exception ex)
{
return Result<GatewayTestResultDto>.Error("测试连接失败", ex);
}
}
public async Task<Result<List<GatewayOptionDto>>> GetOptionsAsync()
{
try
{
var list = await SqlSugarContext.DbContext.Queryable<GatewayEntity>()
.Where(x => x.IsDel == 0 && x.IsEnabled)
.OrderBy(x => x.Code)
.ToListAsync();
return Result<List<GatewayOptionDto>>.Success(list.ToOptionDtoList());
}
catch (Exception ex)
{
return Result<List<GatewayOptionDto>>.Error("查询网关选项失败", ex);
}
}
#region 私有:连接测试方法
private static async Task<(bool success, string message)> TestTcpAsync(string host, int port, int timeoutMs)
{
try
{
using var client = new TcpClient();
var cts = new System.Threading.CancellationTokenSource(timeoutMs);
await client.ConnectAsync(host, port, cts.Token);
return (true, "");
}
catch (OperationCanceledException)
{
return (false, $"连接超时({timeoutMs / 1000}s):{host}:{port}");
}
catch (SocketException ex)
{
return (false, $"连接失败:{host}:{port}{ex.Message}");
}
catch (Exception ex)
{
return (false, $"连接异常:{ex.Message}");
}
}
private static (bool success, string message) TestSerial(string comPort, int baudRate)
{
try
{
using var sp = new SerialPort(comPort, baudRate);
sp.Open();
sp.Close();
return (true, "");
}
catch (UnauthorizedAccessException)
{
return (false, $"串口 {comPort} 被占用");
}
catch (Exception ex)
{
return (false, $"打开串口 {comPort} 失败:{ex.Message}");
}
}
#endregion
}
}