@@ -1,6 +1,7 @@ | |||||
using Confluent.Kafka; | using Confluent.Kafka; | ||||
using JT809.PubSub.Abstractions; | using JT809.PubSub.Abstractions; | ||||
using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||
using Microsoft.Extensions.Options; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading; | using System.Threading; | ||||
@@ -8,137 +9,81 @@ using System.Threading.Tasks; | |||||
namespace JT809.KafkaService | namespace JT809.KafkaService | ||||
{ | { | ||||
public abstract class JT809Consumer<T> : IJT808ConsumerOfT<T> | |||||
public abstract class JT809Consumer<T> : JT809ConsumerBase<T> | |||||
{ | { | ||||
private bool _disposed = false; | private bool _disposed = false; | ||||
public CancellationTokenSource Cts => new CancellationTokenSource(); | |||||
public override CancellationTokenSource Cts => new CancellationTokenSource(); | |||||
public virtual string TopicName => JT809Constants.JT809TopicName; | |||||
protected ILogger logger { get; } | |||||
private readonly ILogger logger; | |||||
private List<TopicPartition> topicPartitionList; | |||||
private IList<IConsumer<string, T>> consumers; | |||||
protected abstract JT809PartitionOptions PartitionOptions { get; } | |||||
protected virtual Deserializer<T> Deserializer { get; } | |||||
protected override IList<IConsumer<string, T>> Consumers { get; } | |||||
protected JT809Consumer( | protected JT809Consumer( | ||||
ConsumerConfig consumerConfig, | |||||
ILoggerFactory loggerFactory) | |||||
IOptions<JT809TopicOptions> topicOptionsAccessor, | |||||
IOptions<ConsumerConfig> consumerConfigAccessor, | |||||
ILoggerFactory loggerFactory) | |||||
: base(topicOptionsAccessor.Value.TopicName, consumerConfigAccessor.Value) | |||||
{ | { | ||||
logger = loggerFactory.CreateLogger("JT809Consumer"); | logger = loggerFactory.CreateLogger("JT809Consumer"); | ||||
CreateTopicPartition(); | |||||
consumers = new List<IConsumer<string, T>>(); | |||||
foreach(var topicPartition in topicPartitionList) | |||||
{ | |||||
ConsumerBuilder<string, T> consumerBuilder = new ConsumerBuilder<string, T>(consumerConfig); | |||||
consumerBuilder.SetErrorHandler((consumer, error) => | |||||
{ | |||||
logger.LogError(error.Reason); | |||||
}); | |||||
if (Deserializer != null) | |||||
{ | |||||
consumerBuilder.SetValueDeserializer(Deserializer); | |||||
} | |||||
if (PartitionOptions.Partition > 1) | |||||
{ | |||||
consumerBuilder.SetPartitionsAssignedHandler((c, p) => { | |||||
p.Add(topicPartition); | |||||
}); | |||||
} | |||||
consumers.Add(consumerBuilder.Build()); | |||||
} | |||||
} | |||||
private void CreateTopicPartition() | |||||
{ | |||||
topicPartitionList = new List<TopicPartition>(); | |||||
if (PartitionOptions.Partition > 1) | |||||
Consumers = new List<IConsumer<string, T>>(); | |||||
ConsumerBuilder<string, T> consumerBuilder = new ConsumerBuilder<string, T>(ConsumerConfig); | |||||
consumerBuilder.SetErrorHandler((consumer, error) => | |||||
{ | { | ||||
if(PartitionOptions.AssignPartitions!=null && PartitionOptions.AssignPartitions.Count>0) | |||||
{ | |||||
foreach(var p in PartitionOptions.AssignPartitions) | |||||
{ | |||||
topicPartitionList.Add(new TopicPartition(TopicName, new Partition(p))); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < PartitionOptions.Partition; i++) | |||||
{ | |||||
topicPartitionList.Add(new TopicPartition(TopicName, new Partition(i))); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
logger.LogError(error.Reason); | |||||
}); | |||||
if (Deserializer != null) | |||||
{ | { | ||||
for (int i = 0; i < PartitionOptions.Partition; i++) | |||||
{ | |||||
topicPartitionList.Add(new TopicPartition(TopicName, new Partition(i))); | |||||
} | |||||
consumerBuilder.SetValueDeserializer(Deserializer); | |||||
} | } | ||||
Consumers.Add(consumerBuilder.Build()); | |||||
} | } | ||||
public void OnMessage(Action<(string MsgId, T data)> callback) | |||||
public override void OnMessage(Action<(string MsgId, T Data)> callback) | |||||
{ | { | ||||
logger.LogDebug($"consumers:{consumers.Count},topicPartitionList:{topicPartitionList.Count}"); | |||||
for (int i = 0; i < consumers.Count; i++) | |||||
Task.Run(() => | |||||
{ | { | ||||
Task.Factory.StartNew((num) => | |||||
while (!Cts.IsCancellationRequested) | |||||
{ | { | ||||
int n = (int)num; | |||||
while (!Cts.IsCancellationRequested) | |||||
try | |||||
{ | { | ||||
try | |||||
{ | |||||
//如果不指定分区,根据kafka的机制会从多个分区中拉取数据 | |||||
//如果指定分区,根据kafka的机制会从相应的分区中拉取数据 | |||||
//consumers[n].Assign(topicPartitionList[n]); | |||||
var data = consumers[n].Consume(Cts.Token); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
{ | |||||
logger.LogDebug($"Topic: {data.Topic} Key: {data.Key} Partition: {data.Partition} Offset: {data.Offset} Data:{string.Join("", data.Value)} TopicPartitionOffset:{data.TopicPartitionOffset}"); | |||||
} | |||||
callback((data.Key, data.Value)); | |||||
} | |||||
catch (ConsumeException ex) | |||||
{ | |||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
} | |||||
catch (Exception ex) | |||||
//如果不指定分区,根据kafka的机制会从多个分区中拉取数据 | |||||
//如果指定分区,根据kafka的机制会从相应的分区中拉取数据 | |||||
//consumers[n].Assign(topicPartitionList[n]); | |||||
var data = Consumers[0].Consume(Cts.Token); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
{ | { | ||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
logger.LogDebug($"Topic: {data.Topic} Key: {data.Key} Partition: {data.Partition} Offset: {data.Offset} Data:{string.Join("", data.Value)} TopicPartitionOffset:{data.TopicPartitionOffset}"); | |||||
} | } | ||||
callback((data.Key, data.Value)); | |||||
} | } | ||||
}, i, Cts.Token); | |||||
} | |||||
catch (ConsumeException ex) | |||||
{ | |||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
} | |||||
} | |||||
}, Cts.Token); | |||||
} | } | ||||
public void Subscribe() | |||||
public override void Subscribe() | |||||
{ | { | ||||
if (_disposed) return; | if (_disposed) return; | ||||
//仅有一个分区才需要订阅 | //仅有一个分区才需要订阅 | ||||
if (topicPartitionList.Count == 1) | |||||
{ | |||||
consumers[0].Subscribe(TopicName); | |||||
} | |||||
Consumers[0].Subscribe(TopicName); | |||||
} | } | ||||
public void Unsubscribe() | |||||
public override void Unsubscribe() | |||||
{ | { | ||||
if (_disposed) return; | if (_disposed) return; | ||||
foreach(var c in consumers) | |||||
{ | |||||
c.Unsubscribe(); | |||||
} | |||||
Consumers[0].Unsubscribe(); | |||||
} | } | ||||
public void Dispose() | |||||
public override void Dispose() | |||||
{ | { | ||||
Dispose(true); | Dispose(true); | ||||
GC.SuppressFinalize(true); | GC.SuppressFinalize(true); | ||||
@@ -156,11 +101,8 @@ namespace JT809.KafkaService | |||||
if (disposing) | if (disposing) | ||||
{ | { | ||||
Cts.Cancel(); | Cts.Cancel(); | ||||
foreach (var c in consumers) | |||||
{ | |||||
c.Close(); | |||||
c.Dispose(); | |||||
} | |||||
Consumers[0].Close(); | |||||
Consumers[0].Dispose(); | |||||
Cts.Dispose(); | Cts.Dispose(); | ||||
} | } | ||||
_disposed = true; | _disposed = true; | ||||
@@ -0,0 +1,34 @@ | |||||
using Confluent.Kafka; | |||||
using JT809.PubSub.Abstractions; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT809.KafkaService | |||||
{ | |||||
public abstract class JT809ConsumerBase<T> : IJT808ConsumerOfT<T> | |||||
{ | |||||
public string TopicName { get; } | |||||
public ConsumerConfig ConsumerConfig { get; } | |||||
protected JT809ConsumerBase(string topicName, ConsumerConfig config) | |||||
{ | |||||
ConsumerConfig = config; | |||||
TopicName = topicName; | |||||
} | |||||
public abstract CancellationTokenSource Cts { get; } | |||||
protected abstract IList<IConsumer<string, T>> Consumers { get; } | |||||
protected virtual Deserializer<T> Deserializer { get; set; } | |||||
public abstract void Dispose(); | |||||
public abstract void OnMessage(Action<(string MsgId, T Data)> callback); | |||||
public abstract void Subscribe(); | |||||
public abstract void Unsubscribe(); | |||||
} | |||||
} |
@@ -18,7 +18,7 @@ namespace JT809.KafkaService | |||||
{ | { | ||||
public static IServiceCollection AddJT809KafkaProducerService(this IServiceCollection serviceDescriptors, IConfiguration configuration) | public static IServiceCollection AddJT809KafkaProducerService(this IServiceCollection serviceDescriptors, IConfiguration configuration) | ||||
{ | { | ||||
serviceDescriptors.Configure<ProducerConfig>(configuration.GetSection("JT809ProducerConfig")); | |||||
serviceDescriptors.Configure<ProducerConfig>(configuration.GetSection("KafkaProducerConfig")); | |||||
serviceDescriptors.AddSingleton(typeof(JT809Producer<byte[]>), typeof(JT809_Same_Producer)); | serviceDescriptors.AddSingleton(typeof(JT809Producer<byte[]>), typeof(JT809_Same_Producer)); | ||||
serviceDescriptors.AddSingleton(typeof(JT809Producer<JT809GpsPosition>), typeof(JT809_GpsPositio_Producer)); | serviceDescriptors.AddSingleton(typeof(JT809Producer<JT809GpsPosition>), typeof(JT809_GpsPositio_Producer)); | ||||
return serviceDescriptors; | return serviceDescriptors; | ||||
@@ -42,7 +42,7 @@ namespace JT809.KafkaService | |||||
public static IServiceCollection AddJT809KafkaConsumerService(this IServiceCollection serviceDescriptors, IConfiguration configuration, Action<JT809PartitionOptions> action = null) | public static IServiceCollection AddJT809KafkaConsumerService(this IServiceCollection serviceDescriptors, IConfiguration configuration, Action<JT809PartitionOptions> action = null) | ||||
{ | { | ||||
serviceDescriptors.Configure<ConsumerConfig>(configuration.GetSection("JT809ConsumerConfig")); | |||||
serviceDescriptors.Configure<ConsumerConfig>(configuration.GetSection("KafkaConsumerConfig")); | |||||
if (configuration.GetSection("JT809PartitionOptions").Exists()) | if (configuration.GetSection("JT809PartitionOptions").Exists()) | ||||
{ | { | ||||
serviceDescriptors.Configure<JT809PartitionOptions>(configuration.GetSection("JT809PartitionOptions")); | serviceDescriptors.Configure<JT809PartitionOptions>(configuration.GetSection("JT809PartitionOptions")); | ||||
@@ -0,0 +1,160 @@ | |||||
using Confluent.Kafka; | |||||
using JT809.PubSub.Abstractions; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT809.KafkaService | |||||
{ | |||||
public abstract class JT809PartitionConsumer<T> : JT809ConsumerBase<T> | |||||
{ | |||||
private bool _disposed = false; | |||||
public override CancellationTokenSource Cts => new CancellationTokenSource(); | |||||
protected ILogger logger { get; } | |||||
private List<TopicPartition> topicPartitionList; | |||||
private JT809PartitionOptions partitionOptions; | |||||
protected override IList<IConsumer<string, T>> Consumers { get; } | |||||
protected JT809PartitionConsumer( | |||||
IOptions<ConsumerConfig> consumerConfigAccessor, | |||||
IOptions<JT809PartitionOptions> partitionOptionsAccessor, | |||||
IOptions<JT809TopicOptions> topicOptionsAccessor, | |||||
ILoggerFactory loggerFactory) : base(topicOptionsAccessor.Value.TopicName, consumerConfigAccessor.Value) | |||||
{ | |||||
logger = loggerFactory.CreateLogger("JT809PartitionConsumer"); | |||||
partitionOptions = partitionOptionsAccessor.Value; | |||||
topicPartitionList = CreateTopicPartition(); | |||||
Consumers = CreateConsumers(); | |||||
} | |||||
protected virtual IList<IConsumer<string, T>> CreateConsumers() | |||||
{ | |||||
List<IConsumer<string, T>> consumers = new List<IConsumer<string, T>>(); | |||||
foreach (var topicPartition in topicPartitionList) | |||||
{ | |||||
ConsumerBuilder<string, T> consumerBuilder = new ConsumerBuilder<string, T>(ConsumerConfig); | |||||
consumerBuilder.SetErrorHandler((consumer, error) => | |||||
{ | |||||
logger.LogError(error.Reason); | |||||
}); | |||||
if (Deserializer != null) | |||||
{ | |||||
consumerBuilder.SetValueDeserializer(Deserializer); | |||||
} | |||||
consumerBuilder.SetPartitionsAssignedHandler((c, p) => | |||||
{ | |||||
p.Add(topicPartition); | |||||
}); | |||||
consumers.Add(consumerBuilder.Build()); | |||||
} | |||||
return consumers; | |||||
} | |||||
protected virtual List<TopicPartition> CreateTopicPartition() | |||||
{ | |||||
var topicPartitions = new List<TopicPartition>(); | |||||
if (partitionOptions.AssignPartitions != null && partitionOptions.AssignPartitions.Count > 0) | |||||
{ | |||||
foreach (var p in partitionOptions.AssignPartitions) | |||||
{ | |||||
topicPartitions.Add(new TopicPartition(TopicName, new Partition(p))); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < partitionOptions.Partition; i++) | |||||
{ | |||||
topicPartitions.Add(new TopicPartition(TopicName, new Partition(i))); | |||||
} | |||||
} | |||||
return topicPartitions; | |||||
} | |||||
public override void OnMessage(Action<(string MsgId, T Data)> callback) | |||||
{ | |||||
if(logger.IsEnabled( LogLevel.Debug)) | |||||
logger.LogDebug($"consumers:{Consumers.Count},topicPartitionList:{topicPartitionList.Count}"); | |||||
for (int i = 0; i < Consumers.Count; i++) | |||||
{ | |||||
Task.Factory.StartNew((num) => | |||||
{ | |||||
int n = (int)num; | |||||
while (!Cts.IsCancellationRequested) | |||||
{ | |||||
try | |||||
{ | |||||
//如果不指定分区,根据kafka的机制会从多个分区中拉取数据 | |||||
//如果指定分区,根据kafka的机制会从相应的分区中拉取数据 | |||||
//consumers[n].Assign(topicPartitionList[n]); | |||||
var data = Consumers[n].Consume(Cts.Token); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
{ | |||||
logger.LogDebug($"Topic: {data.Topic} Key: {data.Key} Partition: {data.Partition} Offset: {data.Offset} Data:{string.Join("", data.Value)} TopicPartitionOffset:{data.TopicPartitionOffset}"); | |||||
} | |||||
callback((data.Key, data.Value)); | |||||
} | |||||
catch (ConsumeException ex) | |||||
{ | |||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, TopicName); | |||||
Thread.Sleep(1000); | |||||
} | |||||
} | |||||
}, i, Cts.Token); | |||||
} | |||||
} | |||||
public override void Subscribe() | |||||
{ | |||||
if (_disposed) return; | |||||
} | |||||
public override void Unsubscribe() | |||||
{ | |||||
if (_disposed) return; | |||||
foreach (var c in Consumers) | |||||
{ | |||||
c.Unsubscribe(); | |||||
} | |||||
} | |||||
public override void Dispose() | |||||
{ | |||||
Dispose(true); | |||||
GC.SuppressFinalize(true); | |||||
} | |||||
~JT809PartitionConsumer() | |||||
{ | |||||
Dispose(false); | |||||
} | |||||
protected virtual void Dispose(bool disposing) | |||||
{ | |||||
if (_disposed) return; | |||||
if (disposing) | |||||
{ | |||||
Cts.Cancel(); | |||||
foreach (var c in Consumers) | |||||
{ | |||||
c.Close(); | |||||
c.Dispose(); | |||||
} | |||||
Cts.Dispose(); | |||||
} | |||||
_disposed = true; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,153 @@ | |||||
using Confluent.Kafka; | |||||
using Confluent.Kafka.Admin; | |||||
using JT809.PubSub.Abstractions; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
namespace JT809.KafkaService | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
/// <typeparam name="T"></typeparam> | |||||
public abstract class JT809PartitionProducer<T> : JT809ProducerBase<T> | |||||
{ | |||||
private bool _disposed = false; | |||||
private ConcurrentDictionary<string, TopicPartition> TopicPartitionCache; | |||||
private readonly IJT809ProducerPartitionFactory ProducerPartitionFactory; | |||||
private readonly JT809PartitionOptions PartitionOptions; | |||||
protected override IProducer<string, T> Producer { get; } | |||||
protected virtual IProducer<string, T> CreateProducer() | |||||
{ | |||||
ProducerBuilder<string, T> producerBuilder = new ProducerBuilder<string, T>(ProducerConfig); | |||||
if (Serializer != null) | |||||
{ | |||||
producerBuilder.SetValueSerializer(Serializer); | |||||
} | |||||
if (PartitionOptions != null) | |||||
{ | |||||
TopicPartitionCache = new ConcurrentDictionary<string, TopicPartition>(); | |||||
if (PartitionOptions.Partition > 1) | |||||
{ | |||||
using (var adminClient = new AdminClient(Producer.Handle)) | |||||
{ | |||||
try | |||||
{ | |||||
adminClient.CreateTopicsAsync(new TopicSpecification[] { new TopicSpecification { Name = TopicName, NumPartitions = 1, ReplicationFactor = 1 } }).Wait(); | |||||
} | |||||
catch (AggregateException ex) | |||||
{ | |||||
//{Confluent.Kafka.Admin.CreateTopicsException: An error occurred creating topics: [jt809]: [Topic 'jt809' already exists.].} | |||||
if (ex.InnerException is Confluent.Kafka.Admin.CreateTopicsException exception) | |||||
{ | |||||
} | |||||
else | |||||
{ | |||||
//记录日志 | |||||
//throw ex.InnerException; | |||||
} | |||||
} | |||||
try | |||||
{ | |||||
//topic IncreaseTo 只增不减 | |||||
adminClient.CreatePartitionsAsync( | |||||
new List<PartitionsSpecification> | |||||
{ | |||||
new PartitionsSpecification | |||||
{ | |||||
IncreaseTo = PartitionOptions.Partition, | |||||
Topic=TopicName | |||||
} | |||||
} | |||||
).Wait(); | |||||
} | |||||
catch (AggregateException ex) | |||||
{ | |||||
//记录日志 | |||||
// throw ex.InnerException; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return producerBuilder.Build(); | |||||
} | |||||
protected JT809PartitionProducer( | |||||
IOptions<JT809TopicOptions> topicOptionAccessor, | |||||
ProducerConfig producerConfig, | |||||
IJT809ProducerPartitionFactory producerPartitionFactory, | |||||
IOptions<JT809PartitionOptions> partitionOptionsAccessor) | |||||
: base(topicOptionAccessor.Value.TopicName, producerConfig) | |||||
{ | |||||
PartitionOptions = partitionOptionsAccessor.Value; | |||||
ProducerPartitionFactory = producerPartitionFactory; | |||||
Producer = CreateProducer(); | |||||
} | |||||
public override void Dispose() | |||||
{ | |||||
Dispose(true); | |||||
GC.SuppressFinalize(true); | |||||
} | |||||
protected virtual void Dispose(bool disposing) | |||||
{ | |||||
if (_disposed) return; | |||||
if (disposing) | |||||
{ | |||||
Producer.Dispose(); | |||||
} | |||||
_disposed = true; | |||||
} | |||||
public override async void ProduceAsync(string msgId, string vno_color, T data) | |||||
{ | |||||
if (_disposed) return; | |||||
if (PartitionOptions != null) | |||||
{ | |||||
if (PartitionOptions.Partition > 1) | |||||
{ | |||||
if (!TopicPartitionCache.TryGetValue(vno_color, out TopicPartition topicPartition)) | |||||
{ | |||||
topicPartition = new TopicPartition(TopicName, new Partition(ProducerPartitionFactory.CreatePartition(TopicName, msgId, vno_color))); | |||||
TopicPartitionCache.TryAdd(vno_color, topicPartition); | |||||
} | |||||
await Producer.ProduceAsync(topicPartition, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
await Producer.ProduceAsync(TopicName, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
await Producer.ProduceAsync(TopicName, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
} | |||||
~JT809PartitionProducer() | |||||
{ | |||||
Dispose(false); | |||||
} | |||||
} | |||||
} |
@@ -1,6 +1,7 @@ | |||||
using Confluent.Kafka; | using Confluent.Kafka; | ||||
using Confluent.Kafka.Admin; | using Confluent.Kafka.Admin; | ||||
using JT809.PubSub.Abstractions; | using JT809.PubSub.Abstractions; | ||||
using Microsoft.Extensions.Options; | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -11,79 +12,31 @@ namespace JT809.KafkaService | |||||
/// | /// | ||||
/// </summary> | /// </summary> | ||||
/// <typeparam name="T"></typeparam> | /// <typeparam name="T"></typeparam> | ||||
public abstract class JT809Producer<T> : IJT809ProducerOfT<T> | |||||
public abstract class JT809Producer<T> : JT809ProducerBase<T> | |||||
{ | { | ||||
private bool _disposed = false; | private bool _disposed = false; | ||||
public virtual string TopicName => JT809Constants.JT809TopicName; | |||||
private ConcurrentDictionary<string, TopicPartition> TopicPartitionCache; | |||||
private IProducer<string, T> producer; | |||||
protected virtual Serializer<T> Serializer { get; } | |||||
protected virtual IJT809ProducerPartitionFactory ProducerPartitionFactory { get; } | |||||
protected virtual JT809PartitionOptions PartitionOptions { get; } | |||||
protected JT809Producer(ProducerConfig producerConfig) | |||||
protected virtual IProducer<string, T> CreateProducer() | |||||
{ | { | ||||
ProducerBuilder<string, T> producerBuilder= new ProducerBuilder<string, T>(producerConfig); | |||||
ProducerBuilder<string, T> producerBuilder = new ProducerBuilder<string, T>(ProducerConfig); | |||||
if (Serializer != null) | if (Serializer != null) | ||||
{ | { | ||||
producerBuilder.SetValueSerializer(Serializer); | producerBuilder.SetValueSerializer(Serializer); | ||||
} | } | ||||
producer = producerBuilder.Build(); | |||||
if (PartitionOptions != null) | |||||
{ | |||||
TopicPartitionCache = new ConcurrentDictionary<string, TopicPartition>(); | |||||
if (PartitionOptions.Partition > 1) | |||||
{ | |||||
using (var adminClient = new AdminClient(producer.Handle)) | |||||
{ | |||||
try | |||||
{ | |||||
adminClient.CreateTopicsAsync(new TopicSpecification[] { new TopicSpecification { Name = TopicName, NumPartitions = 1, ReplicationFactor = 1 } }).Wait(); | |||||
} | |||||
catch (AggregateException ex) | |||||
{ | |||||
//{Confluent.Kafka.Admin.CreateTopicsException: An error occurred creating topics: [jt809]: [Topic 'jt809' already exists.].} | |||||
if (ex.InnerException is Confluent.Kafka.Admin.CreateTopicsException exception) | |||||
{ | |||||
return producerBuilder.Build(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
//记录日志 | |||||
//throw ex.InnerException; | |||||
} | |||||
} | |||||
try | |||||
{ | |||||
//topic IncreaseTo 只增不减 | |||||
adminClient.CreatePartitionsAsync( | |||||
new List<PartitionsSpecification> | |||||
{ | |||||
new PartitionsSpecification | |||||
{ | |||||
IncreaseTo = PartitionOptions.Partition, | |||||
Topic=TopicName | |||||
} | |||||
} | |||||
).Wait(); | |||||
} | |||||
catch (AggregateException ex) | |||||
{ | |||||
//记录日志 | |||||
// throw ex.InnerException; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
protected override IProducer<string, T> Producer { get; } | |||||
protected JT809Producer( | |||||
IOptions<JT809TopicOptions> topicOptionAccessor, | |||||
IOptions<ProducerConfig> producerConfigAccessor) | |||||
: base(topicOptionAccessor.Value.TopicName, producerConfigAccessor.Value) | |||||
{ | |||||
Producer = CreateProducer(); | |||||
} | } | ||||
public void Dispose() | |||||
public override void Dispose() | |||||
{ | { | ||||
Dispose(true); | Dispose(true); | ||||
GC.SuppressFinalize(true); | GC.SuppressFinalize(true); | ||||
@@ -95,46 +48,19 @@ namespace JT809.KafkaService | |||||
if (_disposed) return; | if (_disposed) return; | ||||
if (disposing) | if (disposing) | ||||
{ | { | ||||
producer.Dispose(); | |||||
Producer.Dispose(); | |||||
} | } | ||||
_disposed = true; | _disposed = true; | ||||
} | } | ||||
public void ProduceAsync(string msgId, string vno_color, T data) | |||||
public override async void ProduceAsync(string msgId, string vno_color, T data) | |||||
{ | { | ||||
if (_disposed) return; | if (_disposed) return; | ||||
if (PartitionOptions != null) | |||||
await Producer.ProduceAsync(TopicName, new Message<string, T> | |||||
{ | { | ||||
if (PartitionOptions.Partition > 1) | |||||
{ | |||||
if (!TopicPartitionCache.TryGetValue(vno_color, out TopicPartition topicPartition)) | |||||
{ | |||||
topicPartition = new TopicPartition(TopicName, new Partition(ProducerPartitionFactory.CreatePartition(TopicName, msgId, vno_color))); | |||||
TopicPartitionCache.TryAdd(vno_color, topicPartition); | |||||
} | |||||
producer.ProduceAsync(topicPartition, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
producer.ProduceAsync(TopicName, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
producer.ProduceAsync(TopicName, new Message<string, T> | |||||
{ | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | |||||
Key = msgId, | |||||
Value = data | |||||
}); | |||||
} | } | ||||
~JT809Producer() | ~JT809Producer() | ||||
@@ -0,0 +1,24 @@ | |||||
using Confluent.Kafka; | |||||
using JT809.PubSub.Abstractions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT809.KafkaService | |||||
{ | |||||
public abstract class JT809ProducerBase<T> : IJT809ProducerOfT<T> | |||||
{ | |||||
protected JT809ProducerBase(string topicName,ProducerConfig config) | |||||
{ | |||||
ProducerConfig = config; | |||||
TopicName = topicName; | |||||
} | |||||
public ProducerConfig ProducerConfig { get;} | |||||
public string TopicName { get; } | |||||
protected abstract IProducer<string, T> Producer { get;} | |||||
protected virtual Serializer<T> Serializer { get; set; } | |||||
public abstract void Dispose(); | |||||
public abstract void ProduceAsync(string msgId, string vno_color, T data); | |||||
} | |||||
} |
@@ -8,26 +8,10 @@ namespace JT809.KafkaService | |||||
{ | { | ||||
public sealed class JT809_GpsPositio_Producer : JT809Producer<JT809GpsPosition> | public sealed class JT809_GpsPositio_Producer : JT809Producer<JT809GpsPosition> | ||||
{ | { | ||||
protected override IJT809ProducerPartitionFactory ProducerPartitionFactory { get; } | |||||
protected override Serializer<JT809GpsPosition> Serializer => (position) => position.ToByteArray(); | |||||
protected override JT809PartitionOptions PartitionOptions { get; } | |||||
public JT809_GpsPositio_Producer(IOptions<ProducerConfig> producerConfigAccessor) | |||||
: this(producerConfigAccessor,null, null ) | |||||
public JT809_GpsPositio_Producer(IOptions<JT809TopicOptions> topicOptionAccessor, IOptions<ProducerConfig> producerConfigAccessor) : base(topicOptionAccessor, producerConfigAccessor) | |||||
{ | { | ||||
} | } | ||||
public JT809_GpsPositio_Producer( | |||||
IOptions<ProducerConfig> producerConfigAccessor, | |||||
IJT809ProducerPartitionFactory partitionFactory, | |||||
IOptions<JT809PartitionOptions> partitionAccessor | |||||
):base(producerConfigAccessor.Value) | |||||
{ | |||||
ProducerPartitionFactory = partitionFactory; | |||||
PartitionOptions = partitionAccessor?.Value; | |||||
} | |||||
protected override Serializer<JT809GpsPosition> Serializer => (position) => position.ToByteArray(); | |||||
} | } | ||||
} | } |
@@ -13,26 +13,14 @@ namespace JT809.KafkaService | |||||
{ | { | ||||
public sealed class JT809_GpsPosition_Consumer : JT809Consumer<JT809GpsPosition> | public sealed class JT809_GpsPosition_Consumer : JT809Consumer<JT809GpsPosition> | ||||
{ | { | ||||
public JT809_GpsPosition_Consumer(IOptions<JT809TopicOptions> topicOptionsAccessor, IOptions<ConsumerConfig> consumerConfigAccessor, ILoggerFactory loggerFactory) : base(topicOptionsAccessor, consumerConfigAccessor, loggerFactory) | |||||
{ | |||||
} | |||||
protected override Deserializer<JT809GpsPosition> Deserializer => (data, isNull) => { | protected override Deserializer<JT809GpsPosition> Deserializer => (data, isNull) => { | ||||
if (isNull) return default; | if (isNull) return default; | ||||
return new MessageParser<JT809GpsPosition>(() => new JT809GpsPosition()) | return new MessageParser<JT809GpsPosition>(() => new JT809GpsPosition()) | ||||
.ParseFrom(data.ToArray()); | .ParseFrom(data.ToArray()); | ||||
}; | }; | ||||
protected override JT809PartitionOptions PartitionOptions { get; } | |||||
public JT809_GpsPosition_Consumer( | |||||
IOptions<ConsumerConfig> consumerConfigAccessor, | |||||
ILoggerFactory loggerFactory, | |||||
IOptions<JT809PartitionOptions> partitionAccessor | |||||
) : base(consumerConfigAccessor.Value, loggerFactory) | |||||
{ | |||||
PartitionOptions = partitionAccessor.Value; | |||||
} | |||||
public JT809_GpsPosition_Consumer(IOptions<ConsumerConfig> consumerConfigAccessor,ILoggerFactory loggerFactory) | |||||
: this(consumerConfigAccessor,loggerFactory, new JT809PartitionOptions()) | |||||
{ | |||||
} | |||||
} | } | ||||
} | } |
@@ -13,19 +13,8 @@ namespace JT809.KafkaService | |||||
{ | { | ||||
public sealed class JT809_Same_Consumer : JT809Consumer<byte[]> | public sealed class JT809_Same_Consumer : JT809Consumer<byte[]> | ||||
{ | { | ||||
protected override JT809PartitionOptions PartitionOptions { get; } | |||||
public JT809_Same_Consumer( | |||||
IOptions<ConsumerConfig> consumerConfigAccessor, | |||||
ILoggerFactory loggerFactory, | |||||
IOptions<JT809PartitionOptions> partitionAccessor | |||||
) : base(consumerConfigAccessor.Value, loggerFactory) | |||||
{ | |||||
PartitionOptions = partitionAccessor.Value; | |||||
} | |||||
public JT809_Same_Consumer(IOptions<ConsumerConfig> consumerConfigAccessor,ILoggerFactory loggerFactory) | |||||
: this(consumerConfigAccessor,loggerFactory, new JT809PartitionOptions()) | |||||
public JT809_Same_Consumer(IOptions<JT809TopicOptions> topicOptionsAccessor, IOptions<ConsumerConfig> consumerConfigAccessor, ILoggerFactory loggerFactory) | |||||
: base(topicOptionsAccessor, consumerConfigAccessor, loggerFactory) | |||||
{ | { | ||||
} | } | ||||
} | } | ||||
@@ -9,24 +9,9 @@ namespace JT809.KafkaService | |||||
{ | { | ||||
public sealed class JT809_Same_Producer : JT809Producer<byte[]> | public sealed class JT809_Same_Producer : JT809Producer<byte[]> | ||||
{ | { | ||||
protected override IJT809ProducerPartitionFactory ProducerPartitionFactory { get; } | |||||
protected override JT809PartitionOptions PartitionOptions { get; } | |||||
public JT809_Same_Producer(IOptions<ProducerConfig> producerConfigAccessor) | |||||
: this(producerConfigAccessor, null, null) | |||||
{ | |||||
} | |||||
public JT809_Same_Producer( | |||||
IOptions<ProducerConfig> producerConfigAccessor, | |||||
IJT809ProducerPartitionFactory partitionFactory, | |||||
IOptions<JT809PartitionOptions> partitionAccessor | |||||
):base(producerConfigAccessor.Value) | |||||
public JT809_Same_Producer(IOptions<JT809TopicOptions> topicOptionAccessor, IOptions<ProducerConfig> producerConfigAccessor) | |||||
: base(topicOptionAccessor, producerConfigAccessor) | |||||
{ | { | ||||
ProducerPartitionFactory = partitionFactory; | |||||
PartitionOptions = partitionAccessor?.Value; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,42 @@ | |||||
using System; | |||||
using Xunit; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using JT809.Protocol.Enums; | |||||
using JT809.Protocol.Extensions; | |||||
using System.Threading; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class ConsumerTest: TestConsumerBase | |||||
{ | |||||
[Fact] | |||||
public void Test1() | |||||
{ | |||||
ConsumerTestService producerTestService = ServiceProvider.GetRequiredService<ConsumerTestService>(); | |||||
producerTestService.GpsConsumer.OnMessage((Message)=> | |||||
{ | |||||
Assert.Equal(JT809SubBusinessType.实时上传车辆定位信息.ToValueString(), Message.MsgId); | |||||
Assert.Equal("粤A23456", Message.Data.Vno); | |||||
Assert.Equal(2, Message.Data.VColor); | |||||
Assert.Equal("smallchi", Message.Data.FromChannel); | |||||
}); | |||||
producerTestService.GpsConsumer.Subscribe(); | |||||
Thread.Sleep(100000); | |||||
} | |||||
[Fact] | |||||
public void Test2() | |||||
{ | |||||
ConsumerTestService producerTestService = ServiceProvider.GetRequiredService<ConsumerTestService>(); | |||||
producerTestService.SameConsumer.OnMessage((Message) => | |||||
{ | |||||
Assert.Equal(JT809SubBusinessType.None.ToValueString(), Message.MsgId); | |||||
Assert.Equal(new byte[] { 0x01, 0x02, 0x03 }, Message.Data); | |||||
}); | |||||
producerTestService.SameConsumer.Subscribe(); | |||||
Thread.Sleep(100000); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
using JT809.GrpcProtos; | |||||
using JT809.KafkaService; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class ConsumerTestService | |||||
{ | |||||
public JT809Consumer<byte[]> SameConsumer { get; } | |||||
public JT809Consumer<JT809GpsPosition> GpsConsumer { get; } | |||||
public ConsumerTestService(JT809Consumer<byte[]> sameConsumer, JT809Consumer<JT809GpsPosition> gpsConsumer) | |||||
{ | |||||
SameConsumer = sameConsumer; | |||||
GpsConsumer = gpsConsumer; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netcoreapp2.2</TargetFramework> | |||||
<IsPackable>false</IsPackable> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="JT809" Version="1.2.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> | |||||
<PackageReference Include="xunit" Version="2.4.1" /> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"> | |||||
<PrivateAssets>all</PrivateAssets> | |||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
</PackageReference> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\JT809.KafkaService\JT809.KafkaService.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Update="appsettings.Partition.json"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="appsettings.json"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,38 @@ | |||||
using System; | |||||
using Xunit; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using JT809.Protocol.Enums; | |||||
using JT809.Protocol.Extensions; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class ProducerPartitionTest : TestProducerBase | |||||
{ | |||||
[Fact] | |||||
public void Test1() | |||||
{ | |||||
ProducerTestService producerTestService = ServiceProvider.GetRequiredService<ProducerTestService>(); | |||||
producerTestService.GpsProducer.ProduceAsync(JT809SubBusinessType.实时上传车辆定位信息.ToValueString(), "粤A23456_2", new GrpcProtos.JT809GpsPosition | |||||
{ | |||||
Vno= "粤A23456", | |||||
VColor=2, | |||||
GpsTime= (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000, | |||||
FromChannel="smallchi" | |||||
}); | |||||
} | |||||
[Fact] | |||||
public void Test2() | |||||
{ | |||||
ProducerTestService producerTestService = ServiceProvider.GetRequiredService<ProducerTestService>(); | |||||
producerTestService.SameProducer.ProduceAsync(JT809SubBusinessType.None.ToValueString(), "粤A23457_2", new byte[] { 0x01, 0x02, 0x03 }); | |||||
} | |||||
[Fact] | |||||
public void Test3() | |||||
{ | |||||
ProducerTestService producerTestService = ServiceProvider.GetRequiredService<ProducerTestService>(); | |||||
producerTestService.SameProducer.ProduceAsync(JT809SubBusinessType.None.ToValueString(), "粤A23457_2", new byte[] { 0x01, 0x02, 0x03 }); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
using System; | |||||
using Xunit; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using JT809.Protocol.Enums; | |||||
using JT809.Protocol.Extensions; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class ProducerTest: TestProducerBase | |||||
{ | |||||
[Fact] | |||||
public void Test1() | |||||
{ | |||||
ProducerTestService producerTestService = ServiceProvider.GetRequiredService<ProducerTestService>(); | |||||
producerTestService.GpsProducer.ProduceAsync(JT809SubBusinessType.实时上传车辆定位信息.ToValueString(), "粤A23456_2", new GrpcProtos.JT809GpsPosition | |||||
{ | |||||
Vno= "粤A23456", | |||||
VColor=2, | |||||
GpsTime= (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000, | |||||
FromChannel="smallchi" | |||||
}); | |||||
} | |||||
[Fact] | |||||
public void Test2() | |||||
{ | |||||
ProducerTestService producerTestService = ServiceProvider.GetRequiredService<ProducerTestService>(); | |||||
producerTestService.SameProducer.ProduceAsync(JT809SubBusinessType.None.ToValueString(), "粤A23457_2", new byte[] { 0x01, 0x02, 0x03 }); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
using JT809.GrpcProtos; | |||||
using JT809.KafkaService; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class ProducerTestService | |||||
{ | |||||
public JT809Producer<byte[]> SameProducer { get; } | |||||
public JT809Producer<JT809GpsPosition> GpsProducer { get; } | |||||
public ProducerTestService(JT809Producer<byte[]> sameProducer, JT809Producer<JT809GpsPosition> gpsProducer) | |||||
{ | |||||
SameProducer = sameProducer; | |||||
GpsProducer = gpsProducer; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using JT809.KafkaService; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class TestConsumerBase | |||||
{ | |||||
public IServiceProvider ServiceProvider { get; } | |||||
public IConfigurationRoot ConfigurationRoot { get; } | |||||
public TestConsumerBase() | |||||
{ | |||||
var builder = new ConfigurationBuilder(); | |||||
builder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); | |||||
builder.AddJsonFile("appsettings.json"); | |||||
ConfigurationRoot = builder.Build(); | |||||
ServiceCollection serviceDescriptors = new ServiceCollection(); | |||||
serviceDescriptors.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||||
serviceDescriptors.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||||
serviceDescriptors.AddLogging(configure => | |||||
{ | |||||
configure.AddDebug(); | |||||
configure.SetMinimumLevel(LogLevel.Trace); | |||||
}); | |||||
serviceDescriptors.AddJT809KafkaConsumerService(ConfigurationRoot); | |||||
serviceDescriptors.AddSingleton<ConsumerTestService>(); | |||||
ServiceProvider = serviceDescriptors.BuildServiceProvider(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using JT809.KafkaService; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class TestProducerBase | |||||
{ | |||||
public IServiceProvider ServiceProvider { get; } | |||||
public IConfigurationRoot ConfigurationRoot { get; } | |||||
public TestProducerBase() | |||||
{ | |||||
var builder = new ConfigurationBuilder(); | |||||
builder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); | |||||
builder.AddJsonFile("appsettings.json"); | |||||
ConfigurationRoot = builder.Build(); | |||||
ServiceCollection serviceDescriptors = new ServiceCollection(); | |||||
serviceDescriptors.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||||
serviceDescriptors.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||||
serviceDescriptors.AddLogging(configure => | |||||
{ | |||||
configure.AddDebug(); | |||||
configure.SetMinimumLevel(LogLevel.Trace); | |||||
}); | |||||
serviceDescriptors.AddJT809KafkaProducerService(ConfigurationRoot); | |||||
serviceDescriptors.AddSingleton<ProducerTestService>(); | |||||
ServiceProvider = serviceDescriptors.BuildServiceProvider(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using JT809.KafkaService; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace JT809.KafkaServiceTest | |||||
{ | |||||
public class TestProducerPartitionBase | |||||
{ | |||||
public IServiceProvider ServiceProvider { get; } | |||||
public IConfigurationRoot ConfigurationRoot { get; } | |||||
public TestProducerPartitionBase() | |||||
{ | |||||
var builder = new ConfigurationBuilder(); | |||||
builder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); | |||||
builder.AddJsonFile("appsettings.Partition.json"); | |||||
ConfigurationRoot = builder.Build(); | |||||
ServiceCollection serviceDescriptors = new ServiceCollection(); | |||||
serviceDescriptors.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||||
serviceDescriptors.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||||
serviceDescriptors.AddLogging(configure => | |||||
{ | |||||
configure.AddDebug(); | |||||
configure.SetMinimumLevel(LogLevel.Trace); | |||||
}); | |||||
serviceDescriptors.AddJT809KafkaProducerService(ConfigurationRoot); | |||||
serviceDescriptors.AddSingleton<ProducerTestService>(); | |||||
ServiceProvider = serviceDescriptors.BuildServiceProvider(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
{ | |||||
"Logging": { | |||||
"IncludeScopes": false, | |||||
"Debug": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
}, | |||||
"Console": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
} | |||||
}, | |||||
"KafkaProducerConfig": { | |||||
"BootstrapServers": "127.0.0.1:9092" | |||||
}, | |||||
"KafkaConsumerConfig": { | |||||
"BootstrapServers": "127.0.0.1:9092", | |||||
"EnableAutoCommit": true, | |||||
"GroupId": "JT809.Gps.Test" | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
{ | |||||
"Logging": { | |||||
"IncludeScopes": false, | |||||
"Debug": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
}, | |||||
"Console": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
} | |||||
}, | |||||
"KafkaProducerConfig": { | |||||
"BootstrapServers": "127.0.0.1:9092" | |||||
}, | |||||
"KafkaConsumerConfig": { | |||||
"BootstrapServers": "127.0.0.1:9092", | |||||
"EnableAutoCommit": true, | |||||
"GroupId": "JT809.Gps.Test" | |||||
} | |||||
} |
@@ -11,7 +11,7 @@ namespace JT809.PubSub.Abstractions | |||||
} | } | ||||
public interface IJT808ConsumerOfT<T> :IDisposable | public interface IJT808ConsumerOfT<T> :IDisposable | ||||
{ | { | ||||
void OnMessage(Action<(string MsgId, T data)> callback); | |||||
void OnMessage(Action<(string MsgId, T Data)> callback); | |||||
CancellationTokenSource Cts { get; } | CancellationTokenSource Cts { get; } | ||||
void Subscribe(); | void Subscribe(); | ||||
void Unsubscribe(); | void Unsubscribe(); | ||||
@@ -0,0 +1,14 @@ | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT809.PubSub.Abstractions | |||||
{ | |||||
public class JT809TopicOptions:IOptions<JT809TopicOptions> | |||||
{ | |||||
public string TopicName { get; set; } = JT809Constants.JT809TopicName; | |||||
public JT809TopicOptions Value => this; | |||||
} | |||||
} |
@@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT809.GrpcProtos", "JT809.D | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT809.KafkaService", "JT809.DotNetty.Simples\Superior\JT809.KafkaService\JT809.KafkaService.csproj", "{8119D905-241F-4EFF-B300-1FB474B8C665}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT809.KafkaService", "JT809.DotNetty.Simples\Superior\JT809.KafkaService\JT809.KafkaService.csproj", "{8119D905-241F-4EFF-B300-1FB474B8C665}" | ||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JT809.KafkaServiceTest", "JT809.DotNetty.Simples\Superior\JT809.KafkaServiceTest\JT809.KafkaServiceTest.csproj", "{22F008D5-61F8-4889-80DB-91B37591322F}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -59,6 +61,10 @@ Global | |||||
{8119D905-241F-4EFF-B300-1FB474B8C665}.Debug|Any CPU.Build.0 = Debug|Any CPU | {8119D905-241F-4EFF-B300-1FB474B8C665}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{8119D905-241F-4EFF-B300-1FB474B8C665}.Release|Any CPU.ActiveCfg = Release|Any CPU | {8119D905-241F-4EFF-B300-1FB474B8C665}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{8119D905-241F-4EFF-B300-1FB474B8C665}.Release|Any CPU.Build.0 = Release|Any CPU | {8119D905-241F-4EFF-B300-1FB474B8C665}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{22F008D5-61F8-4889-80DB-91B37591322F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{22F008D5-61F8-4889-80DB-91B37591322F}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{22F008D5-61F8-4889-80DB-91B37591322F}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{22F008D5-61F8-4889-80DB-91B37591322F}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -70,6 +76,7 @@ Global | |||||
{5F8AFD67-FDA8-40A6-A655-FD855E2CCF26} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | {5F8AFD67-FDA8-40A6-A655-FD855E2CCF26} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | ||||
{D64F2F77-DC0C-4120-80DA-45012A794CDF} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | {D64F2F77-DC0C-4120-80DA-45012A794CDF} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | ||||
{8119D905-241F-4EFF-B300-1FB474B8C665} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | {8119D905-241F-4EFF-B300-1FB474B8C665} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | ||||
{22F008D5-61F8-4889-80DB-91B37591322F} = {E9DC871D-EFCE-4D53-A5B5-8A88D2D52EA4} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {0FC2A52E-3B7A-4485-9C3B-9080C825419D} | SolutionGuid = {0FC2A52E-3B7A-4485-9C3B-9080C825419D} | ||||