升级 ,以及修复一些BUG

This commit is contained in:
2026-01-23 09:01:50 +08:00
parent f038ca3260
commit 3a5ace4817
20 changed files with 1218 additions and 25 deletions

Binary file not shown.

View File

@@ -125,7 +125,7 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>55384</DevelopmentServerPort>
<DevelopmentServerPort>4498</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>

View File

@@ -1230,9 +1230,10 @@ namespace RCUHost.Implement
string CODE = context111.SystemHeader.Value.HostNumber.ToHotelCode().ToString();
string hotelCode = CODE;
string EndPoint = context111.RemoteEndPoint.ToString();
//if (cmdType != 0x01)
if (cmdType != 0x01 && cmdType != 0xb1)
//if (!(cmdType==0x01||cmdType==0xb1) )
//在线状态 不排队 注册0x01
if (true)
//if (true)
{
#region
string EndPointStr = context111.RemoteEndPoint.ToString();
@@ -1833,6 +1834,10 @@ namespace RCUHost.Implement
string HostID = CSRedisCacheHelper.HMGet<string>(5, CacheKey.HostId_HostNumber, HostNNN)[0];
string RoomNUMBER = CSRedisCacheHelper.HMGet<string>(5, CacheKey.RoomNumber_HostNumber, HostNNN)[0];
if (string.IsNullOrEmpty(RoomNUMBER))
{
return;
}
List<DingShiReportDate> DeviceList = new List<DingShiReportDate>();
@@ -2051,7 +2056,7 @@ namespace RCUHost.Implement
ccc.UpdateTime = DateTime.Now;
ccc.Modal = new RoomTypeModalCache() { };
ccc.AirConditionData = new AirConditionData();
CSRedisCacheHelper.Set_Partition<HostModal_Cache>(KKey,ccc);
CSRedisCacheHelper.Set_Partition<HostModal_Cache>(KKey, ccc);
}
}

View File

@@ -399,13 +399,20 @@ namespace RCUHost.Implement
var hostModal = CSRedisCacheHelper.Get_Partition<HostModal_Cache>(KKey);
if (hostModal != null)
{
if (hostModal.AirConditionData==null)
if (hostModal.AirConditionData == null)
{
hostModal.AirConditionData = new AirConditionData();
hostModal.AirConditionData = new AirConditionData();
}
if (hostModal.Modal==null)
if (hostModal.Modal == null)
{
hostModal.Modal = new RoomTypeModalCache() { ModalAddress=device.Value.Address};
hostModal.Modal = new RoomTypeModalCache() { ModalAddress = device.Value.Address };
}
if (hostModal.Modal!=null)
{
if (string.IsNullOrEmpty(hostModal.Modal.ModalAddress))
{
hostModal.Modal.ModalAddress = device.Value.Address;
}
}
}
else
@@ -548,12 +555,12 @@ namespace RCUHost.Implement
{
TCLCommon.SendData(hotelcode, roomnum, TCLcuid, skillid, "SLEEP");
}
if (hostModal.Modal.Name.Equals("睡眠息屏"))
{
}
//if (hostModal.Modal.Name.Equals("睡眠息屏"))
//{
//}
//呼叫前台
if (hostModal.Modal.ModalAddress.Equals("004000023"))
if (hostModal.Modal != null && hostModal.Modal.ModalAddress.Equals("004000023"))
{
TCLCommon.SendData(hotelcode, roomnum, TCLcuid, skillid, "SIP");
}
@@ -606,7 +613,7 @@ namespace RCUHost.Implement
else//设备关
{
//清理
if (hostModal.Modal.ModalAddress.Equals("004000003"))
if (hostModal.Modal != null && hostModal.Modal.ModalAddress.Equals("004000003"))
{
//通过LocationUUID确定订单号
string OrderUUID = CSRedisCacheHelper.Get_Partition<string>(CacheKey.FCSRoom_Mapping_Order + "_" + UUID, 3);
@@ -622,6 +629,8 @@ namespace RCUHost.Implement
catch (Exception ex)
{
logger.Error("取电数据有异常: " + host.SysHotel.Code + "," + host.RoomNumber + " Msg:" + ex.Message);
logger.Error(Newtonsoft.Json.JsonConvert.SerializeObject(hostModal));
logger.Error(ex.StackTrace);
}
CSRedisCacheHelper.Set_Partition<HostModal_Cache>(KKey, hostModal);
#endregion

View File

@@ -699,7 +699,7 @@ namespace RCUHost.Implement
}
catch (Exception ex)
{
logger.Error("取电数据有异常: " + host.SysHotel.Code + "," + host.RoomNumber + " Msg:" + ex.Message);
logger.Error("取电数据有异常: " + host.SysHotel.Code + "," + host.RoomNumber + " Msg:" + ex.Message + ex.StackTrace);
}
CSRedisCacheHelper.Set_Partition<HostModal_Cache>(KKey, hostModal);
#endregion

View File

@@ -97,13 +97,13 @@ namespace RCUHost.Implement
Upgrade_Status = "升级失败";
break;
}
BarData bbb = new BarData();
bbb.HostID = host.ID;
bbb.Upgrade_status = Upgrade_Status;
bbb.Upgrade_DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
UploadCurrentVersionReceiver.UP_Grade_Json(host, bbb);
host.UpgradeTime = DateTime.Now;
HostRepository.Update(host);
}

View File

@@ -48,8 +48,8 @@ namespace RCUHost.Implement
//添加升级功能标志,告诉缓存,不能被拦截
foreach (var item in hosts)
{
string Key="Upgrade_UpdateSQL_"+ item.HostNumber;
MemoryCacheHelper.Set(Key,item.ID,DateTimeOffset.Now.AddMinutes(3));
string Key = "Upgrade_UpdateSQL_" + item.HostNumber;
MemoryCacheHelper.Set(Key, item.ID, DateTimeOffset.Now.AddMinutes(3));
}
FileInfo fileInfo = new FileInfo(updateFile);
@@ -85,7 +85,7 @@ namespace RCUHost.Implement
{
int startIndex = StructConverter.SizeOf(context1.SystemHeader);
UpdateHostPacketReply? reply1 = DecodeUpdateHostPacketReply(context1.Data, startIndex);
var TTT = new Tuple<ReceiverContext, UpdateHostPacketReply?>(context1,reply1);
var TTT = new Tuple<ReceiverContext, UpdateHostPacketReply?>(context1, reply1);
//logger.Error(string.Format("收到tftp升级回复命令{0}:{1}{2},解析结果:{3}", context.RemoteEndPoint.Address.ToString(), context.RemoteEndPoint.Port, Tools.ByteToString(context.Data), reply.HasValue));
if (reply1.HasValue)
{
@@ -95,8 +95,20 @@ namespace RCUHost.Implement
var context = NNN.Item1;
var reply = NNN.Item2;
var updateHostWorker = this.updateHostList.FirstOrDefault(r => r.Host.HostNumber == context.SystemHeader.Value.HostNumber.ToString());
//logger.Error(string.Format("酒店{0}客房{1}升级({2}),状态{3}", updateHostWorker.Host.SysHotel.Code, updateHostWorker.Host.RoomNumber, updateHostWorker.RemoteFile, reply.Value.Status));
SaveSystemLog(30, "升级主机", string.Format("收到主机({0})升级回复命令,状态:{1}", updateHostWorker.Host.RoomNumber, reply.Value.Status), "收到命令", "RCU", context.RemoteEndPoint.Address.ToString(), updateHostWorker.Host.SysHotel.ID);
var data= Tools.ByteToString( context1.Data);
logger.Error(string.Format("主机{0}升级返回:{1}", context.SystemHeader.Value.HostNumber.ToString(),data));
if (updateHostWorker == null)
{
return;
}
try
{
SaveSystemLog(30, "升级主机", string.Format("收到主机({0})升级回复命令,状态:{1}", updateHostWorker.Host.RoomNumber, reply.Value.Status), "收到命令", "RCU", context.RemoteEndPoint.Address.ToString(), updateHostWorker.Host.SysHotel.ID);
}
catch (Exception)
{
}
if (updateHostWorker.HostUpdate == null)
{
BarData bbb = new BarData();
@@ -114,6 +126,18 @@ namespace RCUHost.Implement
HostRepository.SetUpgradeStatus(updateHostWorker.Host, 1);//升级完成
bbb.Upgrade_status = "升级完成";
break;
case UpdateHostPacketReply.BlockNumError:
bbb.Upgrade_status = "块错误";
HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
break;
case UpdateHostPacketReply.FileTypeError:
bbb.Upgrade_status = "文件错误";
HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
break;
case UpdateHostPacketReply.FileMD5Error:
bbb.Upgrade_status = "文件MD5校验错误";
HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
break;
default:
HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
bbb.Upgrade_status = "升级失败";
@@ -147,6 +171,21 @@ namespace RCUHost.Implement
hostUpdateStatus.Status = 1;//升级完成
updateHostList.Remove(updateHostWorker);
break;
case UpdateHostPacketReply.BlockNumError:
bbb.Upgrade_status = "块错误";
hostUpdateStatus.Status = 2;//升级失败
updateHostList.Remove(updateHostWorker);
break;
case UpdateHostPacketReply.FileTypeError:
bbb.Upgrade_status = "文件错误";
hostUpdateStatus.Status = 2;//升级失败
updateHostList.Remove(updateHostWorker);
break;
case UpdateHostPacketReply.FileMD5Error:
bbb.Upgrade_status = "文件MD5校验错误";
hostUpdateStatus.Status = 2;//升级失败
updateHostList.Remove(updateHostWorker);
break;
default:
bbb.Upgrade_status = "升级失败";
hostUpdateStatus.Status = 2;//升级失败
@@ -158,7 +197,7 @@ namespace RCUHost.Implement
hostUpdateStatus.UpdatedTime = DateTime.Now;
HostUpdateStatusRepository.SaveOrUpdate(hostUpdateStatus);
}
},TTT);
}, TTT);
}
}
public override CommandType CommandType
@@ -175,6 +214,7 @@ namespace RCUHost.Implement
/// <param name="updateFileMd5">升级文件MD5值</param>
private void SendUpdateRequest(Host host, string updateFileMd5, ushort blockNum, byte fileType, string fileName)
{
logger.Error("主机升级:"+host.SysHotel.Code+":"+host.RoomNumber);
byte[] data = CreateUpdateRequestPacket(updateFileMd5, blockNum, fileType, fileName);
Send(data, host.HostNumber, host.MAC);// host.IP, host.Port);
}

View File

@@ -60,6 +60,10 @@ namespace RCUHost.Implement
{
try
{
if (string.IsNullOrEmpty(bbb.Upgrade_status))
{
return;
}
string id = host.ID.ToString();
string Key = CacheKey.UPGradeProgressBar + "_" + id;
var DDD = CSRedisCacheHelper.Get<BarData>(Key);

View File

@@ -0,0 +1,327 @@
# UDP数据推送与Redis缓存方案
## 1. 需求分析
当UDP数据到达时需要将数据推送到一些外部接口但这些接口所需的数据如房间号在UDP数据中并不包含而是存储在数据库中。为了减少数据库查询开销提高推送效率需要
1. 定期从数据库获取必要信息并更新到Redis
2. UDP数据到达时从Redis快速获取所需信息
3. 实现高效的数据推送机制
## 2. 解决方案设计
### 2.1 定时任务设计
创建一个定时任务定期从ApiController获取主机信息并更新到Redis
```csharp
public class HostInfoSyncJob : IJob
{
public void Execute()
{
try
{
// 创建HttpClient实例
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["CurrentUrl"]);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 调用API获取所有主机信息
var response = client.GetAsync("api/GetAllHostInfo").Result;
if (response.IsSuccessStatusCode)
{
var hostInfos = response.Content.ReadAsAsync<List<HostInfoDto>>().Result;
// 更新Redis缓存
foreach (var hostInfo in hostInfos)
{
// 缓存主机基本信息
string hostInfoKey = CacheKey.HostInfo_Key_HostNumber + "_" + hostInfo.HostNumber;
CSRedisCacheHelper.Forever<HostInfoDto>(hostInfoKey, hostInfo);
// 缓存主机号到房间号的映射
string hostToRoomKey = CacheKey.HostToRoom_Mapping + "_" + hostInfo.HostNumber;
CSRedisCacheHelper.Forever<string>(hostToRoomKey, hostInfo.RoomNumber);
// 缓存房间号到主机号的映射
string roomToHostKey = CacheKey.RoomToHost_Mapping + "_" + hostInfo.RoomNumber;
CSRedisCacheHelper.Forever<string>(roomToHostKey, hostInfo.HostNumber);
}
}
}
}
catch (Exception ex)
{
log4net.LogManager.GetLogger(typeof(HostInfoSyncJob)).Error("同步主机信息到Redis失败: " + ex.Message);
}
}
}
```
### 2.2 ApiController扩展
在ApiController中添加获取所有主机信息的接口
```csharp
/// <summary>
/// 获取所有主机信息用于定时同步到Redis
/// </summary>
/// <returns></returns>
public ActionResult GetAllHostInfo()
{
try
{
List<HostInfoDto> hostInfos = new List<HostInfoDto>();
// 获取所有酒店
var hotels = SysHotelManager.LoadAll();
foreach (var hotel in hotels)
{
// 获取酒店下所有主机
var hosts = HostManager.LoadAll(hotel.ID);
foreach (var host in hosts)
{
HostInfoDto dto = new HostInfoDto
{
HostNumber = host.HostNumber,
RoomNumber = host.RoomNumber,
HotelID = hotel.ID,
HotelCode = hotel.Code,
HotelName = hotel.Name,
Status = host.Status,
RoomStatus = host.RoomStatus?.Name,
MAC = host.MAC
};
hostInfos.Add(dto);
}
}
return Json(hostInfos, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
logger.Error("获取所有主机信息失败: " + ex.Message);
return Json(new List<HostInfoDto>(), JsonRequestBehavior.AllowGet);
}
}
```
### 2.3 UDP数据处理与推送
在UDP数据处理模块中从Redis获取所需信息并推送到外部接口
```csharp
public class UdpDataPusher
{
private static readonly string PushApiUrl = System.Configuration.ConfigurationManager.AppSettings["PushApiUrl"];
public static void PushUdpData(string hostNumber, string udpData)
{
try
{
// 从Redis获取主机信息
string hostInfoKey = CacheKey.HostInfo_Key_HostNumber + "_" + hostNumber;
var hostInfo = CSRedisCacheHelper.Get<HostInfoDto>(hostInfoKey);
if (hostInfo == null)
{
// 如果Redis中没有从数据库获取并更新Redis
hostInfo = GetHostInfoFromDatabase(hostNumber);
if (hostInfo == null)
{
log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("无法获取主机信息: " + hostNumber);
return;
}
// 更新到Redis
CSRedisCacheHelper.Forever<HostInfoDto>(hostInfoKey, hostInfo);
}
// 构建推送数据
PushData pushData = new PushData
{
HostNumber = hostNumber,
RoomNumber = hostInfo.RoomNumber,
HotelCode = hostInfo.HotelCode,
HotelName = hostInfo.HotelName,
UdpData = udpData,
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
// 推送到外部接口
SendPushRequest(pushData);
}
catch (Exception ex)
{
log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("推送UDP数据失败: " + ex.Message);
}
}
private static HostInfoDto GetHostInfoFromDatabase(string hostNumber)
{
// 创建HttpClient实例
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["CurrentUrl"]);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 调用API获取主机信息
var response = client.GetAsync($"api/GetHostInfo?hostNumber={hostNumber}").Result;
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsAsync<HostInfoDto>().Result;
}
}
return null;
}
private static void SendPushRequest(PushData data)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(PushApiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
var response = client.PostAsync("", content).Result;
if (!response.IsSuccessStatusCode)
{
log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("推送失败: " + response.StatusCode);
}
}
}
}
```
### 2.4 缓存键设计
在CacheKey类中添加新的缓存键常量
```csharp
public class CacheKey
{
// 现有缓存键...
// 新增缓存键
public static string HostToRoom_Mapping = "HostToRoom_Mapping";
public static string RoomToHost_Mapping = "RoomToHost_Mapping";
public static string HostInfo_Key_HostNumber = "HostInfo_Key_HostNumber";
}
```
### 2.5 数据模型设计
```csharp
public class HostInfoDto
{
public string HostNumber { get; set; }
public string RoomNumber { get; set; }
public int HotelID { get; set; }
public string HotelCode { get; set; }
public string HotelName { get; set; }
public bool Status { get; set; }
public string RoomStatus { get; set; }
public string MAC { get; set; }
}
public class PushData
{
public string HostNumber { get; set; }
public string RoomNumber { get; set; }
public string HotelCode { get; set; }
public string HotelName { get; set; }
public string UdpData { get; set; }
public string Timestamp { get; set; }
}
```
### 2.6 定时任务注册
在Global.asax.cs中注册定时任务
```csharp
protected override void Application_Start(object sender, EventArgs e)
{
// 现有代码...
// 注册定时任务
JobManager.AddJob(
() => new HostInfoSyncJob().Execute(),
s => s.ToRunEvery(5).Minutes() // 每5分钟执行一次
);
// 现有代码...
}
```
## 3. 性能优化
### 3.1 缓存策略优化
1. **缓存过期时间**:对于主机信息,使用永久缓存,只有在定时任务执行时更新
2. **缓存预热**系统启动时执行一次全量同步确保Redis中有初始数据
3. **增量更新**:定时任务可以只更新发生变化的主机信息,减少网络传输
### 3.2 并发处理优化
1. **异步推送**使用异步方式推送数据避免阻塞UDP处理线程
2. **批量处理**对于短时间内的多个UDP数据进行批量推送
3. **连接池**使用HttpClient连接池减少连接建立开销
### 3.3 错误处理优化
1. **重试机制**:推送失败时进行有限次数的重试
2. **降级策略**当Redis不可用时直接从数据库获取数据
3. **监控告警**:对推送失败的情况进行监控和告警
## 4. 部署与配置
### 4.1 配置文件设置
在Web.config中添加必要的配置
```xml
<appSettings>
<!-- 现有配置... -->
<!-- 推送API配置 -->
<add key="PushApiUrl" value="http://example.com/api/push" />
<!-- 定时同步间隔(分钟) -->
<add key="SyncIntervalMinutes" value="5" />
</appSettings>
```
### 4.2 依赖项
- Newtonsoft.Json
- FluentScheduler用于定时任务
- CSRedis用于Redis操作
## 5. 测试方案
### 5.1 功能测试
1. **定时同步测试**验证定时任务是否正常执行Redis中是否有数据
2. **UDP数据推送测试**模拟UDP数据验证是否能正确获取信息并推送
3. **边界情况测试**测试Redis不可用、网络异常等情况
### 5.2 性能测试
1. **并发测试**模拟多个UDP数据同时到达测试推送性能
2. **响应时间测试**测量从UDP数据到达至推送完成的时间
3. **负载测试**:测试系统在高负载下的稳定性
## 6. 总结
本方案通过以下步骤实现了UDP数据的高效推送
1. **定时同步**定期从数据库获取主机信息并更新到Redis
2. **快速获取**UDP数据到达时从Redis快速获取所需信息
3. **异步推送**:使用异步方式将数据推送到外部接口
4. **容错处理**当Redis不可用时从数据库获取数据作为降级方案
该方案显著减少了数据库查询开销提高了UDP数据处理和推送的效率适用于高并发场景下的实时数据推送需求。

View File

@@ -3856,8 +3856,6 @@ namespace WebSite.Controllers
}
}
private void ReallyUpgrade(string ID, string host_list_str, string fileName)
{
int roomTypeID = 0;
@@ -3937,6 +3935,23 @@ namespace WebSite.Controllers
}
}
[HttpPost()]
public ActionResult Upgrade_V2(string roomtype_id, string host_list_str, string fileName)
{
int roomTypeID = 0;
int.TryParse(roomtype_id, out roomTypeID);
logger.Error("微信端开始升级: " + fileName + " " + roomTypeID + " host_list: " + host_list_str);
var host_list = Newtonsoft.Json.JsonConvert.DeserializeObject<List<int>>(host_list_str);
//不用获取,直接在本地使用
string lll = JsonConvert.SerializeObject(host_list);
HostUpdateController hh = new HostUpdateController();
logger.Error("开始升级: " + fileName + " host_list: " + lll);
//升级
return hh.(host_list, fileName,roomTypeID,HostManager);
}
#endregion

View File

@@ -384,7 +384,67 @@ namespace WebSite.Controllers
}
public ActionResult ShengJI_NEW(IList<int> idList, string fileName, IHostManager HostManager1,int RoomTypeID)
public ActionResult (IList<int> idList, string fileName, int RoomTypeID, IHostManager HostManager1)
{
try
{
IList<Host> hosts = new List<Host>();
foreach (int id in idList)
{
Host host = HostManager1.Get(id);
if (host != null)
{
hosts.Add(host);
}
}
FileType fileType = FileType.Config;
switch (fileName.Substring(fileName.Length - 3).ToLower())
{
case "bin":
case "hex":
fileType = FileType.Firmware;
break;
case "dat":
fileType = FileType.Config;
break;
default:
return Json(new { IsSuccess = false, Message = "升级文件不合法!" });
}
string fileHref = "Uploads/V2/room_type/" + RoomTypeID + "/" + fileName;//升级文件相对路径
string fileMd5 = "";
string FileKey = "FileMD5_" + fileHref;
string kkka = CSRedisCacheHelper.Get_Partition<string>(FileKey, 3);
if (!string.IsNullOrEmpty(kkka))
{
fileMd5 = kkka;
}
else
{
using (Stream stream = System.IO.File.Open(Tools.GetApplicationPath() + fileHref, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
fileMd5 = Tools.ComputeFileHash(stream);
CSRedisCacheHelper.Set_PartitionWithTime<string>(FileKey, fileMd5, 60 * 60, 3);
stream.Close();
}
}
HostManager1.UpdateHostC(null, fileType, fileHref, fileMd5, hosts);
//IList<string> hostNumberList = new List<string>();
//foreach (Host h in hosts)
//{
// hostNumberList.Add(h.RoomNumber);
//}
return Json(new { IsSuccess = true, Message = "shengji" });
}
catch (Exception ex)
{
logger.Error(ex.Message);
logger.Error(ex.StackTrace);
return Json(new { IsSuccess = false, Message = ex.Message });
}
}
public ActionResult ShengJI_NEW(IList<int> idList, string fileName, IHostManager HostManager1, int RoomTypeID)
{
try
{

View File

@@ -7474,7 +7474,7 @@ namespace WebSite.Controllers
var action = item.action;
var name = item.applianceName;
var value = item.value;
var q1 = hostModals.Where(A => A.Modal.Type == DeviceType.AirConditioner && A.Modal.Name.Equals(name));
var q1 = hostModals.Where(A => A.Modal.Type == DeviceType.AirConditioner && (A.Modal.Name.Equals(name)||A.Modal.AliasName.Equals(name)));
foreach (var item_inter in q1)
{
int status = 0;

View File

@@ -0,0 +1,406 @@
# 物联网系统项目分析报告
## 1. 项目概览
本项目是一个基于UDP通信的物联网系统主要用于酒店客房RCURoom Control Unit主机的管理和控制。系统架构较为古老经历了从纯SQLServer存储到SQLServer+Redis混合存储的演进过程。
### 1.1 核心功能
- RCU主机通信与管理
- 客房设备状态监控与控制(灯光、空调、窗帘等)
- 房态管理(入住、退房、待租、空房)
- 智能语音设备集成(小度、天猫精灵等)
- 能源管理与统计
- 故障报警与处理
### 1.2 技术栈
- **开发语言**C#
- **通信协议**UDP
- **数据存储**SQLServer + Redis
- **ORM框架**NHibernate
- **缓存**Redis + C# MemoryCache
- **消息队列**Redis Stream
## 2. 系统架构分析
### 2.1 架构分层
系统采用典型的分层架构设计,主要包含以下层次:
1. **通信层**基于UDP协议的RCU主机通信
2. **业务逻辑层**:服务层和控制器
3. **数据访问层**基于NHibernate的数据库操作
4. **存储层**SQLServer和Redis混合存储
5. **表现层**Web API和管理界面
### 2.2 核心流程图
```mermaid
sequenceDiagram
participant RCU as RCU主机
participant HostServer as UDP通信服务器
participant Redis as Redis缓存
participant SQLServer as SQLServer数据库
participant Service as 业务服务层
participant API as Web API
RCU->>HostServer: 上报设备状态
HostServer->>Redis: 缓存实时状态
HostServer->>Service: 处理业务逻辑
Service->>SQLServer: 持久化数据
API->>Redis: 查询设备状态
API->>Service: 下发控制命令
Service->>HostServer: 转发控制命令
HostServer->>RCU: 控制设备
```
### 2.3 模块依赖关系
- **Common**:公共工具类和辅助方法
- **RCUHost**UDP通信核心模块
- **Service**:业务逻辑服务
- **Dao**:数据访问层
- **Domain**:领域模型
- **WebSite**Web API和管理界面
- **CommonEntity**:公共实体类
## 3. 核心模块分析
### 3.1 UDP通信模块
UDP通信是系统的核心负责与RCU主机的双向通信。
#### 3.1.1 主要功能
- 监听UDP端口3339接收RCU上报数据
- 解析UDP数据包分发到对应接收器
- 封装并发送控制命令到RCU主机
- 心跳检测与设备状态管理
- Redis Stream处理高并发数据
#### 3.1.2 关键代码
```csharp
// UDP服务器启动
public void Start()
{
udpClient = new UdpClient(3339);
uint IOC_IN = 0x80000000;
uint IOC_VENDOR = 0x18000000;
uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
udpClient.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
udpClient.BeginReceive(ReceiveCallback, new UdpState(udpClient));
}
// 数据接收回调
private void ReceiveCallback(IAsyncResult ar)
{
UdpState state = ar.AsyncState as UdpState;
IPEndPoint remoteEP111 = new IPEndPoint(IPAddress.Any, 0);
byte[] receiveBuffer111 = state.UdpClient.EndReceive(ar, ref remoteEP111);
if (receiveBuffer111.Length > 0)
{
ReceiverContext context = new ReceiverContext(receiveBuffer111, remoteEP111, GetNextCustomer());
// 处理数据...
}
}
```
### 3.2 房间状态管理模块
负责处理RCU主机上报的房间状态数据包括设备状态、房卡状态、门磁状态等。
#### 3.2.1 主要功能
- 解析并处理房间状态数据包
- 缓存设备状态到Redis
- 持久化状态到SQLServer
- 触发相关业务逻辑(如欢迎词、节能控制等)
#### 3.2.2 关键代码
```csharp
public override void Process(ReceiverContext context)
{
string HostNumberOnly = context.SystemHeader.Value.HostNumber.ToString();
string hotelcode = context.SystemHeader.Value.HostNumber.ToHotelCode().ToString();
// 获取主机信息
Host host = null;
string Key = CacheKey.HostInfo_Key_HostNumber + "_" + HostNumberOnly;
object obj = MemoryCacheHelper.Get(Key);
if (obj != null)
{
host = obj as Host;
}
else
{
Host host_O = HostRepository.GetByHostNumber(HostNumberOnly);
MemoryCacheHelper.SlideSet(Key, host_O);
host = host_O;
}
// 处理设备状态
using (MemoryStream stream = new MemoryStream(context.Data, offset, length))
{
bool isTriggerWelcomeMsg = true;
Status status = DecodeRoomStatus_NEW(stream, HostNumberOnly, out isTriggerWelcomeMsg, host.SysHotel.Code, host.RoomNumber);
if (status != null)
{
// 缓存状态到Redis
CSRedisCacheHelper.Forever<ushort>(k1, context.SystemHeader.Value.FrameNo);
CSRedisCacheHelper.Forever<bool>(k2, !status.SysLock);
CSRedisCacheHelper.Forever<float>(k3, status.ElecQty);
CSRedisCacheHelper.Forever<byte>(k4, status.HostTemp);
// 处理设备状态
if (status.Devices != null && status.Devices.Count > 0)
{
ProcessModal_NEW_NEW(host, status.Devices, isTriggerWelcomeMsg, context.MessageID, context.IsMonitor);
}
}
}
}
```
### 3.3 主机管理模块
负责RCU主机的基本信息管理、升级、配置等操作。
#### 3.3.1 主要功能
- 主机信息管理(添加、修改、删除)
- 主机固件升级
- 主机配置管理
- 主机状态监控
#### 3.3.2 关键代码
```csharp
public override Host Get(object id)
{
var host = base.Get(id);
if (host != null)
{
host.Status = Common.CSRedisCacheHelper.Contains(host.HostNumber, host.MAC);
}
return host;
}
public void ChangeRoomStatus(Host host, RoomStatus roomStatus, string wxValidate)
{
// 更新主机房态
host.RoomStatus = roomStatus;
CurrentRepository.Update(host);
// 处理相关业务逻辑
if (roomStatus.ID == 2 || roomStatus.ID == 8) // 开房和退房时重置
{
// 重置智能设备
if (!string.IsNullOrEmpty(host.XiaoDuCUID))
{
// 重置小度
}
}
}
```
## 4. 数据存储分析
### 4.1 SQLServer存储
#### 4.1.1 主要存储内容
- 主机基本信息tb_Hosts
- 房间类型tb_RoomTypes
- 设备模板tb_Modals
- 主机设备关联tb_HostModal
- 房态信息tb_RoomStatus
- 历史记录(如房卡记录、故障记录等)
#### 4.1.2 数据访问方式
- NHibernate ORM框架
- 原生SQL查询
- 存储过程调用
### 4.2 Redis存储
#### 4.2.1 主要存储内容
- 实时设备状态
- 会话信息
- 监控日志
- 缓存热点数据
- 消息队列Redis Stream
#### 4.2.2 数据访问方式
- CSRedisClient客户端
- 发布/订阅机制
- Stream数据结构
#### 4.2.3 缓存键设计
```csharp
public class CacheKey
{
public static string HostModalStatus_Prefix = "HostModalStatusReceiver";
public static string RoomStatus_Prefix = "RoomStatusReceiver";
public static string HostInfo_Key_HostNumber = "HostInfo_Key_HostNumber";
public static string HostFrameNo = "HostFrameNo";
public static string PowerSupply = "PowerSupply";
public static string LockVoltage = "LockVoltage";
public static string HostTemp = "HostTemp";
// 更多缓存键...
}
```
### 4.3 存储优化分析
当前系统已经开始使用Redis缓存部分设备状态但仍存在以下问题
1. **缓存策略不完善**:部分热点数据未缓存
2. **数据同步问题**SQLServer与Redis数据同步机制不够健壮
3. **缓存过期策略**:部分缓存项过期时间设置不合理
4. **Redis使用效率**未充分利用Redis的高级特性
## 5. 性能优化建议
### 5.1 UDP通信优化
1. **数据包处理优化**
- 减少数据包解析时间
- 优化线程池使用
- 增加数据包校验机制
2. **并发处理优化**
- 合理设置并发线程数
- 使用异步非阻塞IO
- 优化Redis Stream消费速度
### 5.2 数据存储优化
1. **Redis缓存优化**
- 扩大Redis缓存范围将更多热点数据纳入缓存
- 优化缓存键设计,提高缓存命中率
- 合理设置缓存过期时间
- 使用Redis Pipeline减少网络往返时间
2. **SQLServer优化**
- 优化数据库索引
- 减少大表查询
- 优化存储过程性能
- 考虑使用读写分离
3. **数据同步优化**
- 建立更可靠的SQLServer与Redis数据同步机制
- 使用消息队列确保数据一致性
- 实现数据同步监控和故障恢复
### 5.3 代码质量优化
1. **内存管理**
- 减少大对象分配
- 使用对象池减少GC压力
- 优化Stream处理
2. **线程管理**
- 减少线程创建开销
- 合理使用线程池
- 避免线程死锁和竞态条件
3. **异常处理**
- 优化异常处理逻辑
- 减少异常抛出频率
- 增加异常监控和告警
### 5.4 架构优化
1. **微服务拆分**
- 将核心功能拆分为独立微服务
- 减少服务间耦合
- 提高系统可扩展性
2. **消息队列引入**
- 使用专业消息队列替代Redis Stream
- 提高消息处理可靠性
- 实现消息持久化
3. **API优化**
- 实现RESTful API设计
- 增加API版本控制
- 优化API响应时间
## 6. 代码质量分析
### 6.1 代码结构
- **优点**:模块划分清晰,职责明确
- **缺点**:部分模块代码过于庞大,可维护性差
### 6.2 命名规范
- **优点**大部分代码遵循C#命名规范
- **缺点**:部分变量和方法命名不够清晰
### 6.3 注释文档
- **优点**:关键方法有注释说明
- **缺点**:部分复杂逻辑缺少注释,文档不完善
### 6.4 异常处理
- **优点**:大部分异常有捕获和处理
- **缺点**:部分异常处理过于简单,缺少详细日志
### 6.5 代码重复
- **问题**:存在较多代码重复现象
- **建议**:提取公共方法,减少代码冗余
## 7. 总结与建议
### 7.1 系统优势
1. **成熟稳定**:系统经过长期运行验证,核心功能稳定可靠
2. **功能完善**涵盖了酒店RCU管理的核心功能
3. **扩展性好**:支持多种智能设备集成
4. **实时性强**基于UDP的通信机制保证了实时性
### 7.2 系统劣势
1. **技术栈陈旧**:使用的技术较为古老,缺乏现代化特性
2. **性能瓶颈**:在高并发场景下存在性能瓶颈
3. **代码质量**:部分代码质量较差,维护成本高
4. **存储优化不足**Redis使用不够充分未完全发挥其优势
### 7.3 改进建议
1. **技术栈升级**
- 考虑使用.NET Core/5+替代传统.NET Framework
- 引入现代化的缓存和消息队列解决方案
2. **架构重构**
- 采用微服务架构,提高系统可扩展性
- 引入容器化部署,简化运维
3. **存储优化**
- 进一步扩大Redis的使用范围
- 优化缓存策略,提高缓存命中率
- 建立更可靠的数据同步机制
4. **代码质量提升**
- 进行代码重构,提高可维护性
- 引入代码规范检查工具
- 完善单元测试和集成测试
5. **监控与告警**
- 建立完善的系统监控体系
- 实现智能告警机制
- 提供可视化的系统运行状态 dashboard
### 7.4 结论
本系统作为一个运行多年的物联网系统,虽然架构较为古老,但核心功能稳定可靠。通过合理的优化和重构,可以显著提升系统性能和可维护性,延长系统生命周期。建议在保持系统稳定性的前提下,逐步推进技术升级和架构优化,以适应不断增长的业务需求和技术发展趋势。

View File

@@ -0,0 +1,327 @@
# 物联网系统网页访问性能问题分析报告
## 1. 问题概述
本项目在用户量较大时IIS应用程序池频繁重启主要原因与网页访问功能使用的复杂存储过程查询有关。系统采用SQLServer存储过程进行后台数据查询在高并发场景下性能急剧下降导致应用程序池崩溃重启。
## 2. 核心问题分析
### 2.1 存储过程使用情况
系统在以下关键功能中使用了复杂存储过程:
1. **回路状态记录查询**`QueryHostModalRecords`
2. **回路异常记录查询**`QueryHostAbnormalRecords`
3. **能源统计查询**`QueryEnergyStatistics`
4. **房态查询**`QueryRoomRentState``QueryRoomState`
### 2.2 性能瓶颈分析
通过代码分析,发现以下性能瓶颈:
1. **无缓存机制**所有查询直接访问数据库没有使用Redis或内存缓存
2. **同步处理**:所有请求采用同步方式处理,没有实现异步操作
3. **资源管理不当**:数据库连接管理不规范,可能导致连接泄漏
4. **缺乏请求限流**:没有实现请求频率限制,高并发时系统压力过大
5. **存储过程复杂度**:存储过程逻辑复杂,可能包含大量数据处理和计算
6. **分页参数固定**:如`LoadHostModalRecords`方法中固定使用`page=1, rows=100`,可能查询过多数据
### 2.3 代码问题分析
1. **OverviewController.cs**
- 方法`LoadHostModalRecords`固定使用`page=1, rows=100`,忽略了传入的分页参数
- 代码中存在"严重怀疑这里的调用有问题"的注释,表明开发人员已意识到问题
2. **OverviewRepository.cs**
- 所有数据库操作采用同步方式
- 没有实现查询结果缓存
- 资源管理不规范,如数据库连接的处理
3. **Global.asax.cs**
- 虽然实现了全局异常捕获,但没有针对性能问题的特殊处理
- 没有实现请求限流机制
## 3. 优化建议
### 3.1 存储过程优化
1. **存储过程重构**
- 简化存储过程逻辑,将复杂计算移至应用层
- 添加适当的索引,优化查询性能
- 实现存储过程的参数化避免SQL注入风险
2. **查询优化**
- 合理使用分页,避免一次性查询大量数据
- 添加查询条件过滤,减少返回数据量
- 优化JOIN操作减少表关联复杂度
### 3.2 缓存机制引入
1. **Redis缓存**
- 对高频查询结果进行缓存,如房态信息、设备状态等
- 设置合理的缓存过期时间,确保数据及时性
- 实现缓存预热,系统启动时加载常用数据
2. **内存缓存**
- 对短期高频访问的数据使用MemoryCache
- 实现缓存依赖,确保数据一致性
### 3.3 异步处理
1. **异步API实现**
- 将控制器方法改为异步方法,使用`async/await`
- 实现异步数据库操作,减少线程阻塞
2. **并行处理**
- 对独立的查询操作使用并行处理,提高响应速度
- 合理控制并发度,避免系统资源耗尽
### 3.4 资源管理优化
1. **数据库连接管理**
- 使用连接池,避免频繁创建和销毁连接
- 确保连接正确关闭,避免连接泄漏
2. **内存管理**
- 减少大对象分配避免GC压力
- 使用对象池,重用频繁创建的对象
### 3.5 请求限流与保护
1. **请求限流**
- 实现基于IP的请求频率限制
- 对高频API添加令牌桶限流
2. **熔断机制**
- 实现服务熔断,避免级联失败
- 添加超时设置,防止请求长时间阻塞
3. **监控告警**
- 增加性能监控,实时跟踪系统状态
- 设置告警阈值,及时发现性能异常
### 3.6 架构优化
1. **微服务拆分**
- 将报表查询等重负载功能拆分为独立服务
- 减少服务间耦合,提高系统可扩展性
2. **读写分离**
- 实现数据库读写分离,减轻主库压力
- 报表查询等操作定向到从库
3. **消息队列**
- 使用消息队列处理异步任务,如统计计算等
- 提高系统的可靠性和弹性
## 4. 具体优化方案
### 4.1 紧急优化措施(短期)
1. **添加缓存机制**
- 对房态查询结果使用Redis缓存过期时间5分钟
- 对设备状态查询结果使用内存缓存过期时间30秒
2. **优化存储过程调用**
- 修正`LoadHostModalRecords`方法,使用正确的分页参数
- 添加参数验证,避免无效查询
3. **实现请求限流**
-`Application_BeginRequest`中添加请求频率限制
- 对高频API如`LoadHostModalRecords`设置更严格的限制
### 4.2 中期优化措施
1. **存储过程重构**
- 简化`QueryHostModalRecords`存储过程逻辑
- 添加适当的索引和查询优化
2. **异步处理实现**
- 将控制器方法改为异步方法
- 实现异步数据库操作
3. **缓存策略完善**
- 设计合理的缓存键结构
- 实现缓存预热和缓存更新机制
### 4.3 长期优化措施
1. **微服务架构改造**
- 将报表功能拆分为独立微服务
- 实现服务发现和负载均衡
2. **数据库架构优化**
- 实现读写分离
- 考虑使用时序数据库存储历史数据
3. **监控系统完善**
- 实现分布式追踪
- 建立性能基准和告警机制
## 5. 代码优化建议
### 5.1 OverviewController.cs 优化
```csharp
// 优化前
public ActionResult LoadHostModalRecords(int? page, int? rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds)
{
long total = 0;
var list = OverviewManager.LoadHostModalRecords(out total, 1, 100, order, sort, roomNumber, startTime, endTime, deviceType, modalIds, CurrentHotelID);
var result = new { total = total, rows = list };
return Content(JsonConvert.SerializeObject(result, new DataTableConverter()));
}
// 优化后
public async Task<ActionResult> LoadHostModalRecordsAsync(int? page, int? rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds)
{
// 验证分页参数
int pageNum = page ?? 1;
int pageSize = rows ?? 20;
if (pageSize > 100) pageSize = 100; // 限制最大页大小
// 生成缓存键
string cacheKey = $"HostModalRecords_{CurrentHotelID}_{pageNum}_{pageSize}_{order}_{sort}_{roomNumber}_{startTime}_{endTime}_{deviceType?.ToString()}_{modalIds}";
// 尝试从缓存获取
var cachedResult = CSRedisCacheHelper.Get<object>(cacheKey);
if (cachedResult != null)
{
return Json(cachedResult, JsonRequestBehavior.AllowGet);
}
// 异步查询数据
long total = 0;
var list = await Task.Run(() =>
OverviewManager.LoadHostModalRecords(out total, pageNum, pageSize, order, sort, roomNumber, startTime, endTime, deviceType, modalIds, CurrentHotelID)
);
var result = new { total = total, rows = list };
// 缓存结果过期时间5分钟
CSRedisCacheHelper.Set(cacheKey, result, 300);
return Content(JsonConvert.SerializeObject(result, new DataTableConverter()));
}
```
### 5.2 OverviewRepository.cs 优化
```csharp
// 优化前
public DataTable LoadHostModalRecords(out long total, int page, int rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds, int hotelID)
{
total = 0;
IDbCommand cmd = Session.Connection.CreateCommand();
cmd.CommandText = "QueryHostModalRecords";
cmd.CommandType = CommandType.StoredProcedure;
// 参数设置...
IDbDataAdapter da = new SqlDataAdapter(cmd as SqlCommand);
DataSet ds = new DataSet();
da.Fill(ds);
total = ds.Tables[0].Rows.Count;
return ds.Tables[0];
}
// 优化后
public async Task<DataTable> LoadHostModalRecordsAsync(out long total, int page, int rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds, int hotelID)
{
total = 0;
// 使用异步数据库操作
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = "QueryHostModalRecords";
command.CommandType = CommandType.StoredProcedure;
// 参数设置...
using (var adapter = new SqlDataAdapter(command as SqlCommand))
{
DataSet ds = new DataSet();
// 使用异步填充
await Task.Run(() => adapter.Fill(ds));
total = ds.Tables[0].Rows.Count;
return ds.Tables[0];
}
}
}
}
```
### 5.3 Global.asax.cs 优化
```csharp
// 添加请求限流和缓存管理
protected void Application_BeginRequest(object sender, EventArgs e)
{
try
{
var ip = Request.UserHostAddress;
var url = Request.Url.AbsolutePath.ToLower();
// 定期清理恶意IP记录
var nowUtc = DateTime.UtcNow;
CleanupIpRecordIfNeeded(nowUtc);
// 检查请求频率
if (IsHighFrequencyRequest(ip))
{
logger.Error("高频请求IP:" + ip + " URL:" + url);
Response.StatusCode = 429; // Too Many Requests
Response.Write("请求过于频繁,请稍后再试");
Context.ApplicationInstance.CompleteRequest();
return;
}
// 恶意路径检查...
}
catch (Exception ex)
{
logger.Error("请求处理出错:" + ex.Message);
}
}
private bool IsHighFrequencyRequest(string ip)
{
var now = DateTime.UtcNow;
var info = _ipRequests.GetOrAdd(ip, _ => new RequestInfo());
lock (info)
{
info.RequestCount++;
info.LastRequest = now;
// 如果10秒内超过30次请求认为是高频请求
if (info.RequestCount > 30 && (now - info.FirstRequest).TotalSeconds < 10)
{
return true;
}
// 超过统计窗口则重置计数窗口
if ((now - info.FirstRequest).TotalSeconds >= 10)
{
info.FirstRequest = now;
info.RequestCount = 1;
}
}
return false;
}
```
## 5. 预期优化效果
通过实施上述优化方案,预计可以达到以下效果:
1. **响应时间提升**:高频查询响应时间从秒级降至毫秒级
2. **并发能力提升**系统并发处理能力提升5-10倍
3. **稳定性增强**IIS应用程序池不再频繁重启
4. **资源利用率提高**CPU和内存使用更加合理
5. **用户体验改善**:页面加载速度显著提升
## 6. 结论
本项目网页访问性能问题的根本原因在于复杂存储过程的不合理使用和缺乏有效的缓存机制。通过实施上述优化方案可以显著提高系统的性能和稳定性解决IIS应用程序池频繁重启的问题。
建议按照"紧急优化措施→中期优化措施→长期优化措施"的顺序逐步实施,确保系统在优化过程中的稳定性。同时,建立完善的性能监控体系,及时发现和解决新出现的性能问题。