Compare commits

...

4 Commits

21 changed files with 307 additions and 89 deletions

View File

@ -9,7 +9,7 @@
public static class Topics
{
// ReSharper disable once InconsistentNaming
private const string SN = "5506771257";
private const string SN = "G5506771257";
private const string Version = "1.0.0";

View File

@ -1,22 +1,42 @@
namespace MasstransferCommon.Scheduler;
using Serilog;
namespace MasstransferCommon.Scheduler;
/// <summary>
/// 延时定时任务
/// </summary>
public class DelayScheduler
{
private Timer _timer;
private Action _action;
public void Schedule(Action action, TimeSpan delay)
/// <summary>
/// 设定延时任务
/// </summary>
/// <param name="action"></param>
/// <param name="delay"></param>
/// <param name="cancellationToken"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static async void Delay(Action action, TimeSpan delay, CancellationToken cancellationToken = default)
{
_action = action;
_timer = new Timer(TimerCallback, null, delay, Timeout.InfiniteTimeSpan);
try
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (delay.TotalMilliseconds < 0)
throw new ArgumentOutOfRangeException(nameof(delay), "延时时间不能为负数");
await Task.Delay(delay, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return;
}
private void TimerCallback(object? state)
action();
}
catch (Exception e)
{
_timer?.Dispose();
_action?.Invoke();
if (e is not TaskCanceledException)
{
Log.Error(e, "延时任务执行失败");
}
}
}
}

View File

@ -0,0 +1,9 @@
namespace MasstransferCommon.Atrributes;
public interface Instant
{
/// <summary>
/// 初始化对象后进行回调
/// </summary>
void Initialized();
}

View File

@ -28,11 +28,15 @@ public class JsonUtil
}
}
public static T FromJson<T>(string json)
public static T? FromJson<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.DeserializeObject<T>(json, settings);
}
catch (Exception e)
{
@ -52,7 +56,7 @@ public class JsonUtil
}
}
public static T FromJsonOrDefault<T>(string json)
public static T? FromJsonOrDefault<T>(string json)
{
try
{

View File

@ -22,11 +22,11 @@ public static class RegistryHelper
public static void DeleteValue(string keyPath, string valueName)
{
using var key = Registry.CurrentUser.OpenSubKey(keyPath, writable: true);
key?.DeleteValue(valueName);
key?.DeleteValue(valueName, false);
}
public static void DeleteKey(string keyPath)
{
Registry.CurrentUser.DeleteSubKeyTree(keyPath, throwOnMissingSubKey: false);
Registry.CurrentUser.DeleteSubKeyTree(keyPath, false);
}
}

View File

@ -0,0 +1,103 @@
namespace MasstransferCommon.Utils;
using System;
public class SnowflakeId
{
private static readonly DateTime Epoch = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const int WorkerIdBits = 5;
private const int DatacenterIdBits = 5;
private const int SequenceBits = 12;
private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
private const long WorkerIdShift = SequenceBits;
private const long DatacenterIdShift = SequenceBits + WorkerIdBits;
private const long TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
private const long SequenceMask = -1L ^ (-1L << SequenceBits);
private readonly object _lock = new object();
private long _lastTimestamp = -1L;
private long _sequence = 0L;
private static readonly SnowflakeId Instance = new SnowflakeId(0, 0);
public long WorkerId { get; }
public long DatacenterId { get; }
public SnowflakeId(long workerId, long datacenterId)
{
if (workerId > MaxWorkerId || workerId < 0)
{
throw new ArgumentException($"workerId must be between 0 and {MaxWorkerId}");
}
if (datacenterId > MaxDatacenterId || datacenterId < 0)
{
throw new ArgumentException($"datacenterId must be between 0 and {MaxDatacenterId}");
}
WorkerId = workerId;
DatacenterId = datacenterId;
}
public static string GetNextId()
{
return Instance.NextId().ToString();
}
private long NextId()
{
lock (_lock)
{
var timestamp = TimeGen();
if (timestamp < _lastTimestamp)
{
throw new InvalidOperationException("Clock moved backwards. Refusing to generate id");
}
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & SequenceMask;
if (_sequence == 0)
{
timestamp = TilNextMillis(_lastTimestamp);
}
}
else
{
_sequence = 0L;
}
_lastTimestamp = timestamp;
return ((timestamp - EpochTicks()) << (int)TimestampLeftShift) |
(DatacenterId << (int)DatacenterIdShift) |
(WorkerId << (int)WorkerIdShift) |
_sequence;
}
}
private long TilNextMillis(long lastTimestamp)
{
var timestamp = TimeGen();
while (timestamp <= lastTimestamp)
{
timestamp = TimeGen();
}
return timestamp;
}
private long TimeGen()
{
return (long)(DateTime.UtcNow - Epoch).TotalMilliseconds;
}
private static long EpochTicks()
{
return (long)(Epoch - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}
}

View File

@ -1,4 +1,5 @@
using MasstransferCommon.Events;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Events;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Mqtt.Client;
@ -10,17 +11,12 @@ namespace MasstransferExporter.DataExporter;
/// <summary>
/// 配置服务
/// </summary>
public class ConfigService
public class ConfigService : Instant
{
static ConfigService()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, async () => { await ListenConfigIssuedEvent(); });
}
/// <summary>
/// 监听配置下发事件
/// </summary>
public static async Task ListenConfigIssuedEvent()
private static async Task ListenConfigIssuedEvent(EventType type, bool start)
{
await MessageQueueHelper.Subscribe(Topics.DownloadConfigData, HandleConfigIssuedEvent);
}
@ -54,4 +50,9 @@ public class ConfigService
}
}
}
public void Initialized()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, ListenConfigIssuedEvent);
}
}

View File

@ -1,4 +1,5 @@
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Minio;
using MasstransferCommunicate.Mqtt.Client;
@ -10,7 +11,7 @@ namespace MasstransferExporter.DataExporter;
/// <summary>
/// 坐标信息业务类
/// </summary>
public class CoordinateService
public class CoordinateService : Instant
{
private static readonly SqliteHelper Db = SqliteHelper.GetInstance();
@ -111,4 +112,8 @@ public class CoordinateService
};
await MessageQueueHelper.Publish(Topics.CoordinateUpload, data);
}
public void Initialized()
{
}
}

View File

@ -1,4 +1,5 @@
using System.Drawing;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Events;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Utils;
@ -10,7 +11,7 @@ using MasstransferInfrastructure.Database.Sqlite;
namespace MasstransferExporter.ImageExporter;
public class ImageService
public class ImageService : Instant
{
private static readonly MinioHelper Minio = MinioHelper.GetInstance();
@ -19,15 +20,10 @@ public class ImageService
private const string BasePath = "masstransfer";
static ImageService()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, async () => { await ListenImageQueryEvent(); });
}
/// <summary>
/// 监听图片查询事件
/// </summary>
public static async Task ListenImageQueryEvent()
private static async Task ListenImageQueryEvent(EventType type, bool start)
{
await MessageQueueHelper.Subscribe(Topics.UpdateLicenseEvent, HandleImageQueryEvent);
}
@ -265,4 +261,9 @@ public class ImageService
Console.WriteLine($"图片导出失败,{e}");
}
}
public void Initialized()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, ListenImageQueryEvent);
}
}

View File

@ -0,0 +1,33 @@
using System.Reflection;
using MasstransferCommon.Atrributes;
namespace MasstransferExporter.Init;
public class InstantUtil
{
/// <summary>
/// 启动是对象的初始化
/// </summary>
public static void Init()
{
// 获取当前程序集
var currentAssembly = Assembly.GetExecutingAssembly();
// 获取所有类型
var types = currentAssembly.GetTypes();
// 找到实现了 Instant 接口的类型
var instantImplementations =
types.Where(t => typeof(Instant).IsAssignableFrom(t) && t is { IsInterface: false, IsAbstract: false });
foreach (var type in instantImplementations)
{
// 创建实例
if (Activator.CreateInstance(type) is Instant instance)
{
// 调用 Initialized 方法
instance.Initialized();
}
}
}
}

View File

@ -1,4 +1,5 @@
using MasstransferCommon.Events;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Events;
using MasstransferCommon.Model.Constant;
using MasstransferCommunicate.Mqtt.Client;
using MasstransferCommunicate.Process.Client;
@ -8,17 +9,12 @@ namespace MasstransferExporter.License;
/// <summary>
/// 证书业务
/// </summary>
public class LicenseService
public class LicenseService : Instant
{
static LicenseService()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, async () => { await ListenLicenseUpdateEvent(); });
}
/// <summary>
/// 启动监听证书更新事件
/// </summary>
public static async Task ListenLicenseUpdateEvent()
private static async Task ListenLicenseUpdateEvent(EventType type, bool start)
{
await MessageQueueHelper.Subscribe(Topics.UpdateLicenseEvent, HandleUpdateLicenseEvent);
}
@ -42,4 +38,9 @@ public class LicenseService
{
await MessageQueueHelper.Publish(Topics.UpdateLicenseEventFeedback, result);
}
public void Initialized()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, ListenLicenseUpdateEvent);
}
}

View File

@ -1,21 +1,23 @@
namespace MasstransferExporter.LogExporter.Model;
using Newtonsoft.Json;
namespace MasstransferExporter.LogExporter.Model;
/// <summary>
/// 用户操作日志
/// </summary>
public class OperationLogData
{
public string UserName { get; set; }
[JsonProperty("userName")] public string UserName { get; set; }
public DateTime ControlTimestamp { get; set; }
[JsonProperty("controlTimestamp")] public DateTime ControlTimestamp { get; set; }
public string ControlType { get; set; }
[JsonProperty("controlType")] public string ControlType { get; set; }
public string ControlResult { get; set; }
[JsonProperty("controlResult")] public string ControlResult { get; set; }
public string ControlTarget { get; set; }
[JsonProperty("controlTarget")] public string ControlTarget { get; set; }
public string ControlParams { get; set; }
[JsonProperty("controlParams")] public string ControlParams { get; set; }
public string ControlMessage { get; set; }
[JsonProperty("controlMessage")] public string ControlMessage { get; set; }
}

View File

@ -1,4 +1,5 @@
using MasstransferCommon.Events;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Events;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Mqtt.Client;
@ -9,20 +10,15 @@ using MasstransferInfrastructure.Database.Sqlite;
namespace MasstransferExporter.OTA.Service;
public class OTAService
public class OTAService : Instant
{
private static readonly SqliteHelper Db = SqliteHelper.GetInstance();
private static OTAUpdateFileManager _otaUpdateFileManager;
static OTAService()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, async () => { await StartOTAService(); });
}
/// <summary>
/// 启动OTA服务
/// </summary>
private static async Task StartOTAService()
private static async Task StartOTAService(EventType type, bool start)
{
_otaUpdateFileManager = GetOTAUpdateFileManager();
@ -116,7 +112,7 @@ public class OTAService
try
{
//初始化
initDir(criticalBackupDir);
InitDir(criticalBackupDir);
//备份重要文件
OTAClient.BackupEssentialFiles(appDir, criticalBackupDir, criticalFileExtension, criticalSourceLogPath);
@ -167,7 +163,7 @@ public class OTAService
/// <summary>
/// 初始化重要文件备份目录
/// </summary>
private static void initDir(string criticalBackupDir)
private static void InitDir(string criticalBackupDir)
{
if (Directory.Exists(criticalBackupDir))
{
@ -185,7 +181,7 @@ public class OTAService
{
//删除安装过程中创建的文件、目录
var directoryList = new string[] { appDir, criticalBackupDir };
var filePath = new String[] { updatePackagePath };
var filePath = new string[] { updatePackagePath };
foreach (var directory in directoryList)
{
@ -209,4 +205,9 @@ public class OTAService
//删除旧版本备份
OTAClient.DeleteFile(previousBackupPath);
}
public void Initialized()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, StartOTAService);
}
}

View File

@ -1,7 +1,10 @@
using MasstransferCommon.Events;
using MasstransferCommon.Model.Entity;
using MasstransferCommon.Scheduler;
using MasstransferCommunicate.Mqtt.Client;
using MasstransferCommunicate.Process.Client;
using MasstransferExporter.Init;
using MasstransferExporter.LogExporter;
using MasstransferInfrastructure.Database.Sqlite;
using MasstransferInfrastructure.Mqtt.Model;
@ -13,6 +16,9 @@ class Program
public static async Task Main()
{
// 进行初始化调用
InstantUtil.Init();
var mqttParams = Db.Query<MqttParams>("select * from mqtt_params").FirstOrDefault();
// 启动mqtt连接
@ -24,14 +30,19 @@ class Program
Password = mqttParams.Password
});
// 启动与主程序的通信
ProcessHelper.Init();
Thread.Sleep(3000);
// 启动完成后,广播启动通知
EventBus<bool>.Publish(EventType.StartUp, true);
DelayScheduler.Delay(async () => { await LogFileExporter.ExportLogFile(); },
TimeSpan.FromSeconds(5));
// 启动与主程序的通信
ProcessHelper.Init();
Console.WriteLine("按任意键退出");
Console.ReadKey();
}
}

View File

@ -1,4 +1,6 @@
namespace MasstransferExporter.RemoteControl.Model;
using Newtonsoft.Json;
namespace MasstransferExporter.RemoteControl.Model;
/// <summary>
/// 锁机指令
@ -8,16 +10,19 @@ public class LockCmd
/// <summary>
/// 0 锁机 1 解锁
/// </summary>
[JsonProperty("action")]
public int Action { get; set; }
/// <summary>
/// 0 立即执行
/// 1 到期执行
/// </summary>
[JsonProperty("lockType")]
public int LockType { get; set; }
/// <summary>
/// 到期时间
/// </summary>
[JsonProperty("expiryTime")]
public long ExpiryTime { get; set; }
}

View File

@ -1,4 +1,5 @@
using MasstransferCommon.Events;
using MasstransferCommon.Atrributes;
using MasstransferCommon.Events;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Model.Enum;
using MasstransferCommon.Utils;
@ -11,20 +12,14 @@ namespace MasstransferExporter.RemoteControl;
/// <summary>
/// 远程锁定服务
/// </summary>
public class RemoteLockService
public class RemoteLockService : Instant
{
private const string KeyPath = @"Software\Masstransfer\Security";
static RemoteLockService()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, async () => { await ListenRemoteLockEvent(); });
}
/// <summary>
/// 监听远程锁机事件
/// </summary>
public static async Task ListenRemoteLockEvent()
private static async Task ListenRemoteLockEvent(EventType type, bool start)
{
await MessageQueueHelper.Subscribe(Topics.RemoteControl, HandleLockCmd);
}
@ -43,6 +38,7 @@ public class RemoteLockService
if (action == 0)
{
Console.WriteLine("收到锁机指令");
// 更新注册表锁机状态
var lockType = cmd.LockType;
if (lockType == 0)
@ -59,6 +55,7 @@ public class RemoteLockService
}
else
{
Console.WriteLine("收到解锁指令");
// 解锁
var lockType = cmd.LockType;
if (lockType == 0)
@ -75,4 +72,9 @@ public class RemoteLockService
}
}
}
public void Initialized()
{
EventBus<bool>.AddEventHandler(EventType.StartUp, ListenRemoteLockEvent);
}
}

View File

@ -57,6 +57,8 @@ public class SqliteHelper
/// <typeparam name="T">数据类型</typeparam>
public int Insert<T>(T item)
{
CreateTable(item!.GetType());
var id = item?.GetType().GetProperty("Id");
if (id != null && id.CanWrite) id.SetValue(item, SnowFlakeNew.LongId.ToString());

View File

@ -1,5 +1,6 @@
using MasstransferCommon.Model.Entity;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Mqtt.Model;
using MasstransferInfrastructure.Database.Sqlite;
using MasstransferInfrastructure.Mqtt.Model;
using MQTTnet;
@ -64,7 +65,7 @@ public class MessageQueueHelper
/// <param name="topic"></param>
/// <param name="message"></param>
/// <param name="qos"></param>
public static async Task<bool> Publish(string topic, object message,
public static async Task<bool> Publish<T>(string topic, T message,
MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtMostOnce)
{
try
@ -113,9 +114,12 @@ public class MessageQueueHelper
var methodInfo = subscriber.Method;
var parameters = methodInfo.GetParameters();
if (parameters.Length != 2) continue;
var type = parameters[1].ParameterType;
var payload = JsonUtil.FromJson<Payload<object>>(message);
if (payload == null) continue;
// 通知订阅者
subscriber.DynamicInvoke(topic, JsonUtil.FromJson(type, message));
subscriber.DynamicInvoke(topic, JsonUtil.ToJson(payload.Data));
}
catch (Exception exception)
{

View File

@ -1,7 +1,10 @@
using System.Security.Cryptography.X509Certificates;
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Mqtt.Model;
using MasstransferInfrastructure.Mqtt.Model;
using MasstransferSecurity.Utils;
using Masuit.Tools.Systems;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
@ -33,7 +36,7 @@ class MqttClient
return new MqttClientOptionsBuilder()
.WithTcpServer(options.ServerAddress, options.Port)
// .WithCredentials(options.UserName, options.Password)
.WithClientId(clientId)
.WithClientId(Constants.SN)
.WithCleanSession()
.WithTlsOptions(
o =>
@ -95,10 +98,10 @@ class MqttClient
/// 发送消息
/// </summary>
/// <param name="topic"></param>
/// <param name="message"></param>
/// <param name="data"></param>
/// <param name="qos"></param>
/// <returns></returns>
public async Task<bool> Publish(string topic, object message,
public async Task<bool> Publish<T>(string topic, T data,
MqttQualityOfServiceLevel qos)
{
if (_client is not { IsConnected: true })
@ -106,7 +109,14 @@ class MqttClient
return false;
}
var payload = message as string ?? JsonUtil.ToJson(message);
var message = new Payload<T>()
{
MsgId = SnowFlakeNew.LongId.ToString(),
Data = data,
ConsumeTime = DateTime.Now.Ticks.ToString(),
};
var payload = JsonUtil.ToJson(message);
var result = await _client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)

View File

@ -1,8 +1,10 @@
namespace MasstransferInfrastructure.Mqtt.Model;
using MasstransferCommunicate.Mqtt.Model;
public class Message
namespace MasstransferInfrastructure.Mqtt.Model;
public class Message<T>
{
public string Topic { get; set; }
public Payload Payload { get; set; }
public Payload<T> Payload { get; set; }
}

View File

@ -1,10 +1,12 @@
namespace MasstransferInfrastructure.Mqtt.Model;
using Newtonsoft.Json;
public class Payload
namespace MasstransferCommunicate.Mqtt.Model;
public class Payload<T>
{
public string MsgId { get; set; }
[JsonProperty("msgId")] public string MsgId { get; set; }
public DateTime ConsumeTime { get; set; }
[JsonProperty("consumeTime")] public string ConsumeTime { get; set; }
public object Data { get; set; }
[JsonProperty("data")] public T Data { get; set; }
}