wenxiangchao
2026-11-02 fe2aa4880d5c9fd88088ad0ed81ebaca05629f5f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System;
 
namespace Aging.Infrastructure.Contracts
{
    /// <summary>
    /// 资源键值对象,唯一标识一个可管理的硬件资源。
    /// 格式: "DeviceId:Channel",如 "PSU024:CH1"、"CAN_CARD01:CH2"、"PLC01:OUTPUT_GROUP_A"
    /// </summary>
    public sealed class ResourceKey : IEquatable<ResourceKey>
    {
        /// <summary>
        /// 设备标识符
        /// </summary>
        public string DeviceId { get; }
 
        /// <summary>
        /// 通道标识(可选)
        /// </summary>
        public string? Channel { get; }
 
        /// <summary>
        /// 资源类型(PowerSupply/CAN/PLC/Chamber等)
        /// </summary>
        public string ResourceType { get; }
 
        /// <summary>
        /// 初始化资源键实例
        /// </summary>
        /// <param name="deviceId">设备标识符</param>
        /// <param name="resourceType">资源类型</param>
        /// <param name="channel">通道标识(可选)</param>
        public ResourceKey(string deviceId, string resourceType, string? channel = null)
        {
            DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
            ResourceType = resourceType ?? throw new ArgumentNullException(nameof(resourceType));
            Channel = channel;
        }
 
        /// <summary>
        /// 从字符串解析资源键,格式为 "DeviceId:Channel"
        /// </summary>
        /// <param name="key">资源键字符串</param>
        /// <returns>解析后的资源键实例</returns>
        /// <exception cref="ArgumentException">格式无效时抛出</exception>
        public static ResourceKey FromString(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentException("资源键不能为空", nameof(key));
 
            var parts = key.Split(':');
            if (parts.Length < 2)
                throw new ArgumentException($"无效的资源键格式: '{key}',期望格式为 'DeviceId:Channel'");
 
            var deviceId = parts[0].Trim();
            var channel = string.Join(":", parts.Skip(1)).Trim();
 
            if (string.IsNullOrEmpty(deviceId))
                throw new ArgumentException($"无效的资源键格式: '{key}',DeviceId 不能为空");
 
            // 根据设备ID前缀推断资源类型
            var resourceType = InferResourceType(deviceId);
 
            return new ResourceKey(deviceId, resourceType, string.IsNullOrEmpty(channel) ? null : channel);
        }
 
        /// <summary>
        /// 获取操作资源键,用于物理会话锁
        /// </summary>
        public string OperationResourceKey => $"{DeviceId}:SESSION";
 
        /// <summary>
        /// 根据设备ID前缀推断资源类型
        /// </summary>
        private static string InferResourceType(string deviceId)
        {
            var upper = deviceId.ToUpperInvariant();
            if (upper.StartsWith("PSU") || upper.StartsWith("POWER"))
                return "PowerSupply";
            if (upper.StartsWith("CAN"))
                return "CAN";
            if (upper.StartsWith("PLC"))
                return "PLC";
            if (upper.StartsWith("CHAMBER") || upper.StartsWith("TEMP"))
                return "Chamber";
            if (upper.StartsWith("DAQ"))
                return "DAQ";
            if (upper.StartsWith("SERIAL") || upper.StartsWith("COM"))
                return "Serial";
            return "Generic";
        }
 
        /// <inheritdoc/>
        public bool Equals(ResourceKey? other)
        {
            if (other is null) return false;
            if (ReferenceEquals(this, other)) return true;
            return string.Equals(DeviceId, other.DeviceId, StringComparison.OrdinalIgnoreCase) &&
                   string.Equals(Channel, other.Channel, StringComparison.OrdinalIgnoreCase) &&
                   string.Equals(ResourceType, other.ResourceType, StringComparison.OrdinalIgnoreCase);
        }
 
        /// <inheritdoc/>
        public override bool Equals(object? obj) => Equals(obj as ResourceKey);
 
        /// <inheritdoc/>
        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 17;
                hash = hash * 31 + (DeviceId?.ToUpperInvariant().GetHashCode() ?? 0);
                hash = hash * 31 + (Channel?.ToUpperInvariant().GetHashCode() ?? 0);
                hash = hash * 31 + (ResourceType?.ToUpperInvariant().GetHashCode() ?? 0);
                return hash;
            }
        }
 
        /// <inheritdoc/>
        public override string ToString() =>
            string.IsNullOrEmpty(Channel) ? DeviceId : $"{DeviceId}:{Channel}";
 
        /// <summary>
        /// 相等运算符重载
        /// </summary>
        public static bool operator ==(ResourceKey? left, ResourceKey? right) =>
            Equals(left, right);
 
        /// <summary>
        /// 不等运算符重载
        /// </summary>
        public static bool operator !=(ResourceKey? left, ResourceKey? right) =>
            !Equals(left, right);
    }
}