You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

516 lines
22KB

  1. using DotNetty.Buffers;
  2. using DotNetty.Transport.Channels;
  3. using Microsoft.Extensions.Logging;
  4. using NearCardAttendance.Common.helper;
  5. using NearCardAttendance.Service.TcpServer.Mapper;
  6. using NearCardAttendance.Service.TcpServer.Protocol;
  7. using Newtonsoft.Json;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Threading.Channels;
  13. using System.Threading.Tasks;
  14. using NearCardAttendance.Model;
  15. using Microsoft.Extensions.Options;
  16. using TelpoDataService.Util.Entities.GpsCard;
  17. using TelpoDataService.Util;
  18. using TelpoDataService.Util.Clients;
  19. using TelpoDataService.Util.Models;
  20. using TelpoDataService.Util.QueryObjects;
  21. using NearCardAttendance.Service.MessageQueue;
  22. using NearCardAttendance.Service.MessageQueue.Model;
  23. using Newtonsoft.Json.Linq;
  24. using DotNetty.Common.Concurrency;
  25. namespace NearCardAttendance.Service.TcpServer.Handler
  26. {
  27. public class RegisterHandler : ChannelHandlerAdapter
  28. {
  29. private readonly ILogger<RegisterHandler> _logger;
  30. private readonly HttpHelper _httpHelper = default!;
  31. private readonly MqProcessLogic _serviceMqProcess;
  32. //private readonly IDisposable _loggerScope = default!;
  33. private readonly ServiceConfig _configService;
  34. private readonly GpsCardAccessorClient<GpsDeviceConfig> _deviceConfigApiClient;
  35. private readonly ScheduleResendManager _managerScheduleResend;
  36. //private readonly TcpClientsManager _managerTcpClients;
  37. //private readonly ScheduleResendManager _managerScheduleResend;
  38. public RegisterHandler(ILogger<RegisterHandler> logger, GpsCardAccessorClient<GpsDeviceConfig> deviceConfigApiClient,
  39. ScheduleResendManager scheduleResendManager, HttpHelper httpHelper, IOptions<ServiceConfig> configService, MqProcessLogic serviceMqProcess)
  40. {
  41. _logger = logger;
  42. _httpHelper = httpHelper;
  43. _configService = configService.Value;
  44. _deviceConfigApiClient= deviceConfigApiClient;
  45. _serviceMqProcess = serviceMqProcess;
  46. _managerScheduleResend = scheduleResendManager;
  47. }
  48. public override void ChannelActive(IChannelHandlerContext context)
  49. {
  50. base.ChannelActive(context); // Pass the event to the next handler
  51. }
  52. public override async void ChannelInactive(IChannelHandlerContext context)
  53. {
  54. try
  55. {
  56. await Task.CompletedTask;
  57. }
  58. catch (Exception ex)
  59. {
  60. _logger.LogInformation($"{nameof(RegisterHandler)} --- {nameof(ChannelInactive)} 出错\n{ex.Message}\n{ex.StackTrace}");
  61. }
  62. }
  63. public override void HandlerRemoved(IChannelHandlerContext context)
  64. {
  65. base.HandlerRemoved(context);
  66. }
  67. public override async void ChannelRead(IChannelHandlerContext context, object message)
  68. {
  69. if (message is ProtocolParser parser)
  70. {
  71. try
  72. {
  73. using (_logger.BeginScope(new Dictionary<string, object> { ["RequestId"] = parser.SeqNo }))
  74. {
  75. #region 启动keepalived业务
  76. // _managerScheduleResend.AddOrUpdate(context.Channel.Id.AsShortText(),(context.Channel.Id.AsShortText(),IScheduledTask));
  77. // 超过3分钟无任何数据就断链
  78. _ = _managerScheduleResend.AddOrUpdate(context.Channel.Id.AsShortText(), key =>
  79. {
  80. // 创建新的 IScheduledTask
  81. return context.Channel.EventLoop.Schedule(async () =>
  82. {
  83. // var channel = _managerTcpClients.FirstOrDefault(x => x.Key.Equals(imei)).Value;
  84. //await channel.CloseAsync();
  85. await context.Channel.CloseAsync();
  86. _logger.LogInformation($"3分钟超时无数据,主动断链");
  87. // 执行任务逻辑
  88. },
  89. #if DEBUG
  90. TimeSpan.FromSeconds(99999)
  91. #else
  92. TimeSpan.FromSeconds(180)
  93. #endif
  94. );
  95. },
  96. (oldKey, oldValue) =>
  97. {
  98. // 更新现有的 IScheduledTask
  99. oldValue.Cancel(); // 取消旧的任务
  100. return context.Channel.EventLoop.Schedule(async () =>
  101. {
  102. await context.Channel.CloseAsync();
  103. _logger.LogInformation($"3分钟超时无数据,主动断链");
  104. // 执行任务逻辑
  105. },
  106. #if DEBUG
  107. TimeSpan.FromSeconds(99999)
  108. #else
  109. TimeSpan.FromSeconds(180)
  110. #endif
  111. );
  112. });
  113. #endregion
  114. //认证
  115. //if (parser.FuncNo.Equals("10"))
  116. //{
  117. // #region 认证业务
  118. // // PHONE_AUTHEN
  119. // ProtocolWrapper phoneAuthWrapper = new(parser.FuncNo, parser.SeqNo, "1");
  120. // await SendToTcpClientAsync(phoneAuthWrapper, context.Channel);
  121. // // ABT_STATUS 延时3秒 给终端同步时间
  122. // await context.Channel.EventLoop.Schedule(async () =>
  123. // {
  124. // ProtocolWrapper abtStatusWrapper = new("82", parser.SeqNo, DateTime.Now.ToString("yyyyMMddHHmmss"));
  125. // await SendToTcpClientAsync(abtStatusWrapper, context.Channel);
  126. // },TimeSpan.FromSeconds(3));
  127. // #endregion
  128. //}
  129. //else if (parser.FuncNo.Equals("82"))
  130. //{
  131. // // 认证成功
  132. // _logger.LogInformation($"认证成功{parser.Data}.");
  133. //}
  134. //// 心跳
  135. //else if (parser.FuncNo.Equals("05"))
  136. //{
  137. // ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo,"");
  138. // await SendToTcpClientAsync(stdtSignRecsWrapper, context.Channel);
  139. //} // STDT_SIGN_RECS
  140. //else if (parser.FuncNo.Equals("04"))
  141. //{
  142. // // 回应设备
  143. // ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo,"1");
  144. // await SendToTcpClientAsync(stdtSignRecsWrapper, context.Channel);
  145. // // 刷卡考勤信息,需要推送给第三方平台
  146. // //var url = "";
  147. // //await _httpHelper.HttpToPostAsync(url, new object());
  148. //}
  149. //// STDT_SCHOOL_RECS
  150. //else if (parser.FuncNo.Equals("08"))
  151. //{
  152. // _logger.LogInformation($"接受STDT_SCHOOL_RECS 08 成功{parser.Data}.");
  153. // ProtocolWrapper stdtSchoolRecsWrapper = new(parser.FuncNo, parser.SeqNo, "1");
  154. // await SendToTcpClientAsync(stdtSchoolRecsWrapper, context.Channel);
  155. //}
  156. //switch (parser.FuncNo)
  157. //{
  158. // case "10":
  159. // break;
  160. // case "82":
  161. // break;
  162. // default:
  163. // break;
  164. //}
  165. switch (parser.FuncNo)
  166. {
  167. case "10":
  168. await HandleAuthenticationAsync(parser, context.Channel);
  169. break;
  170. case "82":
  171. await HandleAuthenticationSuccessAsync(parser, context.Channel);
  172. break;
  173. case "05":
  174. //ChannelKeepAlived(context, parser);
  175. await SendHeartbeatAsync(parser, context.Channel);
  176. break;
  177. case "04":
  178. await HandleSignRecsAsync(parser, context.Channel);
  179. break;
  180. case "08":
  181. await HandleStdtSchoolRecsAsync(parser, context.Channel);
  182. break;
  183. default:
  184. _logger.LogInformation($"未处理业务,func_no:{parser.FuncNo},seq_no{parser.SeqNo},data:{parser.Data}");
  185. break;
  186. }
  187. }
  188. }
  189. catch (Exception ex)
  190. {
  191. _logger.LogInformation($"{nameof(RegisterHandler)} --- {nameof(ChannelRead)} 处理消息 {parser.SeqNo} 出错\n{ex.Message}\n{ex.StackTrace}");
  192. }
  193. }
  194. }
  195. /// <summary>
  196. /// 发送到TCP客户端
  197. /// </summary>
  198. /// <param name="wrapper"></param>
  199. /// <param name="channel"></param>
  200. private async Task SendToTcpClientAsync(ProtocolWrapper wrapper, IChannel channel)
  201. {
  202. string protocolMsg = wrapper.GenerateProtocolString();
  203. // 发送protocolMsg到tcp客户端
  204. await channel
  205. .WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(protocolMsg)))
  206. .ContinueWith(Action => {
  207. _logger.LogInformation($"{nameof(RegisterHandler)} -- {nameof(SendToTcpClientAsync)} -- 下发设备内容:\n{protocolMsg}");
  208. });
  209. }
  210. /// <summary>
  211. /// 认证业务
  212. /// </summary>
  213. /// <param name="parser"></param>
  214. /// <param name="channel"></param>
  215. /// <returns></returns>
  216. private async Task HandleAuthenticationAsync(ProtocolParser parser, IChannel channel)
  217. {
  218. ProtocolWrapper phoneAuthWrapper = new(parser.FuncNo, parser.SeqNo, "1");
  219. await SendToTcpClientAsync(phoneAuthWrapper, channel);
  220. await channel.EventLoop.Schedule(async () =>
  221. {
  222. ProtocolWrapper abtStatusWrapper = new("82", parser.SeqNo, DateTime.Now.ToString("yyyyMMddHHmmss"));
  223. await SendToTcpClientAsync(abtStatusWrapper, channel);
  224. }, TimeSpan.FromSeconds(3));
  225. }
  226. /// <summary>
  227. /// 认证业务成功
  228. /// </summary>
  229. /// <param name="parser"></param>
  230. /// <param name="channel"></param>
  231. /// <returns></returns>
  232. private async Task HandleAuthenticationSuccessAsync(ProtocolParser parser, IChannel channel)
  233. {
  234. _logger.LogInformation($"认证成功: func_no:{parser.FuncNo},seq_no{parser.SeqNo},data:{parser.Data}.");
  235. await Task.CompletedTask;
  236. }
  237. /// <summary>
  238. /// CONNECT_STATUS 网络连接状态查询[取值:05] (心跳业务)
  239. /// </summary>
  240. /// <param name="parser"></param>
  241. /// <param name="channel"></param>
  242. /// <returns></returns>
  243. private async Task SendHeartbeatAsync(ProtocolParser parser, IChannel channel)
  244. {
  245. ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo, "");
  246. await SendToTcpClientAsync(stdtSignRecsWrapper, channel);
  247. #region 启动keepalived业务
  248. // // _managerScheduleResend.AddOrUpdate(context.Channel.Id.AsShortText(),(context.Channel.Id.AsShortText(),IScheduledTask));
  249. // // 超过3分钟无任何数据就断链
  250. // _ = _managerScheduleResend.AddOrUpdate(channel.Id.AsShortText(), key =>
  251. // {
  252. // // 创建新的 IScheduledTask
  253. // return channel.EventLoop.Schedule(async () =>
  254. // {
  255. // // var channel = _managerTcpClients.FirstOrDefault(x => x.Key.Equals(imei)).Value;
  256. // //await channel.CloseAsync();
  257. // await channel.CloseAsync();
  258. // // 执行任务逻辑
  259. // },
  260. //#if DEBUG
  261. // TimeSpan.FromSeconds(999999)
  262. //#else
  263. // TimeSpan.FromSeconds(180)
  264. //#endif
  265. // );
  266. // },
  267. // (oldKey, oldValue) =>
  268. // {
  269. // // 更新现有的 IScheduledTask
  270. // oldValue.Cancel(); // 取消旧的任务
  271. // return channel.EventLoop.Schedule(async () =>
  272. // {
  273. // await channel.CloseAsync();
  274. // // 执行任务逻辑
  275. // },
  276. //#if DEBUG
  277. // TimeSpan.FromSeconds(999999)
  278. //#else
  279. // TimeSpan.FromSeconds(180)
  280. //#endif
  281. // );
  282. // });
  283. #endregion
  284. }
  285. private void ChannelKeepAlived(IChannelHandlerContext context, ProtocolParser parser)
  286. {
  287. if (_managerScheduleResend.TryRemove(context.Channel.Id.AsShortText(), out IScheduledTask? scheduledTask))
  288. {
  289. scheduledTask.Cancel();
  290. scheduledTask = null;
  291. _logger.LogInformation($"{nameof(ChannelKeepAlived)} --{parser.SeqNo} -- {context.Channel} 活跃...");
  292. }
  293. else
  294. {
  295. Console.WriteLine("No Task");
  296. }
  297. }
  298. /// <summary>
  299. /// STDT_SIGN_RECS 学生签到记录[取值:04]
  300. /// </summary>
  301. /// <param name="parser"></param>
  302. /// <param name="channel"></param>
  303. /// <returns></returns>
  304. private async Task HandleSignRecsAsync(ProtocolParser parser, IChannel channel)
  305. {
  306. ProtocolWrapper stdtSignRecsWrapper = new(parser.FuncNo, parser.SeqNo, "1");
  307. await SendToTcpClientAsync(stdtSignRecsWrapper, channel);
  308. var deviceId = parser.Data.Substring(0, 18).TrimEnd();
  309. var cardId= parser.Data.Substring(18,18).TrimEnd();
  310. var studentId = parser.Data.Substring(36,18).TrimEnd();
  311. var startTime = parser.Data.Substring(54, 14).TrimEnd();
  312. // Push card attendance information to third-party platform
  313. //var url = "https://midplat.xinhualeyu.com/dev-api/user/electronicCardAttendance/receiveTbAttendanceRecord";
  314. #region IC卡转换为IMEI
  315. var config= await _deviceConfigApiClient.GetFirstAsync(new GeneralParam
  316. {
  317. Filters = new List<QueryFilterCondition>
  318. {
  319. new QueryFilterCondition
  320. {
  321. Key=nameof(GpsDeviceConfig.IdNumber),
  322. Value=cardId,
  323. ValueType=QueryValueTypeEnum.String,
  324. Operator=QueryOperatorEnum.Equal
  325. }
  326. },
  327. OrderBys=new List<OrderByCondition>
  328. {
  329. new OrderByCondition
  330. {
  331. IsDesc=true,
  332. Key=nameof(GpsDeviceConfig.LastUpdate),
  333. }
  334. }
  335. }, new RequestHeader { RequestId = parser.SeqNo }).ConfigureAwait(false);
  336. #endregion
  337. if (config!=null)
  338. {
  339. //var url = _configService.XinHuaLeYuUrl;
  340. var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord";
  341. var data = new
  342. {
  343. attendanceStatus = 2, //考勤状态: 0.进 1.出 2.未知
  344. attendanceTime = DateTime.ParseExact(startTime, "yyyyMMddHHmmss", null).ToString("yyyy-MM-dd HH:mm:ss"),
  345. imei = config.Imei
  346. };
  347. var eventData = new EventData
  348. {
  349. TopicName = "topics.storage.near_card_attendance",
  350. MessageId = $"{config.Imei}-{parser.SeqNo}-{Guid.NewGuid().ToString("N")[^4..]}",
  351. IMEI = config.Imei,
  352. Content = JsonConvert.SerializeObject(data)
  353. };
  354. var res = await _httpHelper.HttpToPostAsync(url, data);
  355. if (!string.IsNullOrEmpty(res))
  356. {
  357. JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
  358. if ((bool)resObj["success"]!)
  359. {
  360. _logger.LogInformation($"{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
  361. }
  362. else
  363. {
  364. await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
  365. _logger.LogInformation($"HTTP 响应业务失败 {res},{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");
  366. }
  367. }
  368. else
  369. {
  370. await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
  371. _logger.LogInformation($"HTTP 响应超时,{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");
  372. }
  373. }
  374. else
  375. {
  376. _logger.LogInformation("找不到IC卡转换为IMEI的对应关系");
  377. }
  378. }
  379. /// <summary>
  380. /// STDT_SCHOOL_RECS 学生进校离校记录[取值:08]
  381. /// </summary>
  382. /// <param name="parser"></param>
  383. /// <param name="channel"></param>
  384. /// <returns></returns>
  385. private async Task HandleStdtSchoolRecsAsync(ProtocolParser parser, IChannel channel)
  386. {
  387. _logger.LogInformation($"Received STDT_SCHOOL_RECS successfully: {parser.Data}.");
  388. ProtocolWrapper stdtSchoolRecsWrapper = new(parser.FuncNo, parser.SeqNo, "1");
  389. await SendToTcpClientAsync(stdtSchoolRecsWrapper, channel);
  390. var deviceId = parser.Data.Substring(0, 18).TrimEnd();
  391. var cardId = parser.Data.Substring(18, 18).TrimEnd();
  392. var studentId = parser.Data.Substring(36, 18).TrimEnd();
  393. var startTime = parser.Data.Substring(54, 14).TrimEnd();
  394. var optType = parser.Data.Substring(68, 1).TrimEnd();
  395. #region IC卡转换为IMEI
  396. var config = await _deviceConfigApiClient.GetFirstAsync(new GeneralParam
  397. {
  398. Filters = new List<QueryFilterCondition>
  399. {
  400. new QueryFilterCondition
  401. {
  402. Key=nameof(GpsDeviceConfig.IdNumber),
  403. Value=cardId,
  404. ValueType=QueryValueTypeEnum.String,
  405. Operator=QueryOperatorEnum.Equal
  406. }
  407. },
  408. OrderBys = new List<OrderByCondition>
  409. {
  410. new OrderByCondition
  411. {
  412. IsDesc=true,
  413. Key=nameof(GpsDeviceConfig.LastUpdate),
  414. }
  415. }
  416. }, new RequestHeader { RequestId = parser.SeqNo }).ConfigureAwait(false);
  417. #endregion
  418. if (config != null)
  419. {
  420. //var url = "https://midplat.xinhualeyu.com/dev-api/user/electronicCardAttendance/receiveTbAttendanceRecord";
  421. #if DEBUG
  422. var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord1";
  423. #else
  424. var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord";
  425. #endif
  426. var data = new
  427. {
  428. attendanceStatus = int.TryParse(optType, out int type) ? type : 0,
  429. attendanceTime = DateTime.ParseExact(startTime, "yyyyMMddHHmmss", null).ToString("yyyy-MM-dd HH:mm:ss"),
  430. imei = config.Imei
  431. };
  432. var eventData = new EventData
  433. {
  434. TopicName = "topics.storage.near_card_attendance",
  435. MessageId = $"{config.Imei}-{parser.SeqNo}-{Guid.NewGuid().ToString("N")[^4..]}",
  436. IMEI = config.Imei,
  437. Content = JsonConvert.SerializeObject(data)
  438. };
  439. var res = await _httpHelper.HttpToPostAsync(url, data);
  440. if (!string.IsNullOrEmpty(res))
  441. {
  442. JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
  443. if ((bool)resObj["success"]!)
  444. {
  445. _logger.LogInformation($"{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
  446. }
  447. else
  448. {
  449. await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
  450. _logger.LogInformation($"HTTP 响应业务失败 {res},{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");
  451. }
  452. }
  453. else
  454. {
  455. await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
  456. _logger.LogInformation($"HTTP 响应超时,{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");
  457. }
  458. }
  459. else
  460. {
  461. _logger.LogInformation("找不到IC卡转换为IMEI的对应关系");
  462. }
  463. }
  464. }
  465. }