添加点胶机框架内容
This commit is contained in:
commit
b9fd07c94c
|
@ -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
|
|
@ -0,0 +1,454 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
|
@ -0,0 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<AvaloniaVersion>11.0.2</AvaloniaVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -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
|
|
@ -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>
|
|
@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace DispenserCommon.Atrributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class BlockTask(string name) : Attribute
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DispenserCommon.Atrributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class Hide : Attribute
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace DispenserCommon.Constant;
|
||||
|
||||
/// <summary>
|
||||
/// 这里管理MQTT 所有的topic
|
||||
/// </summary>
|
||||
public class Topics
|
||||
{
|
||||
// 锁机
|
||||
public const string Locked = "device/{deviceId}/locked";
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
|||
namespace DispenserCommon.Enums;
|
||||
|
||||
public enum BinCoordinateTransformStrategyEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于角度
|
||||
/// </summary>
|
||||
ByAngel,
|
||||
/// <summary>
|
||||
/// 基于矩阵
|
||||
/// </summary>
|
||||
ByMatrix
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace DispenserCommon.Enums;
|
||||
|
||||
public enum ExceptionLevel : int
|
||||
{
|
||||
NORMAL,
|
||||
WARN,
|
||||
ERROR,
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace DispenserCommon.Enums;
|
||||
|
||||
public enum ScannerTypeEnum
|
||||
{
|
||||
Wafer,
|
||||
Pcb,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace DispenserCommon.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 用于声明当前方法为事件处理器
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class EventAction(params EventType[] types) : Attribute
|
||||
{
|
||||
public EventType[] Types => types;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace DispenserCommon.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 用于声明当前类为事件监听
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class EventListener(string name = "") : Attribute
|
||||
{
|
||||
private string Name => name;
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
namespace DispenserCommon.Interface;
|
||||
|
||||
public interface Instant
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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); });
|
||||
}
|
||||
}
|
|
@ -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, "延时任务执行失败");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DispenserCommon.Scheduler;
|
||||
|
||||
public interface ITask : IDisposable
|
||||
{
|
||||
public void Run();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "确认");
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)); });
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace DispenserCore.IOC;
|
||||
|
||||
public class IocService(Type type, Type implement)
|
||||
{
|
||||
public Type Type => type;
|
||||
|
||||
public Type Implement => implement;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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, "关闭任务调度器");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DispenserCore.Model.DTO;
|
||||
|
||||
public class QueryOperationLog : QueryPage
|
||||
{
|
||||
public string? UserName { get; set; }
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum ChipColorEnum
|
||||
{
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 3
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum DirectionEnum
|
||||
{
|
||||
[Description("行方向")] ROW = 1,
|
||||
[Description("列方向")] COLUMN = 2,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum GainAutoEnum
|
||||
{
|
||||
Off,
|
||||
Once,
|
||||
Continuous
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum LineModeEnum
|
||||
{
|
||||
Strobe
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum LineSelectorEnum
|
||||
{
|
||||
Line0,
|
||||
Line1,
|
||||
Line2,
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 动打策略
|
||||
/// </summary>
|
||||
public enum MixBinStrategyEnum
|
||||
{
|
||||
[Description("混Bin")] Mix = 1,
|
||||
[Description("不混")] NotMix = 2
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum PcbDetectStrategyEnum
|
||||
{
|
||||
[Description("Mark点识别")]ByMark = 1,
|
||||
[Description("焊点模板匹配")]TemplateMatch = 2,
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 基材类型
|
||||
/// </summary>
|
||||
public enum SubstrateTypeEnum
|
||||
{
|
||||
[Description("PCB")] PCB = 1,
|
||||
[Description("玻璃")] Glass = 2
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum TlsProtocolsEnum
|
||||
{
|
||||
TLS_1_2,
|
||||
TLS_1_3
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum TriggerActivationEnum
|
||||
{
|
||||
RisingEdge,
|
||||
FallingEdge,
|
||||
LevelHigh,
|
||||
LevelLow,
|
||||
AnyEdge
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum TriggerModeEnum
|
||||
{
|
||||
Off,
|
||||
On
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace DispenserCore.Model.Enum;
|
||||
|
||||
public enum TriggerSourceEnum
|
||||
{
|
||||
Software,
|
||||
Line0,
|
||||
Line2,
|
||||
Counter0,
|
||||
Anyway
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue