Files
Ayay/SHH.CameraDashboard/Services/WebApis/CameraReps/CameraRepositoryEdit.cs
2026-01-01 22:40:32 +08:00

396 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.Json;
namespace SHH.CameraDashboard
{
public partial class CameraRepository
{
#region GetCameraDetailsAsync
/// <summary>
/// 获取摄像头的详细配置信息。
/// 此方法封装了完整的 API 调用和 JSON 到 DTO 的手动映射过程。
/// </summary>
/// <param name="cameraId">要查询的摄像头的唯一 ID。</param>
/// <returns>
/// 一个异步任务,其结果是一个 <see cref="CameraEditInfo"/> 对象,包含摄像头的详细配置。
/// 如果配置节点信息不存在、API 请求失败或 JSON 解析失败,则返回 <c>null</c>。
/// </returns>
public async Task<CameraEditInfo?> GetCameraDetailsAsync(long cameraId)
{
// 1. 从全局数据中获取当前使用的服务节点信息
var serviceNode = AppGlobal.UseServiceNode;
if (serviceNode == null)
{
// 如果没有配置服务节点,则无法获取信息
return null;
}
var ipAddress = serviceNode.ServiceNodeIp;
var port = serviceNode.ServiceNodePort;
// 2. 拼接 API 请求的 URL
string requestUrl = $"http://{ipAddress}:{port}/api/Cameras/{cameraId}";
try
{
// 3. 调用 WebApiService 发送 GET 请求,获取原始 JSON 字符串
string jsonResponse = await WebApiService.Instance.GetAsync(requestUrl, "GetDetail");
// 4. 检查返回的 JSON 是否为空
if (string.IsNullOrEmpty(jsonResponse))
{
return null;
}
// 5. 将原始 JSON 字符串手动映射到目标 DTO 对象
// 这种方式可以精确控制每个字段的转换,处理类型不匹配等问题。
return MapJsonToEditDto(jsonResponse);
}
catch (Exception ex)
{
// 6. 捕获所有可能的异常(网络错误、服务器错误等)
System.Diagnostics.Debug.WriteLine($"[Repository] 获取摄像头详情失败 (ID: {cameraId}): {ex.Message}");
return null;
}
}
#endregion
#region UpdateCameraAsync
/// <summary>
/// [新增] 更新摄像头的配置信息。
/// 此方法负责将 <see cref="CameraEditInfo"/> 对象转换为 API 所需的 JSON 格式,并发送 PUT 请求。
/// </summary>
/// <param name="dto">包含摄像头新配置信息的 DTO 对象。</param>
/// <returns>
/// 一个异步任务,其结果为一个布尔值:
/// - <c>true</c>表示更新成功HTTP 响应为 200 OK 或其他成功状态码)。
/// - <c>false</c>:表示更新失败(如 DTO 为 null、网络错误、服务器返回错误等
/// </returns>
public async Task<bool> UpdateCameraAsync(CameraEditInfo dto, string pageName)
{
// 1. 防御性检查:确保传入的 DTO 对象不为 null
if (dto == null)
{
return false;
}
// 1. 从全局数据中获取当前使用的服务节点信息
var serviceNode = AppGlobal.UseServiceNode;
if (serviceNode == null)
{
// 如果没有配置服务节点,则无法获取信息
return false;
}
var ipAddress = serviceNode.ServiceNodeIp;
var port = serviceNode.ServiceNodePort;
// 2. 拼接 PUT 请求的 URL
// URL 格式: http://{ip}:{port}/api/Cameras/{id}
string requestUrl = $"http://{ipAddress}:{port}/api/Cameras/{dto.Id}";
try
{
// 3. 将 DTO 对象手动映射为 API 所需的 JSON 字符串
// 这一步至关重要,因为它处理了特殊的字段转换,例如:
// 将 DTO 中的 `Brand` (int) 转换为 JSON 中的 `"brand": "HikVision"` (string)。
string jsonPayload = MapDtoToEditJson(dto);
// 4. 调用 WebApiService 发送 PUT 请求
// 注意:请确保您的 `WebApiService` 类中存在 `PutAsync` 方法。
// 如果不存在,可以考虑添加一个,或者在某些 RESTful 设计中,也可以使用 `PostAsync` 代替。
await WebApiService.Instance.PutAsync(requestUrl, jsonPayload, pageName);
// 5. 如果代码执行到这里,说明请求成功且没有抛出异常
return true;
}
catch (Exception ex)
{
// 6. 捕获所有可能的异常(网络错误、服务器错误、序列化错误等)
System.Diagnostics.Debug.WriteLine($"[Repository] 更新摄像头配置失败 (ID: {dto.Id}): {ex.Message}");
return false;
}
}
#endregion
#region CreateCameraAsync
/// <summary>
/// [新增] 创建一个新的摄像头配置。
/// 此方法负责将 <see cref="CameraEditInfo"/> 对象转换为 API 所需的 JSON 格式,并发送 POST 请求到摄像头资源集合的根路径。
/// </summary>
/// <param name="nodeIp">目标服务器节点的 IP 地址。</param>
/// <param name="nodePort">目标服务器节点的端口号。</param>
/// <param name="dto">包含新摄像头配置信息的 DTO 对象。</param>
/// <returns>
/// 一个异步任务,其结果为一个布尔值:
/// - <c>true</c>表示创建成功HTTP 响应为 201 Created 或其他成功状态码)。
/// - <c>false</c>:表示创建失败(如 DTO 为 null、网络错误、服务器返回错误等
/// </returns>
public async Task<bool> CreateCameraAsync(CameraEditInfo dto, string pageName)
{
// 1. 防御性检查:确保传入的 DTO 对象不为 null
if (dto == null)
{
return false;
}
// 1. 从全局数据中获取当前使用的服务节点信息
var serviceNode = AppGlobal.UseServiceNode;
if (serviceNode == null)
{
// 如果没有配置服务节点,则无法获取信息
return false;
}
var ipAddress = serviceNode.ServiceNodeIp;
var port = serviceNode.ServiceNodePort;
// 2. 拼接 POST 请求的 URL
// URL 格式: http://{ip}:{port}/api/Cameras
// 注意:创建新资源通常是 POST 到资源集合的根路径,而不是单个资源的路径。
// 此时 DTO 中的 `Id` 字段通常应为 0 或默认值,由服务器在创建时生成新的唯一 ID。
string requestUrl = $"http://{ipAddress}:{port}/api/Cameras";
try
{
// 3. 将 DTO 对象手动映射为 API 所需的 JSON 字符串
// 复用与更新操作相同的映射逻辑,确保数据格式的一致性。
string jsonPayload = MapDtoToEditJson(dto);
// 4. 调用 WebApiService 发送 POST 请求
await WebApiService.Instance.PostAsync(requestUrl, jsonPayload, pageName);
// 5. 如果代码执行到这里,说明请求成功且没有抛出异常
return true;
}
catch (Exception ex)
{
// 6. 捕获所有可能的异常(网络错误、服务器错误、序列化错误等)
System.Diagnostics.Debug.WriteLine($"[Repository] 创建新摄像头失败: {ex.Message}");
return false;
}
}
#endregion
/// <summary>
/// [新增] 删除摄像头
/// </summary>
public async Task<bool> DeleteCameraAsync(long cameraId)
{
// 1. 从全局数据中获取当前使用的服务节点信息
var serviceNode = AppGlobal.UseServiceNode;
if (serviceNode == null)
{
// 如果没有配置服务节点,则无法获取信息
return false;
}
var ipAddress = serviceNode.ServiceNodeIp;
var port = serviceNode.ServiceNodePort;
// URL: http://{ip}:{port}/api/Cameras/{id}
string url = $"http://{ipAddress}:{port}/api/Cameras/{cameraId}";
try
{
// 发送 DELETE 请求
await WebApiService.Instance.DeleteAsync(url, "DeleteCamera");
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[Repository] 删除失败: {ex.Message}");
return false;
}
}
#region MapJsonToEditDto
/// <summary>
/// 私有辅助方法:手动解析 JSON 字符串并映射到 <see cref="CameraEditInfo"/> DTO。
/// 此方法能精确处理类型转换和提供默认值,避免自动反序列化时因类型不匹配而失败。
/// </summary>
/// <param name="json">从 API 获取的原始 JSON 字符串。</param>
/// <returns>一个填充了数据的 <see cref="CameraEditInfo"/> 对象。即使解析失败,也会返回一个对象实例。</returns>
private CameraEditInfo MapJsonToEditDto(string json)
{
// 初始化一个 DTO 对象,用于存储映射后的数据
var cameraDto = new CameraEditInfo();
try
{
// 使用 System.Text.Json 的 JsonDocument 进行高性能的只读解析
using (JsonDocument doc = JsonDocument.Parse(json))
{
JsonElement root = doc.RootElement;
// --- 基础字段映射 ---
// 使用 TryGetProperty 安全地获取属性,避免因字段不存在而抛出异常
if (root.TryGetProperty("id", out var idElement)) cameraDto.Id = idElement.GetInt64();
if (root.TryGetProperty("name", out var nameElement)) cameraDto.Name = nameElement.GetString() ?? string.Empty;
if (root.TryGetProperty("ipAddress", out var ipElement)) cameraDto.IpAddress = ipElement.GetString();
if (root.TryGetProperty("username", out var userElement)) cameraDto.Username = userElement.GetString();
if (root.TryGetProperty("password", out var passElement)) cameraDto.Password = passElement.GetString();
if (root.TryGetProperty("channelIndex", out var chElement)) cameraDto.ChannelIndex = chElement.GetInt32();
if (root.TryGetProperty("rtspPath", out var rtspElement)) cameraDto.RtspPath = rtspElement.GetString();
if (root.TryGetProperty("mainboardIp", out var mainIpElement)) cameraDto.MainboardIp = mainIpElement.GetString();
if (root.TryGetProperty("mainboardPort", out var mainPortElement)) cameraDto.MainboardPort = mainPortElement.GetInt32();
if (root.TryGetProperty("streamType", out var streamElement)) cameraDto.StreamType = streamElement.GetInt32();
// --- 类型转换 ---
// 将 JSON 中的 int 类型端口号转换为 DTO 中的 ushort 类型
if (root.TryGetProperty("port", out var portElement)) cameraDto.Port = (ushort)portElement.GetInt32();
// --- 特殊逻辑处理 ---
// 将 JSON 中的品牌字符串(如 "HikVision")转换为 DTO 中的整数枚举
if (root.TryGetProperty("brand", out var brandElement))
{
cameraDto.Brand = ParseBrandToEditInt(brandElement.GetString());
}
// --- 设置默认值 ---
// 为未从 API 获取或有特定默认值的字段设置初始值
cameraDto.Location = string.Empty;
cameraDto.UseGrayscale = false;
cameraDto.EnhanceImage = true;
cameraDto.AllowCompress = true;
cameraDto.TargetResolution = string.Empty;
}
}
catch (Exception ex)
{
// 捕获 JSON 解析或映射过程中的任何异常
System.Diagnostics.Debug.WriteLine($"[Repository] JSON 解析或映射到 DTO 时发生异常: {ex.Message}");
// 即使发生异常,也返回一个对象实例,而不是 null以防止上层代码出现空引用异常
}
return cameraDto;
}
#endregion
#region ParseBrandToEditInt
/// <summary>
/// [读] API 字符串 -> DTO 枚举值 (int)
/// 将 WebAPI 返回的 "HikVision" 等字符串解析为 DeviceBrand 枚举对应的 int
/// </summary>
private int ParseBrandToEditInt(string brandApiString)
{
if (string.IsNullOrWhiteSpace(brandApiString))
return (int)DeviceBrand.Unknown;
// 统一转小写进行匹配,防止大小写差异
var lowerName = brandApiString.Trim().ToLower();
// 1. 尝试直接通过枚举名解析 (例如 API 返回 "HikVision", 枚举也是 HikVision)
// 这样如果 API 返回 "Usb",且枚举也有 "Usb",就能自动匹配
if (Enum.TryParse(brandApiString, true, out DeviceBrand result))
{
return (int)result;
}
return (int)DeviceBrand.Unknown;
}
#endregion
#region ConvertBrandToEditString
/// <summary>
/// [写] DTO 枚举值 (int) -> API 字符串
/// 保存时,将 int 转换为 WebAPI 需要的字符串标识
/// </summary>
private string ConvertBrandToEditString(int brandValue)
{
// 将 int 强转为枚举,方便 switch
var brand = (DeviceBrand)brandValue;
switch (brand)
{
case DeviceBrand.HikVision:
return "HikVision"; // API 期望的字符串
case DeviceBrand.Dahua:
return "Dahua";
case DeviceBrand.RtspGeneral:
return "RTSP"; // 假设 API 期望全大写
case DeviceBrand.Usb:
return "Usb";
case DeviceBrand.WebSocketShine:
return "WebSocket"; // 需确认 API 期望什么
case DeviceBrand.File:
return "File";
case DeviceBrand.OnvifGeneral:
return "Onvif";
case DeviceBrand.Unknown:
default:
return "Unknown";
}
}
#endregion
#region MapDtoToEditJson
/// <summary>
/// 辅助方法:将 DTO 转换为 API 需要的 JSON 字符串
/// </summary>
private string MapDtoToEditJson(CameraEditInfo dto)
{
// 根据后端约定的最新格式构建匿名对象
// 特点:
// 1. brand 直接传 int
// 2. 扩展参数(useGrayscale等) 直接放在根节点,不需要 vendorArguments 包裹
var apiModel = new
{
id = dto.Id,
name = dto.Name ?? "",
// ★ 修正1直接使用 int 值,不再转换成 "HikVision" 字符串
brand = dto.Brand,
location = dto.Location ?? "",
ipAddress = dto.IpAddress,
port = dto.Port,
username = dto.Username ?? "",
password = dto.Password ?? "",
// ★ 修正2根据样例renderHandle 传 0 (通常这是运行时句柄,保存时无意义)
renderHandle = 0,
channelIndex = dto.ChannelIndex,
rtspPath = dto.RtspPath ?? "",
mainboardIp = dto.MainboardIp ?? "",
mainboardPort = dto.MainboardPort,
streamType = dto.StreamType,
// ★ 修正3扁平化处理直接放在根层级
useGrayscale = dto.UseGrayscale,
enhanceImage = dto.EnhanceImage,
allowCompress = dto.AllowCompress,
allowExpand = dto.AllowExpand,
targetResolution = dto.TargetResolution ?? "",
allowShrink = dto.AllowShrink,
allowEnlarge = dto.AllowEnlarge,
};
return JsonHelper.Serialize(apiModel);
}
#endregion
}
}