소스 검색

持久化到kafka

develop
H Vs 9 달 전
부모
커밋
f5a00a30f0
15개의 변경된 파일761개의 추가작업 그리고 15개의 파일을 삭제
  1. +17
    -0
      NearCardAttendance.Model/Attendance.cs
  2. +2
    -0
      NearCardAttendance.Model/ServiceConfig.cs
  3. +15
    -0
      NearCardAttendance.Service/MessageQueue/Kafka/IKafkaService.cs
  4. +141
    -0
      NearCardAttendance.Service/MessageQueue/Kafka/KafkaService.cs
  5. +14
    -0
      NearCardAttendance.Service/MessageQueue/Model/EventData.cs
  6. +13
    -0
      NearCardAttendance.Service/MessageQueue/Model/IMEIMessage.cs
  7. +24
    -0
      NearCardAttendance.Service/MessageQueue/MqProcessLogic.cs
  8. +1
    -0
      NearCardAttendance.Service/NearCardAttendance.Service.csproj
  9. +76
    -5
      NearCardAttendance.Service/TcpServer/Handler/RegisterHandler.cs
  10. +1
    -0
      NearCardAttendance.TcpServer/NearCardAttendance.TcpServer.csproj
  11. +4
    -1
      NearCardAttendance.TcpServer/Program.cs
  12. +159
    -5
      NearCardAttendance.TcpServer/Server.cs
  13. +288
    -0
      NearCardAttendance.TcpServer/Worker.cs
  14. +3
    -2
      NearCardAttendance.TcpServer/appsettings.debug.json
  15. +3
    -2
      NearCardAttendance.TcpServer/appsettings.test.json

+ 17
- 0
NearCardAttendance.Model/Attendance.cs 파일 보기

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NearCardAttendance.Model
{
public class Attendance
{
public int AttendanceStatus { get; set; }

public string AttendanceTime { get; set; } =default!;

public string Imei { get; set; } = default!;
}
}

+ 2
- 0
NearCardAttendance.Model/ServiceConfig.cs 파일 보기

@@ -9,5 +9,7 @@ namespace NearCardAttendance.Model
public class ServiceConfig
{
public string XinHuaLeYuUrl { get; set; } = default!;

public string KafkaServerAddress { get; set; } = default!;
}
}

+ 15
- 0
NearCardAttendance.Service/MessageQueue/Kafka/IKafkaService.cs 파일 보기

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NearCardAttendance.Service.MessageQueue.Kafka
{
public interface IKafkaService
{
Task PublishAsync<T>(string topicName, T message) where T : class;

Task SubscribeAsync<T>(IEnumerable<string> topics, Action<T> messageFunc, CancellationToken cancellationToken = default) where T : class;
}
}

+ 141
- 0
NearCardAttendance.Service/MessageQueue/Kafka/KafkaService.cs 파일 보기

@@ -0,0 +1,141 @@
using Confluent.Kafka;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using NearCardAttendance.Common;
using NearCardAttendance.Model;
using NearCardAttendance.Service.MessageQueue.Model;

namespace NearCardAttendance.Service.MessageQueue.Kafka
{
public class KafkaService : IKafkaService
{

private readonly ILogger<KafkaService> _logger;
private readonly ServiceConfig _configService;
public KafkaService(IOptions<ServiceConfig> _optConfigService, ILogger<KafkaService> logger)
{
_configService = _optConfigService.Value;
_logger = logger;
}

public async Task PublishAsync<T>(string topicName, T message) where T : class
{
try
{
Type messageType = typeof(T);
var config = new ProducerConfig
{
BootstrapServers = _configService.KafkaServerAddress,
EnableIdempotence = true,
Acks = Acks.All,
MessageSendMaxRetries = 3
};
if (message.GetType().Equals(typeof(EventData)))
{
using var producer = new ProducerBuilder<string, string>(config).Build();
string imei = messageType.GetProperty("IMEI")!.GetValue(message)!.ToString()!;
//var tailNo = long.Parse(messageType.GetProperty("IMEI")!.GetValue(message)!.ToString()!) % 100;

//int tailNo = SafeType.SafeInt(imei.Substring(imei.Length - 2));
var messageId = messageType.GetProperty("MessageId")!.GetValue(message)!.ToString()!;
//await producer.ProduceAsync(new TopicPartition(topicName, new Partition(tailNo)), new Message<string, string>
//{
// Key = messageId,
// Value = JsonConvert.SerializeObject(message),

//});
await producer.ProduceAsync(topicName, new Message<string, string>
{
Key = messageId,
Value = JsonConvert.SerializeObject(message),
});

// TopicPartition topicPartition = new TopicPartition(topicName, new Partition(tailNo));

}
else
{
using var producer = new ProducerBuilder<string, string>(config).Build();
await producer.ProduceAsync(topicName, new Message<string, string>
{
Key = Guid.NewGuid().ToString(),
Value = JsonConvert.SerializeObject(message)
});
}
}
catch (ProduceException<Null, string> ex)
{
_logger.LogError($"推送到kafka失败,topic: {topicName},\n message:{JsonConvert.SerializeObject(message)}: \n{ex.Error.Reason}");
}
}

public async Task SubscribeAsync<T>(IEnumerable<string> topics, Action<T> messageFunc, CancellationToken cancellationToken = default) where T : class
{
var config = new ConsumerConfig
{
BootstrapServers = _configService.KafkaServerAddress,
GroupId = "Consumer",
EnableAutoCommit = false, // 禁止AutoCommit
Acks = Acks.Leader, // 假设只需要Leader响应即可
AutoOffsetReset = AutoOffsetReset.Earliest // 从最早的开始消费起
};
using (var consumer = new ConsumerBuilder<Ignore, string>(config).Build())
{
consumer.Subscribe(topics);
try
{
while (true)
{
try
{
var consumeResult = consumer.Consume(cancellationToken);
Console.WriteLine($"Consumed message '{consumeResult.Message?.Value}' at: '{consumeResult?.TopicPartitionOffset}'.");
if (consumeResult!.IsPartitionEOF)
{
Console.WriteLine($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} 已经到底了:{consumeResult.Topic}, partition {consumeResult.Partition}, offset {consumeResult.Offset}.");
continue;
}
T? messageResult = null;
try
{
messageResult = JsonConvert.DeserializeObject<T>(consumeResult.Message!.Value)!;
}
catch (Exception ex)
{
var errorMessage = $" - {DateTime.Now:yyyy-MM-dd HH:mm:ss}【Exception 消息反序列化失败,Value:{consumeResult.Message!.Value}】 :{ex.StackTrace?.ToString()}";
Console.WriteLine(errorMessage);
messageResult = null;
}
if (messageResult != null/* && consumeResult.Offset % commitPeriod == 0*/)
{
messageFunc(messageResult);
try
{
consumer.Commit(consumeResult);
}
catch (KafkaException e)
{
Console.WriteLine(e.Message);
}
}
}
catch (ConsumeException e)
{
Console.WriteLine($"Consume error: {e.Error.Reason}");
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Closing consumer.");
consumer.Close();
}
}

await Task.CompletedTask;
}
}
}

+ 14
- 0
NearCardAttendance.Service/MessageQueue/Model/EventData.cs 파일 보기

@@ -0,0 +1,14 @@
namespace NearCardAttendance.Service.MessageQueue.Model
{
public class EventData
{
public string EventTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
public string TopicName { get; set; } = default!;
public string MessageId { get; set; } = default!;

public string IMEI{ get; set; } = default!;

public string Content { get; set; } = default!;
//public T Message { get; set; } = default!;
}
}

+ 13
- 0
NearCardAttendance.Service/MessageQueue/Model/IMEIMessage.cs 파일 보기

@@ -0,0 +1,13 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NearCardAttendance.Service.MessageQueue.Model
{
public class IMEIMessage
{
[JsonProperty("imei")]
public string IMEI { get; set; } = default!;
[JsonProperty("content")]
public JObject Content { get; set; } = default!;
}
}

+ 24
- 0
NearCardAttendance.Service/MessageQueue/MqProcessLogic.cs 파일 보기

@@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using NearCardAttendance.Service.MessageQueue.Kafka;
using NearCardAttendance.Service.MessageQueue.Model;

namespace NearCardAttendance.Service.MessageQueue
{
public class MqProcessLogic
{
private readonly ILogger<MqProcessLogic> _logger;

private readonly KafkaService _serviceKafka;
public MqProcessLogic(ILogger<MqProcessLogic> logger, KafkaService serviceKafka)
{
_logger = logger;
_serviceKafka = serviceKafka;
}
public async Task ProcessIMEIEventMessageAsync(EventData eventData)
{
await _serviceKafka.PublishAsync(eventData.TopicName, eventData);
_logger.LogInformation($"推送消息 {eventData.MessageId} 内容:{JsonConvert.SerializeObject(eventData)}");
}
}
}

+ 1
- 0
NearCardAttendance.Service/NearCardAttendance.Service.csproj 파일 보기

@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
<PackageReference Include="DotNetty.Transport" Version="0.7.5" />
<PackageReference Include="Confluent.Kafka" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>



+ 76
- 5
NearCardAttendance.Service/TcpServer/Handler/RegisterHandler.cs 파일 보기

@@ -18,6 +18,9 @@ using TelpoDataService.Util;
using TelpoDataService.Util.Clients;
using TelpoDataService.Util.Models;
using TelpoDataService.Util.QueryObjects;
using NearCardAttendance.Service.MessageQueue;
using NearCardAttendance.Service.MessageQueue.Model;
using Newtonsoft.Json.Linq;

namespace NearCardAttendance.Service.TcpServer.Handler
{
@@ -25,6 +28,7 @@ namespace NearCardAttendance.Service.TcpServer.Handler
{
private readonly ILogger<RegisterHandler> _logger;
private readonly HttpHelper _httpHelper = default!;
private readonly MqProcessLogic _serviceMqProcess;
//private readonly IDisposable _loggerScope = default!;
private readonly ServiceConfig _configService;
private readonly GpsCardAccessorClient<GpsDeviceConfig> _deviceConfigApiClient;
@@ -32,12 +36,13 @@ namespace NearCardAttendance.Service.TcpServer.Handler
//private readonly TcpClientsManager _managerTcpClients;
//private readonly ScheduleResendManager _managerScheduleResend;

public RegisterHandler(ILogger<RegisterHandler> logger, GpsCardAccessorClient<GpsDeviceConfig> deviceConfigApiClient,HttpHelper httpHelper, IOptions<ServiceConfig> configService)
public RegisterHandler(ILogger<RegisterHandler> logger, GpsCardAccessorClient<GpsDeviceConfig> deviceConfigApiClient,HttpHelper httpHelper, IOptions<ServiceConfig> configService, MqProcessLogic serviceMqProcess)
{
_logger = logger;
_httpHelper = httpHelper;
_configService = configService.Value;
_deviceConfigApiClient= deviceConfigApiClient;
_serviceMqProcess = serviceMqProcess;
}

public override void ChannelActive(IChannelHandlerContext context)
@@ -267,15 +272,43 @@ namespace NearCardAttendance.Service.TcpServer.Handler

if (config!=null)
{
var url = _configService.XinHuaLeYuUrl;
//var url = _configService.XinHuaLeYuUrl;
var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord";
var data = new
{
attendanceStatus = 2, //考勤状态: 0.进 1.出 2.未知
attendanceTime = DateTime.ParseExact(startTime, "yyyyMMddHHmmss", null).ToString("yyyy-MM-dd HH:mm:ss"),
imei = config.Imei
};
var eventData = new EventData
{
TopicName = "topics.storage.near_card_attendance",
MessageId = $"{config.Imei}-{parser.SeqNo}-{Guid.NewGuid().ToString("N")[^4..]}",
IMEI = config.Imei,
Content = JsonConvert.SerializeObject(data)
};
var res = await _httpHelper.HttpToPostAsync(url, data);
_logger.LogInformation($"{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");

if (!string.IsNullOrEmpty(res))
{
JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
if ((bool)resObj["success"]!)
{
_logger.LogInformation($"{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
}
else
{
await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
_logger.LogInformation($"HTTP 响应业务失败 {res},{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");

}
}
else
{
await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
_logger.LogInformation($"HTTP 响应超时,{nameof(HandleSignRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");

}
}
else
{
@@ -328,15 +361,53 @@ namespace NearCardAttendance.Service.TcpServer.Handler
if (config != null)
{
//var url = "https://midplat.xinhualeyu.com/dev-api/user/electronicCardAttendance/receiveTbAttendanceRecord";
var url = _configService.XinHuaLeYuUrl;
#if DEBUG
var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord1";

#else
var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecord";

#endif
var data = new
{
attendanceStatus = int.TryParse(optType, out int type) ? type : 0,
attendanceTime = DateTime.ParseExact(startTime, "yyyyMMddHHmmss", null).ToString("yyyy-MM-dd HH:mm:ss"),
imei = config.Imei
};
var eventData = new EventData
{
TopicName = "topics.storage.near_card_attendance",
MessageId = $"{config.Imei}-{parser.SeqNo}-{Guid.NewGuid().ToString("N")[^4..]}",
IMEI = config.Imei,
Content = JsonConvert.SerializeObject(data)
};

var res = await _httpHelper.HttpToPostAsync(url, data);
_logger.LogInformation($"{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
if (!string.IsNullOrEmpty(res))
{
JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
if ((bool)resObj["success"]!)
{
_logger.LogInformation($"{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
}
else
{
await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
_logger.LogInformation($"HTTP 响应业务失败 {res},{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");

}
}
else
{

await _serviceMqProcess.ProcessIMEIEventMessageAsync(eventData);
_logger.LogInformation($"HTTP 响应超时,{nameof(HandleStdtSchoolRecsAsync)} 推送 {JsonConvert.SerializeObject(eventData)}");

}



}
else
{


+ 1
- 0
NearCardAttendance.TcpServer/NearCardAttendance.TcpServer.csproj 파일 보기

@@ -30,6 +30,7 @@
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
<PackageReference Include="DotNetty.Transport" Version="0.7.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Confluent.Kafka" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="Serilog.Expressions" Version="3.4.0" />


+ 4
- 1
NearCardAttendance.TcpServer/Program.cs 파일 보기

@@ -14,6 +14,8 @@ using Serilog;
using NearCardAttendance.Model;
using TelpoDataService.Util.Clients;
using System.Text;
using NearCardAttendance.Service.MessageQueue.Kafka;
using NearCardAttendance.Service.MessageQueue;

namespace NearCardAttendance.TcpServer
{
@@ -110,8 +112,9 @@ namespace NearCardAttendance.TcpServer
.AddHostedService<Server>()
;
#endregion
services.AddSingleton<MqProcessLogic>();
services.AddSingleton<KafkaService>();

});
}
}

+ 159
- 5
NearCardAttendance.TcpServer/Server.cs 파일 보기

@@ -1,13 +1,20 @@
using DotNetty.Transport.Bootstrapping;
using Confluent.Kafka;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NearCardAttendance.Common.helper;
using NearCardAttendance.Model;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using NearCardAttendance.Service.MessageQueue.Model;

namespace NearCardAttendance.TcpServer
{
@@ -19,16 +26,23 @@ namespace NearCardAttendance.TcpServer
// private IChannel _serverChannel = default!;
private CancellationTokenSource _tokenSource = null!;


private readonly ServiceConfig _configService;
private readonly HttpHelper _httpHelper = default!;
private int _messageCount = 0;


public Server(
ILogger<Server> logger,
ServerBootstrap serverBootstrap,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider, HttpHelper httpHelper, IOptions<ServiceConfig> _optConfigService)
{
_logger = logger;
//_serviceProvider = serviceProvider;
_configService = _optConfigService.Value;
_serverBootstrap = serverBootstrap;
_httpHelper = httpHelper;

}
public override Task StartAsync(CancellationToken cancellationToken)
{
@@ -60,7 +74,51 @@ namespace NearCardAttendance.TcpServer

//_logger.LogInformation("DotNetty server started on {0}.", address);
_logger.LogInformation("DotNetty server started on {0}.", endPoint);

#region kafka
var kafkaConsumer = CreateKafkaConsumer();
kafkaConsumer.Subscribe("topics.storage.near_card_attendance");
var tasks = new List<Task>();
List<ConsumeResult<Ignore, string>> consumeBatchResult = new List<ConsumeResult<Ignore, string>>();
try
{
while (!stoppingToken.IsCancellationRequested)
{
var consumeResult = kafkaConsumer.Consume(stoppingToken);
if (consumeResult != null)
{
_messageCount++;
consumeBatchResult.Add(consumeResult);
//// 30条消息为一批
#if DEBUG
if (!await ProcessBatchMessageAsync(consumeBatchResult, kafkaConsumer))
{ // 返回结果错误暂停5分钟
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
#else
if (_messageCount % 30 == 0)
{
if (!await ProcessBatchMessageAsync(consumeBatchResult, kafkaConsumer))
{ // 返回结果错误暂停5分钟
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
#endif


}
}
}
catch (OperationCanceledException)
{
_logger.LogWarning("Worker exit");
}
catch (Exception ex)
{
_logger.LogError($"An error occurred: {ex.Message}");
}

#endregion

// Wait until the service is stopped
stoppingToken.WaitHandle.WaitOne();
@@ -71,6 +129,102 @@ namespace NearCardAttendance.TcpServer
await _serverChannel.CloseAsync();
_logger.LogInformation("DotNetty server stopped.");
}
private async Task<bool> ProcessBatchMessageAsync(List<ConsumeResult<Ignore, string>> consumeBatchResult, IConsumer<Ignore, string> kafkaConsumer)
{
try
{

var url = $"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecordException";
var list = new List<object>();
//consumeBatchResult.ForEach(x => {
// JObject msg = (JObject)JsonConvert.DeserializeObject(x.Message.Value)!;
// list.Add(new
// {
// attendanceStatus = int.TryParse(msg["data"]!["attendanceStatus"]!.ToString(), out int status) ? status : 0,
// attendanceTime = msg["data"]!["attendanceTime"]!.ToString(),
// imei = msg["data"]!["imei"]!.ToString()
// }) ;
//});

consumeBatchResult.ForEach(x => {
EventData msg = JsonConvert.DeserializeObject<EventData>(x.Message.Value)!;
JObject content = (JObject)JsonConvert.DeserializeObject(msg.Content)!;
list.Add(new
{
attendanceStatus = int.TryParse(content!["attendanceStatus"]!.ToString(), out int status) ? status : 0,
attendanceTime = content!["attendanceTime"]!.ToString(),
imei = content!["imei"]!.ToString()
});
});


var data = new
{
data = list
};
var res = await _httpHelper.HttpToPostAsync(url, data);
if (!string.IsNullOrEmpty(res))
{
JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
if ((bool)resObj["success"]!)
{
_logger.LogInformation($"{nameof(ProcessBatchMessageAsync)} 推送 {JsonConvert.SerializeObject(data)} 结果,{res}");
consumeBatchResult.ForEach(x =>
{
kafkaConsumer.Commit(x);
_logger.LogInformation($"完成消费:{JsonConvert.SerializeObject(x.Message.Value)}");
});
return true;
}
}

}
catch (Exception ex)
{
_logger.LogError($"处理消息出错 \n{ex.Message}\n{ex.StackTrace}");

}
return false;

}

private IConsumer<Ignore, string> CreateKafkaConsumer()
{

var consumerConfig = new ConsumerConfig
{
GroupId = "near_card_attendance",
BootstrapServers = _configService.KafkaServerAddress,
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false, // 关闭自动提交偏移量
CancellationDelayMaxMs = 1//set CancellationDelayMaxMs
};

return new ConsumerBuilder<Ignore, string>(consumerConfig)
.SetErrorHandler((_, e) =>
{

//Console.WriteLine($"消费者创建出错,代码:{e.Code} |原因: {e.Reason}");
_logger.LogInformation($"消费者创建出错,代码:{e.Code} |原因: {e.Reason}");
})
.SetPartitionsAssignedHandler((c, partitions) =>
{
//// 在这里手动指定要消费的分区
//var partitionsToConsume = new List<TopicPartitionOffset>
//{
// new TopicPartitionOffset("topics.storage.near_card_attendance", partitionIndex, Offset.Unset)
//};

////c.Assign(partitionsToConsume);
//Console.WriteLine($"Assigned partitions: {string.Join(", ", partitionsToConsume)}");
//return partitionsToConsume;
})
.SetPartitionsRevokedHandler((c, partitions) =>
{

})
.Build();
}


}


+ 288
- 0
NearCardAttendance.TcpServer/Worker.cs 파일 보기

@@ -0,0 +1,288 @@
using Confluent.Kafka;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NearCardAttendance.Model;
using Newtonsoft.Json;
using Serilog.Context;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NearCardAttendance.Common.helper;
using Newtonsoft.Json.Linq;
using NearCardAttendance.Service.MessageQueue.Model;


namespace NearCardAttendance.TcpServer
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly ServiceConfig _configService;
private readonly HttpHelper _httpHelper = default!;
private int _messageCount = 0;

public Worker(ILogger<Worker> logger, IOptions<ServiceConfig> _optConfigService,HttpHelper httpHelper)
{
_logger = logger;
_configService = _optConfigService.Value;
_httpHelper = httpHelper;
}
/**
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var kafkaConsumer = CreateKafkaConsumer();
kafkaConsumer.Subscribe("topics.storage.near_card_attendance");

// process messages every 50 messages or every minute
// 每隔1分钟或没50条消息就批量处理一次
var tasks = new List<Task>();

#region 每分钟触发一次
var messageCounter = 0; // 消息计数器
var timer = new System.Timers.Timer(TimeSpan.FromMinutes(1).TotalMilliseconds); // 定时器,每分钟触发一次
timer.AutoReset = true;

// 定时器事件处理程序,用于定时处理消息
timer.Elapsed += async (sender, e) =>
{
// 如果计数器大于 0,则处理消息
if (messageCounter > 0)
{
await ProcessMessagesAsync(tasks);
messageCounter = 0; // 处理完成后重置计数器
}
};

timer.Start(); // 启动定时器
#endregion

try
{
while (!stoppingToken.IsCancellationRequested)
{
var consumeResult = kafkaConsumer.Consume(stoppingToken);
if (consumeResult != null)
{
//tasks.Add(ProcessMessageAsync(consumeResult, kafkaConsumer));
// 处理消息
tasks.Add(ProcessMessageAsync(consumeResult, kafkaConsumer));
messageCounter++; // 增加消息计数器

// 如果消息计数达到 30 条,则处理消息并重置计数器
if (messageCounter >= 30)
{
await ProcessMessagesAsync(tasks);
messageCounter = 0; // 处理完成后重置计数器
}
}
}
}
catch (OperationCanceledException)
{
_logger.LogWarning("Worker exit");
}
catch (Exception ex)
{
_logger.LogError($"An error occurred: {ex.Message}");
}

await Task.WhenAll(tasks);
}

// 异步处理消息的方法
private async Task ProcessMessagesAsync(List<Task> tasks)
{
await Task.WhenAll(tasks); // 等待所有任务完成
tasks.Clear(); // 清空任务列表
}

private async Task ProcessMessageAsync(ConsumeResult<Ignore, string> consumeResult, IConsumer<Ignore, string> kafkaConsumer)
{
// 处理消息的逻辑
if (consumeResult != null)
{

try
{
var url = _configService.XinHuaLeYuUrl;
var data = new { };
var res = await _httpHelper.HttpToPostAsync(url, data);

}
catch (Exception ex)
{
//kafkaConsumer.Commit(consumeResult);
_logger.LogError($"消费出错:{consumeResult.Message.Value},\n{ex.Message}\n{ex.StackTrace}");
}
}
}
*/

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var kafkaConsumer = CreateKafkaConsumer();
kafkaConsumer.Subscribe("topics.storage.near_card_attendance");
var tasks = new List<Task>();
List<ConsumeResult<Ignore, string>> consumeBatchResult = new List<ConsumeResult<Ignore, string>>();
try
{
while (!stoppingToken.IsCancellationRequested)
{
var consumeResult = kafkaConsumer.Consume(stoppingToken);
if (consumeResult != null)
{
_messageCount++;
consumeBatchResult.Add(consumeResult);
//// 30条消息为一批
if (_messageCount % 30 == 0)
{
if (!await ProcessBatchMessageAsync(consumeBatchResult, kafkaConsumer))
{ // 返回结果错误暂停5分钟
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}

}
}
}
catch (OperationCanceledException)
{
_logger.LogWarning("Worker exit");
}
catch (Exception ex)
{
_logger.LogError($"An error occurred: {ex.Message}");
}
}
private async Task<bool> ProcessMessageAsync(ConsumeResult<Ignore, string> consumeResult, IConsumer<Ignore, string> kafkaConsumer)
{
try
{
var url = _configService.XinHuaLeYuUrl;
var data = new { };
var res = await _httpHelper.HttpToPostAsync(url, data);
if (!string.IsNullOrEmpty(res))
{
JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
if ((bool)resObj["success"]!)
{
kafkaConsumer.Commit(consumeResult);
return true;
}
}
}
catch (Exception ex)
{
//kafkaConsumer.Commit(consumeResult);
_logger.LogError($"消费出错:{consumeResult.Message.Value},\n{ex.Message}\n{ex.StackTrace}");
}
return false;

}

private async Task<bool> ProcessBatchMessageAsync(List<ConsumeResult<Ignore, string>> consumeBatchResult, IConsumer<Ignore, string> kafkaConsumer)
{
try
{
var url =$"{_configService.XinHuaLeYuUrl}/user/electronicCardAttendance/receiveTbAttendanceRecordException";
var list = new List<object>();
//consumeBatchResult.ForEach(x => {
// JObject msg = (JObject)JsonConvert.DeserializeObject(x.Message.Value)!;
// list.Add(new
// {
// attendanceStatus = int.TryParse(msg["data"]!["attendanceStatus"]!.ToString(), out int status) ? status : 0,
// attendanceTime = msg["data"]!["attendanceTime"]!.ToString(),
// imei = msg["data"]!["imei"]!.ToString()
// }) ;
//});

consumeBatchResult.ForEach(x => {
EventData msg = JsonConvert.DeserializeObject<EventData>(x.Message.Value)!;
JObject content = (JObject)JsonConvert.DeserializeObject(msg.Content)!;
list.Add(new
{
attendanceStatus = int.TryParse(content!["attendanceStatus"]!.ToString(), out int status) ? status : 0,
attendanceTime = content!["attendanceTime"]!.ToString(),
imei = content!["imei"]!.ToString()
});
});


var data = new {
data= list
};
var res = await _httpHelper.HttpToPostAsync(url, data);
if (!string.IsNullOrEmpty(res))
{
JObject resObj = (JObject)JsonConvert.DeserializeObject(res!)!;
if ((bool)resObj["success"]!)
{
consumeBatchResult.ForEach(x =>
{
kafkaConsumer.Commit(x);
});
return true;
}
}

}
catch (Exception ex)
{
_logger.LogError($"处理消息出错 \n{ex.Message}\n{ex.StackTrace}");

}
return false;

}


/// <summary>
/// 创建消费者
/// </summary>
/// <returns></returns>
private IConsumer<Ignore, string> CreateKafkaConsumer()
{
var consumerConfig = new ConsumerConfig
{
GroupId = "near_card_attendance",
BootstrapServers = _configService.KafkaServerAddress,
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false, // 关闭自动提交偏移量
CancellationDelayMaxMs = 1//set CancellationDelayMaxMs
};

return new ConsumerBuilder<Ignore, string>(consumerConfig)
.SetErrorHandler((_, e) =>
{

//Console.WriteLine($"消费者创建出错,代码:{e.Code} |原因: {e.Reason}");
_logger.LogInformation($"消费者创建出错,代码:{e.Code} |原因: {e.Reason}");
})
.SetPartitionsAssignedHandler((c, partitions) =>
{
//// 在这里手动指定要消费的分区
//var partitionsToConsume = new List<TopicPartitionOffset>
//{
// new TopicPartitionOffset("topics.storage.near_card_attendance", partitionIndex, Offset.Unset)
//};

////c.Assign(partitionsToConsume);
//Console.WriteLine($"Assigned partitions: {string.Join(", ", partitionsToConsume)}");
//return partitionsToConsume;
})
.SetPartitionsRevokedHandler((c, partitions) =>
{

})
.Build();
}
}
}

+ 3
- 2
NearCardAttendance.TcpServer/appsettings.debug.json 파일 보기

@@ -1,7 +1,8 @@
{
"AllowedHosts": "*",
"ServiceConfig": {
"XinHuaLeYuUrl": "https://midplat.xinhualeyu.com/dev-api/user/electronicCardAttendance/receiveTbAttendanceRecord",
"TelpoDataUrl": "https://ai.ssjlai.com/data"
"XinHuaLeYuUrl": "https://midplat.xinhualeyu.com/dev-api",
"TelpoDataUrl": "https://ai.ssjlai.com/data",
"KafkaServerAddress": "192.168.2.121:9092"
}
}

+ 3
- 2
NearCardAttendance.TcpServer/appsettings.test.json 파일 보기

@@ -1,7 +1,8 @@
{
"AllowedHosts": "*",
"ServiceConfig": {
"XinHuaLeYuUrl": "https://midplat.xinhualeyu.com/dev-api/user/electronicCardAttendance/receiveTbAttendanceRecord",
"TelpoDataUrl": "https://ai.ssjlai.com/data"
"XinHuaLeYuUrl": "https://midplat.xinhualeyu.com/dev-api",
"TelpoDataUrl": "https://ai.ssjlai.com/data",
"KafkaServerAddress": "172.19.42.53:9092"
}
}

Loading…
취소
저장