Compare commits
15
Commits
9e78aa7769
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60fac00887 | ||
|
|
6aca0297bb | ||
|
|
3059befcf5 | ||
|
|
19fb191c3d | ||
|
|
bcd6484f45 | ||
|
|
3927b0c201 | ||
|
|
38cd47410c | ||
|
|
cfa8e78d33 | ||
|
|
6c20630a40 | ||
|
|
2ecd921d26 | ||
|
|
d9b079142b | ||
|
|
24d67baf65 | ||
|
|
2306fbd36b | ||
|
|
01f51fa3b4 | ||
|
|
a027bf5d9e |
@@ -366,3 +366,4 @@ MigrationBackup/
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
/IOT_API/appsettings.json
|
||||
/IOT_API/wwwroot
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- 引用 Common:复用 ImWebhookSigner 加签算法与 Model 中的 Outbox DTO,保证与主站推送行为一致 -->
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,259 @@
|
||||
using Common.Notify;
|
||||
using Model.Dto.Inspection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AlertBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨网告警转发程序(部署在可同时访问"工控内网 IOT_API"与"外网 IM Webhook"的跳板机上)
|
||||
/// 工作方式:轮询拉取内网发件箱(GET outbox/pull)→ 按报文推送飞书/钉钉/企微 → 回写结果(POST outbox/{id}/result)
|
||||
/// 报文自包含 WebhookUrl/Secret/内容,本程序无需访问数据库,配置仅需 appsettings.json 三项
|
||||
/// </summary>
|
||||
public class Program
|
||||
{
|
||||
private static string _apiBaseUrl = "http://localhost:5287";
|
||||
private static int _pollSeconds = 5;
|
||||
private static int _batchSize = 20;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||
using var cts = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
|
||||
|
||||
Log($"AlertBridge 启动,API 地址: {_apiBaseUrl},轮询间隔: {_pollSeconds}s,批量: {_batchSize}");
|
||||
Log("按 Ctrl+C 停止");
|
||||
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PollOnceAsync(http, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"轮询异常: {ex.Message}");
|
||||
}
|
||||
|
||||
try { await Task.Delay(TimeSpan.FromSeconds(_pollSeconds), cts.Token); }
|
||||
catch (TaskCanceledException) { break; }
|
||||
}
|
||||
Log("AlertBridge 已停止");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单次轮询:拉取 → 逐条推送 → 回写
|
||||
/// </summary>
|
||||
private static async Task PollOnceAsync(HttpClient http, CancellationToken ct)
|
||||
{
|
||||
string pullUrl = $"{_apiBaseUrl}/api/inspection/alert-notify/outbox/pull?batch={_batchSize}";
|
||||
string pullJson = await http.GetStringAsync(pullUrl, ct);
|
||||
|
||||
var pullResult = JsonSerializer.Deserialize<ResultDto<List<AlertOutboxItemDto>>>(pullJson, JsonOpts);
|
||||
if (pullResult == null || pullResult.Code != 0 || pullResult.Data == null || pullResult.Data.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"拉取到 {pullResult.Data.Count} 条待推送消息");
|
||||
foreach (var item in pullResult.Data)
|
||||
{
|
||||
bool success = false;
|
||||
string? error = null;
|
||||
try
|
||||
{
|
||||
var payload = JsonSerializer.Deserialize<AlertOutboxPayload>(item.Payload ?? "{}", JsonOpts);
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.WebhookUrl))
|
||||
{
|
||||
error = "Payload 为空或缺少 WebhookUrl";
|
||||
}
|
||||
else
|
||||
{
|
||||
(success, error) = await SendAsync(http, payload, ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = NotifyError.Describe(ex);
|
||||
}
|
||||
|
||||
// 回写结果
|
||||
try
|
||||
{
|
||||
var resultBody = JsonSerializer.Serialize(new { Success = success, ResultMsg = error }, JsonOpts);
|
||||
using var content = new StringContent(resultBody, Encoding.UTF8, "application/json");
|
||||
await http.PostAsync($"{_apiBaseUrl}/api/inspection/alert-notify/outbox/{item.Id}/result", content, ct);
|
||||
Log($"消息 {item.Id} 推送{(success ? "成功" : $"失败: {error}")},结果已回写");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"消息 {item.Id} 结果回写失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按渠道类型推送(与主站 Notifier 报文格式保持一致)
|
||||
/// </summary>
|
||||
private static async Task<(bool success, string? error)> SendAsync(HttpClient http, AlertOutboxPayload p, CancellationToken ct)
|
||||
{
|
||||
string url = p.WebhookUrl!;
|
||||
object body;
|
||||
|
||||
switch (p.ChannelType)
|
||||
{
|
||||
case 1: // 飞书:post 富文本 + at 标签(user_id=open_id) 真正 @ + 可选加签(timestamp 秒 + sign)
|
||||
{
|
||||
var contentLines = new List<List<Dictionary<string, object>>>();
|
||||
foreach (var raw in (p.Content ?? string.Empty).Split('\n'))
|
||||
{
|
||||
var line = raw.TrimEnd('\r');
|
||||
if (line.Length == 0) continue;
|
||||
contentLines.Add(new List<Dictionary<string, object>>
|
||||
{
|
||||
new Dictionary<string, object> { ["tag"] = "text", ["text"] = line }
|
||||
});
|
||||
}
|
||||
if (p.AtMobiles is { Count: > 0 })
|
||||
{
|
||||
var atLine = new List<Dictionary<string, object>>
|
||||
{
|
||||
new Dictionary<string, object> { ["tag"] = "text", ["text"] = "关注人: " }
|
||||
};
|
||||
foreach (var openId in p.AtMobiles)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(openId)) continue;
|
||||
atLine.Add(new Dictionary<string, object> { ["tag"] = "at", ["user_id"] = openId.Trim() });
|
||||
}
|
||||
contentLines.Add(atLine);
|
||||
}
|
||||
|
||||
var feishuBody = new Dictionary<string, object>
|
||||
{
|
||||
["msg_type"] = "post",
|
||||
["content"] = new Dictionary<string, object>
|
||||
{
|
||||
["post"] = new Dictionary<string, object>
|
||||
{
|
||||
["zh_cn"] = new Dictionary<string, object>
|
||||
{
|
||||
["title"] = p.Title ?? "设备告警",
|
||||
["content"] = contentLines
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(p.Secret))
|
||||
{
|
||||
long ts = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
feishuBody["timestamp"] = ts.ToString();
|
||||
feishuBody["sign"] = ImWebhookSigner.FeishuSign(ts, p.Secret);
|
||||
}
|
||||
body = feishuBody;
|
||||
break;
|
||||
}
|
||||
case 2: // 钉钉:markdown + at.atMobiles + URL 加签(timestamp 毫秒 + sign)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(p.Secret))
|
||||
{
|
||||
long ts = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
url += $"{(url.Contains('?') ? "&" : "?")}timestamp={ts}&sign={ImWebhookSigner.DingTalkSign(ts, p.Secret)}";
|
||||
}
|
||||
var md = new StringBuilder();
|
||||
md.AppendLine($"### {p.Title}");
|
||||
md.AppendLine(p.Content);
|
||||
foreach (var m in p.AtMobiles ?? new List<string>()) md.Append($"@{m} ");
|
||||
|
||||
body = new Dictionary<string, object>
|
||||
{
|
||||
["msgtype"] = "markdown",
|
||||
["markdown"] = new Dictionary<string, object> { ["title"] = p.Title ?? "设备告警", ["text"] = md.ToString() },
|
||||
["at"] = new Dictionary<string, object> { ["atMobiles"] = p.AtMobiles ?? new List<string>(), ["isAtAll"] = false }
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 3: // 企微:text + mentioned_mobile_list(无加签)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine(p.Title);
|
||||
text.AppendLine(p.Content);
|
||||
body = new Dictionary<string, object>
|
||||
{
|
||||
["msgtype"] = "text",
|
||||
["text"] = new Dictionary<string, object>
|
||||
{
|
||||
["content"] = text.ToString(),
|
||||
["mentioned_mobile_list"] = p.AtMobiles ?? new List<string>()
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return (false, $"未知渠道类型: {p.ChannelType}");
|
||||
}
|
||||
|
||||
string json = JsonSerializer.Serialize(body, JsonOpts);
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var response = await http.PostAsync(url, content, ct);
|
||||
string respText = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return (false, $"HTTP {(int)response.StatusCode}: {Truncate(respText)}");
|
||||
}
|
||||
|
||||
// 飞书返回 code/StatusCode,钉钉/企微返回 errcode,0 均为成功
|
||||
using var doc = JsonDocument.Parse(respText);
|
||||
var root = doc.RootElement;
|
||||
bool ok = (root.TryGetProperty("errcode", out var ec) && ec.GetInt32() == 0)
|
||||
|| (root.TryGetProperty("code", out var c) && c.GetInt32() == 0)
|
||||
|| (root.TryGetProperty("StatusCode", out var sc) && sc.GetInt32() == 0);
|
||||
return ok ? (true, null) : (false, Truncate(respText));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 appsettings.json(免依赖 Microsoft.Extensions.Configuration,直接 JSON 解析)
|
||||
/// </summary>
|
||||
private static void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||
if (!File.Exists(path)) return;
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
var root = doc.RootElement;
|
||||
if (root.TryGetProperty("ApiBaseUrl", out var u)) _apiBaseUrl = u.GetString() ?? _apiBaseUrl;
|
||||
if (root.TryGetProperty("PollSeconds", out var p)) _pollSeconds = p.GetInt32();
|
||||
if (root.TryGetProperty("BatchSize", out var b)) _batchSize = b.GetInt32();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"读取配置失败,使用默认值: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string Truncate(string s, int max = 200) =>
|
||||
string.IsNullOrEmpty(s) || s.Length <= max ? s : s.Substring(0, max) + "...";
|
||||
|
||||
private static void Log(string msg) => Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {msg}");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
/// <summary>
|
||||
/// 主站统一返回结构(仅取所需字段)
|
||||
/// </summary>
|
||||
private class ResultDto<T>
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public string? Msg { get; set; }
|
||||
public T? Data { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ApiBaseUrl": "http://localhost:5287",
|
||||
"PollSeconds": 5,
|
||||
"BatchSize": 20
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace Common.Notify
|
||||
{
|
||||
/// <summary>
|
||||
/// 飞书自建应用配置(用于"手机号 → open_id"转换)
|
||||
/// 飞书群机器人 webhook 只能发文本、无法用手机号 @ 人,必须借助自建应用凭证调通讯录接口换取 open_id。
|
||||
/// 由 Program.cs 启动时从 appsettings.json 的 Feishu 节读入;未配置时相关功能自动降级。
|
||||
/// </summary>
|
||||
public static class FeishuConfig
|
||||
{
|
||||
/// <summary>飞书自建应用 App ID(cli_ 开头)</summary>
|
||||
public static string AppId { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>飞书自建应用 App Secret</summary>
|
||||
public static string AppSecret { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>是否已配置(未配置时手机号无法转 open_id,降级为仅支持直接填 open_id / all)</summary>
|
||||
public static bool IsConfigured => !string.IsNullOrWhiteSpace(AppId) && !string.IsNullOrWhiteSpace(AppSecret);
|
||||
|
||||
/// <summary>初始化飞书应用配置</summary>
|
||||
public static void Init(string appId, string appSecret)
|
||||
{
|
||||
AppId = appId ?? string.Empty;
|
||||
AppSecret = appSecret ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Notify
|
||||
{
|
||||
/// <summary>
|
||||
/// IM 机器人 Webhook 加签工具(钉钉/飞书)
|
||||
/// 供 IOT_API 直连推送与 AlertBridge 跨网转发程序共用,保证两种模式签名算法一致
|
||||
/// </summary>
|
||||
public static class ImWebhookSigner
|
||||
{
|
||||
/// <summary>
|
||||
/// 钉钉机器人加签:sign = UrlEncode(Base64(HmacSHA256(timestamp + "\n" + secret, key=secret)))
|
||||
/// </summary>
|
||||
/// <param name="timestampMs">毫秒时间戳</param>
|
||||
/// <param name="secret">加签密钥(SEC 开头)</param>
|
||||
/// <returns>URL 编码后的签名</returns>
|
||||
public static string DingTalkSign(long timestampMs, string secret)
|
||||
{
|
||||
string stringToSign = $"{timestampMs}\n{secret}";
|
||||
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
byte[] signData = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
|
||||
return Uri.EscapeDataString(Convert.ToBase64String(signData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 飞书机器人加签:sign = Base64(HmacSHA256(空串, key=timestamp + "\n" + secret))
|
||||
/// 注意与钉钉相反:飞书以"时间戳\n密钥"为 key、对空内容签名
|
||||
/// </summary>
|
||||
/// <param name="timestampSec">秒级时间戳</param>
|
||||
/// <param name="secret">加签密钥</param>
|
||||
/// <returns>Base64 签名</returns>
|
||||
public static string FeishuSign(long timestampSec, string secret)
|
||||
{
|
||||
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes($"{timestampSec}\n{secret}"));
|
||||
byte[] signData = hmac.ComputeHash(Array.Empty<byte>());
|
||||
return Convert.ToBase64String(signData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Notify
|
||||
{
|
||||
/// <summary>
|
||||
/// 通知推送异常诊断工具
|
||||
/// HttpClient 网络异常(尤其 SSL/TLS 握手失败)的真实根因藏在 InnerException 链里,
|
||||
/// 只记录外层 ex.Message 会得到"see inner exception"这类无用信息。
|
||||
/// 本工具展开整条异常链并针对常见网络故障给出可读排查提示,供三个 Notifier 复用。
|
||||
/// </summary>
|
||||
public static class NotifyError
|
||||
{
|
||||
/// <summary>
|
||||
/// 将异常展开为"外层 -> 内层 -> ..."的可读描述,并附加常见网络故障排查提示
|
||||
/// </summary>
|
||||
public static string Describe(Exception? ex)
|
||||
{
|
||||
if (ex == null) return "未知错误";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var current = ex;
|
||||
int depth = 0;
|
||||
// 最多展开 5 层,防止极端嵌套导致信息过长
|
||||
while (current != null && depth < 5)
|
||||
{
|
||||
if (depth > 0) sb.Append(" -> ");
|
||||
sb.Append(current.Message);
|
||||
current = current.InnerException;
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (IsTimeout(ex))
|
||||
{
|
||||
sb.Append("|排查:请求超时,请确认目标地址可达、Webhook 域名解析正常、无防火墙拦截出站 443");
|
||||
}
|
||||
else if (IsSslFailure(ex))
|
||||
{
|
||||
sb.Append("|排查:SSL/TLS 握手失败,常见原因为 " +
|
||||
"①服务器无法访问外网(工控内网请改用\"跨网中转(Outbox)\"推送模式,由跳板机 AlertBridge 实际发送) " +
|
||||
"②企业代理/防火墙做 SSL 拦截且其根证书未被本机信任 " +
|
||||
"③系统时间不准导致证书校验失败 " +
|
||||
"④Webhook 地址主机名或端口有误(非标准 HTTPS 端口)");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为超时(TaskCanceledException 且非用户主动取消,或 TimeoutException)
|
||||
/// </summary>
|
||||
private static bool IsTimeout(Exception ex)
|
||||
{
|
||||
for (var c = ex; c != null; c = c.InnerException)
|
||||
{
|
||||
if (c is TimeoutException) return true;
|
||||
// HttpClient 超时表现为 TaskCanceledException,内部通常带 TimeoutException
|
||||
if (c is TaskCanceledException && c.InnerException is TimeoutException) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为 SSL/TLS 握手或证书校验失败
|
||||
/// </summary>
|
||||
private static bool IsSslFailure(Exception ex)
|
||||
{
|
||||
for (var c = ex; c != null; c = c.InnerException)
|
||||
{
|
||||
if (c is AuthenticationException) return true;
|
||||
var msg = c.Message;
|
||||
if (!string.IsNullOrEmpty(msg) &&
|
||||
(msg.Contains("SSL", StringComparison.OrdinalIgnoreCase) ||
|
||||
msg.Contains("TLS", StringComparison.OrdinalIgnoreCase) ||
|
||||
msg.Contains("certificate", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -17,40 +17,114 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommand", "DeviceComm
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOT_API", "IOT_API\IOT_API.csproj", "{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlertBridge", "AlertBridge\AlertBridge.csproj", "{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6D9764D9-B4DA-43E2-A9D7-40A6C871A6B3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9150C6A9-AE8D-42C9-8B2D-9DD04A3E7E74}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4DE5DC6C-7121-4EB9-B8A8-90C694F451E2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D8209B91-D7D0-444B-B569-D3FA74D191DD}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C764BDBA-3D96-4BA1-9AA5-D9D284E7C6AA}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Asset;
|
||||
using Model.Entity.Asset;
|
||||
using Model.Mapper;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IOT_API.Controllers.Asset
|
||||
@@ -11,16 +14,21 @@ namespace IOT_API.Controllers.Asset
|
||||
/// <summary>
|
||||
/// 资产管理 - 设备台账控制器
|
||||
/// </summary>
|
||||
[Route("api/Equipment")] //资产管理模块下的设备管理接口
|
||||
[ApiController]
|
||||
[Route("api/asset/equipment")]
|
||||
[Route("api/asset/equipment")] //资产管理模块下的设备管理接口
|
||||
public class EquipmentController : ControllerBase
|
||||
{
|
||||
private readonly IEquipmentService _equipmentService;
|
||||
private readonly IEquipmentAttachmentService _attachmentService;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
|
||||
public EquipmentController(IEquipmentService equipmentService)
|
||||
public EquipmentController(IEquipmentService equipmentService,
|
||||
IEquipmentAttachmentService attachmentService,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_equipmentService = equipmentService;
|
||||
_attachmentService = attachmentService;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,22 +40,27 @@ namespace IOT_API.Controllers.Asset
|
||||
/// <param name="categoryId">设备分类Id(0表示不过滤)</param>
|
||||
/// <param name="status">设备状态(不传表示不过滤)</param>
|
||||
[HttpGet("list")]
|
||||
public async Task<Result<List<EquipmentEntity>>> GetList(int pageIndex = 1, int pageSize = 10,
|
||||
public async Task<Result<List<EquipmentDto>>> GetList(int pageIndex = 1, int pageSize = 10,
|
||||
string? keyword = null, long categoryId = 0, EquipmentStatusEnum? status = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _equipmentService.GetPagedAsync(pageIndex, pageSize, total, keyword, categoryId, status);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result;
|
||||
return result.IsSuccess
|
||||
? Result<List<EquipmentDto>>.Success(result.Data.ToDtoList())
|
||||
: Result<List<EquipmentDto>>.Error(result.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询全部设备(不分页,供下拉选择等场景使用)
|
||||
/// </summary>
|
||||
[HttpGet("all")]
|
||||
public async Task<Result<List<EquipmentEntity>>> GetAll()
|
||||
public async Task<Result<List<EquipmentDto>>> GetAll()
|
||||
{
|
||||
return await _equipmentService.GetAllAsync();
|
||||
var result = await _equipmentService.GetAllAsync();
|
||||
return result.IsSuccess
|
||||
? Result<List<EquipmentDto>>.Success(result.Data.ToDtoList())
|
||||
: Result<List<EquipmentDto>>.Error(result.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,29 +68,33 @@ namespace IOT_API.Controllers.Asset
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Result<EquipmentEntity>> GetById(long id)
|
||||
public async Task<Result<EquipmentDto>> GetById(long id)
|
||||
{
|
||||
return await _equipmentService.GetByIdAsync(id);
|
||||
var res = await _equipmentService.GetByIdAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<EquipmentDto>.Success(res.Data.ToDto())
|
||||
: Result<EquipmentDto>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增设备
|
||||
/// </summary>
|
||||
/// <param name="entity">设备实体</param>
|
||||
/// <param name="dto">设备 DTO(IsDel/CreateTime 等服务端字段不接受入参)</param>
|
||||
[HttpPost]
|
||||
public async Task<Result<bool>> Add([FromBody] EquipmentEntity entity)
|
||||
public async Task<Result<bool>> Add([FromBody] EquipmentDto dto)
|
||||
{
|
||||
return await _equipmentService.InsertAsync(entity);
|
||||
return await _equipmentService.InsertAsync(dto.ToEntity());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改设备
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="entity">设备实体</param>
|
||||
/// <param name="id">设备主键 Id(以路由参数为准,忽略请求体中的 Id)</param>
|
||||
/// <param name="dto">设备 DTO(IsDel/CreateTime 等服务端字段不接受入参)</param>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<Result<bool>> Update(long id, [FromBody] EquipmentEntity entity)
|
||||
public async Task<Result<bool>> Update(long id, [FromBody] EquipmentDto dto)
|
||||
{
|
||||
var entity = dto.ToEntity();
|
||||
entity.Id = id;
|
||||
return await _equipmentService.UpdateAsync(entity);
|
||||
}
|
||||
@@ -96,11 +113,11 @@ namespace IOT_API.Controllers.Asset
|
||||
/// 变更设备状态(同步记录状态变更历史)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="request">状态变更请求</param>
|
||||
/// <param name="dto">状态变更请求(目标状态/操作人/备注)</param>
|
||||
[HttpPost("{id}/status")]
|
||||
public async Task<Result<bool>> ChangeStatus(long id, [FromBody] ChangeStatusRequest request)
|
||||
public async Task<Result<bool>> ChangeStatus(long id, [FromBody] EquipmentStatusChangeDto dto)
|
||||
{
|
||||
return await _equipmentService.ChangeStatusAsync(id, request.Status, request.Operator, request.Remark);
|
||||
return await _equipmentService.ChangeStatusAsync(id, dto.Status, dto.Operator, dto.Remark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,30 +125,65 @@ namespace IOT_API.Controllers.Asset
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}/status-records")]
|
||||
public async Task<Result<List<EquipmentStatusRecordEntity>>> GetStatusRecords(long id)
|
||||
public async Task<Result<List<EquipmentStatusRecordDto>>> GetStatusRecords(long id)
|
||||
{
|
||||
return await _equipmentService.GetStatusRecordsAsync(id);
|
||||
var res = await _equipmentService.GetStatusRecordsAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<List<EquipmentStatusRecordDto>>.Success(res.Data.ToDtoList())
|
||||
: Result<List<EquipmentStatusRecordDto>>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询设备附件列表(资产附件管理)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}/attachments")]
|
||||
public async Task<Result<List<EquipmentAttachmentDto>>> GetAttachments(long id)
|
||||
{
|
||||
var res = await _attachmentService.GetByEquipmentAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<List<EquipmentAttachmentDto>>.Success(res.Data.ToDtoList())
|
||||
: Result<List<EquipmentAttachmentDto>>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传设备附件(multipart/form-data,文件字段名 file)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="file">上传的文件</param>
|
||||
/// <param name="fileType">文件类型(1使用说明书/2合格证明/3校准证书/4保养卡/5合同扫描件/6验收照片/99其他)</param>
|
||||
/// <param name="uploader">上传人</param>
|
||||
/// <param name="remark">备注</param>
|
||||
[HttpPost("{id}/attachments")]
|
||||
[RequestSizeLimit(50 * 1024 * 1024)]
|
||||
public async Task<Result<EquipmentAttachmentDto>> UploadAttachment(long id, IFormFile file,
|
||||
[FromForm] AttachmentTypeEnum fileType = AttachmentTypeEnum.Other,
|
||||
[FromForm] string? uploader = null, [FromForm] string? remark = null)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
return Result<EquipmentAttachmentDto>.Error("请选择要上传的文件");
|
||||
}
|
||||
|
||||
// wwwroot 不存在时退回运行目录下的 wwwroot(上传时会自动创建)
|
||||
string webRootPath = _webHostEnvironment.WebRootPath
|
||||
?? Path.Combine(_webHostEnvironment.ContentRootPath, "wwwroot");
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
var res = await _attachmentService.UploadAsync(id, stream, file.FileName, file.Length, fileType, uploader, remark, webRootPath);
|
||||
return res.IsSuccess
|
||||
? Result<EquipmentAttachmentDto>.Success(res.Data.ToDto())
|
||||
: Result<EquipmentAttachmentDto>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除设备附件
|
||||
/// </summary>
|
||||
/// <param name="attachmentId">附件主键 Id</param>
|
||||
[HttpDelete("attachments/{attachmentId}")]
|
||||
public async Task<Result<bool>> DeleteAttachment(long attachmentId)
|
||||
{
|
||||
return await _attachmentService.DeleteAsync(attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更请求体
|
||||
/// </summary>
|
||||
public class ChangeStatusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标状态
|
||||
/// </summary>
|
||||
public EquipmentStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,86 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Asset;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码
|
||||
/// 设备二维码生成与打印(资产侧;生成/绑定 RFID 需 device:edit,列表/图片/打印页 device:view 可访问)
|
||||
/// 扫码直达:二维码内容 = http://host/asset/ledger/equipment?id={equipmentId}
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/asset/qrcode")]
|
||||
public class QrCodeController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 二维码 相关接口
|
||||
private readonly IQrCodeService _qrService;
|
||||
|
||||
public QrCodeController(IQrCodeService qrService)
|
||||
{
|
||||
_qrService = qrService;
|
||||
}
|
||||
|
||||
// baseUrl 由当前请求构造(scheme://host[:port]);如需固定域名,改为读 sys_param site_base_url
|
||||
private string BaseUrl => $"{Request.Scheme}://{Request.Host}";
|
||||
|
||||
/// <summary>生成二维码 URL(写入设备 QrCodeUrl 字段,不返回图片)</summary>
|
||||
[HttpPost("generate/{equipmentId}")]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> Generate(long equipmentId)
|
||||
{
|
||||
return Ok(await _qrService.GenerateAsync(equipmentId, BaseUrl));
|
||||
}
|
||||
|
||||
/// <summary>生成二维码 PNG 图片(直接返回 image/png 二进制)</summary>
|
||||
[HttpGet("image/{equipmentId}")]
|
||||
[RequirePermission("device:view")]
|
||||
public async Task<IActionResult> GetImage(long equipmentId)
|
||||
{
|
||||
var result = await _qrService.GenerateImageAsync(equipmentId, BaseUrl);
|
||||
if (!result.IsSuccess || result.Data == null)
|
||||
return Ok(Result<byte[]>.Error(result.Msg));
|
||||
return File(result.Data, "image/png");
|
||||
}
|
||||
|
||||
/// <summary>批量生成二维码 URL</summary>
|
||||
[HttpPost("batch-generate")]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> BatchGenerate([FromBody] long[] equipmentIds)
|
||||
{
|
||||
return Ok(await _qrService.BatchGenerateAsync(equipmentIds, BaseUrl));
|
||||
}
|
||||
|
||||
/// <summary>设备二维码列表(分页,qrOnly=1 仅看已生成)</summary>
|
||||
[HttpGet("list")]
|
||||
[RequirePermission("device:view")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, [FromQuery] bool qrOnly = false)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _qrService.GetListPagedAsync(pageIndex, pageSize, total, keyword, qrOnly);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<QrCodeDto>>.Success(result.Data))
|
||||
: Ok(Result<List<QrCodeDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>打印页(返回 text/html,浏览器打开后自动调起打印)</summary>
|
||||
[HttpGet("print/{equipmentId}")]
|
||||
[RequirePermission("device:view")]
|
||||
public async Task<IActionResult> Print(long equipmentId)
|
||||
{
|
||||
var result = await _qrService.GetPrintHtmlAsync(equipmentId, BaseUrl);
|
||||
if (!result.IsSuccess) return Ok(result);
|
||||
return Content(result.Data!, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
/// <summary>绑定 RFID 编号到设备(query 传 rfidCode)</summary>
|
||||
[HttpPost("bind-rfid/{equipmentId}")]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> BindRfid(long equipmentId, [FromQuery] string rfidCode)
|
||||
{
|
||||
return Ok(await _qrService.BindRfidAsync(equipmentId, rfidCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,95 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Config;
|
||||
using Service.Interface.Config;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
namespace WebAPI.Controllers.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 网关管理
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/config/gateway")]
|
||||
[RequirePermission("device:view")]
|
||||
public class GatewayController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 网关管理 相关接口
|
||||
private readonly IGatewayService _gatewayService;
|
||||
|
||||
public GatewayController(IGatewayService gatewayService)
|
||||
{
|
||||
_gatewayService = gatewayService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网关列表(分页)
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<Result<List<GatewayDto>>> GetList([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? keyword = null, [FromQuery] int? protocolType = null)
|
||||
{
|
||||
var total = new RefAsync<int>();
|
||||
var result = await _gatewayService.GetPagedAsync(pageIndex, pageSize, total, keyword, protocolType);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网关详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Result<GatewayDto>> GetById(long id)
|
||||
{
|
||||
return await _gatewayService.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增网关(需 device:edit 权限)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<Result<GatewayDto>> Add([FromBody] GatewayDto dto)
|
||||
{
|
||||
return await _gatewayService.AddAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改网关(需 device:edit 权限)
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<Result<GatewayDto>> Update([FromBody] GatewayDto dto)
|
||||
{
|
||||
return await _gatewayService.UpdateAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除网关(软删除,设备 GatewayId 置 0;需 device:edit 权限)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<Result> Delete(long id)
|
||||
{
|
||||
return await _gatewayService.DeleteAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试网关连接(TCP/串口尝试连接,更新在线状态)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/test-connection")]
|
||||
public async Task<Result<GatewayTestResultDto>> TestConnection(long id)
|
||||
{
|
||||
return await _gatewayService.TestConnectionAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网关下拉选项(设备表单用)
|
||||
/// </summary>
|
||||
[HttpGet("options")]
|
||||
public async Task<Result<List<GatewayOptionDto>>> GetOptions()
|
||||
{
|
||||
return await _gatewayService.GetOptionsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Config;
|
||||
using Service.Interface.Config;
|
||||
using SqlSugar;
|
||||
using System.Threading.Tasks;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
@@ -7,8 +13,108 @@ namespace WebAPI.Controllers
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/config/device")]
|
||||
[RequirePermission("device:view")]
|
||||
public class IotDeviceController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 IOT设备管理 相关接口
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IDeviceCommandService _deviceCommandService;
|
||||
|
||||
public IotDeviceController(IDeviceService deviceService, IDeviceCommandService deviceCommandService)
|
||||
{
|
||||
_deviceService = deviceService;
|
||||
_deviceCommandService = deviceCommandService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备列表(分页查询,支持关键字搜索,可按所属产品筛选)
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页码(从1开始,默认1)</param>
|
||||
/// <param name="pageSize">每页数量(默认10)</param>
|
||||
/// <param name="keyword">关键字(模糊匹配设备编号/名称/类型)</param>
|
||||
/// <param name="productId">所属产品Id(0 表示不过滤)</param>
|
||||
/// <param name="gatewayId">所属网关Id(0 表示不过滤)</param>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, long productId = 0, long gatewayId = 0)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _deviceService.GetPagedAsync(pageIndex, pageSize, total, keyword, productId > 0 ? productId : null, gatewayId > 0 ? gatewayId : null);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<IotDeviceDto>>.Success(result.Data))
|
||||
: Ok(Result<List<IotDeviceDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备详情
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
return Ok(await _deviceService.GetByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增设备(需 device:edit 权限)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> Add([FromBody] IotDeviceDto dto)
|
||||
{
|
||||
return Ok(await _deviceService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改设备(需 device:edit 权限)
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> Update([FromBody] IotDeviceDto dto)
|
||||
{
|
||||
return Ok(await _deviceService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除设备(软删除;需 device:edit 权限)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpDelete("{id}")]
|
||||
[RequirePermission("device:edit")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _deviceService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备日志(设备独享日志,分页查询)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="pageIndex">页码(默认1)</param>
|
||||
/// <param name="pageSize">每页数量(默认20)</param>
|
||||
[HttpGet("{id}/logs")]
|
||||
public async Task<IActionResult> GetLogs(long id, int pageIndex = 1, int pageSize = 20)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _deviceService.GetDeviceLogsAsync(id, pageIndex, pageSize, total);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<DeviceLogDto>>.Success(result.Data))
|
||||
: Ok(Result<List<DeviceLogDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按物模型可写点向设备下发指令(需 device:control 权限)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="dto">指令请求(PointId 点Id + Value 工程值 + IsSimulated 是否模拟)</param>
|
||||
[HttpPost("{id}/command")]
|
||||
[RequirePermission("device:control")]
|
||||
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 Model;
|
||||
using Model.Dto.Config;
|
||||
using Service.Interface.Config;
|
||||
using SqlSugar;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 产品管理
|
||||
/// 产品管理(含产品分类)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/config/product")]
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,156 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Inspection;
|
||||
using Model.Entity.Inspection;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息告警
|
||||
/// 消息告警(告警列表与确认 / 告警统计分析 / 统一告警上报入口)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/inspection/alert-message")]
|
||||
public class AlertMessageController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 消息告警 相关接口
|
||||
private readonly IAlertMessageService _alertMessageService;
|
||||
private readonly IAlertCenterService _alertCenterService;
|
||||
|
||||
public AlertMessageController(IAlertMessageService alertMessageService, IAlertCenterService alertCenterService)
|
||||
{
|
||||
_alertMessageService = alertMessageService;
|
||||
_alertCenterService = alertCenterService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警列表(分页,支持级别/状态/类型/来源/设备/关键字/时间范围筛选;总记录数见响应头 X-Total-Count)
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<Result<List<AlertMessageDto>>> GetList(int pageIndex = 1, int pageSize = 10,
|
||||
AlertLevelEnum? level = null, AlertStatusEnum? status = null, AlertTypeEnum? type = null,
|
||||
AlertSourceEnum? source = null, string? deviceCode = null, string? keyword = null,
|
||||
DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _alertMessageService.GetPagedAsync(pageIndex, pageSize, total,
|
||||
level, status, type, source, deviceCode, keyword, startDate, endDate);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Result<AlertMessageDto>> GetById(long id)
|
||||
{
|
||||
return await _alertMessageService.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警上报统一入口(供其它模块/第三方系统跨进程调用;进程内模块请直接注入 IAlertCenterService)
|
||||
/// </summary>
|
||||
[HttpPost("raise")]
|
||||
public async Task<Result<string>> Raise([FromBody] AlertRaiseDto dto)
|
||||
{
|
||||
return await _alertCenterService.RaiseAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认告警
|
||||
/// </summary>
|
||||
[HttpPost("{id}/ack")]
|
||||
public async Task<Result<bool>> Acknowledge(long id, [FromBody] AlertOperateDto dto)
|
||||
{
|
||||
return await _alertMessageService.AcknowledgeAsync(id, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理告警
|
||||
/// </summary>
|
||||
[HttpPost("{id}/handle")]
|
||||
public async Task<Result<bool>> Handle(long id, [FromBody] AlertOperateDto dto)
|
||||
{
|
||||
return await _alertMessageService.HandleAsync(id, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭告警
|
||||
/// </summary>
|
||||
[HttpPost("{id}/close")]
|
||||
public async Task<Result<bool>> Close(long id, [FromBody] AlertOperateDto dto)
|
||||
{
|
||||
return await _alertMessageService.CloseAsync(id, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 忽略告警(误报等场景)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/ignore")]
|
||||
public async Task<Result<bool>> Ignore(long id, [FromBody] AlertOperateDto dto)
|
||||
{
|
||||
return await _alertMessageService.IgnoreAsync(id, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转工单(一期仅变更状态并记录工单Id,工单模块二期对接)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/to-workorder")]
|
||||
public async Task<Result<bool>> ToWorkOrder(long id, [FromBody] AlertOperateDto dto)
|
||||
{
|
||||
return await _alertMessageService.ToWorkOrderAsync(id, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警概览(未确认数/今日新增/今日紧急/近7日总数)
|
||||
/// </summary>
|
||||
[HttpGet("statistics/overview")]
|
||||
public async Task<Result<AlertOverviewDto>> GetOverview()
|
||||
{
|
||||
return await _alertMessageService.GetOverviewAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警趋势统计(granularity:day/week/month)
|
||||
/// </summary>
|
||||
[HttpGet("statistics/trend")]
|
||||
public async Task<Result<List<AlertTrendItemDto>>> GetTrend(string granularity = "day",
|
||||
DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
return await _alertMessageService.GetTrendAsync(granularity, startDate, endDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警分布统计(dimension:device/type/level/source)
|
||||
/// </summary>
|
||||
[HttpGet("statistics/distribution")]
|
||||
public async Task<Result<List<AlertDistributionItemDto>>> GetDistribution(string dimension = "level",
|
||||
DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
return await _alertMessageService.GetDistributionAsync(dimension, startDate, endDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高频告警 TOP 排名
|
||||
/// </summary>
|
||||
[HttpGet("statistics/top")]
|
||||
public async Task<Result<List<AlertTopItemDto>>> GetTop(int topN = 10,
|
||||
DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
return await _alertMessageService.GetTopAsync(topN, startDate, endDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警处理及时率统计
|
||||
/// </summary>
|
||||
[HttpGet("statistics/timeliness")]
|
||||
public async Task<Result<AlertTimelinessDto>> GetTimeliness(DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
return await _alertMessageService.GetTimelinessAsync(startDate, endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Inspection;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警通知配置(渠道管理 / 通知规则 / 推送日志 / 值班排班 / 跨网发件箱)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/inspection/alert-notify")]
|
||||
public class AlertNotifyController : ControllerBase
|
||||
{
|
||||
private readonly IAlertNotifyService _alertNotifyService;
|
||||
|
||||
public AlertNotifyController(IAlertNotifyService alertNotifyService)
|
||||
{
|
||||
_alertNotifyService = alertNotifyService;
|
||||
}
|
||||
|
||||
#region 通知渠道
|
||||
/// <summary>
|
||||
/// 渠道列表(Secret 脱敏)
|
||||
/// </summary>
|
||||
[HttpGet("channel/list")]
|
||||
public async Task<Result<List<AlertNotifyChannelDto>>> GetChannels()
|
||||
{
|
||||
return await _alertNotifyService.GetChannelsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增渠道
|
||||
/// </summary>
|
||||
[HttpPost("channel")]
|
||||
public async Task<Result<bool>> AddChannel([FromBody] AlertNotifyChannelDto dto)
|
||||
{
|
||||
return await _alertNotifyService.AddChannelAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改渠道(Secret 传空表示保持原值)
|
||||
/// </summary>
|
||||
[HttpPut("channel/{id}")]
|
||||
public async Task<Result<bool>> UpdateChannel(long id, [FromBody] AlertNotifyChannelDto dto)
|
||||
{
|
||||
dto.Id = id.ToString();
|
||||
return await _alertNotifyService.UpdateChannelAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除渠道(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("channel/{id}")]
|
||||
public async Task<Result<bool>> DeleteChannel(long id)
|
||||
{
|
||||
return await _alertNotifyService.DeleteChannelAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渠道测试推送(验证 Webhook 配置是否正确)
|
||||
/// </summary>
|
||||
[HttpPost("channel/{id}/test")]
|
||||
public async Task<Result<bool>> TestChannel(long id)
|
||||
{
|
||||
return await _alertNotifyService.TestSendAsync(id);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 通知规则
|
||||
/// <summary>
|
||||
/// 通知规则列表
|
||||
/// </summary>
|
||||
[HttpGet("rule/list")]
|
||||
public async Task<Result<List<AlertNotifyRuleDto>>> GetRules()
|
||||
{
|
||||
return await _alertNotifyService.GetRulesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增通知规则
|
||||
/// </summary>
|
||||
[HttpPost("rule")]
|
||||
public async Task<Result<bool>> AddRule([FromBody] AlertNotifyRuleDto dto)
|
||||
{
|
||||
return await _alertNotifyService.AddRuleAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改通知规则
|
||||
/// </summary>
|
||||
[HttpPut("rule/{id}")]
|
||||
public async Task<Result<bool>> UpdateRule(long id, [FromBody] AlertNotifyRuleDto dto)
|
||||
{
|
||||
dto.Id = id.ToString();
|
||||
return await _alertNotifyService.UpdateRuleAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除通知规则(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("rule/{id}")]
|
||||
public async Task<Result<bool>> DeleteRule(long id)
|
||||
{
|
||||
return await _alertNotifyService.DeleteRuleAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/停用通知规则
|
||||
/// </summary>
|
||||
[HttpPut("rule/{id}/enabled")]
|
||||
public async Task<Result<bool>> SetRuleEnabled(long id, [FromQuery] bool enabled)
|
||||
{
|
||||
return await _alertNotifyService.SetRuleEnabledAsync(id, enabled);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 推送日志
|
||||
/// <summary>
|
||||
/// 推送日志分页(可按成功状态/渠道筛选;总记录数见响应头 X-Total-Count)
|
||||
/// </summary>
|
||||
[HttpGet("log/list")]
|
||||
public async Task<Result<List<AlertNotifyLogDto>>> GetLogs(int pageIndex = 1, int pageSize = 10,
|
||||
bool? success = null, long channelId = 0)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _alertNotifyService.GetLogsPagedAsync(pageIndex, pageSize, total, success, channelId);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 值班排班
|
||||
/// <summary>
|
||||
/// 值班排班列表(按日期范围,缺省本月)
|
||||
/// </summary>
|
||||
[HttpGet("duty/list")]
|
||||
public async Task<Result<List<DutyRosterDto>>> GetDuties(DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
return await _alertNotifyService.GetDutiesAsync(startDate, endDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增排班
|
||||
/// </summary>
|
||||
[HttpPost("duty")]
|
||||
public async Task<Result<bool>> AddDuty([FromBody] DutyRosterDto dto)
|
||||
{
|
||||
return await _alertNotifyService.AddDutyAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改排班
|
||||
/// </summary>
|
||||
[HttpPut("duty/{id}")]
|
||||
public async Task<Result<bool>> UpdateDuty(long id, [FromBody] DutyRosterDto dto)
|
||||
{
|
||||
dto.Id = id.ToString();
|
||||
return await _alertNotifyService.UpdateDutyAsync(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除排班(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("duty/{id}")]
|
||||
public async Task<Result<bool>> DeleteDuty(long id)
|
||||
{
|
||||
return await _alertNotifyService.DeleteDutyAsync(id);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 跨网发件箱(AlertBridge 跳板机程序对接,非前端页面使用)
|
||||
/// <summary>
|
||||
/// 拉取待推送发件箱(AlertBridge 轮询调用;返回自包含推送报文)
|
||||
/// </summary>
|
||||
[HttpGet("outbox/pull")]
|
||||
public async Task<Result<List<AlertOutboxItemDto>>> PullOutbox([FromQuery] int batch = 20)
|
||||
{
|
||||
return await _alertNotifyService.PullOutboxAsync(batch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回写发件箱推送结果(AlertBridge 推送完成后调用)
|
||||
/// </summary>
|
||||
[HttpPost("outbox/{id}/result")]
|
||||
public async Task<Result<bool>> ReportOutboxResult(long id, [FromBody] OutboxResultDto dto)
|
||||
{
|
||||
return await _alertNotifyService.ReportOutboxResultAsync(id, dto);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,79 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model.Dto.Inspection;
|
||||
using Service.Interface;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警规则
|
||||
/// 告警规则(规则实例:查看所有设备告警的规则列表)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/inspection/alert-rule")]
|
||||
public class AlertRuleController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 告警规则 相关接口
|
||||
private readonly IAlertRuleService _alertRuleService;
|
||||
|
||||
public AlertRuleController(IAlertRuleService alertRuleService)
|
||||
{
|
||||
_alertRuleService = alertRuleService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全部告警规则列表
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList()
|
||||
{
|
||||
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>
|
||||
/// 新增告警规则(前端传 DTO)
|
||||
/// </summary>
|
||||
[HttpPost("add")]
|
||||
public async Task<IActionResult> Add([FromBody] AlertRuleDto dto)
|
||||
{
|
||||
return Ok(await _alertRuleService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改告警规则(前端传 DTO)
|
||||
/// </summary>
|
||||
[HttpPut("update")]
|
||||
public async Task<IActionResult> Update([FromBody] AlertRuleDto dto)
|
||||
{
|
||||
return Ok(await _alertRuleService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除告警规则(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _alertRuleService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/停用告警规则
|
||||
/// </summary>
|
||||
[HttpPut("{id}/enabled")]
|
||||
public async Task<IActionResult> SetEnabled(long id, [FromQuery] bool enabled)
|
||||
{
|
||||
return Ok(await _alertRuleService.SetEnabledAsync(id, enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model.Dto.Inspection;
|
||||
using Service.Interface;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据转发(配置数据转发规则,支持通过消息通知的方式定时转发数据)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/inspection/data-forward")]
|
||||
public class DataForwardController : ControllerBase
|
||||
{
|
||||
private readonly IDataForwardService _dataForwardService;
|
||||
|
||||
public DataForwardController(IDataForwardService dataForwardService)
|
||||
{
|
||||
_dataForwardService = dataForwardService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全部转发规则列表
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList()
|
||||
{
|
||||
return Ok(await _dataForwardService.GetListAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增转发规则(前端传 DTO)
|
||||
/// </summary>
|
||||
[HttpPost("add")]
|
||||
public async Task<IActionResult> Add([FromBody] DataForwardRuleDto dto)
|
||||
{
|
||||
return Ok(await _dataForwardService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改转发规则(前端传 DTO)
|
||||
/// </summary>
|
||||
[HttpPut("update")]
|
||||
public async Task<IActionResult> Update([FromBody] DataForwardRuleDto dto)
|
||||
{
|
||||
return Ok(await _dataForwardService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除转发规则(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _dataForwardService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/停用转发规则
|
||||
/// </summary>
|
||||
[HttpPut("{id}/enabled")]
|
||||
public async Task<IActionResult> SetEnabled(long id, [FromQuery] bool enabled)
|
||||
{
|
||||
return Ok(await _dataForwardService.SetEnabledAsync(id, enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Service.Interface;
|
||||
using Model.Entity.Inspection;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
|
||||
@@ -1,14 +1,114 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Service.Interface;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 统计报表
|
||||
/// 统计报表(设备统计/维护报表/维修报表)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/inspection/report")]
|
||||
public class StatisticsReportController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 统计报表 相关接口
|
||||
private readonly IStatisticsReportService _statisticsReportService;
|
||||
|
||||
public StatisticsReportController(IStatisticsReportService statisticsReportService)
|
||||
{
|
||||
_statisticsReportService = statisticsReportService;
|
||||
}
|
||||
|
||||
#region 设备统计报表
|
||||
|
||||
/// <summary>
|
||||
/// 设备统计概览(总数/在用/闲置/报废等)
|
||||
/// </summary>
|
||||
[HttpGet("device/statistics")]
|
||||
public async Task<IActionResult> GetDeviceStatistics()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetDeviceStatisticsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按分类统计设备数量
|
||||
/// </summary>
|
||||
[HttpGet("device/category")]
|
||||
public async Task<IActionResult> GetCategoryStatistics()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetCategoryStatisticsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按部门统计设备数量
|
||||
/// </summary>
|
||||
[HttpGet("device/department")]
|
||||
public async Task<IActionResult> GetDepartmentStatistics()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetDepartmentStatisticsAsync());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备维护报表
|
||||
|
||||
/// <summary>
|
||||
/// 设备维护统计(校准/保养及时率、超期数量等)
|
||||
/// </summary>
|
||||
[HttpGet("maintenance/statistics")]
|
||||
public async Task<IActionResult> GetMaintenanceStatistics()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetMaintenanceStatisticsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维护超期设备明细
|
||||
/// </summary>
|
||||
[HttpGet("maintenance/overdue")]
|
||||
public async Task<IActionResult> GetMaintenanceOverdueDetails()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetMaintenanceOverdueDetailsAsync());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 维修类报表
|
||||
|
||||
/// <summary>
|
||||
/// 维修统计概览
|
||||
/// </summary>
|
||||
[HttpGet("repair/statistics")]
|
||||
public async Task<IActionResult> GetRepairStatistics()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetRepairStatisticsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 故障类型分布
|
||||
/// </summary>
|
||||
[HttpGet("repair/fault-type")]
|
||||
public async Task<IActionResult> GetFaultTypeDistribution()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetFaultTypeDistributionAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维修工时分析
|
||||
/// </summary>
|
||||
[HttpGet("repair/hours-analysis")]
|
||||
public async Task<IActionResult> GetRepairHoursAnalysis()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetRepairHoursAnalysisAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维修记录明细列表
|
||||
/// </summary>
|
||||
[HttpGet("repair/records")]
|
||||
public async Task<IActionResult> GetRepairRecordDetails()
|
||||
{
|
||||
return Ok(await _statisticsReportService.GetRepairRecordDetailsAsync());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作审计日志
|
||||
/// 操作审计日志(满足实验室审计要求;需 user:manage 权限)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/audit")]
|
||||
[RequirePermission("user:manage")]
|
||||
public class AuditController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 操作审计日志 相关接口
|
||||
private readonly IAuditLogService _auditLogService;
|
||||
|
||||
public AuditController(IAuditLogService auditLogService)
|
||||
{
|
||||
_auditLogService = auditLogService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 审计日志列表(分页;筛选:操作人/描述关键字、操作类型、操作对象、时间段)
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 20,
|
||||
string? keyword = null, string? operationType = null, string? operationTarget = null,
|
||||
DateTime? startTime = null, DateTime? endTime = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _auditLogService.GetPagedAsync(pageIndex, pageSize, total,
|
||||
keyword, operationType, operationTarget, startTime, endTime);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<AuditLogDto>>.Success(result.Data))
|
||||
: Ok(Result<List<AuditLogDto>>.Error(result.Msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 认证授权(登录/登出/当前用户/改密)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public AuthController(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录(用户名+密码,成功返回 Token 与权限列表)
|
||||
/// </summary>
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login([FromBody] LoginDto dto)
|
||||
{
|
||||
var ip = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
return Ok(await _authService.LoginAsync(dto, ip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出登录(记录审计日志)
|
||||
/// </summary>
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
return Ok(await _authService.LogoutAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前登录用户信息 + 权限列表
|
||||
/// </summary>
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
return Ok(await _authService.GetMeAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改自己的密码
|
||||
/// </summary>
|
||||
[HttpPost("change-password")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto dto)
|
||||
{
|
||||
return Ok(await _authService.ChangePasswordAsync(dto));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,121 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据字典管理
|
||||
/// 数据字典管理(管理侧需 system:manage 权限;业务侧 options 接口无权限供其它模块调用)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/dict")]
|
||||
public class DictController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 数据字典管理 相关接口
|
||||
private readonly IDictService _dictService;
|
||||
|
||||
public DictController(IDictService dictService)
|
||||
{
|
||||
_dictService = dictService;
|
||||
}
|
||||
|
||||
// ==================== 字典分类 ====================
|
||||
|
||||
/// <summary>字典分类列表(分页)</summary>
|
||||
[HttpGet("type/list")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> GetTypeList(int pageIndex = 1, int pageSize = 10, string? keyword = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _dictService.GetTypesPagedAsync(pageIndex, pageSize, total, keyword);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<DictTypeDto>>.Success(result.Data))
|
||||
: Ok(Result<List<DictTypeDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>字典分类下拉(全部启用的分类)</summary>
|
||||
[HttpGet("type/options")]
|
||||
public async Task<IActionResult> GetTypeOptions()
|
||||
{
|
||||
return Ok(await _dictService.GetTypesAllAsync());
|
||||
}
|
||||
|
||||
/// <summary>字典分类详情</summary>
|
||||
[HttpGet("type/{id}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> GetTypeById(long id)
|
||||
{
|
||||
return Ok(await _dictService.GetTypeByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>新增字典分类</summary>
|
||||
[HttpPost("type")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> AddType([FromBody] DictTypeDto dto)
|
||||
{
|
||||
return Ok(await _dictService.AddTypeAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>修改字典分类</summary>
|
||||
[HttpPut("type")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> UpdateType([FromBody] DictTypeDto dto)
|
||||
{
|
||||
return Ok(await _dictService.UpdateTypeAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>删除字典分类(分类下有字典项时禁止删除)</summary>
|
||||
[HttpDelete("type/{id}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> DeleteType(long id)
|
||||
{
|
||||
return Ok(await _dictService.DeleteTypeAsync(id));
|
||||
}
|
||||
|
||||
// ==================== 字典项 ====================
|
||||
|
||||
/// <summary>查询指定分类下的字典项</summary>
|
||||
[HttpGet("item/list/{typeId}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> GetItemsByType(long typeId)
|
||||
{
|
||||
return Ok(await _dictService.GetItemsByTypeAsync(typeId));
|
||||
}
|
||||
|
||||
/// <summary>新增字典项</summary>
|
||||
[HttpPost("item")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> AddItem([FromBody] DictItemDto dto)
|
||||
{
|
||||
return Ok(await _dictService.AddItemAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>修改字典项</summary>
|
||||
[HttpPut("item")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> UpdateItem([FromBody] DictItemDto dto)
|
||||
{
|
||||
return Ok(await _dictService.UpdateItemAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>删除字典项</summary>
|
||||
[HttpDelete("item/{id}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> DeleteItem(long id)
|
||||
{
|
||||
return Ok(await _dictService.DeleteItemAsync(id));
|
||||
}
|
||||
|
||||
// ==================== 业务侧(无权限,供其它模块通过字典编码查询) ====================
|
||||
|
||||
/// <summary>按字典编码查询启用项(带缓存,业务侧调用)</summary>
|
||||
[HttpGet("options/{typeCode}")]
|
||||
public async Task<IActionResult> GetOptions(string typeCode)
|
||||
{
|
||||
return Ok(await _dictService.GetOptionsByCodeAsync(typeCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,118 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件存储管理
|
||||
/// 文件存储管理(存储配置 CRUD + 连接测试 + 文件上传 + 文件记录查询,需 system:manage 权限)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/file-storage")]
|
||||
[RequirePermission("system:manage")]
|
||||
public class FileStorageController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 文件存储管理 相关接口
|
||||
private readonly IFileStorageService _storageService;
|
||||
|
||||
public FileStorageController(IFileStorageService storageService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
// ==================== 存储配置 ====================
|
||||
|
||||
/// <summary>存储配置列表(分页)</summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _storageService.GetListPagedAsync(pageIndex, pageSize, total, keyword);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<FileStorageConfigDto>>.Success(result.Data))
|
||||
: Ok(Result<List<FileStorageConfigDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>存储配置详情</summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
return Ok(await _storageService.GetByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>新增存储配置</summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add([FromBody] FileStorageConfigDto dto)
|
||||
{
|
||||
return Ok(await _storageService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>修改存储配置(AccessKey/SecretKey 为 **** 掩码时保留原值)</summary>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Update([FromBody] FileStorageConfigDto dto)
|
||||
{
|
||||
return Ok(await _storageService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>删除存储配置(默认通道禁止删除)</summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _storageService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>设为默认存储通道</summary>
|
||||
[HttpPost("{id}/default")]
|
||||
public async Task<IActionResult> SetDefault(long id)
|
||||
{
|
||||
return Ok(await _storageService.SetDefaultAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>测试连接(按配置调用对应 Provider 的 TestConnectionAsync)</summary>
|
||||
[HttpPost("{id}/test")]
|
||||
public async Task<IActionResult> Test(long id)
|
||||
{
|
||||
return Ok(await _storageService.TestAsync(id));
|
||||
}
|
||||
|
||||
// ==================== 文件上传/记录 ====================
|
||||
|
||||
/// <summary>上传文件(multipart/form-data,字段名 file;使用默认存储通道)</summary>
|
||||
/// <param name="file">文件</param>
|
||||
/// <param name="bizType">业务类型(equipment_attachment/qrcode/generic 等,可选)</param>
|
||||
/// <param name="bizId">业务Id(可选)</param>
|
||||
/// <param name="uploader">上传人(可选,默认取当前登录用户)</param>
|
||||
[HttpPost("upload")]
|
||||
[RequestSizeLimit(100 * 1024 * 1024)]
|
||||
public async Task<IActionResult> Upload(IFormFile file, [FromForm] string? bizType = null, [FromForm] string? bizId = null, [FromForm] string? uploader = null)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
return Ok(Result<FileRecordDto>.Error("请选择要上传的文件"));
|
||||
using var stream = file.OpenReadStream();
|
||||
return Ok(await _storageService.UploadAsync(stream, file.FileName, file.Length, bizType, bizId, uploader));
|
||||
}
|
||||
|
||||
/// <summary>文件记录列表(分页,可按关键字和业务类型过滤)</summary>
|
||||
[HttpGet("file/list")]
|
||||
public async Task<IActionResult> GetFileList(int pageIndex = 1, int pageSize = 10, string? keyword = null, string? bizType = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _storageService.GetFileListPagedAsync(pageIndex, pageSize, total, keyword, bizType);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<FileRecordDto>>.Success(result.Data))
|
||||
: Ok(Result<List<FileRecordDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>文件记录详情</summary>
|
||||
[HttpGet("file/{id}")]
|
||||
public async Task<IActionResult> GetFileById(long id)
|
||||
{
|
||||
return Ok(await _storageService.GetFileByIdAsync(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,69 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织架构管理
|
||||
/// 组织架构管理(公司/实验室/部门/班组树;需 user:manage 权限)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/organization")]
|
||||
[RequirePermission("user:manage")]
|
||||
public class OrganizationController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 组织架构管理 相关接口
|
||||
private readonly IOrgService _orgService;
|
||||
|
||||
public OrganizationController(IOrgService orgService)
|
||||
{
|
||||
_orgService = orgService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完整组织树(嵌套子级,含班组白/夜班)
|
||||
/// </summary>
|
||||
[HttpGet("tree")]
|
||||
public async Task<IActionResult> GetTree()
|
||||
{
|
||||
return Ok(await _orgService.GetTreeAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织下拉选项(平铺)
|
||||
/// </summary>
|
||||
[HttpGet("options")]
|
||||
public async Task<IActionResult> GetOptions()
|
||||
{
|
||||
return Ok(await _orgService.GetOptionsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增组织节点(ParentId 传父级Id,根节点传 "0")
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add([FromBody] OrgDto dto)
|
||||
{
|
||||
return Ok(await _orgService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改组织节点
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Update([FromBody] OrgDto dto)
|
||||
{
|
||||
return Ok(await _orgService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除组织节点(有子级或挂靠用户时不可删)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _orgService.DeleteAsync(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,103 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色权限管理
|
||||
/// 角色权限管理(需 user:manage 权限)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/role")]
|
||||
[RequirePermission("user:manage")]
|
||||
public class RoleController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 角色权限管理 相关接口
|
||||
private readonly IRoleService _roleService;
|
||||
|
||||
public RoleController(IRoleService roleService)
|
||||
{
|
||||
_roleService = roleService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色列表(分页)
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _roleService.GetPagedAsync(pageIndex, pageSize, total, keyword);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<RoleDto>>.Success(result.Data))
|
||||
: Ok(Result<List<RoleDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色下拉选项
|
||||
/// </summary>
|
||||
[HttpGet("options")]
|
||||
public async Task<IActionResult> GetOptions()
|
||||
{
|
||||
return Ok(await _roleService.GetOptionsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全部权限列表(分配权限用)
|
||||
/// </summary>
|
||||
[HttpGet("permissions")]
|
||||
public async Task<IActionResult> GetPermissions()
|
||||
{
|
||||
return Ok(await _roleService.GetPermissionsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情(含权限Id列表)
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
return Ok(await _roleService.GetByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增角色(PermissionIds 传权限)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add([FromBody] RoleDto dto)
|
||||
{
|
||||
return Ok(await _roleService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改角色(PermissionIds 传 null 不改权限)
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Update([FromBody] RoleDto dto)
|
||||
{
|
||||
return Ok(await _roleService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色(系统内置角色不可删)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _roleService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分配权限(全量覆盖)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/permissions")]
|
||||
public async Task<IActionResult> AssignPermissions(long id, [FromBody] List<string> permissionIds)
|
||||
{
|
||||
var ids = permissionIds?.Select(long.Parse).Where(p => p > 0).ToList() ?? new List<long>();
|
||||
return Ok(await _roleService.AssignPermissionsAsync(id, ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,95 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统参数配置
|
||||
/// 系统参数配置(管理侧需 system:manage 权限;业务侧 value/values 接口无权限供其它模块调用)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/param")]
|
||||
public class SystemParamController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 系统参数配置 相关接口
|
||||
private readonly ISystemParamService _paramService;
|
||||
|
||||
public SystemParamController(ISystemParamService paramService)
|
||||
{
|
||||
_paramService = paramService;
|
||||
}
|
||||
|
||||
// ==================== 管理侧 ====================
|
||||
|
||||
/// <summary>参数列表(分页,可按关键字和分组过滤)</summary>
|
||||
[HttpGet("list")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, string? group = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _paramService.GetListPagedAsync(pageIndex, pageSize, total, keyword, group);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<SystemParamDto>>.Success(result.Data))
|
||||
: Ok(Result<List<SystemParamDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>参数详情</summary>
|
||||
[HttpGet("{id}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
return Ok(await _paramService.GetByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>新增参数</summary>
|
||||
[HttpPost]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> Add([FromBody] SystemParamDto dto)
|
||||
{
|
||||
return Ok(await _paramService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>修改参数(内置参数仅允许改 Value/Remark)</summary>
|
||||
[HttpPut]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> Update([FromBody] SystemParamDto dto)
|
||||
{
|
||||
return Ok(await _paramService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>删除参数(内置参数禁止删除)</summary>
|
||||
[HttpDelete("{id}")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _paramService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>清空参数缓存(运维用)</summary>
|
||||
[HttpPost("cache/reload")]
|
||||
[RequirePermission("system:manage")]
|
||||
public async Task<IActionResult> ReloadCache()
|
||||
{
|
||||
return Ok(await _paramService.ReloadCacheAsync());
|
||||
}
|
||||
|
||||
// ==================== 业务侧(无权限,供其它模块按 Key 取值) ====================
|
||||
|
||||
/// <summary>按 Key 取单个值</summary>
|
||||
[HttpGet("value/{key}")]
|
||||
public async Task<IActionResult> GetValue(string key)
|
||||
{
|
||||
return Ok(await _paramService.GetValueAsync(key));
|
||||
}
|
||||
|
||||
/// <summary>按 Key 列表批量取值(query: ?keys=k1&k2&k3)</summary>
|
||||
[HttpGet("values")]
|
||||
public async Task<IActionResult> GetValues([FromQuery] string[] keys)
|
||||
{
|
||||
return Ok(await _paramService.GetValuesAsync(keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,124 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.System;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using WebAPI.Filters;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户管理
|
||||
/// 用户管理(需 user:manage 权限)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/system/user")]
|
||||
[RequirePermission("user:manage")]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
// TODO: 实现 用户管理 相关接口
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UserController(IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户列表(分页,关键字匹配用户名/姓名,可按角色、组织节点筛选;受数据范围约束)
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList(int pageIndex = 1, int pageSize = 10, string? keyword = null, long roleId = 0, long orgId = 0)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _userService.GetPagedAsync(pageIndex, pageSize, total, keyword, roleId > 0 ? roleId : null, orgId > 0 ? orgId : null);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result.IsSuccess
|
||||
? Ok(Result<List<UserDto>>.Success(result.Data))
|
||||
: Ok(Result<List<UserDto>>.Error(result.Msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户下拉选项(启用中的用户)
|
||||
/// </summary>
|
||||
[HttpGet("options")]
|
||||
public async Task<IActionResult> GetOptions()
|
||||
{
|
||||
return Ok(await _userService.GetOptionsAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
return Ok(await _userService.GetByIdAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增用户(InitialPassword 传初始密码,RoleIds 传角色)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Add([FromBody] UserDto dto)
|
||||
{
|
||||
return Ok(await _userService.AddAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改用户(RoleIds 传 null 不改角色;密码走重置接口)
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Update([FromBody] UserDto dto)
|
||||
{
|
||||
return Ok(await _userService.UpdateAsync(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
return Ok(await _userService.DeleteAsync(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分配角色(全量覆盖)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/roles")]
|
||||
public async Task<IActionResult> AssignRoles(long id, [FromBody] List<string> roleIds)
|
||||
{
|
||||
var ids = roleIds?.Select(long.Parse).Where(r => r > 0).ToList() ?? new List<long>();
|
||||
return Ok(await _userService.AssignRolesAsync(id, ids));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置用户密码
|
||||
/// </summary>
|
||||
[HttpPost("{id}/reset-password")]
|
||||
public async Task<IActionResult> ResetPassword(long id, [FromBody] ResetPasswordRequest request)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.NewPassword))
|
||||
return Ok(Result.Error("新密码不能为空"));
|
||||
return Ok(await _userService.ResetPasswordAsync(id, request.NewPassword));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/禁用用户
|
||||
/// </summary>
|
||||
[HttpPost("{id}/enabled")]
|
||||
public async Task<IActionResult> SetEnabled(long id, [FromBody] EnabledRequest request)
|
||||
{
|
||||
return Ok(await _userService.SetEnabledAsync(id, request?.Enabled ?? true));
|
||||
}
|
||||
|
||||
public class ResetPasswordRequest
|
||||
{
|
||||
public string NewPassword { get; set; } = "";
|
||||
}
|
||||
|
||||
public class EnabledRequest
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using ORM;
|
||||
using Service.Implement;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
|
||||
namespace WebAPI
|
||||
{
|
||||
@@ -50,7 +54,44 @@ namespace WebAPI
|
||||
// 泛型基础服务(IBaseService<> -> BaseService<>)单独注册
|
||||
services.AddScoped(typeof(Service.Interface.IBaseService<>), typeof(Service.Implement.BaseService<>));
|
||||
|
||||
// 文件存储 Provider:命名约定 XxxFileStorageProvider 不匹配自动注册,手动登记
|
||||
// 新增 MinIO/OSS 实现时在此追加一行 services.AddScoped<IFileStorageProvider, MinioFileStorageProvider>()
|
||||
services.AddScoped<Service.Interface.IFileStorageProvider, Service.Implement.LocalFileStorageProvider>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册告警通知组件(飞书/钉钉/企微 Notifier,Typed HttpClient 模式)
|
||||
/// 新增渠道:实现 IAlertNotifier 后在此追加一行注册即可,调用方零改动
|
||||
/// </summary>
|
||||
public static IServiceCollection AddAlertNotification(this IServiceCollection services)
|
||||
{
|
||||
ConfigureNotifier(services.AddHttpClient<IAlertNotifier, FeishuNotifier>());
|
||||
ConfigureNotifier(services.AddHttpClient<IAlertNotifier, DingTalkNotifier>());
|
||||
ConfigureNotifier(services.AddHttpClient<IAlertNotifier, WeComNotifier>());
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知渠道 HttpClient 统一配置:
|
||||
/// - 超时 15s(默认 100s 过长,测试推送遇不可达地址会长时间无响应)
|
||||
/// - 显式启用 TLS1.2/1.3(规避个别环境默认协议协商失败导致的 SSL 握手异常)
|
||||
/// - 连接池生命周期 5 分钟(长驻服务定期回收连接,避免陈旧连接/DNS 漂移引发握手失败)
|
||||
/// </summary>
|
||||
private static void ConfigureNotifier(IHttpClientBuilder builder)
|
||||
{
|
||||
builder
|
||||
.ConfigureHttpClient(c => c.Timeout = TimeSpan.FromSeconds(15))
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
},
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
||||
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Model;
|
||||
|
||||
namespace WebAPI.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限校验过滤器:标注在 Controller 或 Action 上,执行前校验当前用户(JWT Claims)是否拥有指定权限
|
||||
/// 用法:[RequirePermission("device:edit")]
|
||||
/// 权限编码在登录时随 JWT 写入 Claims,此处只读 Claims,不查库
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class RequirePermissionAttribute : Attribute, IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly string _permissionCode;
|
||||
|
||||
public RequirePermissionAttribute(string permissionCode)
|
||||
{
|
||||
_permissionCode = permissionCode;
|
||||
}
|
||||
|
||||
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
// 已被 [AllowAnonymous] 标记的接口放行(如登录本身)
|
||||
if (context.ActionDescriptor.EndpointMetadata.Any(m => m is IAllowAnonymous))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var user = context.HttpContext.User;
|
||||
if (user?.Identity?.IsAuthenticated != true)
|
||||
{
|
||||
context.Result = new UnauthorizedObjectResult(Result.Error("未登录或 Token 已失效"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var perms = user.FindFirst("permissions")?.Value;
|
||||
var permList = string.IsNullOrWhiteSpace(perms)
|
||||
? new List<string>()
|
||||
: perms.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
|
||||
|
||||
if (!permList.Contains(_permissionCode) && !permList.Contains("*"))
|
||||
{
|
||||
context.Result = new ObjectResult(Result.Error($"没有操作权限(需要权限:{_permissionCode})"))
|
||||
{
|
||||
StatusCode = StatusCodes.Status403Forbidden
|
||||
};
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -16,4 +18,8 @@
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Converters\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+52
-1
@@ -1,5 +1,10 @@
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using ORM;
|
||||
using Service.Implement;
|
||||
using Service.Interface;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using WebAPI.Services;
|
||||
|
||||
@@ -31,17 +36,58 @@ namespace WebAPI
|
||||
DatabaseConfig.SetTenant(tenantId);
|
||||
DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
||||
SqlSugarContext.InitDatabase();
|
||||
builder.Services.AddControllers()
|
||||
|
||||
// RBAC 数据种子:预定义角色/权限/默认管理员(幂等)
|
||||
DataSeeder.Seed();
|
||||
|
||||
// 飞书自建应用凭证:用于告警 @ 时把接收人手机号转成 open_id(webhook 机器人自身无此能力)
|
||||
var feishu = builder.Configuration.GetSection("Feishu");
|
||||
Common.Notify.FeishuConfig.Init(feishu["AppId"] ?? "", feishu["AppSecret"] ?? "");
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
// 非空字符串属性(如 Remark)缺失时不报 400,前端可以只传部分字段
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString;
|
||||
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||
|
||||
});
|
||||
// 自动注册业务服务(Service.Interface -> Service.Implement)
|
||||
builder.Services.AddBusinessServices();
|
||||
|
||||
// 内存缓存:数据字典等查询频繁的基础数据按 typeCode 缓存,增删改时失效
|
||||
builder.Services.AddMemoryCache();
|
||||
|
||||
// 当前用户上下文(从 JWT Claims 解析,供业务服务取操作人/做权限判断)
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUser, CurrentUser>();
|
||||
|
||||
// JWT 认证:登录后签发 Token,后续请求凭 Bearer Token 识别用户与角色
|
||||
var jwt = builder.Configuration.GetSection("Jwt");
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwt["Issuer"],
|
||||
ValidAudience = jwt["Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt["SecretKey"]!)),
|
||||
ClockSkew = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
});
|
||||
|
||||
// 告警通知渠道(飞书/钉钉/企微 Notifier)+ 后台推送 Worker
|
||||
builder.Services.AddAlertNotification();
|
||||
builder.Services.AddHostedService<AlertNotifyWorker>();
|
||||
|
||||
// 温度箱模拟数据:每秒生成 100 条温湿度入库(真实设备接入后删除)
|
||||
builder.Services.AddHostedService<TemperatureBoxSimulator>();
|
||||
|
||||
@@ -61,8 +107,13 @@ namespace WebAPI
|
||||
// 导致前端 vite 代理被 CORS 拦截(Network Error);上位机/现场部署通常也只用 http
|
||||
// app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
// 静态文件:设备附件上传后通过 /uploads/... 访问(存储于 wwwroot)
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
using Model.Entity.Inspection;
|
||||
using ORM;
|
||||
using Service.Implement;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
|
||||
namespace WebAPI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警通知后台 Worker:消费 AlertNotifyBus 队列,按通知规则分发到飞书/钉钉/企微渠道
|
||||
/// 流程:加载告警 → 匹配启用规则(按级别) → 静默期判断 → 直连推送(重试3次)或写跨网发件箱 → 记推送日志
|
||||
/// </summary>
|
||||
public class AlertNotifyWorker : BackgroundService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<AlertNotifyWorker> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 直连推送最大尝试次数(含首次)
|
||||
/// </summary>
|
||||
private const int MaxAttempts = 3;
|
||||
|
||||
public AlertNotifyWorker(IServiceScopeFactory scopeFactory, ILogger<AlertNotifyWorker> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("告警通知 Worker 已启动");
|
||||
try
|
||||
{
|
||||
await foreach (var evt in AlertNotifyBus.Reader.ReadAllAsync(stoppingToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessEventAsync(evt, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理告警通知事件失败, AlertId={AlertId}", evt.AlertId);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 正常停机
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理单个告警通知事件
|
||||
/// </summary>
|
||||
private async Task ProcessEventAsync(AlertNotifyEvent evt, CancellationToken ct)
|
||||
{
|
||||
var db = SqlSugarContext.DbContext;
|
||||
|
||||
// 1. 加载告警消息
|
||||
var alert = await db.Queryable<AlertMessageEntity>()
|
||||
.Where(x => x.Id == evt.AlertId && x.IsDel == 0)
|
||||
.FirstAsync();
|
||||
if (alert == null) return;
|
||||
|
||||
// 2. 匹配启用的通知规则(按告警级别)
|
||||
var rules = await db.Queryable<AlertNotifyRuleEntity>()
|
||||
.Where(x => x.IsDel == 0 && x.IsEnabled && x.AlertLevel == alert.AlertLevel)
|
||||
.ToListAsync();
|
||||
if (rules.Count == 0) return;
|
||||
|
||||
// 3. 每个事件独立创建作用域(Worker 是单例,服务是 Scoped)
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var notifyService = scope.ServiceProvider.GetRequiredService<IAlertNotifyService>();
|
||||
var notifiers = scope.ServiceProvider.GetServices<IAlertNotifier>().ToList();
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
var channel = await db.Queryable<AlertNotifyChannelEntity>()
|
||||
.Where(x => x.Id == rule.ChannelId && x.IsDel == 0 && x.IsEnabled)
|
||||
.FirstAsync();
|
||||
if (channel == null) continue;
|
||||
|
||||
// 4. 静默期判断:合并触发的重复告警在静默期内不重复推送(新告警必推)
|
||||
if (!evt.IsNewAlert && rule.SilenceMinutes > 0)
|
||||
{
|
||||
var silenceStart = DateTime.Now.AddMinutes(-rule.SilenceMinutes);
|
||||
bool recentlyPushed = await db.Queryable<AlertNotifyLogEntity>()
|
||||
.AnyAsync(x => x.AlertId == alert.Id && x.ChannelId == channel.Id
|
||||
&& x.Success && x.NotifyTime >= silenceStart);
|
||||
if (recentlyPushed) continue;
|
||||
}
|
||||
|
||||
// 5. 组装消息与 @ 列表(规则接收人 + 当班值班人电话)
|
||||
var message = BuildMessage(alert);
|
||||
var atMobiles = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(rule.Receivers))
|
||||
{
|
||||
// 接收人常带 @ 前缀(如 "@18872509102"),但企微 mentioned_mobile_list / 钉钉 atMobiles 只认纯手机号,
|
||||
// 带 @ 的非法值无法匹配群成员导致 @ 不生效,这里统一去掉前导 @ 与空白
|
||||
atMobiles.AddRange(rule.Receivers.Split(',', ',', ';', ';')
|
||||
.Select(s => s.Trim().TrimStart('@').Trim()).Where(s => s.Length > 0));
|
||||
}
|
||||
if (rule.NotifyDutyPerson)
|
||||
{
|
||||
var duty = await notifyService.GetDutyByDateAsync(alert.LastAlertTime.Date);
|
||||
if (duty != null && !string.IsNullOrWhiteSpace(duty.Phone))
|
||||
{
|
||||
atMobiles.Add(duty.Phone.Trim());
|
||||
message.Content += $"\n值班人: {duty.PersonName}";
|
||||
}
|
||||
}
|
||||
atMobiles = atMobiles.Distinct().ToList();
|
||||
|
||||
// 飞书渠道:at 标签只认 open_id,不认手机号——把接收人里的手机号转成 open_id
|
||||
// ("all" 与 ou_ 开头的原样保留)。转换在内网主站完成,直连与跨网(Outbox)两条路径都拿到 open_id,AlertBridge 无需改。
|
||||
if (channel.ChannelType == NotifyChannelTypeEnum.Feishu && atMobiles.Count > 0)
|
||||
{
|
||||
var feishuContact = scope.ServiceProvider.GetRequiredService<IFeishuContactService>();
|
||||
var openIds = new List<string>();
|
||||
foreach (var at in atMobiles)
|
||||
{
|
||||
if (at.Equals("all", StringComparison.OrdinalIgnoreCase) || at.StartsWith("ou_"))
|
||||
{
|
||||
openIds.Add(at);
|
||||
}
|
||||
else
|
||||
{
|
||||
var openId = await feishuContact.GetOpenIdByMobileAsync(at);
|
||||
if (!string.IsNullOrEmpty(openId)) openIds.Add(openId);
|
||||
else _logger.LogWarning("飞书接收人 {Receiver} 未换取到 open_id,已跳过 @(检查飞书应用权限与通讯录范围)", at);
|
||||
}
|
||||
}
|
||||
atMobiles = openIds.Distinct().ToList();
|
||||
}
|
||||
|
||||
// 关键:@ 列表必须回填到 message——直连模式的 Notifier 是从 message.AtMobiles 读取的。
|
||||
// 此前只把 atMobiles 传给了 Outbox 分支,直连 SendAsync(message) 的 message.AtMobiles 始终为空,导致企微/钉钉不 @ 任何人。
|
||||
message.AtMobiles = atMobiles;
|
||||
|
||||
// 6. 按渠道推送模式分发
|
||||
if (channel.PushMode == NotifyPushModeEnum.Outbox)
|
||||
{
|
||||
// 跨网模式:写发件箱,由跳板机 AlertBridge 拉取推送并回写结果
|
||||
await notifyService.EnqueueOutboxAsync(alert.Id, channel.Id, message, atMobiles);
|
||||
_logger.LogInformation("告警 {AlertId} 已转入跨网发件箱(渠道 {Channel})", alert.Id, channel.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 直连模式:查找对应 Notifier,失败重试
|
||||
var notifier = notifiers.FirstOrDefault(n => n.ChannelType == channel.ChannelType);
|
||||
if (notifier == null)
|
||||
{
|
||||
await notifyService.WriteLogAsync(alert.Id, channel.Id, message.Title,
|
||||
false, $"暂不支持的渠道类型: {channel.ChannelType}", 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
NotifyResult result = NotifyResult.Fail("未执行");
|
||||
int attempt = 0;
|
||||
while (attempt < MaxAttempts && !ct.IsCancellationRequested)
|
||||
{
|
||||
attempt++;
|
||||
result = await notifier.SendAsync(message, channel.WebhookUrl, channel.Secret, ct);
|
||||
if (result.Success) break;
|
||||
if (attempt < MaxAttempts)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(attempt * 2), ct); // 2s/4s 退避
|
||||
}
|
||||
}
|
||||
|
||||
await notifyService.WriteLogAsync(alert.Id, channel.Id,
|
||||
message.Title + " " + Truncate(message.Content, 800),
|
||||
result.Success, result.ErrorMsg, attempt - 1);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.LogWarning("告警 {AlertId} 推送渠道 {Channel} 失败: {Error}", alert.Id, channel.Name, result.ErrorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按内置模板构建通知消息(标题 + markdown 正文;后续可由通知模板模块接管)
|
||||
/// </summary>
|
||||
private static AlertNotifyMessage BuildMessage(AlertMessageEntity alert)
|
||||
{
|
||||
string levelLabel = AlertLevelMap.ToLabel(alert.AlertLevel);
|
||||
var content = new System.Text.StringBuilder();
|
||||
content.AppendLine($"- 设备: {alert.DeviceName}({alert.DeviceCode})");
|
||||
if (!string.IsNullOrWhiteSpace(alert.DeviceType)) content.AppendLine($"- 类型: {alert.DeviceType}");
|
||||
if (!string.IsNullOrWhiteSpace(alert.Metric)) content.AppendLine($"- 测点: {alert.Metric}");
|
||||
if (alert.CurrentValue.HasValue) content.AppendLine($"- 当前值: {alert.CurrentValue.Value}");
|
||||
if (alert.ThresholdValue.HasValue) content.AppendLine($"- 阈值: {alert.ThresholdValue.Value}");
|
||||
content.AppendLine($"- 告警类型: {TypeLabel(alert.AlertType)}");
|
||||
content.AppendLine($"- 来源: {SourceLabel(alert.Source)}");
|
||||
content.AppendLine($"- 内容: {alert.Content}");
|
||||
if (alert.TriggerCount > 1) content.AppendLine($"- 累计触发: {alert.TriggerCount} 次");
|
||||
content.Append($"- 时间: {alert.LastAlertTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
return new AlertNotifyMessage
|
||||
{
|
||||
Title = $"【{levelLabel}】设备告警 - {alert.DeviceName ?? alert.DeviceCode}",
|
||||
Content = content.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string TypeLabel(AlertTypeEnum type) => type switch
|
||||
{
|
||||
AlertTypeEnum.ThresholdExceed => "阈值超限",
|
||||
AlertTypeEnum.DeviceOffline => "设备离线",
|
||||
AlertTypeEnum.CommFault => "通讯故障",
|
||||
AlertTypeEnum.PatrolAbnormal => "巡检异常",
|
||||
AlertTypeEnum.Custom => "自定义",
|
||||
_ => "其它"
|
||||
};
|
||||
|
||||
private static string SourceLabel(AlertSourceEnum source) => source switch
|
||||
{
|
||||
AlertSourceEnum.RuleEngine => "规则引擎",
|
||||
AlertSourceEnum.AutoPatrol => "自动巡检",
|
||||
AlertSourceEnum.Dashboard => "数据看板",
|
||||
AlertSourceEnum.Collection => "数据采集",
|
||||
AlertSourceEnum.Manual => "手动上报",
|
||||
_ => "其它"
|
||||
};
|
||||
|
||||
private static string Truncate(string s, int max) =>
|
||||
string.IsNullOrEmpty(s) || s.Length <= max ? s : s.Substring(0, max) + "...";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Security.Claims;
|
||||
using Service.Interface;
|
||||
|
||||
namespace WebAPI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前登录用户上下文实现:从 JWT Claims(HttpContext.User)解析
|
||||
/// </summary>
|
||||
public class CurrentUser : ICurrentUser
|
||||
{
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
|
||||
public CurrentUser(IHttpContextAccessor accessor)
|
||||
{
|
||||
_accessor = accessor;
|
||||
}
|
||||
|
||||
private ClaimsPrincipal? Principal => _accessor?.HttpContext?.User;
|
||||
|
||||
public bool IsAuthenticated => Principal?.Identity?.IsAuthenticated ?? false;
|
||||
|
||||
public long UserId
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return long.TryParse(v, out var id) ? id : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string UserName => Principal?.FindFirst(ClaimTypes.Name)?.Value ?? "";
|
||||
|
||||
public List<long> RoleIds => ParseList("roleIds").Select(long.Parse).ToList();
|
||||
|
||||
public List<string> RoleNames => ParseList("roleNames");
|
||||
|
||||
public List<string> Permissions => ParseList("permissions");
|
||||
|
||||
public byte DataScope
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Principal?.FindFirst("dataScope")?.Value;
|
||||
return byte.TryParse(v, out var b) ? b : (byte)2;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPermission(string code)
|
||||
{
|
||||
if (!IsAuthenticated) return false;
|
||||
var perms = Permissions;
|
||||
return perms.Contains(code) || perms.Contains("*");
|
||||
}
|
||||
|
||||
private List<string> ParseList(string claimType)
|
||||
{
|
||||
var raw = Principal?.FindFirst(claimType)?.Value;
|
||||
if (string.IsNullOrWhiteSpace(raw)) return new List<string>();
|
||||
return raw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Model.Entity;
|
||||
using Model.Dto.Inspection;
|
||||
using Model.Entity.Inspection;
|
||||
using ORM;
|
||||
using Service.Interface;
|
||||
|
||||
namespace WebAPI.Services
|
||||
{
|
||||
@@ -22,9 +24,11 @@ namespace WebAPI.Services
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, BoxState> _states = new();
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public TemperatureBoxSimulator()
|
||||
public TemperatureBoxSimulator(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
for (int i = 1; i <= DeviceCount; i++)
|
||||
{
|
||||
_states[$"BOX-{i:D3}"] = new BoxState();
|
||||
@@ -39,6 +43,7 @@ namespace WebAPI.Services
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var batch = new List<TemperatureBoxDataEntity>(DeviceCount);
|
||||
var alarmDevices = new List<(string Code, double Temp)>();
|
||||
|
||||
foreach (var kv in _states)
|
||||
{
|
||||
@@ -48,6 +53,10 @@ namespace WebAPI.Services
|
||||
s.Humidity = Math.Clamp(s.Humidity + (Rnd.NextDouble() - 0.5) * 3, 20, 90);
|
||||
|
||||
bool alarm = s.Temperature >= AlarmTemp;
|
||||
if (alarm)
|
||||
{
|
||||
alarmDevices.Add((kv.Key, Math.Round(s.Temperature, 1)));
|
||||
}
|
||||
batch.Add(new TemperatureBoxDataEntity
|
||||
{
|
||||
DeviceCode = kv.Key,
|
||||
@@ -62,6 +71,30 @@ namespace WebAPI.Services
|
||||
|
||||
// 批量插入
|
||||
await SqlSugarContext.DbContext.Insertable(batch).ExecuteCommandAsync();
|
||||
|
||||
// 报警设备上报告警中心(告警中心内部有 5 分钟合并窗口,不会每秒重复建告警)
|
||||
if (alarmDevices.Count > 0)
|
||||
{
|
||||
// 单例 HostedService 不能直接注入 Scoped 服务,按批次创建作用域解析
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var alertCenter = scope.ServiceProvider.GetRequiredService<IAlertCenterService>();
|
||||
foreach (var (code, temp) in alarmDevices)
|
||||
{
|
||||
await alertCenter.RaiseAsync(new AlertRaiseDto
|
||||
{
|
||||
Source = AlertSourceEnum.Collection,
|
||||
DeviceCode = code,
|
||||
DeviceName = $"温度箱 {code}",
|
||||
DeviceType = "TemperatureBox",
|
||||
Metric = "Temperature",
|
||||
AlertType = AlertTypeEnum.ThresholdExceed,
|
||||
AlertLevel = AlertLevelEnum.Urgent,
|
||||
CurrentValue = temp,
|
||||
ThresholdValue = AlarmTemp,
|
||||
Content = $"温度箱 {code} 温度超限:当前 {temp}℃ ≥ 报警阈值 {AlarmTemp}℃"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Model.Entity.Asset;
|
||||
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备附件 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class EquipmentAttachmentDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>文件名称</summary>
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>文件类型(说明书/合格证/校准证书等)</summary>
|
||||
public AttachmentTypeEnum FileType { get; set; }
|
||||
|
||||
/// <summary>文件扩展名(如 .pdf、.jpg)</summary>
|
||||
public string? FileExt { get; set; }
|
||||
|
||||
/// <summary>文件存储地址</summary>
|
||||
public string? FileUrl { get; set; }
|
||||
|
||||
/// <summary>文件大小(byte)</summary>
|
||||
public long FileSize { get; set; }
|
||||
|
||||
/// <summary>上传人</summary>
|
||||
public string? Uploader { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备分类 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class EquipmentCategoryDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>父级分类Id(0表示顶级,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string ParentId { get; set; }
|
||||
|
||||
/// <summary>分类名称</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>分类编码</summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>层级(1级、2级……)</summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>排序号</summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using Model.Entity.Asset;
|
||||
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备台账 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class EquipmentDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string;入参可空,新增时无需传递)</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备编号(唯一)</summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>设备分类Id(关联 EquipmentCategoryEntity.Id,雪花 Id 转 string 防精度丢失;可空,未分类时为 "0" 或不传)</summary>
|
||||
public string? CategoryId { get; set; }
|
||||
|
||||
/// <summary>设备类型</summary>
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>设备品牌</summary>
|
||||
public string? Brand { get; set; }
|
||||
|
||||
/// <summary>设备型号</summary>
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>规格参数</summary>
|
||||
public string? Specifications { get; set; }
|
||||
|
||||
/// <summary>制造商</summary>
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
/// <summary>供应商</summary>
|
||||
public string? Supplier { get; set; }
|
||||
|
||||
/// <summary>设备图片地址</summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>二维码编号(一物一码)</summary>
|
||||
public string? QrCode { get; set; }
|
||||
|
||||
/// <summary>二维码扫码直达 URL(生成后写入,扫码跳转设备台账详情)</summary>
|
||||
public string? QrCodeUrl { get; set; }
|
||||
|
||||
/// <summary>RFID 编号(绑定时写入)</summary>
|
||||
public string? RfidCode { get; set; }
|
||||
|
||||
/// <summary>设备描述</summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>使用部门名称</summary>
|
||||
public string? Department { get; set; }
|
||||
|
||||
/// <summary>存放位置</summary>
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>责任人</summary>
|
||||
public string? ResponsiblePerson { get; set; }
|
||||
|
||||
/// <summary>责任人联系电话</summary>
|
||||
public string? ContactPhone { get; set; }
|
||||
|
||||
/// <summary>保管人</summary>
|
||||
public string? Custodian { get; set; }
|
||||
|
||||
/// <summary>购置日期</summary>
|
||||
public DateTime? PurchaseDate { get; set; }
|
||||
|
||||
/// <summary>购置价格</summary>
|
||||
public decimal? PurchasePrice { get; set; }
|
||||
|
||||
/// <summary>保修到期日期</summary>
|
||||
public DateTime? WarrantyExpireDate { get; set; }
|
||||
|
||||
/// <summary>验收日期</summary>
|
||||
public DateTime? AcceptanceDate { get; set; }
|
||||
|
||||
/// <summary>启用日期</summary>
|
||||
public DateTime? EnableDate { get; set; }
|
||||
|
||||
/// <summary>使用年限(年)</summary>
|
||||
public int? ServiceLifeYears { get; set; }
|
||||
|
||||
/// <summary>报废日期</summary>
|
||||
public DateTime? ScrapDate { get; set; }
|
||||
|
||||
/// <summary>设备当前状态</summary>
|
||||
public EquipmentStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>校准状态(正常/超期)</summary>
|
||||
public ComplianceStatusEnum CalibrationStatus { get; set; }
|
||||
|
||||
/// <summary>上次校准日期</summary>
|
||||
public DateTime? LastCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>下次校准到期日期</summary>
|
||||
public DateTime? NextCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>检定日期</summary>
|
||||
public DateTime? InspectionDate { get; set; }
|
||||
|
||||
/// <summary>下次检定到期日期</summary>
|
||||
public DateTime? NextInspectionDate { get; set; }
|
||||
|
||||
/// <summary>保养状态(正常/超期)</summary>
|
||||
public ComplianceStatusEnum MaintenanceStatus { get; set; }
|
||||
|
||||
/// <summary>上次保养日期</summary>
|
||||
public DateTime? LastMaintenanceDate { get; set; }
|
||||
|
||||
/// <summary>下次保养到期日期</summary>
|
||||
public DateTime? NextMaintenanceDate { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>创建人</summary>
|
||||
public string? CreateBy { get; set; }
|
||||
|
||||
/// <summary>更新人</summary>
|
||||
public string? UpdateBy { get; set; }
|
||||
|
||||
/// <summary>更新时间</summary>
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Model.Entity.Asset;
|
||||
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备状态变更请求 DTO(POST api/asset/equipment/{id}/status 入参)
|
||||
/// </summary>
|
||||
public class EquipmentStatusChangeDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标状态(必填)
|
||||
/// </summary>
|
||||
public EquipmentStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Model.Entity.Asset;
|
||||
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备状态记录 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class EquipmentStatusRecordDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>变更前状态</summary>
|
||||
public EquipmentStatusEnum FromStatus { get; set; }
|
||||
|
||||
/// <summary>变更后状态</summary>
|
||||
public EquipmentStatusEnum ToStatus { get; set; }
|
||||
|
||||
/// <summary>状态变更时间</summary>
|
||||
public DateTime ChangeTime { get; set; }
|
||||
|
||||
/// <summary>操作人</summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>变更原因/备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备二维码信息(生成/列表/详情共用)
|
||||
/// </summary>
|
||||
public class QrCodeDto
|
||||
{
|
||||
/// <summary>设备 Id(long → string)</summary>
|
||||
public string EquipmentId { get; set; }
|
||||
/// <summary>设备编号</summary>
|
||||
public string? EquipmentCode { get; set; }
|
||||
/// <summary>设备名称</summary>
|
||||
public string? EquipmentName { get; set; }
|
||||
/// <summary>存放位置(打印标签用)</summary>
|
||||
public string? Location { get; set; }
|
||||
/// <summary>二维码编号(一物一码,人工可读)</summary>
|
||||
public string? QrCode { get; set; }
|
||||
/// <summary>扫码直达 URL(生成后写入;为空表示尚未生成)</summary>
|
||||
public string? QrCodeUrl { get; set; }
|
||||
/// <summary>RFID 编号(已绑定则有值)</summary>
|
||||
public string? RfidCode { get; set; }
|
||||
/// <summary>是否已生成二维码(QrCodeUrl 非空)</summary>
|
||||
public bool HasGenerated => !string.IsNullOrWhiteSpace(QrCodeUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量生成结果
|
||||
/// </summary>
|
||||
public class QrCodeBatchResultDto
|
||||
{
|
||||
public string EquipmentId { get; set; }
|
||||
public string? EquipmentCode { get; set; }
|
||||
public string? EquipmentName { get; set; }
|
||||
public string? QrCodeUrl { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Model.Dto.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备日志 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class DeviceLogDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>设备Id</summary>
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>设备编号</summary>
|
||||
public string DeviceCode { get; set; }
|
||||
|
||||
/// <summary>日志级别(Info/Warn/Error)</summary>
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>日志类型(连接/通讯/采集/指令/系统)</summary>
|
||||
public string LogType { get; set; }
|
||||
|
||||
/// <summary>日志内容</summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>记录时间</summary>
|
||||
public DateTime LogTime { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace Model.Dto.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 网关 DTO
|
||||
/// </summary>
|
||||
public class GatewayDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int ProtocolType { get; set; }
|
||||
|
||||
// TCP
|
||||
public string? Host { get; set; }
|
||||
public int? Port { get; set; }
|
||||
|
||||
// 串口
|
||||
public string? ComPort { get; set; }
|
||||
public int? BaudRate { get; set; }
|
||||
public byte? DataBits { get; set; }
|
||||
public byte? StopBits { get; set; }
|
||||
public byte? Parity { get; set; }
|
||||
|
||||
// S7
|
||||
public string? CpuType { get; set; }
|
||||
public byte? Rack { get; set; }
|
||||
public byte? Slot { get; set; }
|
||||
|
||||
// 状态
|
||||
public bool IsEnabled { get; set; }
|
||||
public byte OnlineStatus { get; set; }
|
||||
public DateTime? LastConnectedTime { get; set; }
|
||||
public string? LastError { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网关下拉选项 DTO(设备表单选用)
|
||||
/// </summary>
|
||||
public class GatewayOptionDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试连接结果 DTO
|
||||
/// </summary>
|
||||
public class GatewayTestResultDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Model.Entity.Config;
|
||||
|
||||
namespace Model.Dto.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// IOT 设备 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class IotDeviceDto
|
||||
{
|
||||
/// <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>设备类型(型号,对应 DeviceCommand 驱动类)</summary>
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>所属产品Id(string,0=未分类)</summary>
|
||||
public string ProductId { get; set; }
|
||||
|
||||
/// <summary>所属产品型号/名称(只读展示)</summary>
|
||||
public string? ProductName { get; set; }
|
||||
|
||||
/// <summary>所属网关Id(string,0=未分配)</summary>
|
||||
public string GatewayId { get; set; }
|
||||
|
||||
/// <summary>所属网关名称(只读展示)</summary>
|
||||
public string? GatewayName { get; set; }
|
||||
|
||||
/// <summary>从站地址</summary>
|
||||
public byte SlaveId { 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 bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>在线状态</summary>
|
||||
public IotDeviceOnlineStatusEnum OnlineStatus { get; set; }
|
||||
|
||||
/// <summary>最后采集时间</summary>
|
||||
public DateTime? LastCollectTime { get; set; }
|
||||
|
||||
/// <summary>最近错误信息</summary>
|
||||
public string? LastError { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { 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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Model.Entity.Inspection;
|
||||
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警消息 DTO(Id 转 string 防前端精度丢失)
|
||||
/// </summary>
|
||||
public class AlertMessageDto
|
||||
{
|
||||
/// <summary>主键 Id</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>告警来源</summary>
|
||||
public AlertSourceEnum Source { get; set; }
|
||||
|
||||
/// <summary>关联告警规则Id("0"表示无)</summary>
|
||||
public string? RuleId { get; set; }
|
||||
|
||||
/// <summary>设备编号</summary>
|
||||
public string? DeviceCode { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? DeviceName { get; set; }
|
||||
|
||||
/// <summary>设备类型</summary>
|
||||
public string? DeviceType { get; set; }
|
||||
|
||||
/// <summary>测点/监测指标</summary>
|
||||
public string? Metric { get; set; }
|
||||
|
||||
/// <summary>告警类型</summary>
|
||||
public AlertTypeEnum AlertType { get; set; }
|
||||
|
||||
/// <summary>告警级别(1信息/2警告/3紧急)</summary>
|
||||
public AlertLevelEnum AlertLevel { get; set; }
|
||||
|
||||
/// <summary>当前值</summary>
|
||||
public double? CurrentValue { get; set; }
|
||||
|
||||
/// <summary>阈值</summary>
|
||||
public double? ThresholdValue { get; set; }
|
||||
|
||||
/// <summary>告警内容描述</summary>
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>通知模板编码(预留)</summary>
|
||||
public string? TemplateCode { get; set; }
|
||||
|
||||
/// <summary>触发次数(合并窗口内累计)</summary>
|
||||
public int TriggerCount { get; set; }
|
||||
|
||||
/// <summary>首次告警时间</summary>
|
||||
public DateTime FirstAlertTime { get; set; }
|
||||
|
||||
/// <summary>最近告警时间</summary>
|
||||
public DateTime LastAlertTime { get; set; }
|
||||
|
||||
/// <summary>告警状态(1未确认/2已确认/3已处理/4已关闭/5已忽略/6已转工单)</summary>
|
||||
public AlertStatusEnum AlertStatus { get; set; }
|
||||
|
||||
/// <summary>确认人</summary>
|
||||
public string? AckBy { get; set; }
|
||||
|
||||
/// <summary>确认时间</summary>
|
||||
public DateTime? AckTime { get; set; }
|
||||
|
||||
/// <summary>处理人</summary>
|
||||
public string? HandleBy { get; set; }
|
||||
|
||||
/// <summary>处理时间</summary>
|
||||
public DateTime? HandleTime { get; set; }
|
||||
|
||||
/// <summary>处理备注</summary>
|
||||
public string? HandleRemark { get; set; }
|
||||
|
||||
/// <summary>关联工单Id(预留二期,"0"表示未转工单)</summary>
|
||||
public string? WorkOrderId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using Model.Entity.Inspection;
|
||||
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 通知渠道 DTO(Id 转 string;Secret 出口脱敏为是否已配置)
|
||||
/// </summary>
|
||||
public class AlertNotifyChannelDto
|
||||
{
|
||||
/// <summary>主键 Id</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>渠道类型(1飞书/2钉钉/3企微)</summary>
|
||||
public NotifyChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>渠道名称</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>Webhook 地址</summary>
|
||||
public string? WebhookUrl { get; set; }
|
||||
|
||||
/// <summary>加签密钥(入参可传;出口仅返回脱敏标记,不回传明文)</summary>
|
||||
public string? Secret { get; set; }
|
||||
|
||||
/// <summary>是否已配置加签密钥(出口专用)</summary>
|
||||
public bool HasSecret { get; set; }
|
||||
|
||||
/// <summary>推送模式(1直连/2Outbox跨网中转)</summary>
|
||||
public NotifyPushModeEnum PushMode { get; set; }
|
||||
|
||||
/// <summary>是否启用</summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知规则 DTO
|
||||
/// </summary>
|
||||
public class AlertNotifyRuleDto
|
||||
{
|
||||
/// <summary>主键 Id</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>规则名称</summary>
|
||||
public string? RuleName { get; set; }
|
||||
|
||||
/// <summary>适用告警级别(1信息/2警告/3紧急)</summary>
|
||||
public AlertLevelEnum AlertLevel { get; set; }
|
||||
|
||||
/// <summary>通知渠道Id</summary>
|
||||
public string? ChannelId { get; set; }
|
||||
|
||||
/// <summary>接收人(@手机号/账号,逗号分隔)</summary>
|
||||
public string? Receivers { get; set; }
|
||||
|
||||
/// <summary>是否通知当班值班人</summary>
|
||||
public bool NotifyDutyPerson { get; set; }
|
||||
|
||||
/// <summary>静默期(分钟)</summary>
|
||||
public int SilenceMinutes { get; set; }
|
||||
|
||||
/// <summary>是否启用</summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 推送日志 DTO
|
||||
/// </summary>
|
||||
public class AlertNotifyLogDto
|
||||
{
|
||||
/// <summary>主键 Id</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>告警消息Id("0"表示渠道测试推送)</summary>
|
||||
public string? AlertId { get; set; }
|
||||
|
||||
/// <summary>渠道类型</summary>
|
||||
public NotifyChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>渠道Id</summary>
|
||||
public string? ChannelId { get; set; }
|
||||
|
||||
/// <summary>推送目标(脱敏)</summary>
|
||||
public string? Target { get; set; }
|
||||
|
||||
/// <summary>推送内容摘要</summary>
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>是否成功</summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>错误信息</summary>
|
||||
public string? ErrorMsg { get; set; }
|
||||
|
||||
/// <summary>重试次数</summary>
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
/// <summary>推送时间</summary>
|
||||
public DateTime NotifyTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值班排班 DTO
|
||||
/// </summary>
|
||||
public class DutyRosterDto
|
||||
{
|
||||
/// <summary>主键 Id</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>值班日期</summary>
|
||||
public DateTime DutyDate { get; set; }
|
||||
|
||||
/// <summary>值班人姓名</summary>
|
||||
public string? PersonName { get; set; }
|
||||
|
||||
/// <summary>联系电话</summary>
|
||||
public string? Phone { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨网发件箱拉取结果 DTO(AlertBridge 拉取到的待推送项;Payload 自包含全部推送要素)
|
||||
/// </summary>
|
||||
public class AlertOutboxItemDto
|
||||
{
|
||||
/// <summary>发件箱 Id(回写结果时使用)</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>告警消息Id</summary>
|
||||
public string? AlertId { get; set; }
|
||||
|
||||
/// <summary>渠道Id</summary>
|
||||
public string? ChannelId { get; set; }
|
||||
|
||||
/// <summary>完整推送报文 JSON(AlertOutboxPayload 序列化)</summary>
|
||||
public string? Payload { get; set; }
|
||||
|
||||
/// <summary>重试次数</summary>
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发件箱推送报文(自包含:AlertBridge 无需访问数据库/配置即可推送)
|
||||
/// </summary>
|
||||
public class AlertOutboxPayload
|
||||
{
|
||||
/// <summary>渠道类型(1飞书/2钉钉/3企微)</summary>
|
||||
public int ChannelType { get; set; }
|
||||
|
||||
/// <summary>Webhook 地址</summary>
|
||||
public string? WebhookUrl { get; set; }
|
||||
|
||||
/// <summary>加签密钥(可空)</summary>
|
||||
public string? Secret { get; set; }
|
||||
|
||||
/// <summary>消息标题</summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>消息正文(markdown)</summary>
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>@ 手机号列表</summary>
|
||||
public List<string>? AtMobiles { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发件箱结果回写 DTO(AlertBridge 推送完成后回写)
|
||||
/// </summary>
|
||||
public class OutboxResultDto
|
||||
{
|
||||
/// <summary>是否成功</summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>结果信息(失败原因)</summary>
|
||||
public string? ResultMsg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Model.Entity.Inspection;
|
||||
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警上报 DTO(其它模块调用告警中心的统一入参:IAlertCenterService.RaiseAsync / POST raise)
|
||||
/// 除设备标识外全部可空,降低调用方接入成本
|
||||
/// </summary>
|
||||
public class AlertRaiseDto
|
||||
{
|
||||
/// <summary>告警来源(调用方标识自身模块)</summary>
|
||||
public AlertSourceEnum Source { get; set; } = AlertSourceEnum.Other;
|
||||
|
||||
/// <summary>关联告警规则Id(来源为规则引擎时传,可空/0 表示无)</summary>
|
||||
public long RuleId { get; set; }
|
||||
|
||||
/// <summary>设备编号(建议必传,用于去重合并)</summary>
|
||||
public string? DeviceCode { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? DeviceName { get; set; }
|
||||
|
||||
/// <summary>设备类型(如 TemperatureBox)</summary>
|
||||
public string? DeviceType { get; set; }
|
||||
|
||||
/// <summary>测点/监测指标(如 Temperature)</summary>
|
||||
public string? Metric { get; set; }
|
||||
|
||||
/// <summary>告警类型(默认阈值超限)</summary>
|
||||
public AlertTypeEnum AlertType { get; set; } = AlertTypeEnum.ThresholdExceed;
|
||||
|
||||
/// <summary>告警级别(1信息/2警告/3紧急)</summary>
|
||||
public AlertLevelEnum AlertLevel { get; set; } = AlertLevelEnum.Warning;
|
||||
|
||||
/// <summary>当前值</summary>
|
||||
public double? CurrentValue { get; set; }
|
||||
|
||||
/// <summary>阈值</summary>
|
||||
public double? ThresholdValue { get; set; }
|
||||
|
||||
/// <summary>告警内容描述(空则由告警中心按内置模板生成)</summary>
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>通知模板编码(预留对接通知模板模块)</summary>
|
||||
public string? TemplateCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警操作 DTO(确认/处理/关闭/忽略/转工单 共用入参)
|
||||
/// </summary>
|
||||
public class AlertOperateDto
|
||||
{
|
||||
/// <summary>操作人</summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>备注(处理说明/忽略原因等)</summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>工单Id(仅转工单时使用,二期工单模块创建后回填,可空)</summary>
|
||||
public long WorkOrderId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警规则 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class AlertRuleDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
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>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
|
||||
/// <summary>适用设备类型(空表示全部设备)</summary>
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>监测指标(Temperature / Humidity 等)</summary>
|
||||
public string Metric { get; set; }
|
||||
|
||||
/// <summary>比较运算符(> >= < <= = ≠)</summary>
|
||||
public string Operator { get; set; }
|
||||
|
||||
/// <summary>阈值</summary>
|
||||
public double? Threshold { get; set; }
|
||||
|
||||
/// <summary>告警级别(信息/警告/紧急)</summary>
|
||||
public string AlarmLevel { get; set; }
|
||||
|
||||
/// <summary>是否启用</summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>触发次数</summary>
|
||||
public int TriggerCount { get; set; }
|
||||
|
||||
/// <summary>最近触发时间</summary>
|
||||
public DateTime? LastAlarmTime { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警趋势统计项(按日/周/月分桶)
|
||||
/// </summary>
|
||||
public class AlertTrendItemDto
|
||||
{
|
||||
/// <summary>时间桶(日:yyyy-MM-dd / 周:yyyy-Www / 月:yyyy-MM)</summary>
|
||||
public string Bucket { get; set; }
|
||||
|
||||
/// <summary>告警总数</summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>信息级数量</summary>
|
||||
public int InfoCount { get; set; }
|
||||
|
||||
/// <summary>警告级数量</summary>
|
||||
public int WarningCount { get; set; }
|
||||
|
||||
/// <summary>紧急级数量</summary>
|
||||
public int UrgentCount { get; set; }
|
||||
|
||||
/// <summary>未闭环数量(未确认+已确认未处理)</summary>
|
||||
public int UnhandledCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警分布统计项(按设备/类型/级别/来源维度)
|
||||
/// </summary>
|
||||
public class AlertDistributionItemDto
|
||||
{
|
||||
/// <summary>维度名称</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>数量</summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高频告警 TOP 排名项
|
||||
/// </summary>
|
||||
public class AlertTopItemDto
|
||||
{
|
||||
/// <summary>设备编号</summary>
|
||||
public string? DeviceCode { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? DeviceName { get; set; }
|
||||
|
||||
/// <summary>告警次数(含合并累计 TriggerCount)</summary>
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>最近告警时间</summary>
|
||||
public DateTime LastAlertTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警处理及时率统计
|
||||
/// </summary>
|
||||
public class AlertTimelinessDto
|
||||
{
|
||||
/// <summary>统计范围内告警总数</summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>已确认数(含已处理/已关闭)</summary>
|
||||
public int AckCount { get; set; }
|
||||
|
||||
/// <summary>已处理/已关闭数</summary>
|
||||
public int HandledCount { get; set; }
|
||||
|
||||
/// <summary>未闭环数</summary>
|
||||
public int UnhandledCount { get; set; }
|
||||
|
||||
/// <summary>平均确认时长(分钟,无确认记录为 null)</summary>
|
||||
public double? AvgAckMinutes { get; set; }
|
||||
|
||||
/// <summary>平均处理时长(分钟,无处理记录为 null)</summary>
|
||||
public double? AvgHandleMinutes { get; set; }
|
||||
|
||||
/// <summary>及时率(%):30 分钟内确认的占比</summary>
|
||||
public double TimelyRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警概览统计(列表页顶部卡片)
|
||||
/// </summary>
|
||||
public class AlertOverviewDto
|
||||
{
|
||||
/// <summary>未确认数量</summary>
|
||||
public int UnacknowledgedCount { get; set; }
|
||||
|
||||
/// <summary>今日新增数量</summary>
|
||||
public int TodayCount { get; set; }
|
||||
|
||||
/// <summary>今日紧急数量</summary>
|
||||
public int TodayUrgentCount { get; set; }
|
||||
|
||||
/// <summary>近7日总数</summary>
|
||||
public int WeekCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据转发规则 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class DataForwardRuleDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>转发规则名称</summary>
|
||||
public string ForwardName { get; set; }
|
||||
|
||||
/// <summary>源设备类型(空表示全部设备)</summary>
|
||||
public string SourceDeviceType { get; set; }
|
||||
|
||||
/// <summary>转发间隔(秒)</summary>
|
||||
public int ForwardInterval { get; set; }
|
||||
|
||||
/// <summary>通知方式(Email / Sms / Http)</summary>
|
||||
public string NotifyType { get; set; }
|
||||
|
||||
/// <summary>目标地址(邮箱 / 手机号 / 接口URL)</summary>
|
||||
public string TargetAddress { get; set; }
|
||||
|
||||
/// <summary>是否启用</summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>最近转发时间</summary>
|
||||
public DateTime? LastForwardTime { get; set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
public string Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
#region 设备统计报表
|
||||
|
||||
/// <summary>
|
||||
/// 设备统计概览
|
||||
/// </summary>
|
||||
public class DeviceStatisticsDto
|
||||
{
|
||||
/// <summary>设备总数</summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>在用数量</summary>
|
||||
public int InUse { get; set; }
|
||||
|
||||
/// <summary>闲置数量</summary>
|
||||
public int Idle { get; set; }
|
||||
|
||||
/// <summary>报废数量(已处置+待处置)</summary>
|
||||
public int Scrapped { get; set; }
|
||||
|
||||
/// <summary>维修中数量</summary>
|
||||
public int Repairing { get; set; }
|
||||
|
||||
/// <summary>已停用数量</summary>
|
||||
public int Suspended { get; set; }
|
||||
|
||||
/// <summary>其他数量(待验收/已借出/入库)</summary>
|
||||
public int Other { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按分类统计
|
||||
/// </summary>
|
||||
public class CategoryStatisticsDto
|
||||
{
|
||||
/// <summary>分类名称</summary>
|
||||
public string? CategoryName { get; set; }
|
||||
|
||||
/// <summary>设备数量</summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按部门统计
|
||||
/// </summary>
|
||||
public class DepartmentStatisticsDto
|
||||
{
|
||||
/// <summary>部门名称</summary>
|
||||
public string? Department { get; set; }
|
||||
|
||||
/// <summary>设备数量</summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备维护报表
|
||||
|
||||
/// <summary>
|
||||
/// 设备维护统计
|
||||
/// </summary>
|
||||
public class MaintenanceStatisticsDto
|
||||
{
|
||||
/// <summary>设备总数</summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>校准正常数量</summary>
|
||||
public int CalibrationNormal { get; set; }
|
||||
|
||||
/// <summary>校准超期数量</summary>
|
||||
public int CalibrationOverdue { get; set; }
|
||||
|
||||
/// <summary>保养正常数量</summary>
|
||||
public int MaintenanceNormal { get; set; }
|
||||
|
||||
/// <summary>保养超期数量</summary>
|
||||
public int MaintenanceOverdue { get; set; }
|
||||
|
||||
/// <summary>校准及时率(%)</summary>
|
||||
public decimal CalibrationTimelyRate { get; set; }
|
||||
|
||||
/// <summary>保养及时率(%)</summary>
|
||||
public decimal MaintenanceTimelyRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维护超期设备明细
|
||||
/// </summary>
|
||||
public class MaintenanceOverdueDetailDto
|
||||
{
|
||||
/// <summary>设备编号</summary>
|
||||
public string? EquipmentCode { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? EquipmentName { get; set; }
|
||||
|
||||
/// <summary>部门</summary>
|
||||
public string? Department { get; set; }
|
||||
|
||||
/// <summary>超期类型(校准超期/保养超期)</summary>
|
||||
public string? OverdueType { get; set; }
|
||||
|
||||
/// <summary>到期日期</summary>
|
||||
public DateTime? ExpireDate { get; set; }
|
||||
|
||||
/// <summary>超期天数</summary>
|
||||
public int OverdueDays { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 维修类报表
|
||||
|
||||
/// <summary>
|
||||
/// 维修统计概览
|
||||
/// </summary>
|
||||
public class RepairStatisticsDto
|
||||
{
|
||||
/// <summary>维修记录总数</summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>已完成数量</summary>
|
||||
public int Completed { get; set; }
|
||||
|
||||
/// <summary>维修中数量</summary>
|
||||
public int InProgress { get; set; }
|
||||
|
||||
/// <summary>待维修数量</summary>
|
||||
public int Pending { get; set; }
|
||||
|
||||
/// <summary>平均维修工时(小时)</summary>
|
||||
public decimal AvgRepairHours { get; set; }
|
||||
|
||||
/// <summary>总维修工时(小时)</summary>
|
||||
public decimal TotalRepairHours { get; set; }
|
||||
|
||||
/// <summary>总维修费用(元)</summary>
|
||||
public decimal TotalRepairCost { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 故障类型分布
|
||||
/// </summary>
|
||||
public class FaultTypeDistributionDto
|
||||
{
|
||||
/// <summary>故障类型</summary>
|
||||
public string? FaultType { get; set; }
|
||||
|
||||
/// <summary>数量</summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维修工时分析
|
||||
/// </summary>
|
||||
public class RepairHoursAnalysisDto
|
||||
{
|
||||
/// <summary>故障类型</summary>
|
||||
public string? FaultType { get; set; }
|
||||
|
||||
/// <summary>平均工时(小时)</summary>
|
||||
public decimal AvgHours { get; set; }
|
||||
|
||||
/// <summary>最大工时(小时)</summary>
|
||||
public decimal MaxHours { get; set; }
|
||||
|
||||
/// <summary>最小工时(小时)</summary>
|
||||
public decimal MinHours { get; set; }
|
||||
|
||||
/// <summary>记录数</summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 维修记录明细
|
||||
/// </summary>
|
||||
public class RepairRecordDetailDto
|
||||
{
|
||||
/// <summary>设备编号</summary>
|
||||
public string? EquipmentCode { get; set; }
|
||||
|
||||
/// <summary>设备名称</summary>
|
||||
public string? EquipmentName { get; set; }
|
||||
|
||||
/// <summary>故障类型</summary>
|
||||
public string? FaultType { get; set; }
|
||||
|
||||
/// <summary>故障描述</summary>
|
||||
public string? FaultDescription { get; set; }
|
||||
|
||||
/// <summary>报修日期</summary>
|
||||
public DateTime? ReportDate { get; set; }
|
||||
|
||||
/// <summary>维修完成日期</summary>
|
||||
public DateTime? RepairEndDate { get; set; }
|
||||
|
||||
/// <summary>维修工时</summary>
|
||||
public decimal? RepairHours { get; set; }
|
||||
|
||||
/// <summary>维修人员</summary>
|
||||
public string? RepairPerson { get; set; }
|
||||
|
||||
/// <summary>维修费用</summary>
|
||||
public decimal? RepairCost { get; set; }
|
||||
|
||||
/// <summary>维修状态</summary>
|
||||
public string? RepairStatus { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Model.Dto.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 温度箱看板数据 DTO(Id 由 long 改为 string,避免前端精度丢失)
|
||||
/// </summary>
|
||||
public class TemperatureBoxDataDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
|
||||
/// <summary>采集时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备标识</summary>
|
||||
public string DeviceCode { get; set; }
|
||||
|
||||
/// <summary>设备类型</summary>
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>设备温度(℃)</summary>
|
||||
public double? DeviceTemperature { get; set; }
|
||||
|
||||
/// <summary>设备湿度(%)</summary>
|
||||
public double? DeviceHumidity { get; set; }
|
||||
|
||||
/// <summary>设备状态</summary>
|
||||
public string Status { get; set; }
|
||||
|
||||
/// <summary>设备告警信息</summary>
|
||||
public string AlarmInfo { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据字典分类 DTO
|
||||
/// </summary>
|
||||
public class DictTypeDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public byte Status { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
/// <summary>该分类下的字典项数量(列表展示用,由 Service 填充)</summary>
|
||||
public int ItemCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据字典项 DTO
|
||||
/// </summary>
|
||||
public class DictItemDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string TypeId { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public byte Status { get; set; }
|
||||
public byte IsDefault { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字典项下拉选项(业务侧通过 typeCode 查询用:仅返回编码+文本+是否默认)
|
||||
/// </summary>
|
||||
public class DictOptionDto
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件存储配置 DTO
|
||||
/// </summary>
|
||||
public class FileStorageConfigDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Provider { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string? Endpoint { get; set; }
|
||||
/// <summary>回显时掩码(仅显示前后各 2 字符);写入时若为掩码样式则保留原值不变</summary>
|
||||
public string? AccessKey { get; set; }
|
||||
public string? SecretKey { get; set; }
|
||||
public string? Bucket { get; set; }
|
||||
public string? Region { get; set; }
|
||||
public string? BasePath { get; set; }
|
||||
public int MaxSizeMB { get; set; }
|
||||
public string? AllowedExts { get; set; }
|
||||
public byte IsDefault { get; set; }
|
||||
public byte Status { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件上传记录 DTO
|
||||
/// </summary>
|
||||
public class FileRecordDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string? OriginalName { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public long Size { get; set; }
|
||||
public string? Ext { get; set; }
|
||||
public string? Provider { get; set; }
|
||||
public string StorageId { get; set; }
|
||||
public string? Uploader { get; set; }
|
||||
public string? BizType { get; set; }
|
||||
public string? BizId { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传结果(Provider 返回,Service 写入 sys_file_record 时用)
|
||||
/// </summary>
|
||||
public class FileUploadResultDto
|
||||
{
|
||||
/// <summary>存储后的相对文件名(含路径,如 2026/09/18/snowflake.jpg)</summary>
|
||||
public string FileName { get; set; }
|
||||
/// <summary>访问 URL</summary>
|
||||
public string Url { get; set; }
|
||||
/// <summary>扩展名</summary>
|
||||
public string? Ext { get; set; }
|
||||
/// <summary>文件大小(字节)</summary>
|
||||
public long Size { get; set; }
|
||||
public string Provider { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织架构树节点 DTO(嵌套子级;含数据权限摘要)
|
||||
/// </summary>
|
||||
public class OrgTreeDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Code { get; set; }
|
||||
public int OrgType { get; set; }
|
||||
public int ShiftType { get; set; }
|
||||
public string? Leader { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public List<OrgTreeDto> Children { get; set; } = new();
|
||||
|
||||
/// <summary>直接挂靠用户数</summary>
|
||||
public int UserCount { get; set; }
|
||||
|
||||
/// <summary>本组织及下级用户总数</summary>
|
||||
public int TotalUserCount { get; set; }
|
||||
|
||||
/// <summary>数据权限摘要:本组织及下级用户的角色+数据范围(去重,如「实验室管理员·本组织及下级」)</summary>
|
||||
public List<string> ScopeSummary { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织架构编辑 DTO(新增/修改用)
|
||||
/// </summary>
|
||||
public class OrgDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Code { get; set; }
|
||||
public int OrgType { get; set; }
|
||||
public int ShiftType { get; set; }
|
||||
public string? Leader { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织下拉选项 DTO(用户表单选组织用)
|
||||
/// </summary>
|
||||
public class OrgOptionDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int OrgType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限 DTO
|
||||
/// </summary>
|
||||
public class PermissionDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string? Group { get; set; }
|
||||
public byte IsSystem { get; set; }
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 审计日志 DTO
|
||||
/// </summary>
|
||||
public class AuditLogDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string? UserName { get; set; }
|
||||
public string RoleId { get; set; }
|
||||
public string? RoleName { get; set; }
|
||||
public string OperationType { get; set; }
|
||||
public string OperationTarget { get; set; }
|
||||
public string TargetId { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
public string? OldValue { get; set; }
|
||||
public string? NewValue { get; set; }
|
||||
public string? Ip { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public DateTime OperateTime { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 DTO
|
||||
/// </summary>
|
||||
public class RoleDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public byte DataScope { get; set; }
|
||||
public byte IsSystem { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
/// <summary>角色的权限Id列表(分配权限用)</summary>
|
||||
public List<string>? PermissionIds { get; set; }
|
||||
/// <summary>角色的权限编码列表(展示用)</summary>
|
||||
public List<string>? PermissionCodes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色下拉选项 DTO
|
||||
/// </summary>
|
||||
public class RoleOptionDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统参数 DTO
|
||||
/// </summary>
|
||||
public class SystemParamDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string ParamKey { get; set; }
|
||||
public string ParamName { get; set; }
|
||||
public string? ParamValue { get; set; }
|
||||
/// <summary>值类型(0=int, 1=text, 2=enum, 3=json)</summary>
|
||||
public byte ParamType { get; set; }
|
||||
/// <summary>枚举选项(原始 JSON 字符串,由前端解析;仅 ParamType=enum 有值)</summary>
|
||||
public string? ParamOptions { get; set; }
|
||||
public string? Unit { get; set; }
|
||||
public string? Group { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
/// <summary>是否系统内置(1=不可删,仅允许改 Value)</summary>
|
||||
public byte IsSystem { get; set; }
|
||||
public string? LastUpdateUser { get; set; }
|
||||
public DateTime? LastUpdateTime { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 业务侧批量取值返回项
|
||||
/// </summary>
|
||||
public class SystemParamValueDto
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace Model.Dto.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户 DTO
|
||||
/// </summary>
|
||||
public class UserDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string? RealName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string OrgId { get; set; }
|
||||
public string? OrgName { get; set; }
|
||||
public string? Position { get; set; }
|
||||
public string? SkillTags { get; set; }
|
||||
public byte IsEnabled { get; set; }
|
||||
public DateTime? LastLoginTime { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
/// <summary>用户的角色Id列表(分配角色用)</summary>
|
||||
public List<string>? RoleIds { get; set; }
|
||||
/// <summary>用户的角色名称(展示用,逗号分隔)</summary>
|
||||
public string? RoleNames { get; set; }
|
||||
/// <summary>初始密码(仅新增用户时传入,不回显)</summary>
|
||||
public string? InitialPassword { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录请求 DTO
|
||||
/// </summary>
|
||||
public class LoginDto
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录结果 DTO
|
||||
/// </summary>
|
||||
public class LoginResultDto
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
public UserDto User { get; set; }
|
||||
public List<string> Permissions { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改密码 DTO
|
||||
/// </summary>
|
||||
public class ChangePasswordDto
|
||||
{
|
||||
public string OldPassword { get; set; }
|
||||
public string NewPassword { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户下拉选项 DTO
|
||||
/// </summary>
|
||||
public class UserOptionDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string? RealName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 文件名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -26,13 +26,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 文件扩展名(如 .pdf、.jpg)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 20)]
|
||||
[SugarColumn(Length = 20, IsNullable = true)]
|
||||
public string? FileExt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储地址(上传后存储的相对路径)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? FileUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -43,13 +43,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上传人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Uploader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 分类名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类编码
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +37,7 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using SqlSugar;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Asset
|
||||
{
|
||||
@@ -11,13 +11,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 设备编号(唯一)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -28,55 +28,68 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 设备类型(实验室/楼层/部门/位置/产品/自定义等分类维度下的类型名称,冗余便于展示)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备品牌
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Brand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备型号
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 规格参数
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? Specifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 制造商(设备制造商)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 供应商(资产供应商)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Supplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备图片地址(图片上传后存储的相对路径)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码编号(一物一码)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? QrCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码扫码直达 URL(生成二维码时写入,扫码后跳转设备台账详情)
|
||||
/// 形如 http://host/asset/ledger/equipment?id={Id};为空表示尚未生成二维码
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? QrCodeUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RFID 编号(绑定时写入,用于 RFID 标签识别设备)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 64, IsNullable = true)]
|
||||
public string? RfidCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备描述
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "text")]
|
||||
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
|
||||
public string? Description { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -84,31 +97,31 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 使用部门名称(资产使用部门)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Department { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存放位置(实验室/楼层/房间等位置信息)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 责任人(资产责任人)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? ResponsiblePerson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 责任人联系电话(资产责任人电话)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 30)]
|
||||
[SugarColumn(Length = 30, IsNullable = true)]
|
||||
public string? ContactPhone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保管人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Custodian { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -116,17 +129,19 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 购置日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? PurchaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 购置价格(资产购置价格)
|
||||
/// </summary>
|
||||
[SugarColumn(DecimalDigits = 2, Length = 18)]
|
||||
[SugarColumn(DecimalDigits = 2, Length = 18, IsNullable = true)]
|
||||
public decimal? PurchasePrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保修到期日期(资产保修到期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? WarrantyExpireDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -134,21 +149,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 验收日期(资产验收日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? AcceptanceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启用日期(资产启用日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? EnableDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用年限(资产使用年限,单位:年)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? ServiceLifeYears { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报废日期(资产报废日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? ScrapDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -168,21 +187,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上次校准日期(校准日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次校准到期日期(用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检定日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? InspectionDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次检定到期日期(用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextInspectionDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -193,11 +216,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上次保养日期(保养日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastMaintenanceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次保养到期日期(保养周期到期,用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextMaintenanceDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -205,24 +230,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? CreateBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新人(修改人)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? UpdateBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间(修改时间)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备日志(每台设备独享的日志记录)
|
||||
/// 记录设备的通讯、连接、数据采集等运行日志,按设备查询
|
||||
/// </summary>
|
||||
public class DeviceLogEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备Id(关联 IotDeviceEntity.Id)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备Id")]
|
||||
public long DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备编号(冗余便于查询展示)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备编号", Length = 64)]
|
||||
public string? DeviceCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志级别(Info/Warn/Error)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "日志级别", Length = 16)]
|
||||
public string? Level { get; set; } = "Info";
|
||||
|
||||
/// <summary>
|
||||
/// 日志类型(连接/通讯/采集/指令/系统)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "日志类型", Length = 32)]
|
||||
public string? LogType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志内容
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "日志内容", ColumnDataType = "text")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 记录时间(日志产生时间,用于时间线展示)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "记录时间")]
|
||||
public DateTime LogTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 网关(通讯通道):TCP 类存 IP+端口,串口类存 COM+波特率,S7 类额外存 CPU/Rack/Slot。
|
||||
/// 同一网关下多设备用从站号(SlaveId)区分。
|
||||
/// </summary>
|
||||
public class GatewayEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 网关编码(唯一,如 GW-ENV-01)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "网关编码", Length = 64)]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 网关名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "网关名称", Length = 100)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通讯协议类型(决定连接参数组:TCP/Serial/S7)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通讯协议类型")]
|
||||
public IotDeviceProtocolEnum ProtocolType { get; set; } = IotDeviceProtocolEnum.ModbusTcp;
|
||||
|
||||
#region TCP 参数(ModbusTcp / Tcp / S7 协议使用)
|
||||
/// <summary>
|
||||
/// IP 地址
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "IP地址", Length = 50, IsNullable = true)]
|
||||
public string? Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 端口号(ModbusTcp 默认 502,S7 默认 102)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "端口号", IsNullable = true)]
|
||||
public int? Port { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 串口参数(ModbusRtu / Serial 协议使用)
|
||||
/// <summary>
|
||||
/// 串口号(如 COM3)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "串口号", Length = 20, IsNullable = true)]
|
||||
public string? ComPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 波特率(默认 9600)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "波特率", IsNullable = true)]
|
||||
public int? BaudRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据位(默认 8)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "数据位", IsNullable = true)]
|
||||
public byte? DataBits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 停止位(1/2,默认 1)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "停止位", IsNullable = true)]
|
||||
public byte? StopBits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 校验位(0=None 1=Odd 2=Even,默认 0)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "校验位", IsNullable = true)]
|
||||
public byte? Parity { get; set; }
|
||||
#endregion
|
||||
|
||||
#region S7 参数(S7 协议专用)
|
||||
/// <summary>
|
||||
/// S7 CPU 型号(如 S71200、S71500)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "S7 CPU型号", Length = 20, IsNullable = true)]
|
||||
public string? CpuType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S7 Rack(默认 0)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "S7 Rack", IsNullable = true)]
|
||||
public byte? Rack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S7 Slot(默认 1)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "S7 Slot", IsNullable = true)]
|
||||
public byte? Slot { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 状态
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 在线状态(0=离线 1=在线 3=异常,由测试连接更新)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "在线状态")]
|
||||
public byte OnlineStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后连接成功时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最后连接时间", IsNullable = true)]
|
||||
public DateTime? LastConnectedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近错误
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最近错误", Length = 500, IsNullable = true)]
|
||||
public string? LastError { get; set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// IOT 设备(IOT设备注册与管理 - 设备管理)
|
||||
/// 一台设备 = 一个从站实例,通过所属网关(IP:端口 或 串口)通讯
|
||||
/// </summary>
|
||||
public class IotDeviceEntity : BaseEntity
|
||||
{
|
||||
#region 基础信息
|
||||
/// <summary>
|
||||
/// 设备编号(唯一)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备编号", Length = 64)]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备名称", Length = 100)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型(型号,如 温度箱/充放电柜,对应 DeviceCommand 驱动类)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备类型", Length = 64)]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属产品Id(关联 ProductEntity.Id,仅用于归类,0=未分类)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "所属产品Id", DefaultValue = "0")]
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属网关Id(关联 GatewayEntity.Id,0=未分配)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "所属网关Id", DefaultValue = "0")]
|
||||
public long GatewayId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从站地址(协议从站号,网关下区分设备)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "从站地址")]
|
||||
public byte SlaveId { 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; }
|
||||
#endregion
|
||||
|
||||
#region 连接与协议配置
|
||||
/// <summary>
|
||||
/// 通讯协议类型(ModbusTcp/ModbusRtu/S7/Tcp/Serial)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通讯协议类型")]
|
||||
public IotDeviceProtocolEnum ProtocolType { get; set; } = IotDeviceProtocolEnum.ModbusTcp;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用采集(停用则采集调度跳过该设备)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
#endregion
|
||||
|
||||
#region 运行状态(采集调度器更新)
|
||||
/// <summary>
|
||||
/// 在线状态
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "在线状态")]
|
||||
public IotDeviceOnlineStatusEnum OnlineStatus { get; set; } = IotDeviceOnlineStatusEnum.Offline;
|
||||
|
||||
/// <summary>
|
||||
/// 最后采集时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最后采集时间", IsNullable = true)]
|
||||
public DateTime? LastCollectTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近错误信息(通讯异常时记录)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最近错误", Length = 500, IsNullable = true)]
|
||||
public string? LastError { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 审计信息
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace Model.Entity.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备通讯协议类型(决定使用 DeviceCommand 中哪类驱动)
|
||||
/// </summary>
|
||||
public enum IotDeviceProtocolEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus TCP(网口,IP + 端口 + 从站地址)
|
||||
/// </summary>
|
||||
ModbusTcp = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Modbus RTU(串口,COM 口 + 波特率 + 从站地址)
|
||||
/// </summary>
|
||||
ModbusRtu = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 西门子 S7 协议
|
||||
/// </summary>
|
||||
S7 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 TCP 协议
|
||||
/// </summary>
|
||||
Tcp = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 串口自定义协议
|
||||
/// </summary>
|
||||
Serial = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备在线状态
|
||||
/// </summary>
|
||||
public enum IotDeviceOnlineStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 离线
|
||||
/// </summary>
|
||||
Offline = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 在线
|
||||
/// </summary>
|
||||
Online = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 连接中(网关已连,设备待确认)
|
||||
/// </summary>
|
||||
Connecting = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 通讯异常
|
||||
/// </summary>
|
||||
Fault = 3
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警级别(与规则引擎 AlertRuleEntity.AlarmLevel 字符串"信息/警告/紧急"保持一致)
|
||||
/// </summary>
|
||||
public enum AlertLevelEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
Info = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
Warning = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 紧急
|
||||
/// </summary>
|
||||
Urgent = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警状态(闭环流转:未确认 → 已确认 → 已处理 → 已关闭;可忽略/转工单)
|
||||
/// </summary>
|
||||
public enum AlertStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 未确认
|
||||
/// </summary>
|
||||
Unacknowledged = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已确认
|
||||
/// </summary>
|
||||
Acknowledged = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 已处理
|
||||
/// </summary>
|
||||
Handled = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 已关闭
|
||||
/// </summary>
|
||||
Closed = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 已忽略
|
||||
/// </summary>
|
||||
Ignored = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 已转工单
|
||||
/// </summary>
|
||||
ToWorkOrder = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警来源(标识上报模块,便于其它模块调用与统计)
|
||||
/// </summary>
|
||||
public enum AlertSourceEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则引擎
|
||||
/// </summary>
|
||||
RuleEngine = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 自动巡检
|
||||
/// </summary>
|
||||
AutoPatrol = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 数据看板
|
||||
/// </summary>
|
||||
Dashboard = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 数据采集
|
||||
/// </summary>
|
||||
Collection = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 手动/API 上报
|
||||
/// </summary>
|
||||
Manual = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 其它
|
||||
/// </summary>
|
||||
Other = 99
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警类型
|
||||
/// </summary>
|
||||
public enum AlertTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 阈值超限
|
||||
/// </summary>
|
||||
ThresholdExceed = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 设备离线
|
||||
/// </summary>
|
||||
DeviceOffline = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 通讯故障
|
||||
/// </summary>
|
||||
CommFault = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 巡检异常
|
||||
/// </summary>
|
||||
PatrolAbnormal = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 自定义
|
||||
/// </summary>
|
||||
Custom = 99
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知渠道类型(一期仅支持飞书/钉钉/企微 Webhook 机器人)
|
||||
/// </summary>
|
||||
public enum NotifyChannelTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 飞书
|
||||
/// </summary>
|
||||
Feishu = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 钉钉
|
||||
/// </summary>
|
||||
DingTalk = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 企业微信
|
||||
/// </summary>
|
||||
WeCom = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渠道推送模式
|
||||
/// </summary>
|
||||
public enum NotifyPushModeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 直连推送(服务器可直接访问外网 Webhook)
|
||||
/// </summary>
|
||||
Direct = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Outbox 中转(跨网场景:内网写发件箱,跳板机 AlertBridge 拉取后在外网推送)
|
||||
/// </summary>
|
||||
Outbox = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跨网发件箱状态
|
||||
/// </summary>
|
||||
public enum OutboxStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 待推送
|
||||
/// </summary>
|
||||
Pending = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已拉取(跳板机取走,等待回写结果)
|
||||
/// </summary>
|
||||
Pulled = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 推送成功
|
||||
/// </summary>
|
||||
Success = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 推送失败
|
||||
/// </summary>
|
||||
Failed = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告警级别枚举 ↔ 规则引擎字符串 映射工具
|
||||
/// </summary>
|
||||
public static class AlertLevelMap
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串(信息/警告/紧急)→ 枚举,无法识别时默认 Info
|
||||
/// </summary>
|
||||
public static AlertLevelEnum FromString(string level)
|
||||
{
|
||||
return level?.Trim() switch
|
||||
{
|
||||
"警告" => AlertLevelEnum.Warning,
|
||||
"紧急" => AlertLevelEnum.Urgent,
|
||||
_ => AlertLevelEnum.Info
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举 → 字符串(信息/警告/紧急)
|
||||
/// </summary>
|
||||
public static string ToLabel(AlertLevelEnum level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
AlertLevelEnum.Warning => "警告",
|
||||
AlertLevelEnum.Urgent => "紧急",
|
||||
_ => "信息"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警消息(消息告警模块核心表:所有模块的告警统一汇聚于此,支持确认/处理/关闭闭环)
|
||||
/// </summary>
|
||||
public class AlertMessageEntity : BaseEntity
|
||||
{
|
||||
#region 溯源信息
|
||||
/// <summary>
|
||||
/// 告警来源(规则引擎/自动巡检/看板/数采/手动等)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警来源")]
|
||||
public AlertSourceEnum Source { get; set; } = AlertSourceEnum.Manual;
|
||||
|
||||
/// <summary>
|
||||
/// 关联告警规则Id(来源为规则引擎时填写,0表示无)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "关联告警规则Id")]
|
||||
public long RuleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备编号
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备编号", Length = 64, IsNullable = true)]
|
||||
public string DeviceCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备名称", Length = 128, IsNullable = true)]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型(如 TemperatureBox,与规则引擎 DeviceType 对齐)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "设备类型", Length = 64, IsNullable = true)]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测点/监测指标(如 Temperature、Humidity)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "测点指标", Length = 64, IsNullable = true)]
|
||||
public string Metric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警类型(超限/离线/通讯故障/巡检异常/自定义)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警类型")]
|
||||
public AlertTypeEnum AlertType { get; set; } = AlertTypeEnum.ThresholdExceed;
|
||||
#endregion
|
||||
|
||||
#region 告警内容
|
||||
/// <summary>
|
||||
/// 告警级别(信息/警告/紧急,与规则引擎三级一致)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警级别")]
|
||||
public AlertLevelEnum AlertLevel { get; set; } = AlertLevelEnum.Info;
|
||||
|
||||
/// <summary>
|
||||
/// 当前值(触发告警时的实际值)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "当前值", IsNullable = true)]
|
||||
public double? CurrentValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 阈值(规则阈值,可空)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "阈值", IsNullable = true)]
|
||||
public double? ThresholdValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警内容描述
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警内容", Length = 500, IsNullable = true)]
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通知模板编码(预留:对接通知模板模块,空表示使用内置模板)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通知模板编码", Length = 64, IsNullable = true)]
|
||||
public string TemplateCode { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 聚合信息(去重合并窗口内累加)
|
||||
/// <summary>
|
||||
/// 触发次数(合并窗口内同设备同测点同类型告警的累计次数)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "触发次数")]
|
||||
public int TriggerCount { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 首次告警时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "首次告警时间")]
|
||||
public DateTime FirstAlertTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近告警时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最近告警时间")]
|
||||
public DateTime LastAlertTime { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 闭环信息
|
||||
/// <summary>
|
||||
/// 告警状态(未确认/已确认/已处理/已关闭/已忽略/已转工单)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警状态")]
|
||||
public AlertStatusEnum AlertStatus { get; set; } = AlertStatusEnum.Unacknowledged;
|
||||
|
||||
/// <summary>
|
||||
/// 确认人
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "确认人", Length = 50, IsNullable = true)]
|
||||
public string AckBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 确认时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "确认时间", IsNullable = true)]
|
||||
public DateTime? AckTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理人
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "处理人", Length = 50, IsNullable = true)]
|
||||
public string HandleBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "处理时间", IsNullable = true)]
|
||||
public DateTime? HandleTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "处理备注", Length = 500, IsNullable = true)]
|
||||
public string HandleRemark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联工单Id(预留二期运维工单模块,0表示未转工单)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "关联工单Id")]
|
||||
public long WorkOrderId { get; set; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警通知渠道(飞书/钉钉/企微 Webhook 机器人配置)
|
||||
/// </summary>
|
||||
public class AlertNotifyChannelEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 渠道类型(1飞书/2钉钉/3企微)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "渠道类型")]
|
||||
public NotifyChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道名称(如:设备告警-研发飞书群)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "渠道名称", Length = 100)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Webhook 地址
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "Webhook地址", Length = 500)]
|
||||
public string WebhookUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加签密钥(钉钉/飞书机器人开启加签时填写,企微机器人无此项)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "加签密钥", Length = 200, IsNullable = true)]
|
||||
public string Secret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 推送模式(1直连推送/2Outbox跨网中转)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "推送模式")]
|
||||
public NotifyPushModeEnum PushMode { get; set; } = NotifyPushModeEnum.Direct;
|
||||
|
||||
/// <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,65 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警通知推送日志(每次向渠道推送的结果记录,失败可追溯)
|
||||
/// </summary>
|
||||
public class AlertNotifyLogEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警消息Id(关联 AlertMessageEntity.Id,0表示渠道测试推送)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警消息Id")]
|
||||
public long AlertId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道类型(1飞书/2钉钉/3企微)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "渠道类型")]
|
||||
public NotifyChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道Id(关联 AlertNotifyChannelEntity.Id)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "渠道Id")]
|
||||
public long ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 推送目标(Webhook 地址脱敏展示)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "推送目标", Length = 200, IsNullable = true)]
|
||||
public string Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 推送内容摘要
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "推送内容", Length = 1000, IsNullable = true)]
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否成功")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "错误信息", Length = 500, IsNullable = true)]
|
||||
public string ErrorMsg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重试次数
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "重试次数")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 推送时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "推送时间")]
|
||||
public DateTime NotifyTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警通知规则(按告警级别配置通知渠道与接收人,支持值班排班关联与静默期)
|
||||
/// </summary>
|
||||
public class AlertNotifyRuleEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "规则名称", Length = 100)]
|
||||
public string RuleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用告警级别(信息/警告/紧急)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "适用告警级别")]
|
||||
public AlertLevelEnum AlertLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通知渠道Id(关联 AlertNotifyChannelEntity.Id)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通知渠道Id")]
|
||||
public long ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收人(@手机号/企微账号,逗号分隔,可空表示不@)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "接收人", Length = 500, IsNullable = true)]
|
||||
public string Receivers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否通知当班值班人(按告警时间查值班排班表)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否通知值班人")]
|
||||
public bool NotifyDutyPerson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 静默期(分钟):同一告警合并窗口内重复触发时,静默期内不重复推送,0表示每次必推
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "静默期分钟")]
|
||||
public int SilenceMinutes { get; set; } = 5;
|
||||
|
||||
/// <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,61 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨网推送发件箱(内网只写此表;跳板机上的 AlertBridge 控制台程序轮询拉取,
|
||||
/// 在外网推送飞书/钉钉/企微后回写结果,实现工控内网与即时通讯软件的隔离穿透)
|
||||
/// </summary>
|
||||
public class AlertOutboxEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警消息Id(关联 AlertMessageEntity.Id)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警消息Id")]
|
||||
public long AlertId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道Id(关联 AlertNotifyChannelEntity.Id)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "渠道Id")]
|
||||
public long ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 完整推送报文(自包含 JSON:渠道类型/WebhookUrl/Secret/标题/内容/@列表,
|
||||
/// AlertBridge 拉取后无需再查任何配置即可直接推送)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "推送报文", ColumnDataType = "text", IsNullable = true)]
|
||||
public string Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态(1待推送/2已拉取/3成功/4失败)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "状态")]
|
||||
public OutboxStatusEnum Status { get; set; } = OutboxStatusEnum.Pending;
|
||||
|
||||
/// <summary>
|
||||
/// 重试次数(AlertBridge 推送失败回写时累加)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "重试次数")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拉取时间
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "拉取时间", IsNullable = true)]
|
||||
public DateTime? PullTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 完成时间(成功/失败回写时间)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "完成时间", IsNullable = true)]
|
||||
public DateTime? FinishTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结果信息(失败原因等)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "结果信息", Length = 500, IsNullable = true)]
|
||||
public string ResultMsg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警规则(规则实例:查看所有设备告警的规则列表)
|
||||
/// </summary>
|
||||
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>
|
||||
[SugarColumn(ColumnDescription = "规则名称", Length = 100)]
|
||||
public string RuleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用设备类型(空表示全部设备,如 TemperatureBox 温度箱)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "适用设备类型", Length = 64, IsNullable = true)]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 监测指标(Temperature 温度 / Humidity 湿度等)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "监测指标", Length = 64)]
|
||||
public string Metric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 比较运算符(> >= < <= = ≠)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "比较运算符", Length = 10)]
|
||||
public string Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 阈值
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "阈值", IsNullable = true)]
|
||||
public double? Threshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警级别(信息/警告/紧急)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "告警级别", Length = 32)]
|
||||
public string AlarmLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 触发次数(系统统计)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "触发次数")]
|
||||
public int TriggerCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近触发时间(系统统计)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最近触发时间", IsNullable = true)]
|
||||
public DateTime? LastAlarmTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据转发规则(配置数据转发规则,支持通过消息通知的方式定时转发数据)
|
||||
/// </summary>
|
||||
public class DataForwardRuleEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 转发规则名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "转发规则名称", Length = 100)]
|
||||
public string ForwardName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 源设备类型(空表示全部设备,如 TemperatureBox 温度箱)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "源设备类型", Length = 64, IsNullable = true)]
|
||||
public string SourceDeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转发间隔(秒)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "转发间隔(秒)")]
|
||||
public int ForwardInterval { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// 通知方式(Email 邮件 / Sms 短信 / Http 接口推送)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通知方式", Length = 32)]
|
||||
public string NotifyType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标地址(邮箱 / 手机号 / 接口URL)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "目标地址", Length = 200)]
|
||||
public string TargetAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "是否启用")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 最近转发时间(系统统计)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最近转发时间", IsNullable = true)]
|
||||
public DateTime? LastForwardTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 值班排班(按天排班;通知规则勾选"通知值班人"时按告警日期匹配当班人)
|
||||
/// </summary>
|
||||
public class DutyRosterEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 值班日期(当天 00:00:00)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "值班日期")]
|
||||
public DateTime DutyDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值班人姓名
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "值班人", Length = 50)]
|
||||
public string PersonName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 联系电话(用于钉钉/企微 @ 手机号)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "联系电话", Length = 30, IsNullable = true)]
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备维修记录
|
||||
/// </summary>
|
||||
public class RepairRecordEntity : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备Id(关联 EquipmentEntity.Id)
|
||||
/// </summary>
|
||||
public long EquipmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备编号(冗余便于展示)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? EquipmentCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称(冗余便于展示)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? EquipmentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障类型(机械故障/电气故障/软件故障/传感器故障/其他)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? FaultType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障描述
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
|
||||
public string? FaultDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报修日期
|
||||
/// </summary>
|
||||
public DateTime? ReportDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修开始日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? RepairStartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修完成日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? RepairEndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修工时(小时)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public decimal? RepairHours { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修人员
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? RepairPerson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修费用(元)
|
||||
/// </summary>
|
||||
[SugarColumn(DecimalDigits = 2, Length = 18, IsNullable = true)]
|
||||
public decimal? RepairCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修状态(待维修/维修中/已完成)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 20, IsNullable = true)]
|
||||
public string? RepairStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维修结果描述
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
|
||||
public string? RepairResult { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
namespace Model.Entity
|
||||
namespace Model.Entity.Inspection
|
||||
{
|
||||
/// <summary>
|
||||
/// 温度箱看板数据实体
|
||||
@@ -0,0 +1,50 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 审计日志表(记录所有关键操作)
|
||||
/// </summary>
|
||||
[SugarTable("sys_audit_log")]
|
||||
public class AuditLogEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "UserId", ColumnDescription = "操作人用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "UserName", ColumnDescription = "操作人用户名", Length = 64, IsNullable = true)]
|
||||
public string? UserName { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "RoleId", ColumnDescription = "操作人角色Id(0=无/多角色)", DefaultValue = "0")]
|
||||
public long RoleId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "RoleName", ColumnDescription = "操作人角色名称", Length = 100, IsNullable = true)]
|
||||
public string? RoleName { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "OperationType", ColumnDescription = "操作类型(Create/Update/Delete/Login/Command等)", Length = 32, IsNullable = false)]
|
||||
public string OperationType { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "OperationTarget", ColumnDescription = "操作对象(如 IotDevice/Gateway/User/Role)", Length = 64, IsNullable = false)]
|
||||
public string OperationTarget { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "TargetId", ColumnDescription = "操作对象Id(0=无具体Id)", DefaultValue = "0")]
|
||||
public long TargetId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "DeviceId", ColumnDescription = "关联设备Id(0=无关设备)", DefaultValue = "0")]
|
||||
public long DeviceId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "OldValue", ColumnDescription = "操作前值(JSON)", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? OldValue { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "NewValue", ColumnDescription = "操作后值(JSON)", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? NewValue { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Ip", ColumnDescription = "操作IP", Length = 45, IsNullable = true)]
|
||||
public string? Ip { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Description", ColumnDescription = "操作描述", Length = 500, IsNullable = true)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "OperateTime", ColumnDescription = "操作时间", IsNullable = false)]
|
||||
public DateTime OperateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据字典项表(某分类下的具体枚举值)
|
||||
/// </summary>
|
||||
[SugarTable("sys_dict_item")]
|
||||
public class DictItemEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "TypeId", ColumnDescription = "所属字典分类Id")]
|
||||
public long TypeId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Code", ColumnDescription = "字典项编码", Length = 64, IsNullable = false)]
|
||||
public string Code { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "字典项文本", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Status", ColumnDescription = "状态(1=启用,0=停用)", ColumnDataType = "smallint", DefaultValue = "1")]
|
||||
public byte Status { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "IsDefault", ColumnDescription = "是否默认选中(同分类下唯一)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte IsDefault { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据字典分类表(如:设备类型、故障类型、告警级别、工单优先级)
|
||||
/// </summary>
|
||||
[SugarTable("sys_dict_type")]
|
||||
public class DictTypeEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "Code", ColumnDescription = "字典编码(唯一)", Length = 64, IsNullable = false)]
|
||||
public string Code { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "字典名称", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Status", ColumnDescription = "状态(1=启用,0=停用)", ColumnDataType = "smallint", DefaultValue = "1")]
|
||||
public byte Status { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件上传记录表(每次上传都登记:来源 Provider、业务类型、访问 URL)
|
||||
/// </summary>
|
||||
[SugarTable("sys_file_record")]
|
||||
public class FileRecordEntity : BaseEntity
|
||||
{
|
||||
/// <summary>存储后的文件名(含相对路径,如 2026/09/18/snowflake.jpg)</summary>
|
||||
[SugarColumn(ColumnName = "FileName", ColumnDescription = "存储文件名(含相对路径)", Length = 255, IsNullable = false)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>原始文件名(用户上传时的文件名)</summary>
|
||||
[SugarColumn(ColumnName = "OriginalName", ColumnDescription = "原始文件名", Length = 255, IsNullable = true)]
|
||||
public string? OriginalName { get; set; }
|
||||
|
||||
/// <summary>访问 URL(Endpoint + 相对路径)</summary>
|
||||
[SugarColumn(ColumnName = "Url", ColumnDescription = "访问URL", Length = 500, IsNullable = true)]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>文件大小(字节)</summary>
|
||||
[SugarColumn(ColumnName = "Size", ColumnDescription = "文件大小(字节)")]
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>扩展名(含点,如 .jpg)</summary>
|
||||
[SugarColumn(ColumnName = "Ext", ColumnDescription = "扩展名", Length = 20, IsNullable = true)]
|
||||
public string? Ext { get; set; }
|
||||
|
||||
/// <summary>使用的存储 Provider(冗余,便于排查)</summary>
|
||||
[SugarColumn(ColumnName = "Provider", ColumnDescription = "存储Provider", Length = 20, IsNullable = true)]
|
||||
public string? Provider { get; set; }
|
||||
|
||||
/// <summary>使用的存储配置Id(关联 sys_file_storage.Id)</summary>
|
||||
public long StorageId { get; set; }
|
||||
|
||||
/// <summary>上传人</summary>
|
||||
[SugarColumn(ColumnName = "Uploader", ColumnDescription = "上传人", Length = 50, IsNullable = true)]
|
||||
public string? Uploader { get; set; }
|
||||
|
||||
/// <summary>业务类型(equipment_attachment/qrcode/generic 等,便于按业务查询)</summary>
|
||||
[SugarColumn(ColumnName = "BizType", ColumnDescription = "业务类型", Length = 50, IsNullable = true)]
|
||||
public string? BizType { get; set; }
|
||||
|
||||
/// <summary>业务Id(关联业务实体的 Id,可空)</summary>
|
||||
[SugarColumn(ColumnName = "BizId", ColumnDescription = "业务Id", Length = 64, IsNullable = true)]
|
||||
public string? BizId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件存储配置表(多 Provider 配置:Local/MinIO/OSS,仅 Local 落地,其它留扩展点)
|
||||
/// 一个 IsDefault=1 的配置为默认上传通道;AccessKey/SecretKey 对 Local 为空
|
||||
/// </summary>
|
||||
[SugarTable("sys_file_storage")]
|
||||
public class FileStorageConfigEntity : BaseEntity
|
||||
{
|
||||
/// <summary>存储 Provider(local/minio/oss)</summary>
|
||||
[SugarColumn(ColumnName = "Provider", ColumnDescription = "存储Provider(local/minio/oss)", Length = 20, IsNullable = false)]
|
||||
public string Provider { get; set; }
|
||||
|
||||
/// <summary>配置名称(如:本地存储 / MinIO测试)</summary>
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "配置名称", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>访问端点(Local 为 URL 前缀如 http://host/uploads 或 /uploads;MinIO 为 http://minio:9000)</summary>
|
||||
[SugarColumn(ColumnName = "Endpoint", ColumnDescription = "访问端点", Length = 255, IsNullable = true)]
|
||||
public string? Endpoint { get; set; }
|
||||
|
||||
/// <summary>访问 Key(Local 为空;MinIO/OSS 为 AccessKey。注:当前明文存储,接入 MinIO/OSS 时应改为 AES 加密)</summary>
|
||||
[SugarColumn(ColumnName = "AccessKey", ColumnDescription = "AccessKey(Local为空)", Length = 128, IsNullable = true)]
|
||||
public string? AccessKey { get; set; }
|
||||
|
||||
/// <summary>密钥(Local 为空;MinIO/OSS 为 SecretKey。注:当前明文存储,接入 MinIO/OSS 时应改为 AES 加密)</summary>
|
||||
[SugarColumn(ColumnName = "SecretKey", ColumnDescription = "SecretKey(Local为空)", Length = 255, IsNullable = true)]
|
||||
public string? SecretKey { get; set; }
|
||||
|
||||
/// <summary>桶名(Local 为 BasePath 下子目录;MinIO/OSS 为 bucket 名)</summary>
|
||||
[SugarColumn(ColumnName = "Bucket", ColumnDescription = "桶名", Length = 100, IsNullable = true)]
|
||||
public string? Bucket { get; set; }
|
||||
|
||||
/// <summary>区域(OSS 用,Local/MinIO 为空)</summary>
|
||||
[SugarColumn(ColumnName = "Region", ColumnDescription = "区域(OSS用)", Length = 50, IsNullable = true)]
|
||||
public string? Region { get; set; }
|
||||
|
||||
/// <summary>本地存储根路径(Local 专用,如 wwwroot/uploads;MinIO/OSS 为空)</summary>
|
||||
[SugarColumn(ColumnName = "BasePath", ColumnDescription = "本地存储根路径(Local专用)", Length = 255, IsNullable = true)]
|
||||
public string? BasePath { get; set; }
|
||||
|
||||
/// <summary>单个文件大小上限(MB)</summary>
|
||||
[SugarColumn(ColumnName = "MaxSizeMB", ColumnDescription = "单个文件大小上限(MB)", DefaultValue = "10")]
|
||||
public int MaxSizeMB { get; set; } = 10;
|
||||
|
||||
/// <summary>允许的扩展名(JSON 数组,如 [".jpg",".png"];为空表示不限制)</summary>
|
||||
[SugarColumn(ColumnName = "AllowedExts", ColumnDescription = "允许扩展名(JSON数组)", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? AllowedExts { get; set; }
|
||||
|
||||
/// <summary>是否默认(1=默认上传通道,全局唯一)</summary>
|
||||
[SugarColumn(ColumnName = "IsDefault", ColumnDescription = "是否默认(1=默认,0=否)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte IsDefault { get; set; }
|
||||
|
||||
/// <summary>状态(1=启用,0=停用)</summary>
|
||||
[SugarColumn(ColumnName = "Status", ColumnDescription = "状态(1=启用,0=停用)", ColumnDataType = "smallint", DefaultValue = "1")]
|
||||
public byte Status { get; set; } = 1;
|
||||
|
||||
/// <summary>备注</summary>
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织架构表(树形:公司/实验室/部门/班组,ParentId 自关联;班组带白/夜班)
|
||||
/// </summary>
|
||||
[SugarTable("sys_org")]
|
||||
public class OrgEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "ParentId", ColumnDescription = "父级组织Id(0=根节点)", DefaultValue = "0")]
|
||||
public long ParentId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "组织名称", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Code", ColumnDescription = "组织编码(唯一)", Length = 64, IsNullable = false)]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>组织类型:1=公司, 2=实验室, 3=部门, 4=班组</summary>
|
||||
[SugarColumn(ColumnName = "OrgType", ColumnDescription = "组织类型(1公司/2实验室/3部门/4班组)", ColumnDataType = "smallint", DefaultValue = "3")]
|
||||
public byte OrgType { get; set; }
|
||||
|
||||
/// <summary>班组班次:0=非班组, 1=白班, 2=夜班</summary>
|
||||
[SugarColumn(ColumnName = "ShiftType", ColumnDescription = "班次(0非班组/1白班/2夜班)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte ShiftType { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Leader", ColumnDescription = "负责人", Length = 50, IsNullable = true)]
|
||||
public string? Leader { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Phone", ColumnDescription = "联系电话", Length = 20, IsNullable = true)]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限表(功能权限码,如 device:view / device:edit / alert:confirm)
|
||||
/// </summary>
|
||||
[SugarTable("sys_permission")]
|
||||
public class PermissionEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "Code", ColumnDescription = "权限编码(唯一,如 device:view)", Length = 64, IsNullable = false)]
|
||||
public string Code { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "权限名称", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Group", ColumnDescription = "权限分组(如 device/alert/inspection/user)", Length = 64, IsNullable = true)]
|
||||
public string? Group { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "IsSystem", ColumnDescription = "是否系统内置(不可删除)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte IsSystem { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色表
|
||||
/// </summary>
|
||||
[SugarTable("sys_role")]
|
||||
public class RoleEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "Code", ColumnDescription = "角色编码(唯一)", Length = 64, IsNullable = false)]
|
||||
public string Code { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "角色名称", Length = 100, IsNullable = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "DataScope", ColumnDescription = "数据范围(1=全部,2=本实验室)", ColumnDataType = "smallint", DefaultValue = "2")]
|
||||
public byte DataScope { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "IsSystem", ColumnDescription = "是否系统内置角色(不可删除)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte IsSystem { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色-权限关联表(多对多)
|
||||
/// </summary>
|
||||
[SugarTable("sys_role_permission")]
|
||||
public class RolePermissionEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "RoleId", ColumnDescription = "角色Id")]
|
||||
public long RoleId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "PermissionId", ColumnDescription = "权限Id")]
|
||||
public long PermissionId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统参数配置表(键值对形式存储业务可调参数,按 Group 分组展示)
|
||||
/// 内置参数(IsSystem=1)禁止删除,仅允许修改 Value
|
||||
/// </summary>
|
||||
[SugarTable("sys_param")]
|
||||
public class SystemParamEntity : BaseEntity
|
||||
{
|
||||
/// <summary>参数键(唯一,业务侧按 Key 取值,如 data_collect_default_frequency)</summary>
|
||||
[SugarColumn(ColumnName = "ParamKey", ColumnDescription = "参数键(唯一)", Length = 64, IsNullable = false)]
|
||||
public string ParamKey { get; set; }
|
||||
|
||||
/// <summary>参数名称(中文展示名)</summary>
|
||||
[SugarColumn(ColumnName = "ParamName", ColumnDescription = "参数名称", Length = 100, IsNullable = false)]
|
||||
public string ParamName { get; set; }
|
||||
|
||||
/// <summary>参数值(统一字符串存储,业务侧按 ParamType 自行转换)</summary>
|
||||
[SugarColumn(ColumnName = "ParamValue", ColumnDescription = "参数值", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? ParamValue { get; set; }
|
||||
|
||||
/// <summary>值类型(0=int, 1=text, 2=enum, 3=json)决定前端渲染控件</summary>
|
||||
[SugarColumn(ColumnName = "ParamType", ColumnDescription = "值类型(0=int,1=text,2=enum,3=json)", ColumnDataType = "smallint", DefaultValue = "1")]
|
||||
public byte ParamType { get; set; }
|
||||
|
||||
/// <summary>枚举选项(JSON 数组,如 [{"value":"feishu","label":"飞书"}];仅 ParamType=enum 使用)</summary>
|
||||
[SugarColumn(ColumnName = "ParamOptions", ColumnDescription = "枚举选项(JSON数组)", ColumnDataType = "text", IsNullable = true)]
|
||||
public string? ParamOptions { get; set; }
|
||||
|
||||
/// <summary>单位(如 秒/MB,前端展示用)</summary>
|
||||
[SugarColumn(ColumnName = "Unit", ColumnDescription = "单位", Length = 20, IsNullable = true)]
|
||||
public string? Unit { get; set; }
|
||||
|
||||
/// <summary>分组(如 数据采集/告警/工单/文件,前端按分组卡片展示)</summary>
|
||||
[SugarColumn(ColumnName = "Group", ColumnDescription = "分组", Length = 50, IsNullable = true)]
|
||||
public string? Group { get; set; }
|
||||
|
||||
/// <summary>排序号</summary>
|
||||
[SugarColumn(ColumnName = "Sort", ColumnDescription = "排序号", DefaultValue = "0")]
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>备注(注释提示,如工单编号规则占位符说明)</summary>
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>是否系统内置(1=内置不可删,0=用户自定义)</summary>
|
||||
[SugarColumn(ColumnName = "IsSystem", ColumnDescription = "是否系统内置(1=不可删,0=可删)", ColumnDataType = "smallint", DefaultValue = "0")]
|
||||
public byte IsSystem { get; set; }
|
||||
|
||||
/// <summary>最后修改人</summary>
|
||||
[SugarColumn(ColumnName = "LastUpdateUser", ColumnDescription = "最后修改人", Length = 50, IsNullable = true)]
|
||||
public string? LastUpdateUser { get; set; }
|
||||
|
||||
/// <summary>最后修改时间</summary>
|
||||
[SugarColumn(ColumnName = "LastUpdateTime", ColumnDescription = "最后修改时间", IsNullable = true)]
|
||||
public DateTime? LastUpdateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户表
|
||||
/// </summary>
|
||||
[SugarTable("sys_user")]
|
||||
public class UserEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "UserName", ColumnDescription = "用户名(登录账号)", Length = 64, IsNullable = false)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "PasswordHash", ColumnDescription = "密码哈希", Length = 256, IsNullable = false)]
|
||||
public string PasswordHash { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "RealName", ColumnDescription = "真实姓名", Length = 100, IsNullable = true)]
|
||||
public string? RealName { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Email", ColumnDescription = "邮箱", Length = 128, IsNullable = true)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Phone", ColumnDescription = "手机号", Length = 20, IsNullable = true)]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "OrgId", ColumnDescription = "所属组织Id(公司/实验室/部门/班组任一节点,0=未分配)", DefaultValue = "0")]
|
||||
public long OrgId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Position", ColumnDescription = "岗位", Length = 64, IsNullable = true)]
|
||||
public string? Position { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "SkillTags", ColumnDescription = "技能标签(逗号分隔,如 Modbus,PLC,温度箱)", Length = 500, IsNullable = true)]
|
||||
public string? SkillTags { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "IsEnabled", ColumnDescription = "是否启用", ColumnDataType = "smallint", DefaultValue = "1")]
|
||||
public byte IsEnabled { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "LastLoginTime", ColumnDescription = "最后登录时间", IsNullable = true)]
|
||||
public DateTime? LastLoginTime { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Avatar", ColumnDescription = "头像URL", Length = 256, IsNullable = true)]
|
||||
public string? Avatar { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "Remark", ColumnDescription = "备注", Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Model.Entity.System
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户-角色关联表(多对多)
|
||||
/// </summary>
|
||||
[SugarTable("sys_user_role")]
|
||||
public class UserRoleEntity : BaseEntity
|
||||
{
|
||||
[SugarColumn(ColumnName = "UserId", ColumnDescription = "用户Id")]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "RoleId", ColumnDescription = "角色Id")]
|
||||
public long RoleId { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user