Compare commits

...

2 Commits

Author SHA1 Message Date
huangxianguo 41ac47205d Merge branch 'master' of https://gitea.haijumasstransfer.com:8443/wangkaiyi/Dispenser
# Conflicts:
#	.gitignore
2024-08-16 15:23:24 +08:00
huangxianguo b9fd07c94c 添加点胶机框架内容 2024-08-16 15:20:09 +08:00
413 changed files with 18253 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

14
.gitignore vendored
View File

@ -1,2 +1,16 @@
.git/
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
=======
.git/
.idea/
>>>>>>> 80593d2e183c48d2489ba8f855b9486fbcf0982c

6
Directory.Build.props Normal file
View File

@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.2</AvaloniaVersion>
</PropertyGroup>
</Project>

67
Dispenser.sln Normal file
View File

@ -0,0 +1,67 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34714.143
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserUI", "DispenserUI\DispenserUI.csproj", "{E08E72A8-4334-4871-AAE0-244B16ADE556}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserDesktop", "DispenserDesktop\DispenserDesktop.csproj", "{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserCommon", "DispenserCommon\DispenserCommon.csproj", "{A8587675-8F8D-4F3E-8809-DDF77743FEFD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserCommon", "DispenserCore\DispenserCore.csproj", "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DispenserDriver", "DispenserDriver", "{42401CD7-B4C8-4F06-AD46-5D987CF4CA53}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserHal", "DispenserHal\DispenserHal.csproj", "{78E3094F-66F1-470A-BA89-37F43E7F3737}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserDriver.CameraMV", "DispenserDriver.CameraMV\DispenserDriver.CameraMV.csproj", "{FFED5A1A-44B8-4C89-B659-9B86A3FF800C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispenserAlgorithm", "DispenserAlgorithm\DispenserAlgorithm.csproj", "{921E3C5C-1347-402E-9838-3B30170F7489}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E08E72A8-4334-4871-AAE0-244B16ADE556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E08E72A8-4334-4871-AAE0-244B16ADE556}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E08E72A8-4334-4871-AAE0-244B16ADE556}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E08E72A8-4334-4871-AAE0-244B16ADE556}.Release|Any CPU.Build.0 = Release|Any CPU
{E08E72A8-4334-4871-AAE0-244B16ADE556}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{2150E333-8FDC-42A3-9474-1A3956D46DE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2150E333-8FDC-42A3-9474-1A3956D46DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2150E333-8FDC-42A3-9474-1A3956D46DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2150E333-8FDC-42A3-9474-1A3956D46DE8}.Release|Any CPU.Build.0 = Release|Any CPU
{2150E333-8FDC-42A3-9474-1A3956D46DE8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}.Release|Any CPU.Build.0 = Release|Any CPU
{A52ACF73-71A1-48A6-9B7F-E06761B26AC9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{A8587675-8F8D-4F3E-8809-DDF77743FEFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8587675-8F8D-4F3E-8809-DDF77743FEFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8587675-8F8D-4F3E-8809-DDF77743FEFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8587675-8F8D-4F3E-8809-DDF77743FEFD}.Release|Any CPU.Build.0 = Release|Any CPU
{A8587675-8F8D-4F3E-8809-DDF77743FEFD}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{78E3094F-66F1-470A-BA89-37F43E7F3737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78E3094F-66F1-470A-BA89-37F43E7F3737}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78E3094F-66F1-470A-BA89-37F43E7F3737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78E3094F-66F1-470A-BA89-37F43E7F3737}.Release|Any CPU.Build.0 = Release|Any CPU
{FFED5A1A-44B8-4C89-B659-9B86A3FF800C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFED5A1A-44B8-4C89-B659-9B86A3FF800C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFED5A1A-44B8-4C89-B659-9B86A3FF800C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFED5A1A-44B8-4C89-B659-9B86A3FF800C}.Release|Any CPU.Build.0 = Release|Any CPU
{921E3C5C-1347-402E-9838-3B30170F7489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{921E3C5C-1347-402E-9838-3B30170F7489}.Debug|Any CPU.Build.0 = Debug|Any CPU
{921E3C5C-1347-402E-9838-3B30170F7489}.Release|Any CPU.ActiveCfg = Release|Any CPU
{921E3C5C-1347-402E-9838-3B30170F7489}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FFED5A1A-44B8-4C89-B659-9B86A3FF800C} = {42401CD7-B4C8-4F06-AD46-5D987CF4CA53}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DispenserAlgorithm</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Stateless" Version="5.15.0"/>
<PackageReference Include="System.Drawing.Common" Version="8.0.0"/>
</ItemGroup>
<ItemGroup>
<Reference Include="DispenserAlgorithms.Halcon">
<HintPath>DispenserAlgorithms.Halcon.dll</HintPath>
</Reference>
<Reference Include="DispenserVision.Halcon">
<HintPath>..\DispenserDesktop\Libs\DispenserVision.Halcon.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="halcondotnet">
<HintPath>halcondotnet.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,46 @@
using AspectInjector.Broker;
using DispenserCommon.Utils;
using Serilog;
namespace DispenserCommon.Aop;
/// <summary>
/// 用于捕获方法执行过程中的异常,并弹窗
/// </summary>
[Aspect(Scope.Global)]
[Injection(typeof(AlertWhenException))]
public class AlertWhenException(string title = "异常提示", string contentTitle = "") : Attribute
{
public AlertWhenException() : this("异常提示")
{
}
/// <summary>
/// 通过AOP实现方法执行前后的时间消耗
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <param name="hostType"></param>
/// <param name="target"></param>
/// <param name="triggers"></param>
/// <returns></returns>
[Advice(Kind.Around)]
public object Around([Argument(Source.Name)] string name,
[Argument(Source.Arguments)] object[] args,
[Argument(Source.Type)] Type hostType,
[Argument(Source.Target)] Func<object[], object> target,
[Argument(Source.Triggers)] Attribute[] triggers)
{
try
{
var result = target(args);
return result;
}
catch (Exception e)
{
Log.Error(e, $"方法 {name} 执行异常");
MessageBoxHelper.Error(e.Message, contentTitle, title, false);
return null!;
}
}
}

View File

@ -0,0 +1,15 @@
using AspectInjector.Broker;
namespace DispenserCommon.Aop;
[Aspect(Scope.Global)]
[Injection(typeof(AsyncOperation))]
public class AsyncOperation(string name) : Attribute
{
public string Name { get; set; } = name;
public AsyncOperation() : this("")
{
Name = "";
}
}

View File

@ -0,0 +1,47 @@
using System.Diagnostics;
using AspectInjector.Broker;
using Serilog;
namespace DispenserCommon.Aop;
[Aspect(Scope.Global)]
[Injection(typeof(ConsumeTime))]
public class ConsumeTime(string title) : Attribute
{
public ConsumeTime() : this("")
{
Title = "";
}
public string Title { get; set; } = title;
/// <summary>
/// 通过AOP实现方法执行前后的时间消耗
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <param name="hostType"></param>
/// <param name="target"></param>
/// <param name="triggers"></param>
/// <returns></returns>
[Advice(Kind.Around)]
public object Around([Argument(Source.Name)] string name,
[Argument(Source.Arguments)] object[] args,
[Argument(Source.Type)] Type hostType,
[Argument(Source.Target)] Func<object[], object> target,
[Argument(Source.Triggers)] Attribute[] triggers)
{
var sw = Stopwatch.StartNew();
try
{
var result = target(args);
return result;
}
finally
{
sw.Stop();
Log.Information(
$"方法 {(Title != "" ? Title : name)} 执行时间:{sw.Elapsed.TotalMilliseconds}ms");
}
}
}

View File

@ -0,0 +1,39 @@
using System.Reflection;
using AspectInjector.Broker;
using DispenserCommon.Utils;
using Serilog;
namespace DispenserCommon.Aop;
[Aspect(Scope.Global)]
[AttributeUsage(AttributeTargets.Class)]
[Injection(typeof(GlobalTry), Inherited = true)]
public class GlobalTry : Attribute
{
/// <summary>
/// 通过AOP实现方法执行前后的时间消耗
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <param name="target"></param>
/// <param name="method"></param>
/// <returns></returns>
[Advice(Kind.Around, Targets = Target.Method)]
public object? HandleMethod([Argument(Source.Name)] string name,
[Argument(Source.Arguments)] object[] args,
[Argument(Source.Target)] Func<object[], object> target,
[Argument(Source.Metadata)] MethodBase method)
{
try
{
var result = target(args);
return result;
}
catch (Exception e)
{
Log.Error(e, $"方法 {name} 执行异常: {e.Message}");
ToastUtil.Error("操作异常: " + e.Message);
return default;
}
}
}

View File

@ -0,0 +1,63 @@
using AspectInjector.Broker;
using DispenserCommon.DTO;
using DispenserCommon.Enums;
using DispenserCommon.Events;
using DispenserCommon.Utils;
using DispenserUI.Exceptions;
using Serilog;
namespace DispenserCommon.Aop;
[Aspect(Scope.Global)]
[Injection(typeof(Operation))]
public class Operation(string name) : Attribute
{
public string Name { get; set; } = name;
public Operation() : this("")
{
Name = "";
}
/// <summary>
/// 通过AOP实现方法执行过程拦截
/// </summary>
/// <param name="args"></param>
/// <param name="target"></param>
/// <param name="triggers"></param>
/// <returns></returns>
[Advice(Kind.Around, Targets = Target.Method)]
public object? Around(
[Argument(Source.Arguments)] object[] args,
[Argument(Source.Target)] Func<object[], object> target,
[Argument(Source.Triggers)] Attribute[] triggers)
{
var trigger = (Operation)triggers[0];
var log = new ActionLog
{
Name = trigger.Name,
Params = args
};
try
{
Log.Information($"正在执行: {trigger.Name}");
return target(args);
}
catch (Exception e)
{
if (e is BizException { Level: ExceptionLevel.NORMAL })
{
ToastUtil.Error($"{trigger.Name}操作失败: {e.Message}");
}
log.Exception = e;
Log.Error($"{trigger.Name}操作失败: {e.Message}");
return default;
}
finally
{
EventBus<ActionLog>.Publish(EventType.OperationLog, log);
}
}
}

View File

@ -0,0 +1,7 @@
namespace DispenserCommon.Atrributes;
[AttributeUsage(AttributeTargets.Method)]
public class BlockTask(string name) : Attribute
{
public string Name { get; } = name;
}

View File

@ -0,0 +1,6 @@
namespace DispenserCommon.Atrributes;
[AttributeUsage(AttributeTargets.Property)]
public class Hide : Attribute
{
}

View File

@ -0,0 +1,30 @@
namespace DispenserCommon.Atrributes;
[AttributeUsage(AttributeTargets.Property)]
public class Property : Attribute
{
public bool IsReadOnly { get; set; } = false;
// 字符串格式
public string? Format { get; set; }
// 显示控件的宽
public double Width { get; set; }
// 显示控件的高
public double Height { get; set; }
public double Max { get; set; }
public double Min { get; set; }
public string Group { get; set; }
public bool IsPassword { get; set; }
public string Variable { get; set; }
public int Axis { get; set; } = -1;
public int Index { get; set; } = -1;
}

View File

@ -0,0 +1,12 @@
namespace DispenserCommon.Constant;
/// <summary>
/// 这里管理MQTT 所有的topic
/// </summary>
public class Topics
{
// 锁机
public const string Locked = "device/{deviceId}/locked";
}

View File

@ -0,0 +1,18 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace DispenserCommon.Converter;
public class StringNullOrEmptyToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = value as string;
return string.IsNullOrEmpty(str);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,83 @@
using System.Reflection;
using DispenserCommon.Events;
using DispenserCommon.Utils;
using SQLite;
namespace DispenserCommon.DB;
/// <summary>
/// 在系统启动的时候记进行数据库初始化校验
/// </summary>
public class DatabaseInitializer
{
private static readonly SqliteHelper Sqlite = ServiceLocator.GetService<SqliteHelper>();
public static void Initialize()
{
EventBus<string>.Publish(EventType.SetupNotify, "正在初始化数据库");
// 扫描所有带有[Table]特性的类
var tables = GetAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => t.GetCustomAttributes(typeof(TableAttribute), true).Length > 0))
.ToList();
tables.ForEach(table =>
{
// 判断当前的表是否存在
var tableName = table.GetCustomAttribute<TableAttribute>()?.Name ?? table.Name;
var tableInfo = Sqlite.GetTableInfo(tableName);
if (!tableInfo.Any())
{
Sqlite.CreateTable(table);
}
else
{
// 读取当前类的变量是否新增了属性
var properties = table.GetProperties();
var colNames = tableInfo.Select(col => col.Name.ToUpper()).ToHashSet();
foreach (var property in properties)
{
var colName = property.GetCustomAttribute<ColumnAttribute>()?.Name ?? property.Name;
// 统一转为大写进行判断
if (colNames.Contains(colName.ToUpper())) continue;
// 重新进行DDL时会自动的判断是否存在新增的列如果有则自动进行新增
Sqlite.CreateTable(table);
}
}
});
}
private static IEnumerable<Assembly> GetAssemblies()
{
var assemblies = new List<Assembly>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var name = assembly.GetName().Name;
if (name != null && name.ToLower().Contains("dispenser")) GetReferenceAssemblies(assembly, assemblies);
}
return assemblies;
}
private static void GetReferenceAssemblies(Assembly assembly, ICollection<Assembly> assemblies)
{
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
var name = assemblyName.Name;
if (name != null && name.ToLower().Contains("dispenser"))
{
var ass = Assembly.Load(assemblyName);
if (assemblies.Contains(ass)) continue;
assemblies.Add(ass);
GetReferenceAssemblies(ass, assemblies);
}
}
}
}

View File

@ -0,0 +1,228 @@
using System.Reflection;
using DispenserCommon.DTO;
using DispenserCommon.Ioc;
using Masuit.Tools;
using Masuit.Tools.Systems;
using SQLite;
namespace DispenserCommon.DB;
/// <summary>
/// SQLite 数据库ORM工具类
/// </summary>
[Component]
public class SqliteHelper
{
// 默认的数据库密码
private const string Password = "88888888";
private readonly SQLiteConnection _db;
public SqliteHelper()
{
var profile = Environment.GetEnvironmentVariable("USERPROFILE");
var path = Path.Combine(profile, "dispenser", "dispenser.db");
if (!Directory.Exists(Path.GetDirectoryName(path)!))
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
// 将文件复模板数据库复制到目标目录下
var template = Path.Combine(Environment.CurrentDirectory, "dispenser.db");
File.Copy(template, path);
}
_db = new SQLiteConnection(path);
}
/// <summary>
/// 插入数据
/// </summary>
/// <param name="item">待插入的数据</param>
/// <typeparam name="T">数据类型</typeparam>
public int Insert<T>(T item)
{
var id = item?.GetType().GetProperty("Id");
if (id != null && id.CanWrite) id.SetValue(item, SnowFlakeNew.LongId.ToString());
var createTime = item?.GetType().GetProperty("CreateTime");
if (createTime != null && createTime.CanWrite) createTime.SetValue(item, DateTime.Now);
return _db.Insert(item);
}
/// <summary>
/// 更新数据
/// </summary>
/// <param name="item">待更新的数据</param>
/// <typeparam name="T">数据类型</typeparam>
public int Update<T>(T item)
{
var updateTime = item?.GetType().GetProperty("UpdateTime");
if (updateTime != null && updateTime.CanWrite) updateTime.SetValue(item, DateTime.Now);
return _db.Update(item);
}
/// <summary>
/// 删除数据
/// </summary>
/// <param name="id">待删除数据的ID</param>
/// <typeparam name="T">数据类型</typeparam>
public int DeleteById<T>(object id) where T : new()
{
var type = typeof(T);
var tableName = type.GetCustomAttribute<TableAttribute>()!.Name ?? type.Name;
return _db.Execute($"delete from {tableName} where id = {id}");
}
/// <summary>
/// 删除数据
/// </summary>
/// <param name="item">待删除数据的对象</param>
/// <typeparam name="T">数据类型</typeparam>
public int Delete<T>(T item)
{
return _db.Delete<T>(item);
}
/// <summary>
/// 根据ID获取数据
/// </summary>
/// <param name="id">数据ID</param>
/// <typeparam name="T">数据类型</typeparam>
public T GetById<T>(object id) where T : new()
{
return _db.Get<T>(id);
}
/// <summary>
/// 查询数据
/// </summary>
/// <param name="sql">查询语句</param>
/// <param name="args">查询参数</param>
/// <typeparam name="T">数据类型</typeparam>
public List<T> Query<T>(string sql, params object[] args) where T : new()
{
return _db.Query<T>(sql, args);
}
/// <summary>
/// 查询所有数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public List<T> ListAll<T>() where T : new()
{
return _db.Table<T>().ToList();
}
public T SaveOrUpdate<T>(T item) where T : new()
{
var id = item?.GetType().GetProperty("Id");
if (id != null && id.GetValue(item).IsNullOrEmpty())
// 插入
Insert(item);
else
// 否则是更新操作
Update(item);
return item;
}
/// <summary>
/// 执行SQL语句
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="args"></param>
public int Execute(string sql, params object[] args)
{
return _db.Execute(sql, args);
}
/// <summary>
/// 获取当前的表信息
/// </summary>
/// <param name="tableName"></param>
/// <returns></returns>
public List<SQLiteConnection.ColumnInfo> GetTableInfo(string tableName)
{
return _db.GetTableInfo(tableName);
}
/// <summary>
/// 创建表
/// </summary>
/// <param name="entity"></param>
public void CreateTable(Type entity)
{
_db.CreateTable(entity);
}
/// <summary>
/// 分页查询结果
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="sql"></param>
/// <param name="args"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public Page<T> Page<T>(int page, int pageSize, string sql, params object[] args) where T : new()
{
var countSql = "select count(*) from ( " + sql + " ) as c";
var total = _db.ExecuteScalar<int>(countSql, args);
var data = Query<T>(sql + " limit " + (page - 1) * pageSize + "," + pageSize, args);
return new Page<T>
{
CurrentPage = page,
PageSize = pageSize,
Total = total,
Pages = total / pageSize + (total % pageSize == 0 ? 0 : 1),
Data = data
};
}
/// <summary>
/// 批量插入
/// </summary>
/// <param name="objects"></param>
/// <param name="runInTransaction"></param>
/// <typeparam name="T"></typeparam>
public void BatchInsert<T>(IEnumerable<T> objects, bool runInTransaction = false) where T : new()
{
if (runInTransaction)
{
_db.RunInTransaction((Action)(() =>
{
foreach (object obj in objects)
Insert(obj);
}));
}
else
{
foreach (object obj in objects)
Insert(obj);
}
}
/// <summary>
/// 更新指定字段
/// </summary>
/// <param name="id"></param>
/// <param name="field"></param>
/// <param name="value"></param>
/// <typeparam name="T"></typeparam>
public void UpdateFieldById<T>(object? id, string field, object? value) where T : new()
{
var type = typeof(T);
var tableName = type.GetCustomAttribute<TableAttribute>()!.Name ?? type.Name;
var sql = $"update {tableName} set {field} = ? where id = ?";
_db.Execute(sql, value, id);
}
}

View File

@ -0,0 +1,15 @@
namespace DispenserCommon.DTO;
/// <summary>
/// 通过AOP方式拦截获取用户操作日志
/// </summary>
public class ActionLog
{
public string Name { get; set; }
public object[] Params { get; set; }
public Exception Exception { get; set; }
public DateTime OperateTime { get; set; } = DateTime.Now;
}

View File

@ -0,0 +1,21 @@
using Serilog.Events;
namespace DispenserCommon.DTO;
/// <summary>
/// 日志信息
/// </summary>
/// <param name="message"></param>
public class LogMessage(string message)
{
public LogMessage(string message, LogEventLevel level) : this(message)
{
Message = message;
Level = level;
}
public DateTime Timestamp { get; set; } = DateTime.Now;
public string Message { get; set; } = message;
public LogEventLevel Level { get; set; } = LogEventLevel.Information;
}

View File

@ -0,0 +1,77 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DispenserCommon.DTO;
/// <summary>
/// 分页结果
/// </summary>
public class Page<T> : INotifyPropertyChanged
{
private int _total;
private int _pages;
private int _currentPage;
private List<T> _data = [];
private int _pageSize;
public int CurrentPage
{
get => _currentPage;
set
{
_currentPage = value;
OnPropertyChanged();
}
}
public int PageSize
{
get => _pageSize;
set
{
_pageSize = value;
OnPropertyChanged();
}
}
public int Total
{
get => _total;
set
{
_total = value;
OnPropertyChanged();
}
}
public int Pages
{
get => _pages;
set
{
_pages = value;
OnPropertyChanged();
}
}
public List<T> Data
{
get => _data;
set
{
_data = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@ -0,0 +1,48 @@
namespace DispenserCommon.DTO;
/// <summary>
/// plc请求结果
/// </summary>
/// <typeparam name="T"></typeparam>
public class Result<T>
{
public Result()
{
Ok = true;
Code = 0;
Message = "成功";
}
public Result(T data)
{
Ok = true;
Code = 0;
Message = "成功";
Data = data;
}
public Result(int code, string message)
{
Ok = false;
Code = code;
Message = message;
}
public Result(int code, string message, Exception? exception)
{
Ok = false;
Code = code;
Message = message;
Exception = exception;
}
public bool Ok { get; set; }
public int Code { get; set; }
public T? Data { get; set; }
public string Message { get; set; }
public Exception? Exception { get; set; }
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspectInjector" Version="2.8.3-pre3"/>
<PackageReference Include="Avalonia" Version="11.0.10" />
<PackageReference Include="Masuit.Tools.Core" Version="2.6.9.9"/>
<PackageReference Include="MathNet.Numerics" Version="6.0.0-beta1"/>
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="Quartz" Version="3.8.1"/>
<PackageReference Include="ReactiveUI" Version="18.3.1" />
<PackageReference Include="Serilog" Version="3.1.1"/>
<PackageReference Include="Serilog.Exceptions" Version="8.4.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00972"/>
<PackageReference Include="sqlite-net" Version="1.6.292"/>
<PackageReference Include="sqlite-net-sqlcipher" Version="1.9.172"/>
<PackageReference Include="Stateless" Version="5.15.0"/>
<PackageReference Include="System.Drawing.Common" Version="8.0.0"/>
<PackageReference Include="System.IO.Ports" Version="8.0.0"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
namespace DispenserCommon.Enums;
public enum BinCoordinateTransformStrategyEnum
{
/// <summary>
/// 基于角度
/// </summary>
ByAngel,
/// <summary>
/// 基于矩阵
/// </summary>
ByMatrix
}

View File

@ -0,0 +1,8 @@
namespace DispenserCommon.Enums;
public enum ExceptionLevel : int
{
NORMAL,
WARN,
ERROR,
}

View File

@ -0,0 +1,7 @@
namespace DispenserCommon.Enums;
public enum ScannerTypeEnum
{
Wafer,
Pcb,
}

View File

@ -0,0 +1,10 @@
namespace DispenserCommon.Events;
/// <summary>
/// 用于声明当前方法为事件处理器
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class EventAction(params EventType[] types) : Attribute
{
public EventType[] Types => types;
}

View File

@ -0,0 +1,65 @@
using Serilog;
namespace DispenserCommon.Events;
/// <summary>
/// 事件总线
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class EventBus<T>
{
private static readonly Dictionary<EventType, List<Delegate>> Subscribers = new();
/// <summary>
/// 添加订阅
/// </summary>
/// <param name="type"></param>
/// <param name="action"></param>
public static void AddEventHandler(EventType type, Delegate action)
{
if (Subscribers.TryGetValue(type, out var subscribers))
{
var any = subscribers.Any(item => item.Equals(action));
if (!any) subscribers.Add(action);
}
else
{
Subscribers.Add(type, [action]);
}
}
/// <summary>
/// 移除订阅逻辑
/// </summary>
/// <param name="type">事件类型</param>
/// <param name="action">回调方法</param>
public static void RemoveEventHandler(EventType type, Delegate action)
{
if (!Subscribers.TryGetValue(type, out var handlers)) return;
handlers.Remove(action);
}
/// <summary>
/// 发布事件
/// </summary>
/// <param name="type"></param>
/// <param name="data"></param>
public static void Publish(EventType type, T data)
{
if (!Subscribers.TryGetValue(type, out var subscribers)) return;
// 创建一个副本,避免在回调中修改订阅列表导致迭代异常
var actions = subscribers.ToList();
foreach (var action in actions)
try
{
action.DynamicInvoke(type, data);
}
catch (Exception e)
{
Log.Error(e, e.Message);
}
}
}

View File

@ -0,0 +1,10 @@
namespace DispenserCommon.Events;
/// <summary>
/// 用于声明当前类为事件监听
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class EventListener(string name = "") : Attribute
{
private string Name => name;
}

View File

@ -0,0 +1,59 @@
using System.Reflection;
using DispenserCommon.Ioc;
using DispenserCommon.Utils;
namespace DispenserCommon.Events;
/// <summary>
/// 通过事件管理者的方式来扫描系统内所有添加了注解的事件订阅者,并注册到事件总线上
/// </summary>
public class EventManager
{
/// <summary>
/// 扫描所有带有 EventListener 注解的类,并注册到事件总线上
/// Action回调方法 为 所有带有 EventAction 注解的方法
/// 方法必须为两个参数的委托,第一个参数为事件类型,第二个参数为事件数据
/// </summary>
/// <exception cref="Exception"></exception>
public static void RegListeners()
{
var listeners = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => t.GetCustomAttributes(typeof(EventListener), true).Length > 0
|| t.GetCustomAttributes(typeof(Component), true).Length > 0))
.ToList();
foreach (var listener in listeners)
{
// 扫描当前类中所有的带有 EventAction 特性的方法
var methods = listener
.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance)
.Where(m => m.GetCustomAttributes(typeof(EventAction), true).Length > 0)
.ToList();
foreach (var method in methods)
{
var handler = method.GetCustomAttribute<EventAction>();
// 获取方法的参数类型
var parameters = method.GetParameters();
if (parameters.Length != 2) throw new Exception("订阅方法的参数必须为两个,第一个参数为事件类型,第二个参数为事件数据");
// 获取参数类型
var parameterType = parameters[1].ParameterType;
var types = handler?.Types;
if (types == null) continue;
foreach (var type in types)
{
var actionType = typeof(Action<,>).MakeGenericType(typeof(EventType), parameterType);
var instance = ServiceLocator.GetService(listener);
var @delegate = method.CreateDelegate(actionType, instance);
var eventBus = typeof(EventBus<>).MakeGenericType(parameterType);
var addEventHandler = eventBus.GetMethod("AddEventHandler");
addEventHandler?.Invoke(null, new object[] { type, @delegate });
}
}
}
}
}

View File

@ -0,0 +1,111 @@
using System.ComponentModel;
namespace DispenserCommon.Events;
/// <summary>
/// 通过事件发布订阅模式实现系统各组件的解耦
/// 这里定义的是事件的驱动类型
/// </summary>
public enum EventType
{
// 系统初始化事件
[Description("系统初始化事件")] SetupNotify,
[Description("系统已经启动事件")] StartUp,
// 锁机事件
[Description("锁机事件")] LockEvent,
[Description("许可无效事件")] LicenseInvalidEvent,
[Description("异常信息")] Exception,
[Description("解决异常信息")] SolveFault,
[Description("操作日志")] OperationLog,
[Description("动打状态")] StrikeState,
[Description("生产作业过程状态信息")] ProductionState,
[Description("动打路径范围")] StrokePath,
[Description("传感器状态更新")] SensorState,
// 相机数据
[Description("测距仪数据")] CameraData,
//参数修改
[Description("参数修改")] ConfigChange,
// 晶环上料确认事件
[Description("晶环上料确认事件")] WaferLoadingConfirmed,
// 芯片缺料事件
[Description("芯片缺料事件")] ChipLacking,
// 晶环上料事件
[Description("晶环上料事件")] WaferLoading,
// 晶环旋转事件
[Description("晶环旋转事件")] WaferRotation,
// 晶环旋转失败事件
[Description("晶环旋转失败事件")] WaferRotationFailure,
// 晶环下料事件
[Description("晶环下料事件")] WaferUnloading,
// 基板上料确认事件
[Description("PCB上料确认事件")] PcbLoadingConfirmed,
// 基板上料事件
[Description("PCB上料事件")] PcbLoading,
// 基板下料事件
[Description("PCB下料事件")] PcbUnloading,
// 针刺组件移动到指定位置事件
[Description("针刺组件移动")] NeedleMoveTo,
// 晶环组件移动到自定位置事件
[Description("晶环组件移动")] WaferMoveTo,
// pcb上料完成事件
[Description("基板上料完成")] PcbLoadingCompleted,
// pcb下料完成事件
[Description("基板下料完成")] PcbUnloadingCompleted,
// 晶圆上料完成事件
[Description("基板上料完成")] WaferLoadingCompleted,
// 晶圆下料完成事件
[Description("基板下料完成")] WaferUnloadingCompleted,
// 整机回零完成事件
[Description("整机回零完成")] TotalResetCompleted,
[Description("基板扫描完成")] PcbScanCompleted,
[Description("晶圆扫描完成")] WaferScanCompleted,
[Description("整体扫描完成")] ScanCompleted,
[Description("步序信息")] StepInfo,
[Description("生产配置界面切换")] SettingChanged,
[Description("切换步骤")] StepChanged,
[Description("切换菜单")] MenuChanged,
[Description("菜单按钮切换事件")] PageChanged,
[Description("MQTT 接收到数据")] MqttMessage,
StartScan,
[Description("错误日志")] PopLog,
[Description("滚动日志")] RollingLog
}

View File

@ -0,0 +1,13 @@
using DispenserCommon.Enums;
using DispenserUI.Exceptions;
namespace DispenserCommon.Exceptions;
public class AssertException(
string message,
string? code = null,
string? module = null,
Exception? exception = null,
ExceptionLevel? level = null) : BizException(message, code, module, exception, level)
{
}

View File

@ -0,0 +1,20 @@
using DispenserCommon.Enums;
namespace DispenserUI.Exceptions;
/// <summary>
/// 业务异常
/// </summary>
public class BizException(
string message,
string? code = null,
string? module = null,
Exception? exception = null,
ExceptionLevel? level = null) : ApplicationException(message, exception)
{
public ExceptionLevel Level { get; set; } = level ?? ExceptionLevel.NORMAL;
public string? Module { get; set; } = module;
public string? Code { get; set; } = code;
}

View File

@ -0,0 +1,11 @@
using DispenserCommon.Enums;
using DispenserUI.Exceptions;
namespace DispenserCommon.Exceptions;
public class CameraException(
string message,
string? code = null,
Exception? exception = null,
ExceptionLevel? level = null)
: BizException(message, code, "视觉相机异常", exception, level);

View File

@ -0,0 +1,15 @@
using DispenserCommon.Enums;
using DispenserUI.Exceptions;
namespace DispenserCommon.Exceptions;
public class ScannerException(
ScannerTypeEnum type,
string message,
string? code = null,
Exception? exception = null,
ExceptionLevel? level = null)
: BizException(message, code, "扫码枪异常", exception, level)
{
public ScannerTypeEnum Type { get; set; } = type;
}

View File

@ -0,0 +1,5 @@
namespace DispenserCommon.Interface;
public interface Instant
{
}

View File

@ -0,0 +1,12 @@
namespace DispenserCommon.Ioc;
/// <summary>
/// 用于声明当前类需要交由IOC管理
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class Component(Type? type = null, string? name = null) : Attribute
{
public Type? Type => type;
public string? Name => name;
}

View File

@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
namespace DispenserCommon.Lazy;
public static class IServiceCollectionExtendtions
{
public static IServiceCollection AddLazyResolution(this IServiceCollection services)
{
return services.AddTransient(
typeof(Lazy<>),
typeof(LazilyResolved<>));
}
private class LazilyResolved<T> : Lazy<T>
{
public LazilyResolved(IServiceProvider serviceProvider)
: base(serviceProvider.GetRequiredService<T>)
{
}
}
}

View File

@ -0,0 +1,32 @@
using System.Text;
using DispenserCommon.DTO;
using DispenserCommon.Events;
using Serilog.Core;
using Serilog.Events;
namespace DispenserCommon.LogUtils;
public class DispenserLogSink : ILogEventSink
{
public void Emit(LogEvent logEvent)
{
//错误日志,用于弹窗提示
if (IsPopLog(logEvent))
EventBus<LogMessage>.Publish(EventType.PopLog,
new LogMessage(logEvent.RenderMessage(), LogEventLevel.Error));
var sb = new StringBuilder(logEvent.RenderMessage());
if (logEvent.Exception != null)
{
sb.Append(logEvent.Exception.Message);
}
EventBus<LogMessage>.Publish(EventType.RollingLog, new LogMessage(sb.ToString(), logEvent.Level));
}
private bool IsPopLog(LogEvent logEvent)
{
return logEvent.Properties.ContainsKey("IsPop") &&
logEvent.Properties["IsPop"].ToString() == true.ToString();
}
}

View File

@ -0,0 +1,63 @@
using System.Collections.Concurrent;
using System.Drawing;
namespace DispenserCommon.Queue;
public class BitmapQueue
{
private bool _isProcessing;
private readonly ConcurrentQueue<Bitmap> _queue = new();
private readonly Action<Bitmap> ImageProcessor;
public BitmapQueue(Delegate @delegate)
{
ImageProcessor = (Action<Bitmap>)@delegate;
;
}
public void Clear()
{
_queue.Clear();
}
// 将BitmapItem加入队列
public void Enqueue(Bitmap item)
{
_queue.Enqueue(item);
StartProcessing(); // 尝试开始处理(如果尚未开始)
}
// 开始处理队列中的BitmapItem
private void StartProcessing()
{
if (_isProcessing) return;
_isProcessing = true;
Task.Run(ProcessQueue).ContinueWith(t =>
{
// 处理完成后的逻辑(如果有的话)
_isProcessing = false;
});
}
// 异步处理队列
private async Task ProcessQueue()
{
while (_queue.TryDequeue(out var item))
try
{
// 这里处理Bitmap例如保存、修改等
await ProcessBitmapAsync(item);
}
finally
{
// 确保释放资源
item.Dispose();
}
}
private Task ProcessBitmapAsync(Bitmap item)
{
return Task.Run(() => { ImageProcessor(item); });
}
}

View File

@ -0,0 +1,39 @@
using Serilog;
namespace DispenserCommon.Scheduler;
/// <summary>
/// 延时定时任务
/// </summary>
public class DelayScheduler
{
/// <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)
{
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;
}
action();
}
catch (Exception e)
{
Log.Error(e, "延时任务执行失败");
}
}
}

View File

@ -0,0 +1,39 @@
namespace DispenserCommon.Scheduler;
/// <summary>
/// 通过定时执行某个委托方法
/// </summary>
public class ExecuteTask : ITask
{
private readonly Timer _timer;
public ExecuteTask(string name, Action action, int interval = 100)
{
Name = name;
Interval = interval;
Action = action;
_timer = new Timer(_ => { Run(); }, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(Interval));
}
public int Interval { get; set; }
public string Name { get; set; }
public Action Action { get; set; }
/// <summary>
/// 移除任务进行定时任务释放
/// </summary>
public void Dispose()
{
_timer.Dispose();
}
/// <summary>
/// 定义一个虚方法, 子类可以重写该方法实现具体的轮询逻辑
/// </summary>
public void Run()
{
Action.Invoke();
}
}

View File

@ -0,0 +1,6 @@
namespace DispenserCommon.Scheduler;
public interface ITask : IDisposable
{
public void Run();
}

View File

@ -0,0 +1,47 @@
using System.Collections.Concurrent;
using Serilog;
namespace DispenserCommon.Scheduler;
public class JobScheduler
{
private static readonly ConcurrentDictionary<string, ExecuteTask> Tasks = new();
/// <summary>
/// 添加调度任务
/// </summary>
/// <param name="name"></param>
/// <param name="action"></param>
/// <param name="interval"></param>
/// <param name="delay"></param>
public static void AddTask(string name, Action action, int interval = 100, int delay = 0)
{
try
{
Task.Run(async () =>
{
if (Tasks.ContainsKey(name)) return;
if (delay > 0)
{
await Task.Delay(delay);
}
Tasks[name] = new ExecuteTask(name, action, interval);
});
}
catch (Exception e)
{
Log.Error(e, $"添加 {name} 任务失败");
}
}
/// <summary>
/// 移除任务
/// </summary>
/// <param name="name"></param>
public static void RemoveTask(string name)
{
if (Tasks.TryRemove(name, out var task)) task.Dispose();
}
}

View File

@ -0,0 +1,35 @@
namespace DispenserCommon.Scheduler;
/// <summary>
/// 轮询任务
/// </summary>
public abstract class PollingTask : ITask
{
private readonly Timer _timer;
protected PollingTask(string name, int interval = 100)
{
Interval = interval;
Name = name;
_timer = new Timer(_ => { Run(); }, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(Interval));
}
public int Interval { get; set; }
public string Name { get; set; }
/// <summary>
/// 移除任务进行定时任务释放
/// </summary>
public void Dispose()
{
_timer.Dispose();
}
/// <summary>
/// 定义一个虚方法, 子类可以重写该方法实现具体的轮询逻辑
/// </summary>
public virtual void Run()
{
}
}

View File

@ -0,0 +1,98 @@
using Quartz;
using Quartz.Impl;
using Serilog;
namespace DispenserCommon.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,47 @@
using Castle.Core.Internal;
using DispenserCommon.Atrributes;
using Serilog;
namespace DispenserCommon.Scheduler;
public class TaskWaiter
{
/// <summary>
/// 在
/// </summary>
/// <param name="action"></param>
/// <param name="timeout"></param>
/// <param name="cts"></param>
/// <returns></returns>
public static async Task<bool> WaitingFor(Func<bool> action, long timeout = 60000,
CancellationTokenSource? cts = null)
{
var method = action.Method;
var blockTask = method.GetAttribute<BlockTask>();
var taskName = blockTask?.Name ?? method.Name;
var span = TimeSpan.FromMilliseconds(timeout);
var endTime = DateTime.Now.Add(span);
return await Task.Run(() =>
{
while (DateTime.Now < endTime)
{
if (cts is { IsCancellationRequested: true })
{
return false;
}
if (action())
{
Log.Information($"{taskName} 任务等待完成");
return true;
}
Thread.Sleep(200);
}
return false;
});
}
}

View File

@ -0,0 +1,34 @@
using MathNet.Numerics.LinearAlgebra;
namespace DispenserCommon.Utils;
public class AffineTransformCalculator
{
public static Matrix<double> Calculate3x2AffineTransform(List<Tuple<double, double>> acupunctureCoordinates,
List<Tuple<double, double>> binCoordinates)
{
if (acupunctureCoordinates.Count != binCoordinates.Count || acupunctureCoordinates.Count < 3)
throw new ArgumentException("Both sets must have the same number of points and at least three points.");
// 创建矩阵X和Y
var rowCount = acupunctureCoordinates.Count;
var matrixX = Matrix<double>.Build.Dense(rowCount, 3);
var matrixY = Matrix<double>.Build.Dense(rowCount, 2);
for (var i = 0; i < rowCount; i++)
{
matrixX[i, 0] = acupunctureCoordinates[i].Item1; // x-coordinate
matrixX[i, 1] = acupunctureCoordinates[i].Item2; // y-coordinate
matrixX[i, 2] = 1; // homogeneous coordinate
matrixY[i, 0] = binCoordinates[i].Item1; // x'-coordinate
matrixY[i, 1] = binCoordinates[i].Item2; // y'-coordinate
}
// 计算仿射变换矩阵 A = Y * X^(-1)
var pseudoInverseX = matrixX.PseudoInverse();
var transformMatrix = matrixY.Transpose() * pseudoInverseX.Transpose();
return transformMatrix;
}
}

View File

@ -0,0 +1,75 @@
using System.Net.Http.Json;
namespace DispenserCommon.Utils;
public class ApiClient : IDisposable
{
private static readonly HttpClient Client;
static ApiClient()
{
Client = new HttpClient();
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Add("Accept", "application/json");
Client.DefaultRequestHeaders.Add("Content-Type", "application/json");
}
/// <summary>
/// 异步Get请求
/// </summary>
/// <param name="url"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async Task<T?> GetAsync<T>(string url)
{
using var response = await Client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
/// <summary>
/// 异步 Post请求
/// </summary>
/// <param name="url"></param>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async Task<T?> PostAsync<T>(string url, object? data)
{
using var response = await Client.PostAsJsonAsync(url, data);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
/// <summary>
/// 异步 Put请求
/// </summary>
/// <param name="url"></param>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async Task<T?> PutAsync<T>(string url, object? data)
{
using var response = await Client.PutAsJsonAsync(url, data);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
/// <summary>
/// 异步 Delete请求
/// </summary>
/// <param name="url"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async Task<T?> DeleteAsync<T>(string url)
{
using var response = await Client.DeleteAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
public void Dispose()
{
Client.Dispose();
}
}

View File

@ -0,0 +1,105 @@
using DispenserCommon.Exceptions;
namespace DispenserCommon.Utils;
/// <summary>
/// 断言工具类
/// </summary>
public class AssertUtil
{
/// <summary>
/// 判断条件是否为真
/// </summary>
/// <param name="expression"></param>
/// <param name="msg"></param>
public static void IsTrue(bool expression, string msg)
{
if (!expression)
{
throw new AssertException(msg);
}
}
/// <summary>
/// 判断条件是否为假
/// </summary>
/// <param name="expression"></param>
/// <param name="msg"></param>
public static void IsFalse(bool expression, string msg)
{
if (expression)
{
throw new AssertException(msg);
}
}
/// <summary>
/// 集合非空断言
/// </summary>
/// <param name="list"></param>
/// <param name="msg"></param>
public static void NotEmpty(List<object>? list, string msg)
{
if (list == null || list.Count == 0)
{
throw new AssertException(msg);
}
}
/// <summary>
/// 集合空断言
/// </summary>
/// <param name="list"></param>
/// <param name="msg"></param>
public static void IsEmpty(List<object> list, string msg)
{
if (list is { Count: > 0 })
{
throw new AssertException(msg);
}
}
/// <summary>
/// 对象非空断言
/// </summary>
/// <param name="model"></param>
/// <param name="msg"></param>
/// <exception cref="AssertException"></exception>
public static void NotNull(object? model, string msg)
{
if (model == null)
{
throw new AssertException(msg);
}
}
/// <summary>
/// 对象空断言
/// </summary>
/// <param name="model"></param>
/// <param name="msg"></param>
/// <exception cref="AssertException"></exception>
public static void IsNull(object? model, string msg)
{
if (model != null)
{
throw new AssertException(msg);
}
}
/// <summary>
/// 字符串非空断言
/// </summary>
/// <param name="str"></param>
/// <param name="msg"></param>
/// <exception cref="AssertException"></exception>
public static void StringNotNullOrEmpty(string str, string msg)
{
if (string.IsNullOrEmpty(str))
{
throw new AssertException(msg);
}
}
}

View File

@ -0,0 +1,39 @@
namespace DispenserCommon.Utils;
/// <summary>
/// bean 工具类
/// </summary>
public class BeanUtil
{
/// <summary>
/// 实现Bean的属性复制
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public static void CopyProperties(object source, object target)
{
var sourceProperties = source.GetType().GetProperties();
var targetProperties = target.GetType().GetProperties();
foreach (var sourceProperty in sourceProperties)
{
var targetProperty = Array.Find(targetProperties, p => p.Name == sourceProperty.Name &&
p.PropertyType == sourceProperty.PropertyType);
if (targetProperty != null && targetProperty.CanWrite)
targetProperty.SetValue(target, sourceProperty.GetValue(source));
}
}
public static T CopyProperties<T>(object source)
{
var instance = Activator.CreateInstance<T>();
CopyProperties(source, instance);
return instance;
}
public static List<T> CopyProperties<T>(IEnumerable<object> source)
{
return source.Select(CopyProperties<T>).ToList();
}
}

View File

@ -0,0 +1,50 @@
namespace DispenserCommon.Utils;
/// <summary>
/// 缓冲队列
/// </summary>
public class BufferQueue<T>(int capacity)
{
private readonly Queue<T?> _queue = new();
private readonly int _capacity = Math.Min(capacity, 10);
private readonly object _lock = new();
// 尝试添加元素到队列中
public bool TryEnqueue(T? item)
{
lock (_lock)
{
if (_queue.Count >= _capacity) return false;
_queue.Enqueue(item);
return true;
}
}
// 尝试从队列中移除并返回元素
public bool TryDequeue(out T? result)
{
lock (_lock)
{
if (_queue.Count > 0)
{
result = _queue.Dequeue();
return true;
}
result = default(T);
return false;
}
}
// 获取当前队列中的元素数量
public int Count
{
get
{
lock (_lock)
{
return _queue.Count;
}
}
}
}

View File

@ -0,0 +1,10 @@
namespace DispenserCommon.Utils;
/// <summary>
/// 弹窗工具类
/// </summary>
public interface ConfirmDialogHelper
{
Task<bool> ShowConfirm(string title, bool showCancel = true, bool showConfirm = true,
string cancelText = "取消", string confirmText = "确认");
}

View File

@ -0,0 +1,577 @@
using Masuit.Tools;
namespace DispenserCommon.Utils;
/// <summary>
/// 数组工具类
/// </summary>
public class CoordinateUtil
{
/// <summary>
/// 将一个二维坐标数组拆分成两个一维数组
/// </summary>
/// <param name="coordinates">二维坐标数组</param>
/// <param name="xAxis">X轴坐标数组</param>
/// <param name="yAxis">Y轴坐标数组</param>
public static void SplitCoordinates(double[][] coordinates, out double[] xAxis, out double[] yAxis)
{
var totalPoints = coordinates.GetLength(0);
xAxis = new double[totalPoints];
yAxis = new double[totalPoints];
for (var i = 0; i < totalPoints; i++)
{
// 提取x坐标
xAxis[i] = coordinates[i][0];
// 提取y坐标
yAxis[i] = coordinates[i][1];
}
}
public static void SplitCoordinates(double[][] coordinates, out double[] xAxis, out double[] yAxis,
out double[] depth)
{
var totalPoints = coordinates.GetLength(0);
xAxis = new double[totalPoints];
yAxis = new double[totalPoints];
depth = new double[totalPoints];
for (var i = 0; i < totalPoints; i++)
{
// 提取x坐标
xAxis[i] = coordinates[i][0];
// 提取y坐标
yAxis[i] = coordinates[i][1];
// 提取针刺深度
depth[i] = coordinates[i][3];
}
}
/// <summary>
/// 将两个坐标数组合并成一个二维坐标数组
/// </summary>
/// <param name="coordinates1"></param>
/// <param name="coordinates2"></param>
/// <returns></returns>
public static double[][] MergeArray(IEnumerable<double> coordinates1, double[] coordinates2)
{
List<double[]> merged = [];
merged.AddRange(coordinates1.Select((t, i) => (double[]) [t, coordinates2[i]]));
return merged.ToArray();
}
/// <summary>
/// 判断数组是否为空
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static bool IsEmpty(double[]? array)
{
return array == null || !array.Any();
}
/// <summary>
/// 判断数组是否为空
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static bool IsEmpty(double[][]? array)
{
return array == null || !array.Any();
}
/// <summary>
/// 判断数组是否为空
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static bool IsEmpty(double[,]? array)
{
return array == null || array.GetLength(0) == 0 || array.GetLength(1) == 0;
}
/// <summary>
/// 合并相似点
/// </summary>
/// <param name="points"></param>
/// <param name="window">相似划分窗口</param>
/// <param name="rowSimilarPitchThreshold">行相似值判断阈值</param>
/// <param name="colSimilarPitchThreshold">列相似值判断阈值</param>
/// <returns></returns>
public static List<double[]> MergeSimilarPoints(List<double[]> points, string window,
double rowSimilarPitchThreshold,
double colSimilarPitchThreshold)
{
var windowConfig = window;
var windows = windowConfig.Split(",");
points = windows
.Select(int.Parse)
.Aggregate(points,
(current, windowSize) =>
MergeSimilarPoints(current, windowSize, rowSimilarPitchThreshold, colSimilarPitchThreshold));
return points;
}
/// <summary>
/// 对坐标进行分区计算,提高计算效率
/// </summary>
/// <param name="points">合并前的坐标点</param>
/// <param name="space">分区数</param>
/// <returns></returns>
private static List<List<List<double[]>>> DivideIntoRegions(List<double[]> points, int space)
{
// 找到坐标范围
var minX = points.Min(p => p[0]);
var maxX = points.Max(p => p[0]);
var minY = points.Min(p => p[1]);
var maxY = points.Max(p => p[1]);
// 计算每个区域的尺寸
var regionWidth = (maxX - minX) / space;
var regionHeight = (maxY - minY) / space;
// 初始化区域列表
var regions = new List<List<List<double[]>>>(space);
for (var i = 0; i < space; i++)
{
regions.Add(new List<List<double[]>>(space));
for (var j = 0; j < space; j++) regions[i].Add([]);
}
// 将点分配到相应的区域
foreach (var point in points)
{
var x = point[0];
var y = point[1];
var row = (int)Math.Floor((y - minY) / regionHeight);
var col = (int)Math.Floor((x - minX) / regionWidth);
// 防止由于边界值导致的索引超出范围
row = Math.Min(row, space - 1);
col = Math.Min(col, space - 1);
regions[row][col].Add(point);
}
return regions;
}
/// <summary>
/// 合并相似点
/// </summary>
/// <param name="points">合并前的坐标点集合</param>
/// <param name="space">区域数</param>
/// <param name="rowSimilarPitchThreshold">行相似值判断阈值</param>
/// <param name="colSimilarPitchThreshold">列相似值判断阈值</param>
/// <returns></returns>
private static List<double[]> MergeSimilarPoints(List<double[]> points, int space, double rowSimilarPitchThreshold,
double colSimilarPitchThreshold)
{
// 将整个区域划分为 若干个区域
var regions = DivideIntoRegions(points, space);
var merged = new List<double[]>();
// 遍历每个区域,对每个区域进行相似点合并
foreach (var region in regions)
foreach (var col in region)
merged.AddRange(MergePoints(col, rowSimilarPitchThreshold, colSimilarPitchThreshold));
return merged;
}
/// <summary>
/// 对区域内的点进行合并处理
/// </summary>
/// <param name="points">待合并区域</param>
/// <param name="rowSimilarPitchThreshold">行相似值判断阈值</param>
/// <param name="colSimilarPitchThreshold">列相似值判断阈值</param>
/// <returns></returns>
private static List<double[]> MergePoints(List<double[]> points, double rowSimilarPitchThreshold,
double colSimilarPitchThreshold)
{
if (points.Count == 0)
return new List<double[]>();
// 如果两个两个点的 abs(x2-x1) <= 0.2 && abs(y2-y1) <= 0.35, 那么可以认为这两个点是相似的,只保留第一个点即可
var merged = new List<double[]>();
foreach (var point in points)
// 同当前区域的其他点进行比较
if (merged.Count == 0)
{
merged.Add(point);
}
else
{
var similar = false;
foreach (var other in merged)
if (IsSimilar(point, other, 0.1, 0.1))
{
Console.WriteLine(
$"{point[0]},{point[1]} 与 {other[0]},{other[1]} 相似, X差值{point[0] - other[0]} y差值{point[1] - other[1]}");
similar = true;
break;
}
if (!similar) merged.Add(point);
}
return merged;
}
/// <summary>
/// 判断两个点是否相似
/// </summary>
/// <param name="p1">第一个点</param>
/// <param name="p2">第二个点</param>
/// <param name="rowSimilarPitchThreshold">行相似值判断阈值</param>
/// <param name="colSimilarPitchThreshold">列相似值判断阈值</param>
/// <returns></returns>
private static bool IsSimilar(double[] p1, double[] p2, double rowSimilarPitchThreshold,
double colSimilarPitchThreshold)
{
return Math.Abs(p2[0] - p1[0]) <= rowSimilarPitchThreshold &&
Math.Abs(p2[1] - p1[1]) <= colSimilarPitchThreshold;
}
/// <summary>
/// 从原数组中根据匹配点数组查找相似点
/// </summary>
/// <param name="referencePoints"></param>
/// <param name="comparisonPoints"></param>
/// <param name="rowSimilarPitchThreshold">行相似值判断阈值</param>
/// <param name="colSimilarPitchThreshold">列相似值判断阈值</param>
/// <returns></returns>
public static List<double[]> FindSimilarPoints(List<double[]> referencePoints, List<double[]> comparisonPoints,
double rowSimilarPitchThreshold,
double colSimilarPitchThreshold)
{
// 从原数组中根据匹配点数组查找相似点
return referencePoints.AsParallel().Where(sourcePoint =>
comparisonPoints.Any(matchPoint =>
IsSimilar(sourcePoint, matchPoint, rowSimilarPitchThreshold, colSimilarPitchThreshold))
).ToList();
}
/// <summary>
/// 晶环和pcb坐标长度对齐
/// </summary>
/// <param name="pcbCoordinates">原始pcb坐标</param>
/// <param name="waferCoordinates">原始晶环坐标</param>
/// <param name="pcbAlignedCoordinates">对齐后的pcb坐标</param>
/// <param name="waferAlignedCoordinates">对齐后的晶环坐标</param>
public static void CoordinateAlign(List<double[]> pcbCoordinates, List<double[]> waferCoordinates,
out List<double[]> pcbAlignedCoordinates, out List<double[]> waferAlignedCoordinates)
{
pcbAlignedCoordinates = [];
waferAlignedCoordinates = [];
var pcbStartIndex = 0;
var waferStartIndex = 0;
var pStart = 0;
var wStart = 0;
while (pcbStartIndex <= pcbCoordinates.Count && waferStartIndex <= waferCoordinates.Count)
if ((IsReal(pcbCoordinates, pcbStartIndex) && IsReal(waferCoordinates, waferStartIndex)) ||
(!IsReal(pcbCoordinates, pcbStartIndex) && !IsReal(waferCoordinates, waferStartIndex)))
{
pcbAlignedCoordinates[pStart++] = pcbCoordinates[pcbStartIndex++];
waferAlignedCoordinates[wStart++] = waferCoordinates[waferStartIndex++];
}
else if (IsReal(pcbCoordinates, pcbStartIndex) && !IsReal(waferCoordinates, waferStartIndex))
{
var pcbCoordinate = new double[3];
Array.Copy(pcbCoordinates[pcbStartIndex], pcbCoordinate, 3);
pcbCoordinate[2] = 0;
pcbAlignedCoordinates[pStart++] = pcbCoordinate;
waferAlignedCoordinates[wStart++] = waferCoordinates[waferStartIndex++];
}
else
{
pcbAlignedCoordinates[pStart++] = pcbCoordinates[pcbStartIndex++];
var waferCoordinate = new double[3];
Array.Copy(waferCoordinates[waferStartIndex], waferCoordinate, 3);
waferCoordinate[2] = 0;
waferAlignedCoordinates[wStart++] = waferCoordinate;
}
}
/// <summary>
/// 判断是否是真实坐标
/// </summary>
/// <param name="pcbCoordinates"></param>
/// <param name="pcbStartIndex"></param>
/// <returns></returns>
private static bool IsReal(IReadOnlyList<double[]> pcbCoordinates, int pcbStartIndex)
{
return pcbCoordinates[pcbStartIndex][2] != 0;
}
/// <summary>
/// 从插值路径中提取动打坐标信息
/// </summary>
/// <param name="coordinates">插值后的坐标数组</param>
/// <param name="startPoint">开始的坐标</param>
/// <param name="endPoint">借宿的坐标</param>
public static List<double[]> SubCurrentBatchCoordinate(IEnumerable<double[]> coordinates, double[] startPoint,
double[] endPoint)
{
// 保存当前截取后的新坐标
List<double[]> newCoordinates = [];
var target = false;
foreach (var coordinate in coordinates)
{
if (Equals(coordinate, startPoint))
{
target = true;
}
else if (Equals(coordinate, endPoint))
{
newCoordinates.Add(coordinate);
break;
}
if (target)
// 都是目标值
newCoordinates.Add(coordinate);
}
return newCoordinates;
}
public static bool Equals(double[] a, double[] b)
{
if (IsEmpty(a) || IsEmpty(b)) return false;
return Math.Abs(a[0] - b[0]) < 0.00001 && Math.Abs(a[1] - b[1]) < 0.00001;
}
public static double[][] FillDefaultFlagIfNotExist(double[][] path, double defaultFlag = 1)
{
if (path.IsNullOrEmpty()) return path;
if (path[0].Length == 2) return path.Select(v => new[] { v[0], v[1], defaultFlag }).ToArray();
return path;
}
public static bool IsRealPoint(double flag)
{
return Math.Abs(Math.Round(flag, MidpointRounding.ToEven) - 1) < 0.0001;
}
public static double GetTowCoordinatesDistinct(double[] p1, double[] p2)
{
return Math.Sqrt(Math.Pow(p1[0] - p2[0], 2) + Math.Pow(p1[1] - p2[1], 2));
}
public static double[][] SubPcbPath(double[][] path, int rowAmount, int startColumn, int pathRowAmount)
{
List<double[]> result = new();
if ((startColumn + pathRowAmount - 1) > rowAmount)
{
pathRowAmount = rowAmount - startColumn + 1;
}
var rows = RowBy(path.ToList(), rowAmount);
for (var i = 0; i < rows.Count; i++)
{
var row = rows[i];
List<double[]> chunkRow;
if (i % 2 == 0)
{
chunkRow = row.Skip(startColumn - 1).Take(pathRowAmount).ToList();
}
else
{
chunkRow = row.Skip(rowAmount - (pathRowAmount + startColumn - 1)).Take(pathRowAmount).ToList();
}
result.AddRange(chunkRow);
}
return result.ToArray();
}
static List<List<double[]>> RowBy(List<double[]> source, int chunkSize)
{
List<List<double[]>> result = new List<List<double[]>>();
for (int i = 0; i < source.Count; i += chunkSize)
{
result.Add(source.GetRange(i, Math.Min(chunkSize, source.Count - i)));
}
return result;
}
public static double[] AffineTransform(double[,] matrix, IReadOnlyList<double> point)
{
if (matrix.GetLength(0) != 3 || matrix.GetLength(1) != 3)
throw new ArgumentException("Matrix must be a 3x3 array.");
return new double[]
{
matrix[0, 0] * point[0] + matrix[0, 1] * point[1] + matrix[0, 2] * point[2],
matrix[1, 0] * point[0] + matrix[1, 1] * point[1] + matrix[1, 2] * point[2]
};
}
public static double[][] Convert2DArrayToJaggedArray(double[,] twoDArray)
{
int rows = twoDArray.GetLength(0); // 获取第一维度的长度,即行数
int cols = twoDArray.GetLength(1); // 获取第二维度的长度,即列数
double[][] jaggedArray = new double[rows][]; // 创建外层数组
for (int i = 0; i < rows; i++)
{
jaggedArray[i] = new double[cols]; // 每一行创建一个新的内层数组
for (int j = 0; j < cols; j++)
{
jaggedArray[i][j] = twoDArray[i, j]; // 复制元素
}
}
return jaggedArray;
}
public static List<List<double[]>> GroupPointsIntoRows(double[][] pointsArray, double yThreshold)
{
// 对点数组进行排序
var pointsArraySorted = pointsArray.OrderBy(p => p[0]).ToArray();
List<List<double[]>> rows = new List<List<double[]>>();
foreach (var point in pointsArraySorted)
{
bool placed = false;
foreach (var row in rows)
{
if (Math.Abs(row.Last()[1] - point[1]) <= yThreshold && point[0] > row.Last()[0])
{
row.Add(point);
placed = true;
break;
}
}
if (!placed)
{
rows.Add(new List<double[]> { point });
}
}
return rows.OrderBy(p => p[0][1]).ToList();
}
public static List<double[]> ConvertMatrixToList(double[,] matrix)
{
int rows = matrix.GetLength(0);
int columns = matrix.GetLength(1);
var list = new List<double[]>();
for (int i = 0; i < rows; i++)
{
double[] rowArray = new double[columns];
for (int j = 0; j < columns; j++)
{
rowArray[j] = matrix[i, j];
}
list.Add(rowArray);
}
return list;
}
/// <summary>
/// 将 坐标数组转为 矩阵
/// </summary>
/// <param name="array">坐标数组</param>
/// <param name="cols">标准分列数</param>
public static double[,][] Array2Matrix(double[][] array, int cols)
{
// 总的行数
var rows = array.Length / cols;
var matrix = new double[rows, cols][];
for (var i = 0; i < rows; i++)
{
for (var j = 0; j < cols; j++)
{
matrix[i, j] = array[i * cols + j];
}
}
return matrix;
}
/// <summary>
/// 从矩阵的中心位置将其一分为二
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static (double[,][], double[,][]) SplitMatrixByHalf(double[,][] array)
{
var rows = array.GetLength(0);
var cols = array.GetLength(1);
var middle = cols / 2;
var part1 = new double[rows, middle][];
var part2 = new double[rows, cols - middle][];
for (var i = 0; i < rows; i++)
{
for (var j = 0; j < cols; j++)
{
if (j < middle)
part1[i, j] = array[i, j];
else
part2[i, j - middle] = array[i, j];
}
}
return (part1, part2);
}
/// <summary>
/// 将矩阵数组转为弓字形数组
/// </summary>
/// <param name="matrix"></param>
/// <returns></returns>
public static double[][] ConvertMatrix2BowShapeArray(double[,][] matrix)
{
var rows = matrix.GetLength(0);
var cols = matrix.GetLength(1);
var result = new double[rows * cols][];
var index = 0;
for (var d = 0; d < rows + cols - 1; d++)
{
if (d % 2 == 0) // 从左下角到右上角
{
for (var i = Math.Min(d, rows - 1); i >= Math.Max(0, d - cols + 1); i--)
{
result[index++] = matrix[i, d - i];
}
}
else // 从右上角到左下角
{
for (var i = Math.Max(0, d - cols + 1); i <= Math.Min(d, rows - 1); i++)
{
result[index++] = matrix[i, d - i];
}
}
}
return result;
}
}

View File

@ -0,0 +1,38 @@
using System.Runtime.InteropServices;
using System.Text;
namespace DispenserCommon.Utils;
public class INIFileReader
{
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
// 声明INI文件的读操作函数 GetPrivateProfileString()
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal,
int size, string filePath);
/// 写入INI的方法
public void INIWrite(string section, string key, string value, string path)
{
// section=配置节点名称key=键名value=返回键值path=路径
WritePrivateProfileString(section, key, value, path);
}
//读取INI的方法
public string INIRead(string section, string key, string path)
{
// 每次从ini中读取多少字节
var temp = new StringBuilder(255);
// section=配置节点名称key=键名temp=上面path=路径
GetPrivateProfileString(section, key, "", temp, 255, path);
return temp.ToString();
}
//删除一个INI文件
public void INIDelete(string FilePath)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,68 @@
using Newtonsoft.Json;
using Serilog;
namespace DispenserCommon.Utils;
public class JsonUtil
{
public static string ToJson(object obj)
{
try
{
return JsonConvert.SerializeObject(obj);
}
catch (Exception e)
{
throw new ArgumentException($" 无效的json 对象 {obj} ");
}
}
public static Dictionary<string, object>? ToDictionary(string json)
{
try
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
}
catch (Exception e)
{
throw new ArgumentException($" 无效的json 对象 {json} ");
}
}
public static T FromJson<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch (Exception e)
{
Log.Error($"json解析异常 {e.Message}");
return default;
}
}
public static object? FromJson(Type type, string json)
{
try
{
return JsonConvert.DeserializeObject(json, type);
}
catch (Exception e)
{
throw new ArgumentException($" 无效的json 字符串 {json} ");
}
}
public static T FromJsonOrDefault<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch (Exception e)
{
return default;
}
}
}

View File

@ -0,0 +1,46 @@
namespace DispenserCommon.Utils;
public class LineEquationUtil
{
public static double CalculateX(LineEquation lineEquation, double y)
{
return lineEquation.CalculateXCoordinate(y);
}
public static double CalculateY(LineEquation lineEquation, double x)
{
return lineEquation.CalculateYCoordinate(x);
}
public static LineEquation CreateLineEquation(double x1, double y1, double x2, double y2)
{
return new LineEquation(x1, y1, x2, y2);
}
public class LineEquation
{
public LineEquation(double x1, double y1, double x2, double y2)
{
CalculateSlopeAndIntercept(x1, y1, x2, y2);
}
public double Slope { get; private set; }
public double Intercept { get; private set; }
private void CalculateSlopeAndIntercept(double x1, double y1, double x2, double y2)
{
Slope = (y2 - y1) / (x2 - x1);
Intercept = y1 - Slope * x1;
}
public double CalculateXCoordinate(double y)
{
return (y - Intercept) / Slope;
}
public double CalculateYCoordinate(double x)
{
return Slope * x + Intercept;
}
}
}

View File

@ -0,0 +1,24 @@
using System.Security.Cryptography;
using System.Text;
namespace DispenserCommon.Utils;
/// <summary>
/// MD5 加密工具类
/// </summary>
public class Md5Util
{
public static string Md5(string input)
{
// 创建一个MD5对象
// 将输入字符串转换为字节数组
var inputBytes = Encoding.UTF8.GetBytes(input);
// 计算输入字节数组的哈希值
var bytes = MD5.HashData(inputBytes);
// 将字节数组转换为字符串
var hashString = new StringBuilder();
foreach (var b in bytes) hashString.Append($"{b:x2}");
return hashString.ToString();
}
}

View File

@ -0,0 +1,154 @@
using Avalonia.Controls;
using Avalonia.Threading;
using MsBox.Avalonia;
using MsBox.Avalonia.Base;
using MsBox.Avalonia.Dto;
using MsBox.Avalonia.Models;
using Serilog;
namespace DispenserCommon.Utils;
/// <summary>
/// 用于弹窗提示
/// </summary>
public static class MessageBoxHelper
{
private const string SUCCESS = "Assets/success.png";
private const string INFO = "Assets/info.png";
private const string WARNING = "Assets/warning.png";
private const string ERROR = "Assets/error.png";
private const string NOTIFY = "Assets/notify.png";
// 定义 5分钟的滑动时间窗口
private static readonly SlidingWindow SlidingWindow = new(5 * 60 * 1000);
/// <summary>
/// 构建弹窗对象
/// </summary>
/// <param name="title">消息标题,用于显示在弹窗最顶方</param>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">内容标题,用于显示在内容的顶部</param>
/// <param name="icon">弹窗左上角icon</param>
/// <returns></returns>
private static IMsBox<string> CreateMessageBox(
string title,
string msg,
string contentTitle = "",
string icon = "Assets/info.png")
{
return MessageBoxManager.GetMessageBoxCustom(
new MessageBoxCustomParams
{
ButtonDefinitions = new List<ButtonDefinition>
{
new() { Name = "关闭", IsCancel = true }
},
WindowIcon = new WindowIcon(icon),
ContentTitle = title,
ContentHeader = contentTitle,
ContentMessage = msg,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
CanResize = false,
MinWidth = 300,
MinHeight = 200,
MaxWidth = 800,
MaxHeight = 1000,
SizeToContent = SizeToContent.WidthAndHeight,
ShowInCenter = true,
Topmost = true
});
}
private static async void Show(string title, string msg, string icon, string contentTitle = "", bool filter = true,
Action? callback = null)
{
try
{
// 对弹窗内容进行滤波,减少相同内容的弹窗频次
if (filter && !SlidingWindow.AllowValue(msg)) return;
await Dispatcher.UIThread.InvokeAsync(async () =>
{
var box = CreateMessageBox(title, msg, contentTitle, icon);
await box.ShowWindowAsync();
});
if (callback != null) callback();
}
catch (Exception e)
{
Log.Error(e, "弹窗出现异常");
}
}
/// <summary>
/// 消息级别弹窗
/// </summary>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">消息标题,默认为空字符串,不显示</param>
/// <param name="title">弹窗标题</param>
/// <param name="filter">是否过滤弹窗</param>
/// <param name="callback"></param>
public static void Info(string msg, string contentTitle = "", string title = "提示", bool filter = true,
Action? callback = null)
{
Show(title, msg, INFO, contentTitle, filter, callback);
}
/// <summary>
/// 告警级别弹窗
/// </summary>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">消息标题,默认为空字符串,不显示</param>
/// <param name="title">弹窗标题</param>
/// <param name="filter">是否过滤弹窗</param>
/// <param name="callback"></param>
public static void Warning(string msg, string contentTitle = "", string title = "警告", bool filter = true,
Action? callback = null)
{
Show(title, msg, WARNING, contentTitle, filter, callback);
}
/// <summary>
/// 错误级别弹窗
/// </summary>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">消息标题,默认为空字符串,不显示</param>
/// <param name="title">弹窗标题</param>
/// <param name="filter">是否过滤弹窗</param>
/// <param name="callback"></param>
public static void Error(string msg, string contentTitle = "", string title = "错误", bool filter = true,
Action? callback = null)
{
Show(title, msg, ERROR, contentTitle, filter, callback);
}
/// <summary>
/// 通知级别弹窗
/// </summary>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">消息标题,默认为空字符串,不显示</param>
/// <param name="title">弹窗标题</param>
/// <param name="filter">是否过滤弹窗</param>
/// <param name="callback"></param>
public static void Notify(string msg, string contentTitle = "", string title = "通知", bool filter = true,
Action? callback = null)
{
Show(title, msg, NOTIFY, contentTitle, filter, callback);
}
/// <summary>
/// 成功通知级别弹窗
/// </summary>
/// <param name="msg">消息内容</param>
/// <param name="contentTitle">消息标题,默认为空字符串,不显示</param>
/// <param name="title">弹窗标题</param>
/// <param name="filter">是否过滤弹窗</param>
/// <param name="callback"></param>
public static void Success(string msg, string contentTitle = "", string title = "成功", bool filter = true,
Action? callback = null)
{
Show(title, msg, SUCCESS, contentTitle, filter, callback);
}
}

View File

@ -0,0 +1,32 @@
using Microsoft.Win32;
namespace DispenserCommon.Utils;
/// <summary>
/// 注册表工具类
/// </summary>
public static class RegistryHelper
{
public static void WriteValue(string keyPath, string valueName, object value, RegistryValueKind valueKind)
{
using var key = Registry.CurrentUser.CreateSubKey(keyPath);
key.SetValue(valueName, value, valueKind);
}
public static object? ReadValue(string keyPath, string valueName)
{
using var key = Registry.CurrentUser.OpenSubKey(keyPath);
return key?.GetValue(valueName);
}
public static void DeleteValue(string keyPath, string valueName)
{
using var key = Registry.CurrentUser.OpenSubKey(keyPath, writable: true);
key?.DeleteValue(valueName, false);
}
public static void DeleteKey(string keyPath)
{
Registry.CurrentUser.DeleteSubKeyTree(keyPath, throwOnMissingSubKey: false);
}
}

View File

@ -0,0 +1,38 @@
namespace DispenserCommon.Utils;
/// <summary>
/// 重试工具类
/// </summary>
public class RetryHelper
{
/// <summary>
/// 进行重试
/// </summary>
/// <param name="action"></param>
/// <param name="maxRetries"></param>
/// <returns></returns>
public static bool Retry(Action action, int maxRetries)
{
for (var attempt = 0; attempt < maxRetries; attempt++)
{
try
{
action();
// 成功后返回 true
return true;
}
catch
{
if (attempt == maxRetries - 1)
{
// 超过重试次数返回 false
return false;
}
}
Thread.Sleep(200);
}
return false;
}
}

View File

@ -0,0 +1,75 @@
using DispenserCommon.Interface;
using Microsoft.Extensions.DependencyInjection;
namespace DispenserCommon.Utils;
/// <summary>
/// 获取服务实例工具类
/// </summary>
public class ServiceLocator
{
private static IServiceProvider? _serviceProvider;
/// <summary>
/// 注册 IServiceProvider
/// </summary>
/// <param name="serviceProvider"></param>
public static void Initialize(IServiceProvider? serviceProvider, IServiceCollection services)
{
_serviceProvider = serviceProvider;
// 注册服务定位器
foreach (var service in services)
{
var serviceType = service.ServiceType;
if (typeof(Instant).IsAssignableFrom(serviceType)) serviceProvider.GetService(service.ServiceType);
}
}
/// <summary>
/// 获取服务实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static T GetService<T>() where T : class
{
return _serviceProvider.GetService(typeof(T)) is not T service
? throw new ArgumentException(
$"{typeof(T)} needs to be registered in ConfigureServices within App.axaml.cs.")
: service;
}
/// <summary>
/// 根据指定的实现类型获取服务实例
/// </summary>
/// <param name="impl"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static T GetService<T>(Type impl) where T : class
{
var services = GetServices<T>();
return services.FirstOrDefault(x => x.GetType() == impl) ?? throw new ArgumentException(
$"{typeof(T)} needs to be registered in ConfigureServices within App.axaml.cs.");
}
public static object? GetService(Type type)
{
return _serviceProvider.GetService(type);
}
/// <summary>
/// 根据类型获取服务实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<T> GetServices<T>() where T : class
{
var services = _serviceProvider.GetServices<T>();
return services;
}
}

View File

@ -0,0 +1,41 @@
namespace DispenserCommon.Utils;
/// <summary>
/// 基于滑动时间窗口算法来判断指定时间窗口内是否存在目标值
/// </summary>
public class SlidingWindow(int milliseconds, int thresold = 1)
{
private readonly Queue<(DateTime time, object value)> _window = new();
private readonly TimeSpan _windowSize = TimeSpan.FromMilliseconds(milliseconds);
/// <summary>
/// 添加值
/// </summary>
/// <param name="value">待添加值</param>
public bool AllowValue(object value)
{
var currentTime = DateTime.Now;
while (_window.Count > 0 && currentTime - _window.Peek().time > _windowSize) _window.Dequeue();
if (_window.Count < thresold)
{
_window.Enqueue((currentTime, value));
return true;
}
return false;
}
/// <summary>
/// 判断是否已经包含了该值
/// </summary>
/// <param name="targetValue">待匹配值</param>
/// <returns></returns>
public bool Contains(object targetValue)
{
return _window.Any(item => item.value.Equals(targetValue));
}
}

View File

@ -0,0 +1,88 @@
using System.Text;
namespace DispenserCommon.Utils;
public class TimeUtil
{
public static void Sleep(int milliseconds)
{
Thread.Sleep(milliseconds);
}
/// <summary>
/// 获取当前的时间戳
/// </summary>
/// <returns></returns>
public static long Now()
{
return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
}
/// <summary>
/// 获取当前的时间,格式化为目标格式
/// </summary>
/// <param name="format"></param>
/// <returns></returns>
public static string GetNowTime(string format = "yyyy-MM-dd HH:mm:ss")
{
return DateTime.Now.ToString(format);
}
/// <summary>
/// 格式化时间
/// </summary>
/// <param name="time"></param>
/// <param name="format"></param>
/// <returns></returns>
public static string FormatTime(long time, string format = "yyyy-MM-dd HH:mm:ss")
{
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(time).DateTime;
return dateTime.ToString(format);
}
public static string ToTimeSpan(long time)
{
// 使用TimeSpan.FromMilliseconds来创建TimeSpan对象
var timeSpan = TimeSpan.FromMilliseconds(time);
// 获取小时、分钟和秒
var hours = timeSpan.Hours;
var minutes = timeSpan.Minutes;
var seconds = timeSpan.Seconds;
var sb = new StringBuilder();
if (hours > 0)
{
sb.Append(hours).Append("小时");
}
if (minutes > 0)
{
sb.Append(minutes).Append('分');
}
if (seconds > 0)
{
sb.Append(seconds).Append('秒');
}
return sb.ToString();
}
/// <summary>
/// 将时间戳转为DateTime对象
/// </summary>
/// <param name="timestamp"></param>
/// <returns></returns>
public static DateTime TimeStampToDateTime(long timestamp)
{
var dateTimeOffset = (timestamp + "").Length == 10
? DateTimeOffset.FromUnixTimeSeconds(timestamp)
: DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
return dateTimeOffset.DateTime;
}
}

View File

@ -0,0 +1,39 @@
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using Serilog;
namespace DispenserCommon.Utils;
public class ToastUtil
{
private static WindowNotificationManager? _manager;
public static void SetManager(WindowNotificationManager manager)
{
_manager = manager;
}
public static void Info(string msg)
{
Log.Information(msg);
Dispatcher.UIThread.Invoke(() => { _manager?.Show(new Notification("", msg)); });
}
public static void Error(string msg)
{
Log.Error(msg);
Dispatcher.UIThread.Invoke(() => { _manager?.Show(new Notification("", msg, NotificationType.Error)); });
}
public static void Warn(string msg)
{
Log.Warning(msg);
Dispatcher.UIThread.Invoke(() => { _manager?.Show(new Notification("", msg, NotificationType.Warning)); });
}
public static void Success(string msg)
{
Log.Information(msg);
Dispatcher.UIThread.Invoke(() => { _manager?.Show(new Notification("", msg, NotificationType.Success)); });
}
}

View File

@ -0,0 +1,42 @@
namespace DispenserCommon.Utils;
public static class ValueUtil
{
/// <summary>
/// 将 int 转为 8 bit 的二进制字符串,前面不足的补 0
/// </summary>
public static string Int2BitStr(int val, int bitLen)
{
return new string(Int2BitChars(val, bitLen));
}
/// <summary>
/// </summary>
/// <param name="val"></param>
/// <param name="bitLen"></param>
/// <returns></returns>
public static char[] Int2BitChars(int val, int bitLen)
{
// 直接将整数val转换为二进制字符串不考虑十六进制。
var binaryString = Convert.ToString(val, 2);
// 如果val为负数binaryString将包含二进制补码形式的字符串其长度可能超过bitLen。
// 根据需要裁剪或填充字符串以适应指定的位长度(bitLen)。
if (binaryString.Length > bitLen)
// 对于负数,去除多余的前导'1'。
binaryString = binaryString.Substring(binaryString.Length - bitLen);
else
// 填充以达到所需长度。
binaryString = binaryString.PadLeft(bitLen, '0');
// 反转和转换为字符数组。
var chars = binaryString.Reverse().ToArray();
return chars;
}
public static int BitChars2Int(char[] chars)
{
return Convert.ToInt32(new string(chars), 2);
}
}

View File

@ -0,0 +1,62 @@
using Avalonia.Controls;
namespace DispenserCommon.Utils;
public class WindowUtil
{
private static Window? _mainWindow;
public static void SetMainWindow(Window window)
{
_mainWindow = window;
}
/// <summary>
/// 显示弹窗
/// </summary>
/// <param name="dialog">弹窗</param>
/// <typeparam name="TD">弹窗类型</typeparam>
public static void ShowDialog<TD>(TD dialog) where TD : Window
{
if (_mainWindow == null) return;
dialog.ShowDialog(_mainWindow);
}
/// <summary>
/// 显示弹窗
/// </summary>
/// <param name="dialog">弹窗</param>
/// <param name="action">弹窗回调</param>
/// <typeparam name="TD">弹窗类型</typeparam>
public static void ShowDialog<TD>(TD dialog, Action<TD> action) where TD : Window
{
if (_mainWindow == null) return;
dialog.ShowDialog(_mainWindow);
action(dialog);
}
/// <summary>
/// 显示弹窗
/// </summary>
/// <param name="dialog">弹窗实体</param>
/// <typeparam name="TD">弹窗类型</typeparam>
/// <typeparam name="TR">弹窗回调信息</typeparam>
/// <returns></returns>
public static async Task<TR> ShowDialog<TD, TR>(TD dialog) where TD : Window
{
if (_mainWindow == null) return default;
return await dialog.ShowDialog<TR>(_mainWindow);
}
/// <summary>
/// 无返回值的弹窗
/// </summary>
/// <typeparam name="TD"></typeparam>
public static void ShowDialog<TD>() where TD : Window
{
if (_mainWindow == null) return;
var dialog = Activator.CreateInstance<TD>();
dialog.ShowDialog(_mainWindow);
}
}

View File

@ -0,0 +1,80 @@
using DispenserCommon.LogUtils;
using DispenserCore.Service;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Exceptions;
namespace DispenserCommon.LogConfig;
/// <summary>
/// 日志配置类
/// </summary>
public class LogConfiguration
{
private static LogParamsService _logParamsService = new();
/// <summary>
/// 获取日志配置对象
/// </summary>
/// <returns></returns>
public static Logger GetLogger()
{
var logParams = _logParamsService.GetLogParams();
// 日志输出目录
var basePath = logParams!.Path ?? AppDomain.CurrentDomain.BaseDirectory;
// 适配最小日志级别
Enum.TryParse(logParams.Level, true, out LogEventLevel miniLevel);
return new LoggerConfiguration()
#if DEBUG
// 测试环境的话输出debug级别
.MinimumLevel.Debug()
#else
// 其他环境输出info 级别
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", miniLevel)
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.WriteTo.Logger(
l =>
l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug)
.WriteTo.File(
Path.Combine(basePath, "logs", "debug", "debug-.log"),
rollingInterval: RollingInterval.Hour,
retainedFileCountLimit: 24
)
)
.WriteTo.Logger(
l => l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Information)
.WriteTo.File(
Path.Combine(basePath, "logs", "info", "info-.log"),
rollingInterval: RollingInterval.Hour,
retainedFileCountLimit: 72
)
)
.WriteTo.Logger(
l => l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Warning)
.WriteTo.File(
Path.Combine(basePath, "logs", "warning", "warning-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30
)
)
.WriteTo.Logger(
l => l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Error)
.WriteTo.File(
Path.Combine(basePath, "logs", "error", "error-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 90
)
)
// 测试环境同步输出到控制台
.WriteTo.Console()
.WriteTo.Sink(new DispenserLogSink())
.CreateLogger();
}
}

View File

@ -0,0 +1,60 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using DispenserCore.Model.DTO;
namespace DispenserCore.Context;
[DispenserCommon.Ioc.Component]
public class GlobalSessionHolder : INotifyPropertyChanged
{
private Session _session;
private Session Session
{
get => _session;
set
{
_session = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 设置用户会话
/// </summary>
/// <param name="session"></param>
public void SetSession(Session session)
{
Session = session;
}
/// <summary>
/// 获取用户会话信息
/// </summary>
/// <returns></returns>
public Session GetSession()
{
return Session;
}
public bool Logged()
{
return Session != null;
}
/// <summary>
/// 清理会话会话
/// </summary>
public void ClearSession()
{
Session = null;
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Masuit.Tools.Core" Version="2.6.9.9"/>
<PackageReference Include="Stateless" Version="5.15.0"/>
<PackageReference Include="System.Security.Permissions" Version="8.0.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DispenserAlgorithm\DispenserAlgorithm.csproj"/>
<ProjectReference Include="..\DispenserCommon\DispenserCommon.csproj"/>
<ProjectReference Include="..\DispenserHal\DispenserHal.csproj"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="Flow\Core\Node.cs"/>
</ItemGroup>
<ItemGroup>
<Reference Include="DispenserVision.Halcon">
<HintPath>..\DispenserDesktop\Libs\DispenserVision.Halcon.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,52 @@
using System.Reflection;
using DispenserCommon.Ioc;
namespace DispenserCore.IOC;
public class IocScanner
{
/// <summary>
/// 扫描所有的类将所有带有Component的类注册到IOC容器中
/// </summary>
public static List<IocService> Scan()
{
var components = GetAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => t.GetCustomAttributes(typeof(Component), true).Length > 0))
.ToList();
List<IocService> services = [];
components.ForEach(component =>
{
var attribute = component.GetCustomAttribute<Component>();
services.Add(new IocService(attribute?.Type ?? component, component));
});
return services;
}
private static IEnumerable<Assembly> GetAssemblies()
{
var assemblies = new List<Assembly>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var name = assembly.GetName().Name;
if (name != null && name.ToLower().Contains("dispenser")) GetReferenceAssemblies(assembly, assemblies);
}
return assemblies;
}
private static void GetReferenceAssemblies(Assembly assembly, ICollection<Assembly> assemblies)
{
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
var name = assemblyName.Name;
if (name != null && name.ToLower().Contains("dispenser"))
{
var ass = Assembly.Load(assemblyName);
if (assemblies.Contains(ass)) continue;
assemblies.Add(ass);
GetReferenceAssemblies(ass, assemblies);
}
}
}
}

View File

@ -0,0 +1,8 @@
namespace DispenserCore.IOC;
public class IocService(Type type, Type implement)
{
public Type Type => type;
public Type Implement => implement;
}

View File

@ -0,0 +1,57 @@
using System.IO.Compression;
using DispenserCore.Service;
using Quartz;
using Serilog;
namespace DispenserCore.Job;
public class LogUploadJob : IJob
{
private static LogParamsService _logParamsService = new();
public Task Execute(IJobExecutionContext context)
{
var logParams = _logParamsService.GetLogParams();
var basePath = logParams!.Path ?? AppDomain.CurrentDomain.BaseDirectory;
//前一天的日期
var date = DateTime.Now.AddDays(-1).ToString("yyyyMMdd");
var levels = logParams.UploadLevels!.Split(",").ToList();
string? sourceFolder = null;
string? zipFilePath = null;
foreach (var level in levels)
try
{
sourceFolder = Path.Combine(basePath, "logs", level);
zipFilePath = Path.Combine(sourceFolder, $"{level}-{date}.log");
var searchPattern = $"*{level}-{date}*.log";
using (var zipFile = new FileStream(zipFilePath, FileMode.Create))
{
using (var archive = new ZipArchive(zipFile, ZipArchiveMode.Create))
{
foreach (var file in Directory.GetFiles(sourceFolder, searchPattern))
{
var fileName = Path.GetFileName(file);
var entry = archive.CreateEntry(fileName);
using (var entryStream = entry.Open())
using (var fileStream = File.OpenRead(file))
{
fileStream.CopyTo(entryStream);
}
}
}
}
//TODO 上传到云端
}
catch (Exception e)
{
Log.Error(e, "日志{0}上传失败", sourceFolder);
}
finally
{
if (zipFilePath != null && File.Exists(zipFilePath)) File.Delete(zipFilePath);
}
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,37 @@
using DispenserCommon.Events;
using DispenserCommon.scheduler;
using DispenserCore.Service;
using Serilog;
namespace DispenserCore.Job;
public class SchedulerManager
{
private static readonly LogParamsService LogParamsService = new();
public static async Task<bool> StartAll()
{
Log.Information("启动所有定时任务");
EventBus<string>.Publish(EventType.SetupNotify, "正在启动定时任务");
var logParams = LogParamsService.GetLogParams();
await SchedulerHelper.Start();
await SchedulerHelper.SchedulerCorn<LogUploadJob>(null, logParams?.UploadCorn);
return true;
}
public static async void Shutdown()
{
try
{
Log.Information("关闭任务调度器");
await SchedulerHelper.Shutdown();
}
catch (Exception e)
{
Log.Error(e, "关闭任务调度器");
}
}
}

View File

@ -0,0 +1,6 @@
namespace DispenserCore.Model.DTO;
public class QueryOperationLog : QueryPage
{
public string? UserName { get; set; }
}

View File

@ -0,0 +1,62 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DispenserCore.Model.DTO;
public class QueryPage : INotifyPropertyChanged
{
private int _currentPage = 1;
private int _pageSize = 10;
private DateTime? _startTime;
private DateTime? _endTime;
public int CurrentPage
{
get => _currentPage;
set
{
_currentPage = value;
OnPropertyChanged();
}
}
public int PageSize
{
get => _pageSize;
set
{
_pageSize = value;
OnPropertyChanged();
}
}
public DateTime? StartTime
{
get => _startTime;
set
{
_startTime = value;
OnPropertyChanged();
}
}
public DateTime? EndTime
{
get => _endTime;
set
{
_endTime = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@ -0,0 +1,15 @@
using DispenserCore.Model.Entity;
namespace DispenserCore.Model.DTO;
/// <summary>
/// 用户登录会话信息
/// </summary>
public class Session(User user)
{
// 用户信息
public User User { get; set; } = user;
// 登录时间
public DateTime LoginTime { get; set; } = DateTime.Now;
}

View File

@ -0,0 +1,60 @@
using System.ComponentModel;
using DispenserCommon.Atrributes;
using DispenserCore.Model.Enum;
using SQLite;
namespace DispenserCore.Model.Entity;
[Table("camera_internal_params"), Description("相机内部参数")]
public class CameraInternalParams : Entity
{
[Column("type"), Description("参数类型"), Hide]
public string Type { get; set; }
[Column("balance_ratio"), Description("白平衡值")]
public int BalanceRatio { get; set; }
[Column("exposure_time"), Description("曝光时间"), Property(Min = 15, Max = 2000)]
public int ExposureTime { get; set; }
[Column("exposure_auto"), Description("自动曝光")]
public bool ExposureAuto { get; set; }
[Column("gain"), Description("增益")] public float Gain { get; set; }
[Column("gain_auto"), Description("自动增益")]
public GainAutoEnum GainAuto { get; set; }
[Column("black_level"), Description("灰度值")]
public float BlackLevel { get; set; }
[Column("black_level_enable"), Description("黑电平调节使能")]
public bool BlackLevelEnable { get; set; }
[Column("balance_white_auto"), Description("自动白平衡")]
public bool BalanceWhiteAuto { get; set; }
[Column("resulting_frame_rate"), Description("实际采集帧率fps")]
public float ResultingFrameRate { get; set; }
[Column("gamma"), Description("gamma值"), Property(Min = 0, Max = 4)]
public float Gamma { get; set; }
[Column("gamma_enable"), Description("是否gamma使能")]
public bool GammaEnable { get; set; }
[Column("line_mode"), Description("IO 模式")]
public LineModeEnum LineMode { get; set; }
[Column("line_selector"), Description("IO 选择")]
public LineSelectorEnum LineSelector { get; set; }
[Column("trigger_activation"), Description("触发激活")]
public TriggerActivationEnum TriggerActivation { get; set; }
[Column("trigger_mode"), Description("触发模式")]
public TriggerModeEnum TriggerMode { get; set; }
[Column("trigger_source"), Description("触发源")]
public TriggerSourceEnum TriggerSource { get; set; }
}

View File

@ -0,0 +1,32 @@
using System.ComponentModel;
using DispenserCommon.Atrributes;
using SQLite;
namespace DispenserCore.Model.Entity;
/// <summary>
/// 相机参数
/// </summary>
[Table("camera_params")]
[Description("相机参数")]
public class CameraParams : Entity
{
[Column("camera_sn"), Description("相机序列号")]
public string CameraSn { get; set; }
[Column("sdk"), Description("SDK")] public string Sdk { get; set; }
[Column("dll"), Description("DLL")] public string Dll { get; set; }
[Column("scale_ratio"), Description("视频缩放比例")]
public double ScaleRatio { get; set; }
[Column("pixel_length"), Description("像素长度"), Property(Format = "0.########")]
public double PixelLength { get; set; }
[Column("deflect_angle"), Description("相机偏转角度"), Property(Format = "0.#####")]
public double DeflectAngle { get; set; }
[Column("camera_inner_param_template"), Description("相机内参模板")]
public string CameraInnerParamTemplate { get; set; }
}

View File

@ -0,0 +1,15 @@
using SQLite;
namespace DispenserCore.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 DispenserCore.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,20 @@
using System.ComponentModel;
using SQLite;
namespace DispenserCore.Model.Entity;
[Table("minio_params"), Description("MinIO参数")]
public class MinioParams : Entity
{
[Column("minio_access_key"), Description("Minio AccessKey")]
public string MinioAccessKey { get; set; }
[Column("minio_secret_key"), Description("Minio SecretKey")]
public string MinioSecretKey { get; set; }
[Column("minio_bucket"), Description("Minio Bucket")]
public string MinioBucket { get; set; }
[Column("minio_endpoint"), Description("Minio Endpoint")]
public string MinioEndpoint { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel;
using DispenserCommon.Atrributes;
using SQLite;
namespace DispenserCore.Model.Entity;
[Table("mqtt_params"), Description("Mqtt连接参数")]
public class MqttParams : Entity
{
[Column("server_address"), Description("服务器地址")]
public string ServerAddress { get; set; }
[Column("port"), Description("端口")] public int Port { get; set; }
[Column("user_name"), Description("用户名")]
public string UserName { get; set; }
[Column("password"), Description("密码"), Property(IsPassword = true)]
public string Password { get; set; }
}

View File

@ -0,0 +1,29 @@
using System.ComponentModel;
using DispenserCommon.Atrributes;
using SQLite;
namespace DispenserCore.Model.Entity;
/// <summary>
/// 用户操作日志
/// </summary>
[Table("operation_logs")]
public class OperationLog : Entity
{
[Column("user_id"), Description("用户ID"), Hide]
public string UserId { get; set; }
[Column("user_name"), Description("用户名")]
public string UserName { get; set; }
[Column("action"), Description("操作")] public string Action { get; set; }
[Column("params"), Description("参数"), Hide]
public string? Params { get; set; }
[Column("exception"), Description("异常信息"), Hide]
public string? Exception { get; set; }
[Column("operate_time"), Description("操作时间")]
public DateTime OperateTime { get; set; }
}

View File

@ -0,0 +1,36 @@
using System.ComponentModel;
using DispenserCommon.Atrributes;
using SQLite;
namespace DispenserCore.Model.Entity;
/// <summary>
/// 系统参数
/// </summary>
[Table("system_params")]
[Description("系统参数")]
public class SystemParams : Entity
{
[Column("device_type"), Description("设备类型")]
public string? DeviceType { get; set; }
[Column("version"), Description("版本号"), Property(IsReadOnly = true)]
public string? Version { get; set; }
[Column("name"), Description("名称")] public string? Name { get; set; }
[Column("acs_ip"), Description("ACS 控制器IP")]
public string AcsIp { get; set; }
[Column("image_storage_path"), Description("照片存储路径")]
public string? ImageStoragePath { get; set; }
[Column("camera_viewer_storage_path"), Description("相机预览控件图片存储路径")]
public string? CameraViewerStoragePath { get; set; }
[Column("enable_auto_clear_image"), Description("是否自动清除历史照片")]
public bool EnableAutoClearImage { get; set; }
[Column("retained_day"), Description("照片保留天数")]
public int RetainedDay { get; set; }
}

View File

@ -0,0 +1,18 @@
using SQLite;
namespace DispenserCore.Model.Entity;
/// <summary>
/// 用户信息
/// </summary>
[Table("users")]
public class User : Entity
{
[Column("user_name")] public string UserName { get; set; }
[Column("nick_name")] public string NickName { get; set; }
[Column("password")] public string Password { get; set; }
[Column("role")] public int Role { get; set; }
}

View File

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

View File

@ -0,0 +1,9 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
public enum DirectionEnum
{
[Description("行方向")] ROW = 1,
[Description("列方向")] COLUMN = 2,
}

View File

@ -0,0 +1,8 @@
namespace DispenserCore.Model.Enum;
public enum GainAutoEnum
{
Off,
Once,
Continuous
}

View File

@ -0,0 +1,19 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
/// <summary>
/// 生产作业状态
/// </summary>
public enum JobStateEnum
{
[Description("待生产")] Waiting = 0,
[Description("生产中")] Producing = 1,
[Description("已完成")] Completed = 2,
[Description("已取消")] Canceled = 3,
[Description("生产异常")] Abnormal = 4,
}

View File

@ -0,0 +1,6 @@
namespace DispenserCore.Model.Enum;
public enum LineModeEnum
{
Strobe
}

View File

@ -0,0 +1,8 @@
namespace DispenserCore.Model.Enum;
public enum LineSelectorEnum
{
Line0,
Line1,
Line2,
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
/// <summary>
/// 动打策略
/// </summary>
public enum MixBinStrategyEnum
{
[Description("混Bin")] Mix = 1,
[Description("不混")] NotMix = 2
}

View File

@ -0,0 +1,9 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
public enum PcbDetectStrategyEnum
{
[Description("Mark点识别")]ByMark = 1,
[Description("焊点模板匹配")]TemplateMatch = 2,
}

View File

@ -0,0 +1,34 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
/// <summary>
/// 扫码枪接口类型
/// </summary>
public enum ScannerInterfaceEnum
{
/// <summary>
/// 串口
/// </summary>
[Description("串口")] Serial,
/// <summary>
/// TCP
/// </summary>
[Description("网口")] Tcp,
/// <summary>
/// USB
/// </summary>
[Description("USB")] Usb,
/// <summary>
/// 蓝牙
/// </summary>
[Description("蓝牙")] Bluetooth,
/// <summary>
/// 无线
/// </summary>
[Description("无线")] Wireless
}

View File

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

View File

@ -0,0 +1,7 @@
namespace DispenserCore.Model.Enum;
public enum TlsProtocolsEnum
{
TLS_1_2,
TLS_1_3
}

View File

@ -0,0 +1,10 @@
namespace DispenserCore.Model.Enum;
public enum TriggerActivationEnum
{
RisingEdge,
FallingEdge,
LevelHigh,
LevelLow,
AnyEdge
}

View File

@ -0,0 +1,7 @@
namespace DispenserCore.Model.Enum;
public enum TriggerModeEnum
{
Off,
On
}

View File

@ -0,0 +1,10 @@
namespace DispenserCore.Model.Enum;
public enum TriggerSourceEnum
{
Software,
Line0,
Line2,
Counter0,
Anyway
}

View File

@ -0,0 +1,9 @@
using System.ComponentModel;
namespace DispenserCore.Model.Enum;
public enum WaferScanStrategyEnum
{
[Description("快速二值化")]FastThreshold = 1,
[Description("模板匹配")]TemplateMatch = 2,
}

Some files were not shown because too many files have changed in this diff Show More