Compare commits

...

4 Commits

23 changed files with 484 additions and 7 deletions

View File

@ -8,7 +8,10 @@
<ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="Quartz" Version="3.10.0" />
<PackageReference Include="sqlite-net-sqlcipher" Version="1.9.172" />
<PackageReference Include="System.Management" Version="8.0.0"/>
<PackageReference Include="Serilog" Version="4.0.0"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00972"/>
@ -36,4 +39,8 @@
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<Folder Include="Service\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
namespace MasstransferCommon.Model.Entity;
public enum ChipColorEnum
{
R = 1,
G = 2,
B = 3
}

View File

@ -0,0 +1,15 @@
using SQLite;
namespace MasstransferCommon.Model.Entity;
/// <summary>
/// 数据库实体的父类
/// </summary>
public class Entity
{
[PrimaryKey] public string? Id { get; set; }
[Column("create_time")] public DateTime CreateTime { get; set; } = DateTime.Now;
[Column("update_time")] public DateTime UpdateTime { get; set; } = DateTime.Now;
}

View File

@ -0,0 +1,23 @@
using System.ComponentModel;
using SQLite;
namespace MasstransferCommon.Model.Entity;
/// <summary>
/// 日志参数
/// </summary>
[Table("log_params")]
[Description("日志参数")]
public class LogParams : Entity
{
[Column("level"), Description("日志级别")] public string? Level { get; set; }
[Column("path"), Description("日志存放路径")]
public string? Path { get; set; }
[Column("upload_corn"), Description("日志上传时间")]
public string? UploadCorn { get; set; }
[Column("upload_levels"), Description("日志上传级别")]
public string? UploadLevels { get; set; }
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel;
using SQLite;
namespace MasstransferCommon.Model.Entity;
[Table("message_failure_record"), Description("消息发送失败记录")]
public class MessageFailureRecord : Entity
{
[Column("topic"), Description("主题")] public string Topic { get; set; }
[Column("payload"), Description("消息内容")]
public string Payload { get; set; }
}

View File

@ -0,0 +1,34 @@
using System.ComponentModel;
using MasstransferCommon.Model.Enum;
using SQLite;
namespace MasstransferCommon.Model.Entity;
/// <summary>
/// 基板信息
/// </summary>
[Table("substrates")]
public class Substrate : Entity
{
[Column("context_id"), Description("上下文ID")]
public string? ContextId { get; set; }
[Column("substrate_code"), Description("基板编号")]
public string SubstrateCode { get; set; }
[Column("jig_code"), Description("治具编号")]
public string JigCode { get; set; }
[Column("substrate_type"), Description("基板类型")]
public SubstrateTypeEnum SubstrateType { get; set; }
[Column("row"), Description("基板行")] public int Row { get; set; }
[Column("column"), Description("基板列")] public int Column { get; set; }
[Column("batch_no"), Description("批次号")]
public string BatchNo { get; set; }
[Column("formula_id"), Description("配方ID")]
public string FormulaId { get; set; }
}

View File

@ -0,0 +1,22 @@
using System.ComponentModel;
using SQLite;
namespace MasstransferCommon.Model.Entity;
[Table("wafers"), Description("晶环信息")]
public class Wafer : Entity
{
[Column("wafer_code"), Description("晶片编号")]
public string? WaferCode { get; set; }
[Column("color"), Description("晶片颜色")] public ChipColorEnum Color { get; set; }
[Column("context_id"), Description("上下文id")]
public string? ContextId { get; set; }
[Column("column"), Description("列")] public int Column { get; set; }
[Column("row"), Description("行")] public int Row { get; set; }
[Column("used"), Description("是否已使用")] public bool Used { get; set; }
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel;
using SQLite;
namespace MasstransferCommon.Model.Entity;
[Table("wafer_used_record"), Description("晶环使用记录")]
public class WaferUsedRecord : Entity
{
[Column("context_id"), Description("上下文编号")]
public string? ContextId { get; set; }
[Column("wafer_code"), Description("晶环编号")]
public string? WaferCode { get; set; }
[Column("substrate_code"), Description("基板编号")]
public string? SubstrateCode { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace MasstransferCommon.Model.Enum;
/// <summary>
/// 防止注册表被恶意篡改,这里的注册许可类型需要做一些混淆
/// </summary>
public enum LicenseTypeEnum
{
// 临时许可
Temporary = unchecked(int.MaxValue - 123456),
// 正式许可
Formal = unchecked(int.MaxValue - 123457)
}

View File

@ -0,0 +1,19 @@
namespace MasstransferCommon.Model.Enum;
/// <summary>
/// 锁定状态
/// </summary>
public enum LockStateEnum
{
// 锁定状态
LockedState = int.MaxValue / 3,
// 等待锁定状态
WaitToLockedState = int.MaxValue / 4,
// 解锁状态
UnLockedState = int.MaxValue / 5,
// 等待解锁状态
WaitToUnLockedState = int.MaxValue / 6
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel;
namespace MasstransferCommon.Model.Enum;
/// <summary>
/// 基材类型
/// </summary>
public enum SubstrateTypeEnum
{
[Description("PCB")] PCB = 1,
[Description("玻璃")] Glass = 2
}

View File

@ -0,0 +1,98 @@
using Quartz;
using Quartz.Impl;
using Serilog;
namespace MasstransferCommon.scheduler;
public class SchedulerHelper
{
private static readonly Lazy<IScheduler> lazyScheduler = new(() => InitSchedulerAsync().GetAwaiter().GetResult());
private static readonly Dictionary<string, IJobDetail> _jobDetails = new();
private static IScheduler Scheduler => lazyScheduler.Value;
private static async Task<IScheduler> InitSchedulerAsync()
{
try
{
return await new StdSchedulerFactory().GetScheduler();
}
catch (Exception ex)
{
Log.Error($"Failed to initialize scheduler: {ex.Message}");
throw;
}
}
public static async Task Start()
{
await Scheduler.Start();
}
public static async Task SchedulerInterval<T>(Dictionary<string, object>? data, int interval,
string group = "defaultGroup") where T : IJob
{
var job = CreateJob<T>(data, group);
var trigger = TriggerBuilder.Create()
.WithIdentity(typeof(T).Name, group)
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(interval).RepeatForever())
.Build();
await Scheduler.ScheduleJob(job, trigger);
}
public static async Task SchedulerCorn<T>(Dictionary<string, object>? data, string? cronExpression,
string group = "defaultGroup") where T : IJob
{
var job = CreateJob<T>(data, group);
var trigger = TriggerBuilder.Create()
.WithIdentity(typeof(T).Name, group)
.StartNow()
.WithSchedule(CronScheduleBuilder.CronSchedule(cronExpression))
.Build();
await Scheduler.ScheduleJob(job, trigger);
}
private static IJobDetail CreateJob<T>(Dictionary<string, object>? data, string group) where T : IJob
{
if (_jobDetails.ContainsKey(typeof(T).Name)) return _jobDetails[typeof(T).Name];
var job = JobBuilder.Create<T>()
.WithIdentity(typeof(T).Name, group)
.Build();
if (data != null && data.Count > 0)
foreach (var item in data)
job.JobDataMap.Add(item.Key, item.Value);
_jobDetails[typeof(T).Name] = job;
return job;
}
public static async Task PauseJob<T>(string group = "defaultGroup")
{
if (_jobDetails.ContainsKey(typeof(T).Name)) await Scheduler.PauseJob(JobKey.Create(typeof(T).Name, group));
}
public static async Task ResumeJob<T>(string group = "defaultGroup")
{
if (_jobDetails.ContainsKey(typeof(T).Name)) await Scheduler.ResumeJob(JobKey.Create(typeof(T).Name, group));
}
public static async Task Shutdown()
{
if (!Scheduler.IsShutdown) await Scheduler.Shutdown();
}
public static async Task TriggerOnceImmediately<T>(string group = "defaultGroup") where T : IJob
{
if (!_jobDetails.ContainsKey(typeof(T).Name)) return;
await Scheduler.TriggerJob(new JobKey(typeof(T).Name, group));
}
}

View File

@ -0,0 +1,17 @@
namespace MasstransferExporter.DataExporter.Model;
/// <summary>
/// 动打记录
/// 每次基板动打完成后进行触发上传
/// </summary>
public class StrikeRecord
{
public string BatchNumber { get; set; }
public string PcbNumber { get; set; }
public string ChipType { get; set; }
public string HitQuantity { get; set; }
public string PcbInputTimeCost { get; set; }
public string PcbScanTimeCost { get; set; }
public string PcbOutputTimeCost { get; set; }
public List<StrikeWaferRecord> Rounds { get; set; }
}

View File

@ -0,0 +1,16 @@
namespace MasstransferExporter.DataExporter.Model;
/// <summary>
/// 每次动打期间用到的wafer记录
/// </summary>
public class StrikeWaferRecord
{
public string WaferNumber { get; set; }
public string ChipQuantity { get; set; }
public string WaferInputTimeCost { get; set; }
public string ChipScanTimeCost { get; set; }
public string ProdutionTimeCost { get; set; }
public string PcbCheckTimeCost { get; set; }
public string WaferOutputTimeCost { get; set; }
public string HitedQuantity { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace MasstransferExporter.DataExporter;
public class StrikeRecordService
{
/// <summary>
/// 上报动打记录
/// </summary>
private static void ReportStrikeRecord()
{
// 根据这个基板编号,从记录中找到所有的跟基板有关的生产记录
}
}

View File

@ -0,0 +1,11 @@
namespace MasstransferExporter.LogExporter;
public class LogFileExporter
{
public async Task Export(string logFilePath)
{
}
}

View File

@ -34,4 +34,8 @@
<None Remove="obj\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="ImageExporter\" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,22 @@
class Program
using MasstransferExporter.RemoteControl;
using MasstransferExporter.RemoteControl.Model;
class Program
{
static void Main()
{
Thread.Sleep(30000);
var cmd = new LockCmd
{
Action = 1,
ExpiryTime = 0,
LockType = 0
};
RemoteLockService.HandleLockCmd(cmd);
Console.WriteLine("按任意键退出");
Console.ReadKey();
}

View File

@ -0,0 +1,23 @@
namespace MasstransferExporter.RemoteControl.Model;
/// <summary>
/// 锁机指令
/// </summary>
public class LockCmd
{
/// <summary>
/// 0 锁机 1 解锁
/// </summary>
public int Action { get; set; }
/// <summary>
/// 0 立即执行
/// 1 到期执行
/// </summary>
public int LockType { get; set; }
/// <summary>
/// 到期时间
/// </summary>
public long ExpiryTime { get; set; }
}

View File

@ -0,0 +1,56 @@
using MasstransferCommon.Model.Enum;
using MasstransferCommon.Utils;
using MasstransferExporter.RemoteControl.Model;
using Microsoft.Win32;
namespace MasstransferExporter.RemoteControl;
/// <summary>
/// 远程锁定服务
/// </summary>
public class RemoteLockService
{
private const string KeyPath = @"Software\Masstransfer\Security";
/// <summary>
/// 处理接收到锁机业务指令
/// </summary>
public static void HandleLockCmd(LockCmd cmd)
{
var action = cmd.Action;
if (action == 0)
{
// 更新注册表锁机状态
var lockType = cmd.LockType;
if (lockType == 0)
{
RegistryHelper.WriteValue(KeyPath, "LockState", LockStateEnum.LockedState, RegistryValueKind.DWord);
}
else
{
RegistryHelper.WriteValue(KeyPath, "LockState", LockStateEnum.WaitToLockedState,
RegistryValueKind.DWord);
// 写入超时的时间
RegistryHelper.WriteValue(KeyPath, "ExpireTime", cmd.ExpiryTime, RegistryValueKind.DWord);
}
}
else
{
// 解锁
var lockType = cmd.LockType;
if (lockType == 0)
{
RegistryHelper.WriteValue(KeyPath, "LockState", LockStateEnum.UnLockedState,
RegistryValueKind.DWord);
}
else
{
RegistryHelper.WriteValue(KeyPath, "LockState", LockStateEnum.WaitToUnLockedState,
RegistryValueKind.DWord);
// 写入等待解锁超时的时间
RegistryHelper.WriteValue(KeyPath, "ExpireTime", cmd.ExpiryTime, RegistryValueKind.DWord);
}
}
}
}

View File

@ -15,7 +15,11 @@ public class SqliteHelper
private const string Password = "88888888";
private readonly SQLiteConnection _db;
public SqliteHelper()
private static SqliteHelper? _instance;
private static readonly object Locker = new();
private SqliteHelper()
{
var profile = Environment.GetEnvironmentVariable("USERPROFILE");
var path = Path.Combine(profile, "masstransfer", "mass-transfer.db");
@ -34,6 +38,17 @@ public class SqliteHelper
_db.Execute($"PRAGMA key = '{Password}'");
}
public static SqliteHelper GetInstance()
{
lock (Locker)
{
// 如果类的实例不存在则创建,否则直接返回
_instance ??= new SqliteHelper();
}
return _instance;
}
/// <summary>
/// 插入数据

View File

@ -1,5 +1,6 @@
using System.Reflection;
using MasstransferCommon.Model.Entity;
using MasstransferCommon.Utils;
using MasstransferInfrastructure.Database.Sqlite;
using MasstransferInfrastructure.Mqtt.Model;
using MQTTnet;
using MQTTnet.Client;
@ -10,6 +11,8 @@ namespace MasstransferInfrastructure.Mqtt.Client;
public class MessageQueueHelper
{
private static readonly SqliteHelper Helper = SqliteHelper.GetInstance();
private static readonly Dictionary<string, List<Delegate>> Subscribers = new();
private static readonly MqttClient Client = new();
@ -63,7 +66,29 @@ public class MessageQueueHelper
public static async Task<bool> Publish(string topic, object message,
MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtMostOnce)
{
return await Client.Publish(topic, message, qos);
try
{
var isSuccess = await Client.Publish(topic, message, qos);
if (!isSuccess)
{
throw new Exception("发送消息失败");
}
return true;
}
catch (Exception)
{
MessageFailureRecord record = new()
{
Payload = JsonUtil.ToJson(message),
Topic = topic
};
// 将当前消息记录到数据库
Helper.Insert(record);
return false;
}
}
/// <summary>

View File

@ -108,12 +108,13 @@ class MqttClient
var payload = message as string ?? JsonUtil.ToJson(message);
await _client.PublishAsync(new MqttApplicationMessageBuilder()
var result = await _client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qos)
.Build());
return true;
return result.IsSuccess;
}
/// <summary>