Q 2 mesi fa
commit
3d62ab365c
100 ha cambiato i file con 11557 aggiunte e 0 eliminazioni
  1. 4 0
      .gitignore
  2. 37 0
      DaJiaoYan.sln
  3. 30 0
      DaJiaoYan/App.config
  4. 218 0
      DaJiaoYan/DaJiaoYan.csproj
  5. 13 0
      DaJiaoYan/DaJiaoYan.csproj.user
  6. 211 0
      DaJiaoYan/FormMain.Designer.cs
  7. 226 0
      DaJiaoYan/FormMain.cs
  8. 343 0
      DaJiaoYan/FormMain.resx
  9. 31 0
      DaJiaoYan/Models/AliyunAccessModel.cs
  10. 97 0
      DaJiaoYan/Models/DividePartitionConfig.cs
  11. 366 0
      DaJiaoYan/Models/Enumerates.cs
  12. 183 0
      DaJiaoYan/Models/PartitionConfig.cs
  13. 41 0
      DaJiaoYan/Models/QuestionConfig.cs
  14. 63 0
      DaJiaoYan/Models/Rect.cs
  15. 89 0
      DaJiaoYan/Models/ScanResultPost.cs
  16. 56 0
      DaJiaoYan/Models/ScanTask.cs
  17. 17 0
      DaJiaoYan/Models/ScannerList.cs
  18. 87 0
      DaJiaoYan/Models/ScoreRect.cs
  19. 120 0
      DaJiaoYan/Models/TemplateRect.cs
  20. 29 0
      DaJiaoYan/Models/TestAnswerSheet.cs
  21. 13 0
      DaJiaoYan/Models/TestOptionConfig.cs
  22. 123 0
      DaJiaoYan/Models/TestTemplate.cs
  23. 8 0
      DaJiaoYan/Models/UpdateInfo.cs
  24. 207 0
      DaJiaoYan/Models/UploadHandlerPostParam.cs
  25. 42 0
      DaJiaoYan/Models/UploadTaskInfo.cs
  26. 46 0
      DaJiaoYan/Program.cs
  27. 32 0
      DaJiaoYan/Properties/AssemblyInfo.cs
  28. 71 0
      DaJiaoYan/Properties/Resources.Designer.cs
  29. 117 0
      DaJiaoYan/Properties/Resources.resx
  30. 30 0
      DaJiaoYan/Properties/Settings.Designer.cs
  31. 7 0
      DaJiaoYan/Properties/Settings.settings
  32. 206 0
      DaJiaoYan/Services/AliyunOss.cs
  33. 345 0
      DaJiaoYan/Services/Api.cs
  34. 526 0
      DaJiaoYan/Services/HttpHandlers.cs
  35. 323 0
      DaJiaoYan/Services/HttpServer.cs
  36. 64 0
      DaJiaoYan/Services/Instance.cs
  37. 439 0
      DaJiaoYan/Services/Scan.cs
  38. 100 0
      DaJiaoYan/Services/UploadTask.cs
  39. 64 0
      DaJiaoYan/Utils/ControlExts.cs
  40. 300 0
      DaJiaoYan/Utils/FileExts.cs
  41. 353 0
      DaJiaoYan/Utils/Functions.cs
  42. 402 0
      DaJiaoYan/Utils/Http.cs
  43. 108 0
      DaJiaoYan/Utils/ImageUtils.cs
  44. 67 0
      DaJiaoYan/Utils/Json.cs
  45. 100 0
      DaJiaoYan/Utils/Log.cs
  46. 93 0
      DaJiaoYan/Utils/ProcessExts.cs
  47. 31 0
      DaJiaoYan/Utils/Reg.cs
  48. 68 0
      DaJiaoYan/Variables/Const.cs
  49. 88 0
      DaJiaoYan/Variables/Vars.cs
  50. BIN
      DaJiaoYan/dlls/TWAINDSM.dll
  51. BIN
      DaJiaoYan/dlls/TWAINDSM32.msm
  52. BIN
      DaJiaoYan/logo.ico
  53. 19 0
      DaJiaoYan/packages.config
  54. BIN
      DaJiaoYan/resources/logo.ico
  55. 6 0
      ReleaseHelper/App.config
  56. 243 0
      ReleaseHelper/FormMain.Designer.cs
  57. 77 0
      ReleaseHelper/FormMain.cs
  58. 123 0
      ReleaseHelper/FormMain.resx
  59. 31 0
      ReleaseHelper/Models/AliyunAccessModel.cs
  60. 8 0
      ReleaseHelper/Models/UpdateInfo.cs
  61. 123 0
      ReleaseHelper/Models/UserInfo.cs
  62. 78 0
      ReleaseHelper/Models/UserLoginPost.cs
  63. 22 0
      ReleaseHelper/Program.cs
  64. 33 0
      ReleaseHelper/Properties/AssemblyInfo.cs
  65. 71 0
      ReleaseHelper/Properties/Resources.Designer.cs
  66. 117 0
      ReleaseHelper/Properties/Resources.resx
  67. 30 0
      ReleaseHelper/Properties/Settings.Designer.cs
  68. 7 0
      ReleaseHelper/Properties/Settings.settings
  69. 108 0
      ReleaseHelper/ReleaseHelper.csproj
  70. 163 0
      ReleaseHelper/Services/AliyunOss.cs
  71. 252 0
      ReleaseHelper/Services/Api.cs
  72. 86 0
      ReleaseHelper/Services/Release.cs
  73. 64 0
      ReleaseHelper/Utils/ControlExts.cs
  74. 227 0
      ReleaseHelper/Utils/FileExtensions.cs
  75. 383 0
      ReleaseHelper/Utils/Functions.cs
  76. 398 0
      ReleaseHelper/Utils/Http.cs
  77. 67 0
      ReleaseHelper/Utils/Json.cs
  78. 105 0
      ReleaseHelper/Utils/Log.cs
  79. 54 0
      ReleaseHelper/Utils/MutexExtensions.cs
  80. 84 0
      ReleaseHelper/Utils/ProcessExtensions.cs
  81. 41 0
      ReleaseHelper/Variables/Const.cs
  82. 24 0
      ReleaseHelper/Variables/Variables.cs
  83. 5 0
      ReleaseHelper/packages.config
  84. 6 0
      Update/App.config
  85. 77 0
      Update/Form1.Designer.cs
  86. 75 0
      Update/Form1.cs
  87. 343 0
      Update/Form1.resx
  88. 22 0
      Update/Program.cs
  89. 33 0
      Update/Properties/AssemblyInfo.cs
  90. 71 0
      Update/Properties/Resources.Designer.cs
  91. 117 0
      Update/Properties/Resources.resx
  92. 30 0
      Update/Properties/Settings.Designer.cs
  93. 7 0
      Update/Properties/Settings.settings
  94. 266 0
      Update/Services/Update.cs
  95. 102 0
      Update/Update.csproj
  96. 110 0
      Update/Utils/FileExtensions.cs
  97. 45 0
      Update/Utils/Functions.cs
  98. 398 0
      Update/Utils/Http.cs
  99. 67 0
      Update/Utils/Json.cs
  100. 105 0
      Update/Utils/Log.cs

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+/.vs/
+/packages/
+bin/
+obj/

+ 37 - 0
DaJiaoYan.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35327.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DaJiaoYan", "DaJiaoYan\DaJiaoYan.csproj", "{31157941-F4C9-4F25-A141-2CDBF5316DA5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Update", "update\Update.csproj", "{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseHelper", "ReleaseHelper\ReleaseHelper.csproj", "{63A2943C-0563-403C-B146-67780CC2DC46}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{31157941-F4C9-4F25-A141-2CDBF5316DA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{31157941-F4C9-4F25-A141-2CDBF5316DA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{31157941-F4C9-4F25-A141-2CDBF5316DA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{31157941-F4C9-4F25-A141-2CDBF5316DA5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}.Release|Any CPU.Build.0 = Release|Any CPU
+		{63A2943C-0563-403C-B146-67780CC2DC46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{63A2943C-0563-403C-B146-67780CC2DC46}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{63A2943C-0563-403C-B146-67780CC2DC46}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{63A2943C-0563-403C-B146-67780CC2DC46}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {25924F58-2A15-4B82-883A-C958156A864E}
+	EndGlobalSection
+EndGlobal

+ 30 - 0
DaJiaoYan/App.config

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 218 - 0
DaJiaoYan/DaJiaoYan.csproj

@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\OpenCvSharp4.runtime.win.4.11.0.20250507\build\netstandard\OpenCvSharp4.runtime.win.props" Condition="Exists('..\packages\OpenCvSharp4.runtime.win.4.11.0.20250507\build\netstandard\OpenCvSharp4.runtime.win.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{31157941-F4C9-4F25-A141-2CDBF5316DA5}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>DaJiaoYan</RootNamespace>
+    <AssemblyName>DaJiaoYan</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>none</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>logo.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Aliyun.OSS, Version=2.14.1.0, Culture=neutral, PublicKeyToken=0ad4175f0dac0b9b, processorArchitecture=MSIL">
+      <HintPath>..\packages\Aliyun.OSS.SDK.2.14.1\lib\net461\Aliyun.OSS.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=9.0.0.10, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.10\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="NTwain, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b8a88f4e3308e7c2, processorArchitecture=MSIL">
+      <HintPath>..\packages\NTwain.3.7.5\lib\net462\NTwain.dll</HintPath>
+    </Reference>
+    <Reference Include="OpenCvSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6adad1e807fea099, processorArchitecture=MSIL">
+      <HintPath>..\packages\OpenCvSharp4.4.11.0.20250507\lib\netstandard2.0\OpenCvSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="OpenCvSharp.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6adad1e807fea099, processorArchitecture=MSIL">
+      <HintPath>..\packages\OpenCvSharp4.Extensions.4.11.0.20250507\lib\netstandard2.0\OpenCvSharp.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing.Common, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Drawing.Common.9.0.10\lib\net462\System.Drawing.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="System.IO.Pipelines, Version=9.0.0.10, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.IO.Pipelines.9.0.10\lib\net462\System.IO.Pipelines.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=9.0.0.10, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.9.0.10\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FormMain.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="FormMain.Designer.cs">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Models\AliyunAccessModel.cs" />
+    <Compile Include="Models\DividePartitionConfig.cs" />
+    <Compile Include="Models\Enumerates.cs" />
+    <Compile Include="Models\PartitionConfig.cs" />
+    <Compile Include="Models\QuestionConfig.cs" />
+    <Compile Include="Models\Rect.cs" />
+    <Compile Include="Models\ScannerList.cs" />
+    <Compile Include="Models\ScanResultPost.cs" />
+    <Compile Include="Models\ScanTask.cs" />
+    <Compile Include="Models\ScoreRect.cs" />
+    <Compile Include="Models\TemplateRect.cs" />
+    <Compile Include="Models\TestAnswerSheet.cs" />
+    <Compile Include="Models\TestOptionConfig.cs" />
+    <Compile Include="Models\TestTemplate.cs" />
+    <Compile Include="Models\UpdateInfo.cs" />
+    <Compile Include="Models\UploadHandlerPostParam.cs" />
+    <Compile Include="Models\UploadTaskInfo.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Services\AliyunOss.cs" />
+    <Compile Include="Services\Api.cs" />
+    <Compile Include="Services\HttpHandlers.cs" />
+    <Compile Include="Services\HttpServer.cs" />
+    <Compile Include="Services\Instance.cs" />
+    <Compile Include="Services\Scan.cs" />
+    <Compile Include="Services\UploadTask.cs" />
+    <Compile Include="Utils\ControlExts.cs" />
+    <Compile Include="Utils\FileExts.cs" />
+    <Compile Include="Utils\Functions.cs" />
+    <Compile Include="Utils\Http.cs" />
+    <Compile Include="Utils\ImageUtils.cs" />
+    <Compile Include="Utils\Json.cs" />
+    <Compile Include="Utils\Log.cs" />
+    <Compile Include="Utils\ProcessExts.cs" />
+    <Compile Include="Utils\Reg.cs" />
+    <Compile Include="Variables\Const.cs" />
+    <Compile Include="Variables\Vars.cs" />
+    <EmbeddedResource Include="FormMain.resx">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.7.2 %28x86 和 x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="logo.ico" />
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
+    <Error Condition="!Exists('..\packages\OpenCvSharp4.runtime.win.4.11.0.20250507\build\netstandard\OpenCvSharp4.runtime.win.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\OpenCvSharp4.runtime.win.4.11.0.20250507\build\netstandard\OpenCvSharp4.runtime.win.props'))" />
+  </Target>
+  <PropertyGroup>
+    <PreBuildEvent>copy "$(ProjectDir)dlls" "$(TargetDir)"
+copy "$(ProjectDir)resources/logo.ico" "$(TargetDir)/logo.ico"
+
+if not exist "$(TargetDir)update" md "$(TargetDir)update"
+copy "$(SolutionDir)Update/bin/Release" "$(TargetDir)update"</PreBuildEvent>
+  </PropertyGroup>
+</Project>

+ 13 - 0
DaJiaoYan/DaJiaoYan.csproj.user

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishUrlHistory>publish\</PublishUrlHistory>
+    <InstallUrlHistory />
+    <SupportUrlHistory />
+    <UpdateUrlHistory />
+    <BootstrapperUrlHistory />
+    <ErrorReportUrlHistory />
+    <FallbackCulture>zh-CN</FallbackCulture>
+    <VerifyUploadedFiles>false</VerifyUploadedFiles>
+  </PropertyGroup>
+</Project>

+ 211 - 0
DaJiaoYan/FormMain.Designer.cs

@@ -0,0 +1,211 @@
+namespace DaJiaoYan
+{
+    partial class FormMain
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
+            this.labelScannerName = new System.Windows.Forms.Label();
+            this.labelUploadedCount = new System.Windows.Forms.Label();
+            this.labelScannerDirectory = new System.Windows.Forms.Label();
+            this.labelScannerStatus = new System.Windows.Forms.Label();
+            this.label12 = new System.Windows.Forms.Label();
+            this.label15 = new System.Windows.Forms.Label();
+            this.label14 = new System.Windows.Forms.Label();
+            this.label13 = new System.Windows.Forms.Label();
+            this.labelScannerDriver = new System.Windows.Forms.Label();
+            this.label11 = new System.Windows.Forms.Label();
+            this.labelScanCount = new System.Windows.Forms.Label();
+            this.label2 = new System.Windows.Forms.Label();
+            this.SuspendLayout();
+            // 
+            // labelScannerName
+            // 
+            this.labelScannerName.AutoSize = true;
+            this.labelScannerName.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelScannerName.Location = new System.Drawing.Point(114, 57);
+            this.labelScannerName.Name = "labelScannerName";
+            this.labelScannerName.Size = new System.Drawing.Size(0, 12);
+            this.labelScannerName.TabIndex = 7;
+            // 
+            // labelUploadedCount
+            // 
+            this.labelUploadedCount.AutoSize = true;
+            this.labelUploadedCount.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelUploadedCount.Location = new System.Drawing.Point(114, 150);
+            this.labelUploadedCount.Name = "labelUploadedCount";
+            this.labelUploadedCount.Size = new System.Drawing.Size(11, 12);
+            this.labelUploadedCount.TabIndex = 8;
+            this.labelUploadedCount.Text = "0";
+            // 
+            // labelScannerDirectory
+            // 
+            this.labelScannerDirectory.AutoSize = true;
+            this.labelScannerDirectory.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelScannerDirectory.Location = new System.Drawing.Point(114, 119);
+            this.labelScannerDirectory.Name = "labelScannerDirectory";
+            this.labelScannerDirectory.Size = new System.Drawing.Size(0, 12);
+            this.labelScannerDirectory.TabIndex = 9;
+            this.labelScannerDirectory.DoubleClick += new System.EventHandler(this.LabelScannerDirectory_DoubleClick);
+            // 
+            // labelScannerStatus
+            // 
+            this.labelScannerStatus.AutoSize = true;
+            this.labelScannerStatus.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelScannerStatus.Location = new System.Drawing.Point(114, 88);
+            this.labelScannerStatus.Name = "labelScannerStatus";
+            this.labelScannerStatus.Size = new System.Drawing.Size(0, 12);
+            this.labelScannerStatus.TabIndex = 10;
+            // 
+            // label12
+            // 
+            this.label12.AutoSize = true;
+            this.label12.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label12.Location = new System.Drawing.Point(59, 57);
+            this.label12.Name = "label12";
+            this.label12.Size = new System.Drawing.Size(44, 12);
+            this.label12.TabIndex = 2;
+            this.label12.Text = "扫描仪";
+            // 
+            // label15
+            // 
+            this.label15.AutoSize = true;
+            this.label15.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label15.Location = new System.Drawing.Point(20, 150);
+            this.label15.Name = "label15";
+            this.label15.Size = new System.Drawing.Size(83, 12);
+            this.label15.TabIndex = 3;
+            this.label15.Text = "上传图片数量";
+            // 
+            // label14
+            // 
+            this.label14.AutoSize = true;
+            this.label14.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label14.Location = new System.Drawing.Point(20, 119);
+            this.label14.Name = "label14";
+            this.label14.Size = new System.Drawing.Size(83, 12);
+            this.label14.TabIndex = 4;
+            this.label14.Text = "扫描保存目录";
+            // 
+            // label13
+            // 
+            this.label13.AutoSize = true;
+            this.label13.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label13.Location = new System.Drawing.Point(33, 88);
+            this.label13.Name = "label13";
+            this.label13.Size = new System.Drawing.Size(70, 12);
+            this.label13.TabIndex = 5;
+            this.label13.Text = "扫描仪状态";
+            // 
+            // labelScannerDriver
+            // 
+            this.labelScannerDriver.AutoSize = true;
+            this.labelScannerDriver.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelScannerDriver.ForeColor = System.Drawing.SystemColors.ControlText;
+            this.labelScannerDriver.Location = new System.Drawing.Point(114, 26);
+            this.labelScannerDriver.Name = "labelScannerDriver";
+            this.labelScannerDriver.Size = new System.Drawing.Size(0, 12);
+            this.labelScannerDriver.TabIndex = 11;
+            // 
+            // label11
+            // 
+            this.label11.AutoSize = true;
+            this.label11.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label11.Location = new System.Drawing.Point(33, 26);
+            this.label11.Name = "label11";
+            this.label11.Size = new System.Drawing.Size(70, 12);
+            this.label11.TabIndex = 6;
+            this.label11.Text = "扫描仪驱动";
+            // 
+            // labelScanCount
+            // 
+            this.labelScanCount.AutoSize = true;
+            this.labelScanCount.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.labelScanCount.Location = new System.Drawing.Point(114, 181);
+            this.labelScanCount.Name = "labelScanCount";
+            this.labelScanCount.Size = new System.Drawing.Size(11, 12);
+            this.labelScanCount.TabIndex = 13;
+            this.labelScanCount.Text = "0";
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.label2.Location = new System.Drawing.Point(20, 181);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(83, 12);
+            this.label2.TabIndex = 12;
+            this.label2.Text = "扫描图片数量";
+            // 
+            // FormMain
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+            this.ClientSize = new System.Drawing.Size(800, 218);
+            this.Controls.Add(this.labelScanCount);
+            this.Controls.Add(this.label2);
+            this.Controls.Add(this.labelScannerName);
+            this.Controls.Add(this.labelUploadedCount);
+            this.Controls.Add(this.labelScannerDirectory);
+            this.Controls.Add(this.labelScannerStatus);
+            this.Controls.Add(this.label12);
+            this.Controls.Add(this.label15);
+            this.Controls.Add(this.label14);
+            this.Controls.Add(this.label13);
+            this.Controls.Add(this.labelScannerDriver);
+            this.Controls.Add(this.label11);
+            this.DoubleBuffered = true;
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MaximizeBox = false;
+            this.Name = "FormMain";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "大教研扫描服务端";
+            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormMain_FormClosing);
+            this.SizeChanged += new System.EventHandler(this.FormMain_SizeChanged);
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label labelScannerName;
+        private System.Windows.Forms.Label labelUploadedCount;
+        private System.Windows.Forms.Label labelScannerDirectory;
+        private System.Windows.Forms.Label labelScannerStatus;
+        private System.Windows.Forms.Label label12;
+        private System.Windows.Forms.Label label15;
+        private System.Windows.Forms.Label label14;
+        private System.Windows.Forms.Label label13;
+        private System.Windows.Forms.Label labelScannerDriver;
+        private System.Windows.Forms.Label label11;
+        private System.Windows.Forms.Label labelScanCount;
+        private System.Windows.Forms.Label label2;
+    }
+}
+

+ 226 - 0
DaJiaoYan/FormMain.cs

@@ -0,0 +1,226 @@
+using DaJiaoYan.Services;
+using DaJiaoYan.Utils;
+using DaJiaoYan.Variables;
+using System;
+using System.Drawing;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace DaJiaoYan
+{
+    public partial class FormMain : Form
+    {
+        private readonly NotifyIcon notifyIcon;
+        private static readonly Icon icon = new Icon(Path.Combine(Const.APP_PATH, Const.ICON_FILE));
+
+        public FormMain()
+        {
+            InitializeComponent();
+            notifyIcon = new NotifyIcon
+            {
+                Icon = icon,
+                Text = Const.APP_SHOW_NAME,
+                Visible = true,
+            };
+            notifyIcon.Click += NotifyIcon_Click;
+            ControlExts.MessageBoxShowForm = this;
+            Scan.OwnerFormHandle = this.Handle;
+            Task.Run(() =>
+            {
+                Init();
+            });
+            //Scan.OwnerFormHandle = this.Handle;
+        }
+
+        private void Init()
+        {
+            ControlExts.FormInvoke(this, () =>
+            {
+#if DEBUG
+                Text = $"{Const.APP_FORM_NAME} (debug {Const.APP_DEBUG_BUILD_VERSION} {Const.HTTP_HOST})";
+#else
+                Text = $"{Const.APP_FORM_NAME} (release {Const.APP_DEBUG_BUILD_VERSION})";
+#endif
+                this.Icon = icon;
+            });
+#if !DEBUG
+            CheckForUpdate();
+#endif
+            Scan.DelegateLoadDriver = ChangeScannerDriverLabel;
+            Scan.DelegateChangeScannerName = ChangeScannerNameLabel;
+            Scan.DelegateChangeScannerStatus = ChangeScannerStatusLabel;
+
+            Variables.Vars.DelegateCounterChange = ChangeUploadedCount;
+            Task.Factory.StartNew(() =>
+            {
+                Services.Instance.Run();
+                Models.ScannerList scannerList = Scan.GetScannerList();
+                Variables.Vars.Scanners = scannerList.Resources;
+                Variables.Vars.ScannerSelected = scannerList.Selected;
+                //Instance.Init();
+                ShowFreeSpace();
+            });
+        }
+
+        private void NotifyIcon_Click(object sender, EventArgs e)
+        {
+            ShowMainForm();
+        }
+
+        private void ShowMainForm()
+        {
+            if (WindowState == FormWindowState.Minimized || !ShowInTaskbar)
+            {
+                Show();
+                Activate();
+                WindowState = FormWindowState.Normal;
+                TopMost = true;
+                Task.Run(() =>
+                {
+                    Task.Delay(100);
+                    ControlExts.FormInvoke(this, () =>
+                    {
+                        TopMost = false;
+                    });
+                });
+            }
+            else
+            {
+                WindowState = FormWindowState.Minimized;
+                //ShowInTaskbar = false;
+                Hide();
+            }
+        }
+
+        private void ChangeLabel(Label label, string txt, Color color)
+        {
+            ControlExts.FormInvoke(this, () =>
+            {
+                label.Text = txt;
+                label.ForeColor = color;
+            });
+        }
+
+        private void ChangeLabel(Label label, string txt)
+        {
+            ChangeLabel(label, txt, Color.Black);
+        }
+
+
+        private void ChangeScannerDriverLabel(bool ok)
+        {
+            string txt = ok ? "载入成功" : "载入失败";
+            Color color = ok ? Color.Green : Color.Red;
+            ChangeLabel(labelScannerDriver, txt, color);
+        }
+
+        private void ChangeScannerNameLabel(string txt)
+        {
+            ChangeLabel(labelScannerName, txt);
+        }
+
+        private async void ChangeScannerStatusLabel(string txt, Color color)
+        {
+            ChangeLabel(labelScannerStatus, txt, color);
+            await ShowFreeSpace();
+        }
+
+        private void ChangeScannerDirectory(string txt)
+        {
+            ChangeLabel(labelScannerDirectory, txt);
+        }
+
+        private Task ShowFreeSpace()
+        {
+            return Task.Run(() =>
+            {
+                string FreeSpace = FileExts.GetHardDiskFreeSpace(Const.TMP_PATH.Substring(0, 1), FileExts.FileSizeUnit.GB);
+                Utils.FileExts.CheckDirectory(Const.TMP_PATH, true);
+                ChangeScannerDirectory($"{Const.TMP_PATH}\n[ 可用空间:{FreeSpace} ]");
+            });
+        }
+
+        private void CheckForUpdate()
+        {
+            Task.Run(async () =>
+            {
+                var info = await Api.GetUpdateInfo();
+                if (info.Version > Const.APP_BUILD_VERSION)
+                {
+                    try
+                    {
+                        ProcessExts.RunUpdate();
+                    }
+                    catch
+                    {
+                        ShowMessageBox("启动升级程序失败!", Const.MSG_CAPTION_ERROR);
+                    }
+
+                    Close();
+                }
+            });
+        }
+
+
+        /// <summary>
+        /// 显示消息提示框
+        /// </summary>
+        /// <param name="text"></param>
+        /// <param name="caption"></param>
+        /// <param name="buttons"></param>
+        /// <param name="icon"></param>
+        /// <param name="defaultButton"></param>
+        /// <returns></returns>
+        private DialogResult ShowMessageBox(string text, string caption = "提示", MessageBoxButtons buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
+        {
+            return ControlExts.ShowMessageBox(text, caption, buttons, icon, defaultButton);
+        }
+
+
+        private void LabelScannerDirectory_DoubleClick(object sender, EventArgs e)
+        {
+            FileExts.OpenFileInExplore(Const.TMP_PATH, () =>
+            {
+                ShowMessageBox("打开文件夹失败!", Const.MSG_CAPTION_ERROR);
+            });
+        }
+
+        private void ChangeUploadedCount(int count)
+        {
+            ChangeLabel(labelUploadedCount, count.ToString());
+        }
+
+        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
+        {
+            void exitFunc()
+            {
+                notifyIcon.Visible = false;
+                notifyIcon.Icon.Dispose();
+                notifyIcon.Dispose();
+            }
+
+            if (Vars.AllTaskFinished())
+            {
+                exitFunc();
+            }
+            else
+            {
+                if (ShowMessageBox("正在上传图片,如果强制退出将导致上传的批次图片不完整!\n确定退出吗?", Const.MSG_CAPTION_WARN, MessageBoxButtons.OKCancel) == DialogResult.OK)
+                {
+                    exitFunc();
+                }
+                else
+                {
+                    e.Cancel = true;
+                }
+            }
+        }
+
+        private void FormMain_SizeChanged(object sender, EventArgs e)
+        {
+            ShowInTaskbar = Visible = WindowState != FormWindowState.Minimized;
+        }
+    }
+
+}

+ 343 - 0
DaJiaoYan/FormMain.resx

@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAEAQEEAAAEAGADwMgAAFgAAACgAAABAAAAAggAAAAEAGAAAAAAAyDIAABMLAAATCwAAAAAAAAAA
+        AAD/////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////889/557/zz4Dxw2Dut0Dut0Dut0Dut0Dut0Dut0Dut0D225//////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////557/yyXDrqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////++e/225/rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHssTD/////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////++e/zz4DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////44a/rqyDonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225/////////////77c/ssTDyyXD889//////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////++e/vvVDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHxw2D77c/onwHonwHonwHonwHonwH77c/////////////zz4DonwHo
+        nwHonwHxw2D889//////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////77c/rqyDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHyyXDzz4DvvVDonwHonwHonwH11Y/onwHonwHonwHonwHppRD/////////
+        ///////vvVDonwHonwHonwHonwHppRD11Y//////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////44a/ppRDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D////////xw2DonwHonwHonwHonwHonwHonwHo
+        nwHut0D////////////////rqyDonwHonwHonwHonwHonwHonwHut0D889//////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///225/onwHonwHonwHonwHonwHonwHonwHut0D++e/77c/ppRDonwHonwHxw2D////////xw2DonwHo
+        nwHonwHonwHonwHonwHyyXD////////////889/onwHonwHonwHonwHonwHonwHonwHonwHppRD44a//
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////225/onwHonwHonwHonwHonwHonwHonwHonwHppRD77c/////77c/ppRDonwHonwHv
+        vVD++e/////vvVDonwHonwHonwHonwHonwH225/////////////44a/onwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwH225//////////////////////////////////////////////////////////////
+        ///////////////////////////77c/onwHonwHonwHonwHonwHonwHssTDvvVDonwHonwHppRD77c//
+        ///77c/ppRDonwHonwHssTD++e/889/onwHonwHonwHonwHonwH889/////////////zz4DonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////////////////////////////////
+        ///////////////////////////////////////889/ppRDonwHonwHonwHonwHonwHrqyD++e/////x
+        w2DonwHonwHppRD77c/////77c/ppRDonwHonwHssTD889/onwHonwHonwHonwHppRD/////////////
+        ///ut0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////////////
+        ///////////////////////////////////////////////////////ssTDonwHonwHonwHonwHonwHo
+        nwHonwHssTD++e/////xw2DonwHonwHppRD77c/////77c/ppRDonwHonwHonwHonwHonwHonwHonwHv
+        vVD////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH77c//
+        ///////////////////////////////////////////////////////////////////zz4DonwHonwHo
+        nwHonwHonwHrqyDppRDonwHonwHssTD++e/////xw2DonwHonwHppRDzz4D225/zz4DonwHonwHonwHo
+        nwHonwHonwHonwHzz4D////////////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHrqyD++e/////////////////////////////////////////////////////////////8
+        89/ppRDonwHonwHonwHonwHrqyD++e/77c/ppRDonwHonwHssTD++e/////xw2DonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwH557/////////////225/onwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////
+        ///////////////xw2DonwHonwHonwHonwHonwHonwH225/////77c/ppRDonwHonwHssTD++e/////v
+        vVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH////////////////zz4DonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH557//////////////////////////
+        ///////////////////////////889/onwHonwHonwHonwHonwHonwHonwHonwH225/////77c/ppRDo
+        nwHonwHvvVDzz4DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD////////////////1
+        1Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////
+        ///////////////////////////////////////////xw2DonwHonwHonwHonwHssTDxw2DonwHonwHo
+        nwH225/////77c/ppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0Dut0D225//
+        ///////////////++e/xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwH225/////////////////////////////////////////////++e/onwHonwHonwHonwHonwH4
+        4a/////xw2DonwHonwHonwH225/////77c/onwHonwHonwHonwHonwHonwHonwHonwHut0D225/889//
+        ///////////////////////////////////////44a/ppRDonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHssTD////////////////////////////////////////////11Y/onwHo
+        nwHonwHonwHonwHssTD++e/////xw2DonwHonwHonwH225/77c/onwHonwHonwHonwHonwHonwHssTD7
+        7c/////////////////////////////////////////////////////////77c/ppRDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH557//////////////////////////////////
+        ///////ssTDonwHonwHonwHonwHonwHonwHssTD++e/////xw2DonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHxw2D////////////////////////////////////////////////////////////////////7
+        7c/ppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D/////////////////
+        ///////////////////++e/onwHonwHonwHonwHonwHppRDonwHonwHssTD++e/////xw2DonwHonwHo
+        nwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////////
+        ///////////////////44a/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD/
+        ///////////////////////////////////225/onwHonwHonwHonwHssTD77c/ppRDonwHonwHssTD+
+        +e/////xw2DonwHonwHonwHonwHonwHssTD/////////////////////////////////////////////
+        ///////////////////////////////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwH77c/////////////////////////////////yyXDonwHonwHonwHonwHxw2D////7
+        7c/ppRDonwHonwHssTD77c/44a/onwHonwHonwHonwHonwH889//////////////////////////////
+        ///////////////////////////////////////////////////////889/onwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwH11Y/////////////////////////////////ut0DonwHonwHo
+        nwHonwHonwH225/////77c/ppRDonwHonwHppRDssTDonwHonwHonwHonwHxw2D/////////////////
+        ///////////////////////////////////////////////////////////////////////////ut0Do
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD/////////////////////////////
+        ///rqyDonwHonwHonwHonwHonwHonwH225/////77c/ppRDonwHonwHonwHonwHonwHonwHonwH889//
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////11Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////////
+        ///////////////////onwHonwHonwHonwHonwHonwHonwHonwH225/////++e/ssTDonwHonwHonwHo
+        nwHonwHssTD/////////////////////////////////////////////////////////////////////
+        ///////////////////////////77c/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH////////////////////////////////onwHonwHonwHonwHppRDssTDonwHonwHonwH11Y/77c/z
+        z4DonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////
+        ///////////////////////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH////////////////////////////////onwHonwHonwHonwHut0D++e/rqyDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////////////////////
+        ///////////////////////////////////////////////////////////////onwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwH////////////////////////////////onwHonwHonwHo
+        nwHonwHppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////
+        ///////////////////////////////////////////////////////////////////////////////u
+        t0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////////////////////////////
+        ///onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////////////
+        ///////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHu
+        t0D11Y/++e//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH////////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyDx
+        w2Dzz4D557//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////77c/onwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH////////////////////////////////ppRDonwHonwHonwHonwHonwHssTDy
+        yXD225/77c//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////11Y/onwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHssTD////////////////////////////////vvVDonwHssTDy
+        yXD44a/++e//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////////ssTDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD/////////////////////////////
+        ///////++e//////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH11Y//////////////
+        ///////////////////////////////////////////////////////////////////889/44a/yyXDs
+        sTDppRD889//////////////////////////////////////////////////////////////////////
+        ///////////////////////ut0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH7
+        7c/////////////////////////////////////////////////////////////////77c/225/xw2Dr
+        qyDonwHonwHonwHonwHonwHut0D/////////////////////////////////////////////////////
+        ///////////////////////////////////11Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHppRD////////////////////////////////////////////////////77c/zz4Dut0Dp
+        pRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////////////////
+        ///////////////////////////////////////////////77c/ppRDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////
+        ///ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////
+        ///////////////////////////////////////////////////////////77c/ppRDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH77c//////////////////////////////////
+        ///////////////////zz4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHyyXD////////////////////////////////////////////////////////////////44a/ppRDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////////////////
+        ///////////////////////////////////++e/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHssTD77c/////////////////////////////////////////////////8
+        89/vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////
+        ///////////////////////////////////////////////////////zz4DonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0D/////////////////////////////////
+        ///////77c/xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHrqyD////////////////////////////////////////////////////////////////++e/rqyDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyD/////////////////
+        ///////77c/yyXDut0DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH557//////////////////////////////////////////////////////////
+        ///////////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D/
+        ///////////////////////xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHyyXD/////////////////////////////////////////////
+        ///////////////////////////////yyXDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwH225/////////////////////////ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyD++e//////////////////////////////
+        ///////////////////////////////////////////////////ssTDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwH77c/////////////////////++e/onwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD77c//////////////////
+        ///////////////////////////////////////////////////////////////////++e/ssTDonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD////////////////////////557/onwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH44a//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////889/rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0D/////////////////////
+        ///zz4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH225//////////////////////////////////////////////////////////////////////////
+        ///////////////////////////++e/ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////
+        ///////////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHppRD225//////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////++e/vvVDonwHonwHonwHonwHonwHonwHo
+        nwHonwH44a/////////////////////////rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHrqyD77c//////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////zz4DonwHo
+        nwHonwHonwHonwHonwHonwH889/////////////////////++e/onwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD++e//////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////77c/ut0DonwHonwHonwHonwHppRD////////////////////////557/onwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD44a//////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////225/rqyDonwHonwHut0D////////////////////////z
+        z4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRDzz4D++e//////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////44a/xw2D77c//////////
+        ///////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD11Y/++e//////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////rqyDonwHonwHonwHonwHonwHonwHppRDvvVD11Y/889//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////225/zz4Dzz4Dzz4D557/557/++e//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+</value>
+  </data>
+</root>

+ 31 - 0
DaJiaoYan/Models/AliyunAccessModel.cs

@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace DaJiaoYan.Models
+{
+
+    public class AliyunAccessModel
+    {
+        //public int Status { get; set; }
+        [JsonProperty(PropertyName = "endpoint")]
+        public string EndPoint { get; set; }
+
+        [JsonProperty(PropertyName = "access_key_id")]
+        public string AccessKeyId { get; set; }
+
+        [JsonProperty(PropertyName = "access_key_secret")]
+        public string AccessKeySecret { get; set; }
+
+        [JsonProperty(PropertyName = "security_token")]
+        public string SecurityToken { get; set; }
+
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        [JsonProperty(PropertyName = "expiration")]
+        public long Expiration { get; set; }
+
+        [JsonProperty(PropertyName = "bucket_name")]
+        public string BucketName { get; set; }
+    }
+
+}

+ 97 - 0
DaJiaoYan/Models/DividePartitionConfig.cs

@@ -0,0 +1,97 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    /// <summary>
+    /// 划分模版
+    /// </summary>
+    public class DividePartitionConfig
+    {
+        [JsonProperty(PropertyName = "test_name")]
+        public string Name { get; set; }
+
+        [JsonProperty(PropertyName = "subject_id")]
+        public int SubjectId { get; set; }
+
+        // TODO 需要服务端返回模版所属考试id
+        public int ExamId { get; set; }
+
+        public int TestId { get; set; }
+
+        [JsonProperty(PropertyName = "test_correct_type")]
+        public Enumerates.TestCorrectType TestCorrectType { get; set; }
+
+        /// <summary>
+        /// 是否自动分配老师阅卷;
+        /// true自动分配
+        /// </summary>
+        [JsonProperty(PropertyName = "auto_assign_teacher_marking")]
+        public bool AutoAssignTeacherMarking { get; set; }
+
+        [JsonProperty(PropertyName = "answer_sheet")]
+        public TestAnswerSheet[] AnswerSheets { get; set; }
+
+        [JsonProperty(PropertyName = "template_info")]
+        public TestTemplate Template { get; set; }
+
+        [JsonProperty(PropertyName = "data")]
+        public List<PartitionConfig> Partitions { get; set; }
+
+        /// <summary>
+        /// 客观题数量
+        /// </summary>
+        //public int ObjectiveQuestCount { get; set; }
+
+        public void SetObjectivePaintThreshold(PartitionConfig p, Dictionary<string, double> baseThreshold, int weights)
+        {
+            if (p.Config.OptionRects != null)
+            {
+                // 设置客观题填涂阈值
+                foreach (var op in p.Config.OptionRects)
+                {
+                    //op.PaintThreshold = baseThreshold[op.Option.ToUpper()] + weights / 100.0;
+                    op.PaintThreshold = 0.01 + weights / 100.0;
+                    if (op.PaintThreshold > 0.9)
+                    {
+                        op.PaintThreshold = 0.9;
+                    }
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// 识别前预处理
+        /// </summary>
+        /// <param name="baseThreshold"></param>
+        /// <param name="weights"></param>
+        public void Init(Dictionary<string, double> baseThreshold = null, int weights = 0)
+        {
+            //Dictionary<string, int> objectives = new Dictionary<string, int>();
+            Partitions.ForEach(p =>
+            {
+                //if (p.Config.CorrectType == Enumerates.QuestionType.Objective)
+                //{
+                //    if (Template.ParsedVersion == TestTemplate.TemplateVersion.V2)
+                //    {
+                //        ObjectiveQuestCount += p.Config.RectDivides.Keys.Count;
+                //    }
+                //    else
+                //    {
+                //        p.Config.OptionRects.ForEach(op =>
+                //        {
+                //            if( !objectives.ContainsKey(op.Key) )
+                //            {
+                //                objectives[op.Key] = 0;
+                //            }
+                //        });
+                //    }
+                //}
+                SetObjectivePaintThreshold(p, baseThreshold, weights);
+            });
+            //ObjectiveQuestCount += objectives.Keys.Count;
+        }
+
+    }
+}

+ 366 - 0
DaJiaoYan/Models/Enumerates.cs

@@ -0,0 +1,366 @@
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    public static class Enumerates
+    {
+        /// <summary>
+        /// 答题卡尺寸
+        /// </summary>
+        public enum CardSizeType
+        {
+            Other = 0,
+            K16 = 1,
+            A4 = 2,
+            K8 = 3,
+            A3 = 4,
+            A5 = 5,
+            B5 = 6,
+        }
+
+        /// <summary>
+        /// 打分方式
+        /// </summary>
+        public enum ScorePartType
+        {
+            /// <summary>
+            /// 填涂
+            /// </summary>
+            Paint = 0,
+            /// <summary>
+            /// 手写
+            /// </summary>
+            Write = 1,
+        }
+
+        /// <summary>
+        /// 批改类型
+        /// </summary>
+        public enum TestCorrectType
+        {
+            /// <summary>
+            /// 按区
+            /// </summary>
+            Area = 1,
+            /// <summary>
+            /// 按空
+            /// </summary>
+            Empty = 2,
+            /// <summary>
+            /// 按区划分按空批改
+            /// </summary>
+            AreaEmpty = 3,
+        }
+
+        /// <summary>
+        /// 试卷状态
+        /// </summary>
+        public enum TestStatus
+        {
+            /// <summary>
+            /// 等待
+            /// </summary>
+            StandBy = 0,
+            /// <summary>
+            /// 进行中
+            /// </summary>
+            Progressing = 1,
+            /// <summary>
+            /// 完成/结束
+            /// </summary>
+            Finish = 2,
+        }
+
+        /// <summary>
+        /// 纸张方向
+        /// </summary>
+        public enum PaperDirection
+        {
+            /// <summary>
+            /// 横向
+            /// </summary>
+            Horizontal,
+            /// <summary>
+            /// 纵向
+            /// </summary>
+            Vertical
+        }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        public enum QuestionType
+        {
+            /// <summary>
+            /// 未设置
+            /// </summary>
+            NotSet = 0,
+            /// <summary>
+            /// 客观题
+            /// </summary>
+            Objective = 1,
+            /// <summary>
+            /// 主观题
+            /// </summary>
+            Subjective = 2,
+        }
+
+        /// <summary>
+        /// 学生上传类型
+        /// </summary>
+        public enum StudentUploadType
+        {
+            All = 0,
+            NotUploaded = 1,
+            Uploaded = 2,
+        }
+
+        /// <summary>
+        /// 字体
+        /// </summary>
+        public enum FontWeight
+        {
+            /// <summary>
+            /// 普通
+            /// </summary>
+            Normal,
+            /// <summary>
+            /// 粗体
+            /// </summary>
+            Bold
+        }
+
+        /// <summary>
+        /// 系统题型
+        /// </summary>
+        public static class SystemQuestType
+        {
+            /// <summary>
+            /// 单选
+            /// </summary>
+            private const string MULTIPLE_ONE = "multiple_one";
+            /// <summary>
+            /// 多选
+            /// </summary>
+            private const string MULTIPLE_MANY = "multiple_many";
+            /// <summary>
+            /// 大题
+            /// </summary>
+            private const string BIG_SPACES = "big_spaces";
+            /// <summary>
+            /// 填空题
+            /// </summary>
+            private const string EMPTY_SPACES = "empty_spaces";
+
+            /// <summary>
+            /// 所有类型
+            /// </summary>
+            private static readonly List<string> allTypes = new List<string>()
+            {
+                MULTIPLE_ONE, MULTIPLE_MANY, EMPTY_SPACES, BIG_SPACES
+            };
+
+
+            /// <summary>
+            /// 单选
+            /// </summary>
+            public static string MultipleOne
+            {
+                get { return MULTIPLE_ONE; }
+            }
+
+            /// <summary>
+            /// 多选
+            /// </summary>
+            public static string MultipleMany
+            {
+                get { return MULTIPLE_MANY; }
+            }
+
+            /// <summary>
+            /// 大题
+            /// </summary>
+            public static string BigSpaces
+            {
+                get { return BIG_SPACES; }
+            }
+
+            /// <summary>
+            /// 填空题
+            /// </summary>
+            public static string EmptySpaces
+            {
+                get { return EMPTY_SPACES; }
+            }
+
+            /// <summary>
+            /// 所有类型
+            /// </summary>
+            public static List<string> AllTypes
+            {
+                get
+                {
+                    return allTypes;
+                }
+            }
+
+            /// <summary>
+            /// 判断题型是否相等
+            /// </summary>
+            /// <param name="res"></param>
+            /// <param name="dst"></param>
+            /// <returns></returns>
+            public static bool EqualType(string res, string dst)
+            {
+                return string.Equals(res, dst, System.StringComparison.OrdinalIgnoreCase);
+            }
+
+
+        }
+
+
+        /// <summary>
+        /// 选项字体的字体
+        /// </summary>
+        public static Dictionary<FontWeight, string> FontWeights = new Dictionary<FontWeight, string>()
+        {
+            {FontWeight.Normal, "普通" },
+            {FontWeight.Bold, "粗体" },
+        };
+
+        /// <summary>
+        /// 选项字体系数
+        /// </summary>
+        public static Dictionary<FontWeight, double> FontWeightPct = new Dictionary<FontWeight, double>()
+        {
+            {FontWeight.Normal, 1 },
+            {FontWeight.Bold, 1.3 },
+        };
+
+        /// <summary>
+        /// 填涂的轻重
+        /// </summary>
+        public enum SignWeight
+        {
+            /// <summary>
+            /// 很浅
+            /// </summary>
+            Lightest,
+            /// <summary>
+            /// 较浅
+            /// </summary>
+            Lighter,
+            /// <summary>
+            /// 普通
+            /// </summary>
+            Normal,
+            /// <summary>
+            /// 较深
+            /// </summary>
+            Harder,
+            /// <summary>
+            /// 很深
+            /// </summary>
+            Hardest
+        }
+
+        /// <summary>
+        /// 填涂的深浅
+        /// </summary>
+        public static Dictionary<SignWeight, string> SignWeigths = new Dictionary<SignWeight, string>()
+        {
+            {SignWeight.Lightest, "很浅" },
+            {SignWeight.Lighter, "较浅" },
+            {SignWeight.Normal, "普通" },
+            {SignWeight.Harder, "较深" },
+            {SignWeight.Hardest, "很深" },
+       };
+
+        /// <summary>
+        /// 填涂系数百分比
+        /// </summary>
+        public static Dictionary<SignWeight, double> SignWeigthPct = new Dictionary<SignWeight, double>()
+        {
+            {SignWeight.Lightest, 0.75 },
+            {SignWeight.Lighter, 0.9 },
+            {SignWeight.Normal, 1 },
+            {SignWeight.Harder, 1.1 },
+            {SignWeight.Hardest, 1.25 },
+       };
+
+        /// <summary>
+        /// 题型字典
+        /// </summary>
+        public static Dictionary<QuestionType, string> QuestTypes = new Dictionary<QuestionType, string>()
+        {
+            {QuestionType.NotSet, "未设定" },
+            {QuestionType.Objective, "客观题" },
+            {QuestionType.Subjective, "主观题" },
+        };
+
+        /// <summary>
+        /// 上传学生类型
+        /// </summary>
+        public static Dictionary<StudentUploadType, string> StudentUploadTypes = new Dictionary<StudentUploadType, string>()
+        {
+            {StudentUploadType.All, "所有" },
+            {StudentUploadType.NotUploaded, "未上传" },
+            {StudentUploadType.Uploaded, "已上传" },
+        };
+
+
+
+        /// <summary>
+        /// 任务状态
+        /// </summary>
+        public enum TaskState
+        {
+            /// <summary>
+            /// 等待中
+            /// </summary>
+            StandBy,
+            /// <summary>
+            /// 运行中
+            /// </summary>
+            Running,
+            /// <summary>
+            /// 已完成
+            /// </summary>
+            Finish,
+        }
+
+        /// <summary>
+        /// 任务状态
+        /// </summary>
+        public static Dictionary<TaskState, string> TaskTypes = new Dictionary<TaskState, string>()
+        {
+            { TaskState.StandBy, "未启动" },
+            {TaskState.Running, "运行中" },
+            {TaskState.Finish, "已结束" }
+        };
+
+        /// <summary>
+        /// 主观题未批改处理类型
+        /// </summary>
+        public enum SubjectiveNoMarkingType
+        {
+            /// <summary>
+            /// 未批改部分强制给0分
+            /// </summary>
+            ForceZero = 0,
+            /// <summary>
+            /// 未批改部分为线上批改
+            /// </summary>
+            OnlineMarking = 1
+        }
+
+        /// <summary>
+        /// 主观题未批改处理类型描述
+        /// </summary>
+        public static Dictionary<SubjectiveNoMarkingType, string> SubjectiveNoMarkingTypes = new Dictionary<SubjectiveNoMarkingType, string>()
+        {
+            {SubjectiveNoMarkingType.OnlineMarking, "线上阅卷" },
+            {SubjectiveNoMarkingType.ForceZero, "强制0分" },
+        };
+    }
+}

+ 183 - 0
DaJiaoYan/Models/PartitionConfig.cs

@@ -0,0 +1,183 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    public class PartitionConfig
+    {
+        /// <summary>
+        /// 选项相关
+        /// {"height":38,"optionCount":"4","options":"A,B,C,D","width":282,"x":149,"y":458}
+        /// </summary>
+        public class TopicRect
+        {
+            public int X { get; set; }
+            public int Y { get; set; }
+            public int Width { get; set; }
+            public int Height { get; set; }
+            public int OptionCount { get; set; }
+            /// <summary>
+            /// 选项 
+            /// </summary>
+            public string Options { get; set; }
+
+            /// <summary>
+            /// 选项列表,将Options 切分
+            /// </summary>
+            public List<string> OptionList { get; set; }
+        }
+
+        public class OptionRect
+        {
+            public int X { get; set; }
+            public int Y { get; set; }
+            public int Width { get; set; }
+            public int Height { get; set; }
+            public string OptionValue { get; set; }
+
+            /// <summary>
+            /// 该选项的点数量(需要预先载入模版统计点的数量)
+            /// </summary>
+            public int Points { get; set; }
+
+            /// <summary>
+            /// 题号?
+            /// </summary>
+            public string Key { get; set; }
+
+            /// <summary>
+            /// 选项的顺序
+            /// </summary>
+            public int Index { get; set; }
+
+            /// <summary>
+            /// 选项的内容
+            /// </summary>
+            public string Option { get; set; }
+
+            /// <summary>
+            /// 填涂的百分比
+            /// </summary>
+            public double PaintThreshold
+            {
+                get; set;
+            }
+        }
+
+        public class PartitionAuthority
+        {
+            public bool Divide { get; set; }
+
+            [JsonProperty(PropertyName = "upload_student")]
+            public bool UploadStudent { get; set; }
+        }
+
+        public class DivideConfig
+        {
+            /// <summary>
+            /// 小空划分信息
+            /// </summary>
+            [JsonProperty(PropertyName = "choose_rects")]
+            public List<TestOptionConfig> EmptyRects { get; set; }
+
+
+            [JsonProperty(PropertyName = "correct_type"), JsonConverter(typeof(StringEnumConverter))]
+            public Enumerates.QuestionType? CorrectType { get; set; }
+
+
+            [JsonProperty(PropertyName = "end_index")]
+            public int EndIndex { get; set; }
+
+
+            [JsonProperty(PropertyName = "start_index")]
+            public int StartIndex { get; set; }
+
+
+            [JsonProperty(PropertyName = "pager_type")]
+            public string PaperType { get; set; }
+
+
+            [JsonProperty(PropertyName = "option_type")]
+            public string OptionType { get; set; }
+
+            /// <summary>
+            /// 选项划分信息
+            /// </summary>
+            [JsonProperty(PropertyName = "option_rects")]
+            public List<OptionRect> OptionRects { get; set; }
+
+            [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+            public Rect Rect { get; set; }
+
+
+            [JsonProperty(PropertyName = "score_rects")]
+            public List<Models.ScoreRect> ScoreRects { get; set; }
+
+
+            [JsonProperty(PropertyName = "topic_rects")]
+            public Dictionary<string, TopicRect> TopicRects { get; set; }
+
+
+            [JsonProperty(PropertyName = "rect_divide", NullValueHandling = NullValueHandling.Ignore)]
+            public Dictionary<string, RectDivide> RectDivides { get; set; }
+
+        }
+
+        public class RectDivide
+        {
+            [JsonProperty(PropertyName = "rect")]
+            public List<Rect> Rect { get; set; }
+
+            [JsonProperty(PropertyName = "type")]
+            public int Type { get; set; }
+
+            [JsonProperty(PropertyName = "type_tips")]
+            public string TypeTips { get; set; }
+
+            [JsonProperty(PropertyName = "name")]
+            public string Name { get; set; }
+
+            [JsonProperty(PropertyName = "order")]
+            public int Order { get; set; }
+
+            [JsonProperty(PropertyName = "option_rect")]
+            public Dictionary<string, RectDivide> OptionRect { get; set; }
+
+            [JsonProperty(PropertyName = "index")]
+            public int Index { get; set; }
+
+            [JsonProperty(PropertyName = "children")]
+            public Dictionary<string, RectDivide> Children { get; set; }
+
+
+        }
+
+
+        public string Id { get; set; }
+        public string Name { get; set; }
+
+        [JsonProperty(PropertyName = "correct_type")]
+        public Enumerates.QuestionType CorrectType { get; set; }
+
+        [JsonProperty(PropertyName = "show_order")]
+        public int ShowOrder { get; set; }
+
+        [JsonProperty(PropertyName = "partition_score")]
+        public float PartitionScore { get; set; }
+
+        public DivideConfig Config { get; set; }
+
+        [JsonProperty(PropertyName = "empty_count")]
+        public int EmptyCount { get; set; }
+
+        [JsonProperty(PropertyName = "option_count")]
+        public int OptionCount { get; set; }
+
+        [JsonProperty(PropertyName = "question_config")]
+        public List<QuestionConfig> QuestionConfigs { get; set; }
+
+        public PartitionAuthority Authority { get; set; }
+
+    }
+}

+ 41 - 0
DaJiaoYan/Models/QuestionConfig.cs

@@ -0,0 +1,41 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    public class QuestionConfig
+    {
+        public class EmptyScore
+        {
+            public int Key { get; set; }
+            public string Label { get; set; }
+            public float Value { get; set; }
+        }
+
+        [JsonProperty(PropertyName = "label", NullValueHandling = NullValueHandling.Ignore)]
+        public string Label { get; set; }
+        /// <summary>
+        /// 系统题型,如:big_spaces, multiple_one
+        /// </summary>
+        [JsonProperty(PropertyName = "question_type")]
+        public string QuestionType { get; set; }
+
+        [JsonProperty(PropertyName = "show_order")]
+        public int ShowOrder { get; set; }
+
+        [JsonProperty(PropertyName = "empty_count")]
+        public int EmptyCount { get; set; }
+
+        [JsonProperty(PropertyName = "option_count")]
+        public int OptionCount { get; set; }
+
+        [JsonProperty(PropertyName = "question_score")]
+        public float QuestionScore { get; set; }
+
+        [JsonProperty(PropertyName = "empty_score")]
+        public List<EmptyScore> EmptyScores { get; set; }
+
+        [JsonProperty(PropertyName = "answer")]
+        public List<string> Answers { get; set; }
+    }
+}

+ 63 - 0
DaJiaoYan/Models/Rect.cs

@@ -0,0 +1,63 @@
+using Newtonsoft.Json;
+
+namespace DaJiaoYan.Models
+{
+
+    public class Rect
+    {
+        private OpenCvSharp.Rect _rect;
+
+
+        [JsonProperty(PropertyName = "x", NullValueHandling = NullValueHandling.Ignore)]
+        public int X { get; set; }
+
+        [JsonProperty(PropertyName = "y", NullValueHandling = NullValueHandling.Ignore)]
+        public int Y { get; set; }
+
+        [JsonProperty(PropertyName = "width", NullValueHandling = NullValueHandling.Ignore)]
+        public int Width { get; set; }
+
+        [JsonProperty(PropertyName = "height", NullValueHandling = NullValueHandling.Ignore)]
+        public int Height { get; set; }
+
+        /// <summary>
+        /// ÇøÓòËùÔÚµÄÖ½ÕÅA/BÃæ
+        /// </summary>
+        [JsonProperty(PropertyName = "side", NullValueHandling = NullValueHandling.Ignore)]
+        public string Side { get; set; }
+
+        [JsonProperty(PropertyName = "uuid", NullValueHandling = NullValueHandling.Ignore)]
+        public string UUID { get; set; }
+
+        public Rect(int x, int y, int w, int h)
+        {
+            X = x; Y = y; Width = w; Height = h;
+        }
+
+        public OpenCvSharp.Rect CvRect
+        {
+            get
+            {
+                if (_rect == null || _rect.X != X || _rect.Y != Y || _rect.Width != Width || _rect.Height != Height)
+                {
+                    _rect = new OpenCvSharp.Rect
+                    {
+                        X = X,
+                        Y = Y,
+                        Width = Width,
+                        Height = Height,
+                    };
+                }
+                return _rect;
+            }
+        }
+
+        public bool Avaliable
+        {
+            get
+            {
+                return X >= 0 && Y >= 0 && Width > 0 && Height > 0;
+            }
+        }
+    }
+}

+ 89 - 0
DaJiaoYan/Models/ScanResultPost.cs

@@ -0,0 +1,89 @@
+using DaJiaoYan.Utils;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using static DaJiaoYan.Models.UploadHandlerPostParam;
+
+namespace DaJiaoYan.Models
+{
+    public class ScanResultPost
+    {
+        public class ImageInfo
+        {
+            [JsonProperty(PropertyName = "has_error")]
+            public int HasError { get; set; }
+
+            [JsonProperty(PropertyName = "image")]
+            public string Image { get; set; }
+
+            [JsonProperty(PropertyName = "index")]
+            public int Index { get; set; }
+
+        }
+
+
+        [JsonProperty(PropertyName = "test_id")]
+        public int TestId { get; set; }
+
+        [JsonProperty(PropertyName = "school_id")]
+        public int SchoolId { get; set; }
+
+        [JsonProperty(PropertyName = "campus_id")]
+        public int CampusId { get; set; }
+
+        [JsonProperty(PropertyName = "class_id")]
+        public int ClassId { get; set; }
+
+        [JsonProperty(PropertyName = "batch_id")]
+        public string BatchId { get; set; }
+
+        [JsonProperty(PropertyName = "file_path")]
+        public string FilePath { get; set; }
+
+        [JsonProperty(PropertyName = "images")]
+        public List<ImageInfo> Images { get; set; }
+
+        [JsonProperty(PropertyName = "finish")]
+        public bool Finish { get; set; }
+
+        [JsonProperty(PropertyName = "is_school_no")]
+        public int IsSchoolNo { get; set; }
+
+        [JsonProperty(PropertyName = "sign_weight")]
+        public int SignWeight { get; set; }
+
+        [JsonProperty(PropertyName = "total")]
+        public int Total { get; set; }
+
+        [JsonProperty(PropertyName = "marking_type")]
+        public MarkType MarkingType { get; set; }
+
+        [JsonProperty(PropertyName = "ver")]
+        public int Ver { get; set; }
+
+        public ScanResultPost(int testId, int schoolId, int campusId, int classId, int isSchoolNo, int signWeight, string directory, MarkType markType, string batchId = null, bool finish = false, int ver = 1)
+        {
+            Init(testId, schoolId, campusId, classId, isSchoolNo, signWeight, directory, markType, batchId, finish, ver);
+        }
+
+        public ScanResultPost(UploadHandlerPostParam p, string batchId = null, bool finish = false)
+        {
+            Init(p.TestId, p.SchoolId, p.CampusId, p.ClassId, p.IsSchoolNo, p.SignWeight, p.Directory, p.MarkingType, batchId, finish, p.Ver);
+        }
+
+        private void Init(int testId, int schoolId, int campusId, int classId, int isSchoolNo, int signWeight, string directory, MarkType markingType, string batchId = null, bool finish = false, int ver = 1)
+        {
+            Images = new List<ImageInfo>();
+            BatchId = string.IsNullOrEmpty(batchId) ? Functions.GenUUID() : batchId;
+            TestId = testId;
+            SchoolId = schoolId;
+            CampusId = campusId;
+            ClassId = classId;
+            IsSchoolNo = isSchoolNo;
+            SignWeight = signWeight;
+            MarkingType = markingType;
+            Finish = finish;
+            FilePath = directory;
+            Ver = ver;
+        }
+    }
+}

+ 56 - 0
DaJiaoYan/Models/ScanTask.cs

@@ -0,0 +1,56 @@
+namespace DaJiaoYan.Models
+{
+    internal class ScanTask
+    {
+        /// <summary>
+        /// 使用扫描仪
+        /// </summary>
+        public string Scanner { get; set; }
+        /// <summary>
+        /// 图片保存路径
+        /// </summary>
+        public string Path { get; set; }
+        /// <summary>
+        /// 图片保存的后缀名,如:jpg
+        /// </summary>
+        public string ImageSuffix { get; set; }
+        /// <summary>
+        /// 图片DPI
+        /// </summary>
+        public int ImageDpi { get; set; }
+        /// <summary>
+        /// 是否彩色
+        /// </summary>
+        public bool Colorful { get; set; }
+
+        /// <summary>
+        /// 扫描张数。-1:连续扫描
+        /// </summary>
+        public int CapXferCount { get; set; }
+
+
+        public delegate void DeleScanFileCompleted(string filename, int index);
+        /// <summary>
+        /// 扫描文件完成
+        /// </summary>
+        public DeleScanFileCompleted OnDeleScanFileCompleted;
+
+        public delegate void ScanTaskCompleted();
+        /// <summary>
+        /// 扫描任务完成
+        /// </summary>
+        public ScanTaskCompleted OnScanTaskCompleted;
+
+        public delegate void ScannerError();
+        /// <summary>
+        /// 扫描错误
+        /// </summary>
+        public ScannerError OnScannerError;
+
+        public delegate void ScanBegin(bool success, string msg);
+        /// <summary>
+        /// 扫描开始
+        /// </summary>
+        public ScanBegin OnScanBegin;
+    }
+}

+ 17 - 0
DaJiaoYan/Models/ScannerList.cs

@@ -0,0 +1,17 @@
+using Newtonsoft.Json;
+
+namespace DaJiaoYan.Models
+{
+    internal class ScannerList
+    {
+        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+        /// <summary>
+        /// 当前选择的扫描仪
+        /// </summary>
+        public string Selected { get; set; }
+        /// <summary>
+        /// 扫描仪资源数组
+        /// </summary>
+        public string[] Resources { get; set; }
+    }
+}

+ 87 - 0
DaJiaoYan/Models/ScoreRect.cs

@@ -0,0 +1,87 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    /// <summary>
+    /// 先阅后扫打分模版
+    /// </summary>
+    public class ScoreRect
+    {
+        public class PaintScorePartRect
+        {
+            public int X { get; set; }
+            public int Y { get; set; }
+            public int Width { get; set; }
+            public int Height { get; set; }
+
+            //public OpenCvSharp.CPlusPlus.Rect Rect { get; set; }
+
+            /// <summary>
+            /// 该空分值
+            /// </summary>
+            public double Score { get; set; }
+
+            //public PaintScorePartRect(OpenCvSharp.CPlusPlus.Rect Rect)
+            //{
+            //    X = Rect.X;
+            //    Y = Rect.Y;
+            //    Width = Rect.Width;
+            //    Height = Rect.Height;
+            //    Score = 0;
+            //}
+
+            /// <summary>
+            /// 该空填涂的百分比
+            /// </summary>
+            public int PaintPointCount { get; set; }
+        }
+
+        public class ScorePartRect
+        {
+            [JsonProperty(PropertyName = "id")]
+            public string Id { get; set; }
+            [JsonProperty(PropertyName = "isEdit")]
+            public bool IsEdit { get; set; }
+            [JsonProperty(PropertyName = "key")]
+            public string Key { get; set; }
+            [JsonProperty(PropertyName = "max_score")]
+            public double MaxScore { get; set; }
+            [JsonProperty(PropertyName = "min_score")]
+            public double MinScore { get; set; }
+            [JsonProperty(PropertyName = "score_type")]
+            public int ScoreType { get; set; }
+            [JsonProperty(PropertyName = "step")]
+            public double Step { get; set; }
+            [JsonProperty(PropertyName = "width")]
+            public int Width { get; set; }
+            [JsonProperty(PropertyName = "height")]
+            public int Height { get; set; }
+            [JsonProperty(PropertyName = "x")]
+            public int X { get; set; }
+            [JsonProperty(PropertyName = "y")]
+            public int Y { get; set; }
+            [JsonProperty(PropertyName = "PaintScorePartRects")]
+            public List<PaintScorePartRect> PaintScorePartRects { get; set; }
+        }
+
+
+        public class EmptyScoreRect
+        {
+            [JsonProperty(PropertyName = "key")]
+            public string Key { get; set; }
+            [JsonProperty(PropertyName = "name")]
+            public string Name { get; set; }
+            [JsonProperty(PropertyName = "value")]
+            public string Value { get; set; }
+            [JsonProperty(PropertyName = "score_part_rects")]
+            public List<ScorePartRect> ScorePartRects = new List<ScorePartRect>();
+        }
+
+
+        [JsonProperty(PropertyName = "name")]
+        public string Name { get; set; }
+        [JsonProperty(PropertyName = "empty_score_rects")]
+        public List<EmptyScoreRect> EmptyScoreRects { get; set; }
+    }
+}

+ 120 - 0
DaJiaoYan/Models/TemplateRect.cs

@@ -0,0 +1,120 @@
+using DaJiaoYan.Utils;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    public class TemplateRect
+    {
+        private readonly string data;
+        //private TestTemplate.TemplateVersion version;
+        private readonly List<Rect> rects = default;
+
+        private Rect V1Rect;
+        private List<Rect> V2Rects;
+
+        /// <summary>
+        /// 当前版本的锚点信息
+        /// </summary>
+        public TestTemplate.TemplateVersion Version { get; set; }
+
+        public TemplateRect()
+        {
+
+        }
+
+        public TemplateRect(TestTemplate.TemplateVersion version, string data)
+        {
+            this.Version = version;
+            this.data = data;
+            if (version == TestTemplate.TemplateVersion.V2)
+            {
+                rects = V2Rects = Json.Decode<List<Rect>>(data);
+
+            }
+            else
+            {
+                V1Rect = Json.Decode<Rect>(data);
+                if (V1Rect != null)
+                {
+                    rects = new List<Rect>() { V1Rect };
+                }
+
+            }
+        }
+
+        public TemplateRect(TestTemplate.TemplateVersion version, List<Rect> data)
+        {
+            this.Version = version;
+            if (version == TestTemplate.TemplateVersion.V2)
+            {
+                V2Rects = data;
+
+            }
+            else
+            {
+                if (data != null && data.Count > 0)
+                {
+                    V1Rect = data[0];
+                }
+            }
+            rects = data;
+        }
+
+
+
+        public TemplateRect(int x, int y, int w, int h)
+        {
+            this.Version = TestTemplate.TemplateVersion.V1;
+            rects = new List<Rect>() { new Rect(x, y, w, h) };
+        }
+
+
+        /// <summary>
+        /// 获取所有锚点信息
+        /// </summary>
+        public List<Rect> Rects
+        {
+            get
+            {
+                return rects;
+            }
+        }
+
+        /// <summary>
+        /// 获取答卷指定面的所有锚点
+        /// </summary>
+        /// <param name="side"></param>
+        /// <returns></returns>
+        public List<Rect> SideRects(string side = "A")
+        {
+            List<Rect> res = new List<Rect>();
+            if (Version == TestTemplate.TemplateVersion.V2 && rects != null && rects.Count > 0)
+            {
+                res = rects.FindAll(x => { return string.Equals(x.Side, side, System.StringComparison.OrdinalIgnoreCase); });
+            }
+            else
+            {
+                res = rects;
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 获取答卷指定面的第一个锚点
+        /// </summary>
+        /// <param name="side"></param>
+        /// <returns></returns>
+        public Rect SideRect(string side = "A")
+        {
+            Rect res = default;
+            var rects = SideRects(side);
+            if (rects != null && rects.Count > 0)
+            {
+                res = rects[0];
+            }
+            return res;
+        }
+
+
+    }
+}

+ 29 - 0
DaJiaoYan/Models/TestAnswerSheet.cs

@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+
+namespace DaJiaoYan.Models
+{
+
+    /// <summary>
+    /// 答题卡
+    /// </summary>
+    public class TestAnswerSheet
+    {
+        /// <summary>
+        /// 页码,如:A、B
+        /// </summary>
+        public string Page { get; set; }
+        /// <summary>
+        /// 答题卡图片地址(相对地址)
+        /// </summary>
+        public string Url { get; set; }
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 处理后的图片地址(相对地址)
+        /// </summary>
+        [JsonProperty(PropertyName = "inited_url")]
+        public string InitedUrl { get; set; }
+
+    }
+}

+ 13 - 0
DaJiaoYan/Models/TestOptionConfig.cs

@@ -0,0 +1,13 @@
+namespace DaJiaoYan.Models
+{
+    public class TestOptionConfig
+    {
+        public int X { get; set; }
+        public int Y { get; set; }
+        public int Width { get; set; }
+        public int Height { get; set; }
+        public string Index { get; set; }
+        public string Value { get; set; }
+        public bool IsEdit { get; set; }
+    }
+}

+ 123 - 0
DaJiaoYan/Models/TestTemplate.cs

@@ -0,0 +1,123 @@
+using Newtonsoft.Json;
+//using OpenCvSharp;
+
+namespace DaJiaoYan.Models
+{
+
+    public class TestTemplate
+    {
+        private const string V2 = "v2";
+
+        public enum TemplateVersion
+        {
+            V1, V2
+        }
+
+        //     {
+        //    "barcodeRect": {
+        //        "height": 394,
+        //        "width": 631,
+        //        "x": 953,
+        //        "y": 411
+        //    },
+        //    "card_size_type": 4,
+        //    "missedRect": {
+        //        "height": 20,
+        //        "width": 56,
+        //        "x": 702,
+        //        "y": 234
+        //    },
+        //    "nameRect": {
+        //        "height": 292,
+        //        "width": 492,
+        //        "x": 251,
+        //        "y": 233
+        //    },
+        //    "numberRect": {
+        //        "height": 154,
+        //        "width": 670,
+        //        "x": 839,
+        //        "y": 272
+        //    },
+        //    "temp_height": 2004,
+        //    "temp_rect": {
+        //        "height": 26,
+        //        "width": 48,
+        //        "x": 125,
+        //        "y": 126
+        //    },
+        //    "temp_width": 2937
+        //}
+        [JsonProperty(PropertyName = "barcodeRect", NullValueHandling = NullValueHandling.Ignore)]
+        public Rect BarcodeRect { get; set; }
+
+        [JsonProperty(PropertyName = "card_size_type")]
+        public Enumerates.CardSizeType CardSizeType { get; set; }
+
+        [JsonProperty(PropertyName = "missedRect", NullValueHandling = NullValueHandling.Ignore)]
+        public Rect MissedRect { get; set; }
+
+        [JsonProperty(PropertyName = "nameRect", NullValueHandling = NullValueHandling.Ignore)]
+        public Rect NameRect { get; set; }
+
+        [JsonProperty(PropertyName = "numberRect", NullValueHandling = NullValueHandling.Ignore)]
+        public Rect NumberRect { get; set; }
+
+        //[JsonProperty(PropertyName = "temp_rect", NullValueHandling = NullValueHandling.Ignore)]
+        public TemplateRect TemplateRect { get; set; }
+
+        /// <summary>
+        /// 锚点内宽度
+        /// </summary>
+        [JsonProperty(PropertyName = "temp_width", NullValueHandling = NullValueHandling.Ignore)]
+        public int TemplateWidth { get; set; }
+
+        /// <summary>
+        /// 锚点内高度
+        /// </summary>
+        [JsonProperty(PropertyName = "temp_height", NullValueHandling = NullValueHandling.Ignore)]
+        public int TemplateHeight { get; set; }
+
+        /// <summary>
+        /// 锚点宽度
+        /// </summary>
+        [JsonProperty(PropertyName = "anchor_width", NullValueHandling = NullValueHandling.Ignore)]
+        public int AnchorWidth { get; set; }
+
+        /// <summary>
+        /// 锚点高度
+        /// </summary>
+        [JsonProperty(PropertyName = "anchor_height", NullValueHandling = NullValueHandling.Ignore)]
+        public int AnchorHeight { get; set; }
+
+        /// <summary>
+        /// 版本
+        /// </summary>
+        [JsonProperty(PropertyName = "version", NullValueHandling = NullValueHandling.Ignore)]
+        public string Version { get; set; }
+
+        //[JsonProperty(PropertyName = "Version", NullValueHandling = NullValueHandling.Ignore), JsonConverter(typeof(StringEnumConverter))]
+        /// <summary>
+        /// 当前模版信息
+        /// </summary>
+        public TemplateVersion ParsedVersion
+        {
+            get
+            {
+                return string.Equals(Version, V2, System.StringComparison.OrdinalIgnoreCase) ? TemplateVersion.V2 : TemplateVersion.V1;
+            }
+            set
+            {
+                Version = value == TemplateVersion.V2 ? "v2" : "";
+            }
+        }
+
+        /// <summary>
+        /// 模版dpi
+        /// </summary>
+        public int Dpi { get; set; }
+
+        [JsonProperty(PropertyName = "answer_sheet")]
+        public TestAnswerSheet[] AnswerSheets { get; set; }
+    }
+}

+ 8 - 0
DaJiaoYan/Models/UpdateInfo.cs

@@ -0,0 +1,8 @@
+namespace DaJiaoYan.Models
+{
+    public struct UpdateInfo
+    {
+        public string MD5 { get; set; }
+        public double Version { get; set; }
+    }
+}

+ 207 - 0
DaJiaoYan/Models/UploadHandlerPostParam.cs

@@ -0,0 +1,207 @@
+using DaJiaoYan.Utils;
+using Newtonsoft.Json;
+using NTwain.Data;
+using System;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Models
+{
+    public class UploadHandlerPostParam
+    {
+        public enum UploadType
+        {
+            /// <summary>
+            /// 扫描上传
+            /// </summary>
+            Scan = 1,
+            /// <summary>
+            /// 文件夹上传
+            /// </summary>
+            Directory = 2,
+        }
+
+        public enum MarkType
+        {
+            LocalMarking = 2,
+            OnlineMarking = 1,
+        }
+
+        public enum ErrorType
+        {
+            None,
+            TypeError,
+            SchoolIdError,
+            CampusIdError,
+            TestIdError,
+            SubjectIdError,
+            IsSchoolNumberError,
+            MarkingTypeError,
+            SignWeightError,
+            TokenError,
+            ScannerError,
+            DirectoryError,
+            NoImageError,
+        }
+
+        private readonly Dictionary<ErrorType, string> errorInfos = new Dictionary<ErrorType, string>()
+        {
+            { ErrorType.None, "" },
+            { ErrorType.TypeError, "上传类型错误" },
+            { ErrorType.SchoolIdError, "学校信息错误" },
+            { ErrorType.CampusIdError, "校区信息错误" },
+            { ErrorType.SubjectIdError, "科目信息错误" },
+            { ErrorType.TestIdError, "试卷信息错误" },
+            { ErrorType.IsSchoolNumberError, "考号匹配模式错误" },
+            { ErrorType.MarkingTypeError, "阅卷模式模式错误" },
+            { ErrorType.SignWeightError, "填涂深浅参数错误" },
+            { ErrorType.TokenError, "用户信息异常,请重新登录" },
+            { ErrorType.ScannerError, "扫描仪异常" },
+            { ErrorType.DirectoryError, "选择的文件夹异常" },
+            { ErrorType.NoImageError, "没有可上传的图片" },
+        };
+
+        private readonly ErrorType errorType;
+
+        [JsonProperty(PropertyName = "token")]
+        public string Token { get; set; }
+
+        [JsonProperty(PropertyName = "type")]
+        public UploadType Type { get; set; }
+
+        [JsonProperty(PropertyName = "school_id")]
+        public int SchoolId { get; set; }
+
+        [JsonProperty(PropertyName = "campus_id")]
+        public int CampusId { get; set; }
+
+        [JsonProperty(PropertyName = "class_id")]
+        public int ClassId { get; set; }
+
+        [JsonProperty(PropertyName = "test_id")]
+        public int TestId { get; set; }
+
+        [JsonProperty(PropertyName = "subject_id")]
+        public int SubjectId { get; set; }
+
+        [JsonProperty(PropertyName = "is_school_no")]
+        public int IsSchoolNo { get; set; }
+
+        [JsonProperty(PropertyName = "sign_weight")]
+        public int SignWeight { get; set; }
+
+        [JsonProperty(PropertyName = "scanner")]
+        public string Scanner { get; set; }
+
+        [JsonProperty(PropertyName = "directory")]
+        public string Directory { get; set; }
+        [JsonProperty(PropertyName = "ver")]
+        public int Ver { get; set; }
+
+        public List<string> Files { get; set; }
+
+        public int Progress { get; set; }
+
+        public bool ScanFinish { get; set; }
+
+        public MarkType MarkingType { get; set; }
+
+        public ErrorType GetErrorType
+        {
+            get
+            {
+                return errorType;
+            }
+        }
+
+        public string ErrorMessage
+        {
+            get
+            {
+                return errorInfos[GetErrorType];
+            }
+        }
+
+
+        public UploadHandlerPostParam(string token, string type, string schoolId, string campusId, string classId, string testId, string subjectId, string isSchoolNo, string signWeight, string scanner, string directory, string markingType, int ver)
+        {
+            if (string.IsNullOrEmpty(type) || !Reg.Match(type, "[12]"))
+            {
+                errorType = ErrorType.TypeError;
+            }
+            else if (string.IsNullOrEmpty(schoolId) || !Reg.Match(schoolId, Reg.NUMBER))
+            {
+                errorType = ErrorType.SchoolIdError;
+            }
+            else if (string.IsNullOrEmpty(campusId) || !Reg.Match(campusId, Reg.NUMBER))
+            {
+                errorType = ErrorType.CampusIdError;
+            }
+            else if (string.IsNullOrEmpty(testId) || !Reg.Match(testId, Reg.NUMBER))
+            {
+                errorType = ErrorType.TestIdError;
+            }
+            else if (string.IsNullOrEmpty(subjectId) || !Reg.Match(subjectId, Reg.NUMBER))
+            {
+                errorType = ErrorType.SubjectIdError;
+            }
+            else if (string.IsNullOrEmpty(isSchoolNo) || !Reg.Match(isSchoolNo, "[01]"))
+            {
+                errorType = ErrorType.IsSchoolNumberError;
+            }
+            else if (string.IsNullOrEmpty(signWeight) || !Reg.Match(signWeight, Reg.NUMBER))
+            {
+                errorType = ErrorType.SignWeightError;
+            }
+            else if (string.IsNullOrEmpty(markingType) || !Reg.Match(markingType, "[12]"))
+            {
+                errorType = ErrorType.MarkingTypeError;
+            }
+            if (errorType == ErrorType.None)
+            {
+                Token = token;
+                Type = type.ConvertToEnum<UploadType>();
+                SchoolId = Convert.ToInt32(schoolId);
+                CampusId = Convert.ToInt32(campusId);
+                if (Reg.Match(classId, Reg.NUMBER))
+                {
+                    ClassId = Convert.ToInt32(classId);
+                }
+                TestId = Convert.ToInt32(testId);
+                SubjectId = Convert.ToInt32(subjectId);
+                IsSchoolNo = Convert.ToInt32(isSchoolNo);
+                SignWeight = Convert.ToInt32(signWeight);
+                Scanner = scanner;
+                Directory = directory;
+                MarkingType = markingType.ConvertToEnum<MarkType>();
+                //Files = files;
+                //Progress = progress;
+                //ScanFinish = scanFinish;
+                if (string.IsNullOrEmpty(Token))
+                {
+                    errorType = ErrorType.TokenError;
+                }
+                else if (Type == UploadType.Scan && string.IsNullOrEmpty(Scanner))
+                {
+                    errorType = ErrorType.ScannerError;
+                }
+                else if (Type == UploadType.Directory)
+                {
+                    if (string.IsNullOrEmpty(Directory) || !FileExts.CheckDirectory(Directory, false))
+                    {
+                        errorType = ErrorType.DirectoryError;
+                    }
+                    else
+                    {
+                        Files = FileExts.GetImageFiles(Directory);
+                        if (Files?.Count < 1)
+                        {
+                            errorType = ErrorType.NoImageError;
+                        }
+                    }
+                }
+            }
+
+            Ver = ver;
+        }
+    }
+}

+ 42 - 0
DaJiaoYan/Models/UploadTaskInfo.cs

@@ -0,0 +1,42 @@
+using Newtonsoft.Json;
+
+namespace DaJiaoYan.Models
+{
+    internal class UploadTaskInfo
+    {
+        [JsonProperty(PropertyName = "id")]
+        public string Id { get; set; }
+
+        [JsonProperty(PropertyName = "scan_count")]
+        public int ScanCount { get; set; }
+
+        [JsonProperty(PropertyName = "upload_count")]
+        public int UploadCount { get; set; }
+
+        [JsonProperty(PropertyName = "scan_finished")]
+        public int ScanFinished { get; set; }
+
+        [JsonProperty(PropertyName = "upload_finish")]
+        public int UploadFinished { get; set; }
+
+        public UploadTaskInfo(string id)
+        {
+            Id = id;
+            ScanCount = 0;
+            UploadCount = 0;
+            ScanFinished = 0;
+            UploadFinished = 0;
+        }
+
+        public UploadTaskInfo Copy()
+        {
+            return new UploadTaskInfo(Id)
+            {
+                ScanCount = ScanCount,
+                UploadCount = UploadCount,
+                ScanFinished = ScanFinished,
+                UploadFinished = UploadFinished
+            };
+        }
+    }
+}

+ 46 - 0
DaJiaoYan/Program.cs

@@ -0,0 +1,46 @@
+using DaJiaoYan.Utils;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace DaJiaoYan
+{
+    internal static class Program
+    {
+        [STAThread]
+        [DllImport("User32.dll")]
+        private static extern bool ShowWindowAsync(System.IntPtr hWnd, int cmdShow);
+        [DllImport("User32.dll")]
+        private static extern bool SetForegroundWindow(System.IntPtr hWnd);
+
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Process current = Process.GetCurrentProcess();
+            Process instance = ProcessExts.RunningInstance(ref current);
+            if (instance == null)
+            {
+                Application.EnableVisualStyles();
+                Application.SetCompatibleTextRenderingDefault(false);
+                Application.Run(new FormMain());
+            }
+            else
+            {
+                HandleRunningInstance(instance);
+            }
+        }
+
+        #region  确保程序只运行一个实例
+        private static void HandleRunningInstance(Process instance)
+        {
+            MessageBox.Show("程序已经在运行!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+            ShowWindowAsync(instance.MainWindowHandle, 1);  //调用api函数,正常显示窗口
+            SetForegroundWindow(instance.MainWindowHandle); //将窗口放置最前端
+        }
+        #endregion
+    }
+}

+ 32 - 0
DaJiaoYan/Properties/AssemblyInfo.cs

@@ -0,0 +1,32 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("DaJiaoYan")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DaJiaoYan")]
+[assembly: AssemblyCopyright("Copyright ©  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("31157941-f4c9-4f25-a141-2cdbf5316da5")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
DaJiaoYan/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本: 4.0.30319.42000
+//
+//     对此文件的更改可能导致不正确的行为,如果
+//     重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DaJiaoYan.Properties
+{
+
+
+    /// <summary>
+    ///   强类型资源类,用于查找本地化字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   返回此类使用的缓存 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DaJiaoYan.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
DaJiaoYan/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
DaJiaoYan/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DaJiaoYan.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
DaJiaoYan/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 206 - 0
DaJiaoYan/Services/AliyunOss.cs

@@ -0,0 +1,206 @@
+using Aliyun.OSS;
+using DaJiaoYan.Models;
+using DaJiaoYan.Utils;
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace DaJiaoYan.Services
+{
+    public static class AliyunOss
+    {
+        private static AliyunOssClient client = null;
+        /// <summary>
+        /// 客户端锁
+        /// </summary>
+        private readonly static object ClientLock = new object();
+
+        private static AliyunOssClient Client
+        {
+            get
+            {
+                if (client == null)
+                {
+                    lock (ClientLock)
+                    {
+                        client = new AliyunOssClient();
+                    }
+                }
+
+                return client;
+            }
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="requestContent"></param>
+        /// <returns></returns>
+        public static bool SimpleUpload(string objectName, MemoryStream requestContent)
+        {
+            return Client.SimpleUpload(objectName, requestContent);
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="localFilename"></param>
+        /// <returns></returns>
+        public static bool SimpleUpload(string objectName, string localFilename)
+        {
+            return Client.SimpleUpload(objectName, localFilename);
+        }
+    }
+
+    public class AliyunOssClient
+    {
+        public delegate void DeleWriteLog(string txt);
+        public static DeleWriteLog WriteLog;
+
+        private static OssClient client = null;
+        private static AliyunAccessModel accessModel = null;
+        /// <summary>
+        /// 客户端锁
+        /// </summary>
+        private readonly static object ClientLock = new object();
+
+        /// <summary>
+        /// 授权有效期临界值
+        /// </summary>
+        private const int EXP_THRESHOLD = 60 * 2 * 1000;
+        private readonly string token = null;
+
+        public AliyunOssClient(string token = null)
+        {
+            this.token = token;
+        }
+
+        private AliyunAccessModel Access
+        {
+            get
+            {
+                lock (ClientLock)
+                {
+                    if (accessModel == null || accessModel.Expiration - EXP_THRESHOLD < Functions.GetTimeStamp())
+                    {
+                        //lock (accessModel)
+                        //{
+                        accessModel = Api.GetAliyunAccess(token).Result;
+                        if (accessModel != null)
+                        {
+                            accessModel.Expiration = Functions.GetTimeStamp() + accessModel.Expiration * 1000;
+                            accessModel.EndPoint = Regex.Replace(accessModel.EndPoint, @"-internal", "", RegexOptions.IgnoreCase);
+                        }
+
+                        //}
+                    }
+                }
+
+                return accessModel;
+            }
+        }
+
+        public bool AccessAvailable
+        {
+            get
+            {
+                return !string.IsNullOrEmpty(Access?.EndPoint) && !string.IsNullOrEmpty(Access?.AccessKeyId) && !string.IsNullOrEmpty(Access?.AccessKeySecret);
+            }
+        }
+
+
+        private OssClient Client
+        {
+            get
+            {
+                if (client == null && AccessAvailable)
+                {
+                    client = new OssClient(Access?.EndPoint, Access?.AccessKeyId, Access?.AccessKeySecret);
+                }
+                return client;
+            }
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="requestContent"></param>
+        /// <returns></returns>
+        public bool SimpleUpload(string objectName, Stream requestContent)
+        {
+            //// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
+            //var endpoint = "yourEndpoint";
+            //// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
+            //var accessKeyId = "yourAccessKeyId";
+            //var accessKeySecret = "yourAccessKeySecret";
+            //// 填写Bucket名称。
+            //var bucketName = "examplebucket";
+            //// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
+            //var objectName = "exampleobject.txt";
+            //// 填写字符串。
+            //var objectContent = "More than just cloud.";
+
+            // 创建OssClient实例。
+            //var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
+            bool res = false;
+            try
+            {
+                //byte[] binaryData = Encoding.ASCII.GetBytes(objectContent);
+                //MemoryStream requestContent = new MemoryStream(binaryData);
+                // 上传文件。
+                Client?.PutObject(Access?.BucketName, objectName, requestContent);
+                res = true;
+            }
+            catch (Exception ex)
+            {
+                WriteLog?.Invoke($"Put object failed, {ex.Message}");
+                WriteLog?.Invoke(ex.StackTrace);
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="localFilename"></param>
+        /// <returns></returns>
+        public bool SimpleUpload(string objectName, string localFilename)
+        {
+            bool res = false;
+            //PutObjectResult r;
+            try
+            {
+                //byte[] binaryData = Encoding.ASCII.GetBytes(objectContent);
+                //MemoryStream requestContent = new MemoryStream(binaryData);
+                for (int i = 0; i < 5; i++)
+                {
+                    using (PutObjectResult r = Client?.PutObject(Access?.BucketName, objectName, localFilename))
+                    {
+                        //r = t.
+                        if (r?.HttpStatusCode == System.Net.HttpStatusCode.OK)
+                        {
+                            res = true;
+                            break;
+                        }
+                    }
+                }
+                // 上传文件。
+            }
+            catch (Exception ex)
+            {
+                WriteLog?.Invoke($"Put object failed, {ex.Message}");
+                WriteLog?.Invoke(ex.StackTrace);
+            }
+            return res;
+        }
+
+    }
+}

+ 345 - 0
DaJiaoYan/Services/Api.cs

@@ -0,0 +1,345 @@
+using DaJiaoYan.Models;
+using DaJiaoYan.Utils;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace DaJiaoYan.Services
+{
+    public static class Api
+    {
+        public delegate void DeleLogin(bool runAfterLoginFn);
+        public static DeleLogin LoginFn;
+
+        /// <summary>
+        /// 枚举类型
+        /// </summary>
+        public static class Enumerates
+        {
+            /// <summary>
+            /// 考号类型
+            /// </summary>
+            public enum ExamNumberType
+            {
+                /// <summary>
+                /// 系统号(云学情学号)
+                /// </summary>
+                System = 1,
+                /// <summary>
+                /// 学校学号
+                /// </summary>
+                School = 2,
+            }
+
+            /// <summary>
+            /// 是否获取所有有权限参考学生
+            /// </summary>
+            public enum AllExamStudentType
+            {
+                /// <summary>
+                /// 是
+                /// </summary>
+                Yes = 1,
+                /// <summary>
+                /// 只获取考试的学生
+                /// </summary>
+                No = 0,
+            }
+
+            /// <summary>
+            /// 搜索名称类型
+            /// </summary>
+            public enum ExamStudentSearchNameType
+            {
+                /// <summary>
+                /// 所有
+                /// </summary>
+                All = 0,
+                /// <summary>
+                /// 只搜索账号名
+                /// </summary>
+                OnlyUserName = 1,
+                /// <summary>
+                /// 用户名跟真实姓名
+                /// </summary>
+                UserNameAndRealName = 2,
+            }
+        }
+
+
+
+        private const int PAGE_SIZE = 999999;
+        private const int PAGE_NO = 1;
+
+        /// <summary>
+        /// 获取试卷划分区域信息
+        /// </summary>
+        /// <param name="testId">试卷ID</param>
+        /// <returns></returns>
+        public static async Task<DividePartitionConfig> GetDividePartitionConfig(int testId, string token = null)
+        {
+            DividePartitionConfig res = null;
+            Dictionary<string, object> keyValuePairs;
+            var (code, response) = await Fetch(new Http.Request(@"v1/examination/divide-partition", Http.Method.Get, new Dictionary<string, object>()
+            {
+                {"test_id", testId },
+                {"page_size", 99 },
+                {"page_no", 1 },
+                {"correct_type", 0 },
+            }), token);
+            if (!string.IsNullOrEmpty(response) && code == 200)
+            {
+                res = Json.Decode<DividePartitionConfig>(response);
+                keyValuePairs = Json.Decode<Dictionary<string, object>>(response);
+                keyValuePairs.TryGetValue("template_info", out object templateInfoObj);
+                if (templateInfoObj != null)
+                {
+                    Dictionary<string, object> templateInfo = Json.Decode<Dictionary<string, object>>(Json.Encode(templateInfoObj));
+                    templateInfo.TryGetValue("temp_rect", out object tempRect);
+                    if (tempRect != null)
+                    {
+                        res.Template.TemplateRect = new TemplateRect(res.Template.ParsedVersion, Json.Encode(tempRect));
+                    }
+                }
+
+
+            }
+            return res;
+
+        }
+
+        /// <summary>
+        /// 获取静态文件流
+        /// </summary>
+        /// <param name="fileName"></param>
+        /// <returns></returns>
+        public static async Task<byte[]> GetStaticFileBytes(string fileName)
+        {
+            byte[] result = null;
+            fileName = Regex.Replace(fileName, @"^[\/\\]*", "");
+            long begin = Functions.GetTimeStamp(), end;
+            Http.Request request = new Http.Request($"{Variables.Const.HTTP_STATIC_HOST}{fileName}", Http.Method.Get);
+            end = Functions.GetTimeStamp();
+            Log.WriteLine($"request static file create http: {end - begin}");
+            begin = end;
+            using (HttpResponseMessage response = await Http.Fetch(request))
+            {
+                end = Functions.GetTimeStamp();
+                Log.WriteLine($"request static file fetch http: {end - begin}");
+                begin = end;
+                if (response != null && response.StatusCode == HttpStatusCode.OK)
+                {
+                    result = response.Content.ReadAsByteArrayAsync().Result;
+                    end = Functions.GetTimeStamp();
+                    Log.WriteLine($"request static file read byte: {end - begin}");
+                    begin = end;
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 获取升级信息
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<Models.UpdateInfo> GetUpdateInfo()
+        {
+            UpdateInfo info = new UpdateInfo();
+            try
+            {
+                string url = Regex.Replace(Variables.Const.HTTP_UPDATE_FILE, "\\.[^.]{2,5}$", ".txt", RegexOptions.IgnoreCase);
+                HttpResponseMessage response = await Http.Fetch(new Http.Request(url, Http.Method.Get));
+                if (response.IsSuccessStatusCode)
+                {
+                    string txt = await response.Content.ReadAsStringAsync();
+                    info = Utils.Json.Decode<UpdateInfo>(txt);
+                }
+            }
+            catch
+            {
+
+            }
+            return info;
+        }
+
+
+
+        /// <summary>
+        /// 获取阿里云OSS STS 权限
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<Models.AliyunAccessModel> GetAliyunAccess(string token = null)
+        {
+            var (_, response) = await Fetch(new Http.Request(@"v1/oss/authorize", Http.Method.Post, null), token);
+            Models.AliyunAccessModel res = null;
+            if (response != null)
+            {
+                res = Json.Decode<Models.AliyunAccessModel>(response);
+            }
+            return res;
+        }
+
+
+
+        public static async Task<(int, string)> PostExaminationScanResult(Models.ScanResultPost p, string token = null)
+        {
+            var req = new Http.Request(@"v1/examination/scan-result", Http.Method.Post)
+            {
+                data = Json.Decode<Dictionary<string, object>>(Json.Encode(p))
+            };
+            return await Fetch(req, token);
+        }
+
+
+        /// <summary>
+        /// 获取系统返回的内容
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="url"></param>
+        /// <param name="method"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private static async Task<T> GetResponse<T>(string url, Http.Method method, Dictionary<string, object> data)
+        {
+            T res = default;
+            var (_, response) = await Fetch(new Http.Request(url, method, data));
+            if (!string.IsNullOrEmpty(response))
+            {
+                var tmp = Json.Decode<Dictionary<string, object>>(response);
+                if (tmp != null && tmp.ContainsKey("result"))
+                {
+                    res = Json.Decode<T>(tmp["result"].ToString());
+                }
+            }
+            return res;
+        }
+
+        #region 私有方法
+        /// <summary>
+        /// 请求
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        private static async Task<(int, string)> Fetch(Http.Request request, string token = null)
+        {
+            foreach (var item in Variables.Vars.RequestHeaders)
+            {
+                // 添加请求头
+                request.headers.Add(item.Key, item.Value);
+            }
+            if (!string.IsNullOrEmpty(token))
+            {
+                request.headers["Authorization"] = $"Bearer {token}";
+                request.headers["AppName"] = "com.teacherUploadMark.web";
+            }
+            // 请求地址
+#if DEBUG
+            if (request.uri.IndexOf("/oss/") >= 0 && Variables.Const.HTTP_HOST.IndexOf("local") >= 0)
+            {
+                request.uri = string.Concat("http://local.api.dajiaoyan.com/", request.uri);
+            }
+            else
+            {
+                request.uri = string.Concat(Variables.Const.HTTP_HOST, request.uri);
+            }
+#else
+            // 请求地址
+            request.uri = string.Concat(Variables.Const.HTTP_HOST, request.uri);
+#endif
+            string apiResponse = null;
+            int code = 200;
+            Dictionary<string, object> resp;
+            using (HttpResponseMessage response = await Http.Fetch(request))
+            {
+                if (response != null)
+                {
+                    switch (response.StatusCode)
+                    {
+                        case HttpStatusCode.OK:
+                            // 正常响应
+
+                            resp = JsonConvert.DeserializeObject<Dictionary<string, object>>(await response.Content.ReadAsStringAsync());
+                            code = Convert.ToInt32(resp["code"]);
+                            switch (code)
+                            {
+                                case 400:
+                                    Response400();
+                                    apiResponse = resp["errors"] != null ? resp["errors"].ToString() : "接口错误";
+                                    break;
+                                case 401:
+                                    Response401();
+                                    break;
+                                case 4000:
+                                    Response401();
+                                    break;
+                                case 404:
+                                    Response404();
+                                    break;
+                                case 500:
+                                    Response500();
+                                    apiResponse = resp["errors"].ToString();
+                                    break;
+                                case 502:
+                                    Response500();
+                                    break;
+                                default:
+                                    apiResponse = resp["data"] != null ? resp["data"].ToString() : "";
+                                    break;
+                            }
+                            break;
+                        case HttpStatusCode.NotFound:
+                            // 页面不存在
+                            Response404();
+                            break;
+                        case HttpStatusCode.Unauthorized:
+                            // 无权限
+                            if (string.IsNullOrEmpty(token))
+                            {
+                                Response401();
+                            }
+
+                            break;
+                        case HttpStatusCode.BadRequest:
+                            // 页面错误
+                            Response400();
+                            break;
+                        default:
+                            // 其它错误
+                            Response500();
+                            break;
+                    }
+                }
+                else
+                {
+                    // 没有网络
+                }
+            }
+            return (code, apiResponse);
+        }
+
+        private static void Response400()
+        {
+
+        }
+
+        private static void Response401()
+        {
+            LoginFn?.Invoke(false);
+        }
+
+        private static void Response404()
+        {
+
+        }
+
+        private static void Response500()
+        {
+
+        }
+        #endregion
+    }
+}

+ 526 - 0
DaJiaoYan/Services/HttpHandlers.cs

@@ -0,0 +1,526 @@
+using DaJiaoYan.Models;
+using DaJiaoYan.Utils;
+using DaJiaoYan.Variables;
+using OpenCvSharp;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace DaJiaoYan.Services
+{
+
+    struct ScanResult
+    {
+        public string Filename;
+        public int Index;
+    }
+
+    internal class PingHandler : HttpHandler
+    {
+        private ResponseData pongResponse = new ResponseData { Code = 200, Data = "pong" };
+
+        public PingHandler(AsyncHttpServer httpServer) : base(httpServer)
+        {
+        }
+
+        public override void Get(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            Json(ref response, ref pongResponse);
+        }
+    }
+
+
+    internal class ScannerHandler : HttpHandler
+    {
+        public ScannerHandler(AsyncHttpServer httpServer) : base(httpServer)
+        {
+        }
+
+        public override void Get(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            ResponseData result = new ResponseData { Code = 200, Data = Variables.Vars.Scanners };
+            Json(ref response, ref result);
+        }
+    }
+
+
+    public class ScanHandler : HttpHandler
+    {
+        public ScanHandler(AsyncHttpServer httpServer) : base(httpServer)
+        {
+        }
+
+        public override async void Post(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            ResponseData result = new ResponseData { Code = 200 };
+            var form = this.HttpServer.ParseForm(ref request);
+            form.TryGetValue("scanner", out string scanner);
+            var files = new List<string>();
+            var finished = false;
+            result.Data = files;
+            if (string.IsNullOrEmpty(scanner))
+            {
+                result.Code = 400;
+                result.Errors = "请选择扫描仪";
+                finished = true;
+            }
+            else if (!Vars.Scanners.Contains(scanner))
+            {
+                result.Code = 400;
+                result.Errors = "请选择正确的扫描仪";
+                finished = true;
+            }
+            else if (Scan.State == Scan.ScannerState.Running)
+            {
+                result.Code = 400;
+                result.Errors = "正在扫描中";
+                finished = true;
+            }
+            else
+            {
+                ScanTask scanTask = new ScanTask
+                {
+                    Scanner = scanner,
+                    ImageDpi = 200,
+                    ImageSuffix = "png",
+                    Colorful = true,
+                    Path = Const.TMP_PATH,
+                    CapXferCount = 2,
+                    OnScanBegin = (bool begin, string err) =>
+                    {
+                        if (!begin)
+                        {
+                            result.Code = 400;
+                            result.Errors = err;
+                            finished = true;
+                        }
+                    },
+                    OnDeleScanFileCompleted = (string f, int index) =>
+                    {
+                        try
+                        {
+                            Mat img = Cv2.ImRead(f);
+
+                            if (img != null && img.Channels() > 1)
+                            {
+                                ImageUtils.Red2OtherColor(ref img, Scalar.Black);
+                                Mat dstImg = new Mat(img.Size(), MatType.CV_8UC1);
+                                Cv2.CvtColor(img, dstImg, ColorConversionCodes.BGR2GRAY);
+                                Cv2.ImWrite(f, dstImg);
+                                dstImg.Dispose();
+                            }
+                            files.Add(ImageUtils.ToBase64(f));
+                            img?.Dispose();
+                        }
+                        catch
+                        {
+                        }
+                    },
+                    OnScanTaskCompleted = () =>
+                    {
+                        result.Code = 200;
+                        finished = true;
+                    },
+                    OnScannerError = () =>
+                    {
+                        result.Code = 200;
+                        result.Errors = "扫描出错!";
+                        finished = true;
+                    }
+                };
+                Scan.Start(scanTask);
+                while (!finished || files.Count < 2)
+                {
+                    await Task.Delay(100);
+                }
+            }
+            Json(ref response, ref result);
+        }
+    }
+
+
+    public class UploadHandler : HttpHandler
+    {
+        public UploadHandler(AsyncHttpServer httpServer) : base(httpServer)
+        {
+        }
+
+        /// <summary>
+        /// 扫描图片上传接口
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="response"></param>
+        public override async void Post(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            ResponseData result = new ResponseData { Code = 200 };
+            int ocrVer = 0;
+            var form = this.HttpServer.ParseForm(ref request);
+            form.TryGetValue("token", out string token);
+            form.TryGetValue("type", out string type);
+            form.TryGetValue("school_id", out string school_id);
+            form.TryGetValue("campus_id", out string campus_id);
+            form.TryGetValue("class_id", out string class_id);
+            form.TryGetValue("test_id", out string test_id);
+            form.TryGetValue("subject_id", out string subject_id);
+            form.TryGetValue("is_school_no", out string is_school_no);
+            form.TryGetValue("sign_weight", out string sign_weight);
+            form.TryGetValue("marking_type", out string marking_type);
+            form.TryGetValue("scanner", out string scanner);
+            form.TryGetValue("directory", out string directory);
+            form.TryGetValue("ver", out string ver);
+            if (!string.IsNullOrEmpty(ver))
+            {
+                int.TryParse(ver, out ocrVer);
+            }
+
+            UploadHandlerPostParam param = new UploadHandlerPostParam(
+                token, type, school_id, campus_id, class_id, test_id, subject_id, is_school_no, sign_weight, scanner, directory, marking_type,
+                ocrVer
+                );
+
+            if (param.GetErrorType != UploadHandlerPostParam.ErrorType.None)
+            {
+                result.Code = 400;
+                result.Errors = param.ErrorMessage;
+            }
+            else
+            {
+                if (param.Type == UploadHandlerPostParam.UploadType.Scan)
+                {
+                    if (Scan.State == Scan.ScannerState.Running)
+                    {
+                        result.Code = 400;
+                        result.Errors = "正在扫描中,请稍候";
+                    }
+                    if (string.IsNullOrEmpty(scanner))
+                    {
+                        result.Code = 400;
+                        result.Errors = "请选择扫描仪";
+                        param.ScanFinish = true;
+                    }
+                    else if (!Vars.Scanners.Contains(scanner))
+                    {
+                        result.Code = 400;
+                        result.Errors = "请选择正确的扫描仪";
+                        param.ScanFinish = true;
+                    }
+                    else
+                    {
+                        AliyunOssClient ossClient = new AliyunOssClient(param.Token);
+                        if (!ossClient.AccessAvailable)
+                        {
+                            result.Code = 400;
+                            result.Errors = "没有上传权限,请检查";
+                        }
+                        else
+                        {
+
+                            string batchId = Functions.GenUUID();
+                            string uploadTaskInfoId = batchId;
+                            result.Data = batchId;
+                            UploadTask.deleNewUploadTask(uploadTaskInfoId);
+                            string path = Path.Combine(Const.TMP_PATH, campus_id.ToString(), test_id.ToString(), batchId);
+                            FileExts.CheckDirectory(path, true);
+                            param.Directory = path;
+                            object _locker = new object();
+                            ScanResultPost scanResult = new ScanResultPost(param);
+                            //List<string> files = new List<string>();
+                            List<ScanResult> scanFiles = new List<ScanResult>();
+                            int n = 0, finished = 0;
+                            const int BATCH_SIZE = 2; //扫描1张就上传
+
+                            bool uploadImage(string ossFn, string fn)
+                            {
+                                bool res = false;
+                                if (scanResult.MarkingType == UploadHandlerPostParam.MarkType.OnlineMarking)
+                                {
+                                    try
+                                    {
+                                        Mat img = Cv2.ImRead(fn);
+
+                                        if (img != null && img.Channels() > 1)
+                                        {
+                                            Mat dstImg = new Mat(img.Size(), MatType.CV_8UC1);
+                                            Cv2.CvtColor(img, dstImg, ColorConversionCodes.BGR2GRAY);
+                                            Cv2.ImWrite(fn, dstImg);
+                                            img.Dispose();
+                                            dstImg.Dispose();
+                                        }
+                                        img?.Dispose();
+                                    }
+                                    catch
+                                    {
+
+                                    }
+                                }
+                                res = ossClient.SimpleUpload(ossFn, fn);
+                                UploadTask.deleIncreaseUploadTask(uploadTaskInfoId);
+                                return res;
+                            }
+                            async void uploadedFilenames()
+                            {
+                                //Console.WriteLine($"n={n}, total={scanResult.Total}, finish={scanResult.Finish}");
+                                if (n >= BATCH_SIZE || scanResult.Finish)
+                                {
+                                    List<Task> tasks = new List<Task>();
+                                    ScanResultPost sr = new ScanResultPost(param);
+                                    lock (_locker)
+                                    {
+                                        sr.Finish = scanResult.Finish;
+                                        sr.Total = scanResult.Total;
+                                        sr.BatchId = scanResult.BatchId;
+                                    }
+                                    for (int i = sr.Total - n; i < sr.Total; i++)
+                                    {
+                                        int idx = i;
+                                        ScanResultPost.ImageInfo info = new ScanResultPost.ImageInfo()
+                                        {
+                                            Image = scanFiles[idx].Filename,
+                                            HasError = 1,
+                                            Index = scanFiles[idx].Index
+                                        };
+                                        sr.Images.Add(info);
+                                        tasks.Add(Task.Run(() =>
+                                        {
+                                            if (uploadImage(scanFiles[idx].Filename, Path.Combine(path, Path.GetFileName(scanFiles[idx].Filename))))
+                                            {
+                                                info.HasError = 0;
+                                            }
+                                        }));
+                                    }
+                                    n = 0;
+                                    await Task.Factory.StartNew(async () =>
+                                    {
+                                        await Task.WhenAll(tasks.ToArray());
+                                        // 上传图片地址
+                                        await Api.PostExaminationScanResult(sr, param.Token);
+                                        Vars.UploadedCounterIncrease(tasks.Count);
+                                        lock (_locker)
+                                        {
+                                            finished += BATCH_SIZE;
+                                        }
+                                        //UploadTask.deleIncreaseUploadTask(uploadTaskInfoId, BATCH_SIZE);
+                                    });
+                                }
+                            }
+
+                            ScanTask scanTask = new ScanTask
+                            {
+                                Scanner = scanner,
+                                ImageDpi = 200,
+                                ImageSuffix = "jpg",
+                                Colorful = param.MarkingType == UploadHandlerPostParam.MarkType.LocalMarking,
+                                Path = path,
+                                CapXferCount = -1,
+                                OnScanBegin = (bool begin, string err) =>
+                                {
+                                    if (!begin)
+                                    {
+                                        result.Code = 400;
+                                        result.Errors = err;
+                                        scanResult.Finish = true;
+                                    }
+                                },
+                                OnDeleScanFileCompleted = (string f, int index) =>
+                                {
+                                    try
+                                    {
+                                        Task.Factory.StartNew(() =>
+                                        {
+                                            string originFilename = Functions.GenUploadOriginImageName(param.TestId, param.CampusId, f, scanResult.BatchId);
+                                            lock (_locker)
+                                            {
+                                                //files.Add(originFilename);
+                                                scanFiles.Add(new ScanResult { Filename = originFilename, Index = index });
+                                                n++;
+                                                scanResult.Total++;
+                                            }
+                                            UploadTask.deleIncreaseScanTask(uploadTaskInfoId, 1);
+                                            uploadedFilenames();
+                                        });
+
+                                    }
+                                    catch
+                                    {
+                                    }
+                                },
+                                OnScanTaskCompleted = async () =>
+                                {
+                                    result.Code = 200;
+                                    if (scanFiles.Count > 0)
+                                    {
+                                        while (finished < scanResult.Total)
+                                        {
+                                            await Task.Delay(1000);
+                                        }
+                                    }
+                                    scanResult.Finish = true;
+                                    UploadTask.deleFinishScanTask(uploadTaskInfoId);
+                                },
+                                OnScannerError = () =>
+                                {
+                                    result.Code = 200;
+                                    result.Errors = "扫描出错!";
+                                    scanResult.Finish = true;
+                                }
+                            };
+                            await Task.Factory.StartNew(async () =>
+                            {
+                                Scan.Start(scanTask);
+                                Vars.SetTaskStatus(batchId, false);
+                                while (!scanResult.Finish)
+                                {
+                                    await Task.Delay(100);
+                                }
+                                uploadedFilenames();
+                                Vars.SetTaskStatus(batchId, true);
+                                UploadTask.deleFinishUploadTask(uploadTaskInfoId);
+                            });
+
+                        }
+                    }
+                }
+                else
+                {
+                    var divideConfig = await Api.GetDividePartitionConfig(param.TestId, param.Token);
+                    int answerSheetsLength = 2;
+                    if (divideConfig != null)
+                    {
+                        answerSheetsLength = divideConfig.AnswerSheets.Length;
+                    }
+
+                    // 上传文件夹
+                    List<string> files = FileExts.GetImageFiles(param.Directory);
+                    if (divideConfig != null && files.Count > 0 && answerSheetsLength > 0 && files.Count % answerSheetsLength != 0)
+                    {
+                        result.Code = 400;
+                        result.Errors = "本地答卷数量与模板数量不一致,请核查";
+                    }
+                    else if (files.Count > 0)
+                    {
+                        AliyunOssClient ossClient = new AliyunOssClient(param.Token);
+                        if (!ossClient.AccessAvailable)
+                        {
+                            result.Code = 400;
+                            result.Errors = "没有上传权限,请检查";
+                        }
+                        else
+                        {
+                            const int BATCH_SIZE = 4; //作为一个批次上传的图片数量
+                            string uploadTaskInfoId = Functions.GenUUID();
+                            UploadTask.deleNewUploadTask(uploadTaskInfoId);
+                            UploadTask.deleIncreaseScanTask(uploadTaskInfoId, files.Count);
+                            UploadTask.deleFinishScanTask(uploadTaskInfoId);
+                            result.Data = uploadTaskInfoId;
+                            int n = 0, idx = 0;
+                            ScanResultPost scanResult = new ScanResultPost(param)
+                            {
+                                Total = files.Count
+                            };
+                            bool uploadImage(string ossFn, string fn)
+                            {
+                                bool res = false;
+                                MemoryStream ms = new MemoryStream();
+                                string suffix = Path.GetExtension(fn).ToLower();
+                                ImageEncodingParam imageEncodingParam = Vars.IMAGE_SAVE_PNG_PARAM;
+                                Mat img;
+                                if (suffix.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) || suffix.Equals(".jpg", StringComparison.OrdinalIgnoreCase))
+                                {
+                                    imageEncodingParam = Vars.IMAGE_SAVE_JPEG_PARAM;
+                                }
+                                if (scanResult.MarkingType == UploadHandlerPostParam.MarkType.OnlineMarking)
+                                {
+                                    img = ImageUtils.Read(fn, ImreadModes.Grayscale);
+                                }
+                                else
+                                {
+                                    img = ImageUtils.Read(fn);
+                                }
+                                if (img != null)
+                                {
+                                    ms = img.ToMemoryStream(suffix, imageEncodingParam);
+                                    res = ossClient.SimpleUpload(ossFn, ms);
+                                    img?.Dispose();
+                                    ms?.Dispose();
+                                }
+
+                                return res;
+                            }
+                            async Task uploadedFilenames()
+                            {
+                                //Console.WriteLine($"n={n}, total={scanResult.Total}, finish={scanResult.Finish}");
+                                List<Task> tasks = new List<Task>();
+                                if (n >= BATCH_SIZE || scanResult.Finish)
+                                {
+                                    scanResult.Images.Clear();
+                                    for (int i = 0; idx < scanResult.Total && i < n; i++)
+                                    {
+                                        int _k = idx;
+                                        string originImageFilename = Functions.GenUploadOriginImageName(scanResult.TestId, scanResult.CampusId, files[_k], scanResult.BatchId);
+                                        ScanResultPost.ImageInfo info = new ScanResultPost.ImageInfo() { Image = originImageFilename, HasError = 1 };
+                                        scanResult.Images.Add(info);
+                                        tasks.Add(Task.Run(() =>
+                                        {
+                                            if (uploadImage(originImageFilename, files[_k]))
+                                            {
+                                                Console.WriteLine($"{originImageFilename} uploaded.");
+                                                info.HasError = 0;
+                                                info.Index = _k + 1;
+                                                UploadTask.deleIncreaseUploadTask(uploadTaskInfoId);
+                                            }
+                                        }));
+                                        idx++;
+                                    }
+                                    n = 0;
+                                }
+                                Task.WaitAll(tasks.ToArray());
+                                // 上传图片地址
+                                await Api.PostExaminationScanResult(scanResult, param.Token);
+                                Vars.UploadedCounterIncrease(tasks.Count);
+                            }
+                            _ = Task.Factory.StartNew(async () =>
+                            {
+                                string batchId = scanResult.BatchId;
+                                Vars.SetTaskStatus(batchId, false);
+                                for (int i = 0; i < files.Count; i += BATCH_SIZE)
+                                {
+                                    n = BATCH_SIZE;
+                                    await uploadedFilenames();
+
+                                    await Task.Delay(100);
+                                }
+                                scanResult.Finish = true;
+                                n = BATCH_SIZE;
+                                await uploadedFilenames();
+                                Vars.SetTaskStatus(batchId, true);
+                                UploadTask.deleFinishUploadTask(uploadTaskInfoId);
+                            });
+                        }
+                    }
+                    else
+                    {
+                        result.Code = 400;
+                        result.Errors = "没有可上传的图片";
+                    }
+                }
+            }
+
+            Json(ref response, ref result);
+        }
+
+
+        public override void Get(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            ResponseData result = new ResponseData { Code = 200 };
+            string id = request.QueryString.Get("id");
+
+            result.Data = UploadTask.deleGetUploadTask(id);
+            Json(ref response, ref result);
+        }
+    }
+
+
+}

+ 323 - 0
DaJiaoYan/Services/HttpServer.cs

@@ -0,0 +1,323 @@
+using DaJiaoYan.Utils;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace DaJiaoYan.Services
+{
+
+    public class HttpHandler
+    {
+        public readonly AsyncHttpServer HttpServer;
+        public readonly string path;
+        private ResponseData DefaultResponse = new ResponseData { Code = 405 };
+
+        public HttpHandler(AsyncHttpServer httpServer, string path = null)
+        {
+            HttpServer = httpServer;
+            this.path = path;
+        }
+
+        public virtual void Get(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            //StaticFile(request, ref response, (HttpListenerRequest req, HttpListenerResponse rep) =>
+            //{
+            //    Json(ref rep, ref DefaultResponse);
+            //});
+
+            Json(ref response, ref DefaultResponse);
+        }
+
+        public virtual void Post(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            Json(ref response, ref DefaultResponse);
+        }
+
+        public virtual void Patch(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            Json(ref response, ref DefaultResponse);
+        }
+
+        public virtual void Put(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            Json(ref response, ref DefaultResponse);
+        }
+
+        public virtual void Delete(HttpListenerRequest request, HttpListenerResponse response)
+        {
+            Json(ref response, ref DefaultResponse);
+        }
+
+
+        /// <summary>
+        /// 输出数据
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="bytes"></param>
+        public virtual void Response(ref HttpListenerResponse response, ref byte[] bytes)
+        {
+            if (bytes != null)
+            {
+                using (var stream = response.OutputStream)
+                {
+                    ///获取数据,要返回给接口的
+                    try
+                    {
+                        stream.Write(bytes, 0, bytes.Length);
+                    }
+                    catch { }
+
+                }
+            }
+            response.Close();
+        }
+
+        /// <summary>
+        /// 响应Json格式数据
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="result"></param>
+        public virtual void Json(ref HttpListenerResponse response, ref ResponseData result)
+        {
+            response.AddHeader("Content-type", "application/json");//添加响应头信息
+            response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+            response.AddHeader("Pragma", "no-cache");
+            response.AddHeader("Expires", "0");
+            response.StatusCode = result.Code;
+            //response.ContentType = "text/plain;charset=UTF-8";//告诉客户端返回的ContentType类型为纯文本格式,编码为UTF-8
+            response.ContentEncoding = Encoding.UTF8;
+            //string d = YxqService.Utils.Json.Encode(result);
+            var data = Encoding.UTF8.GetBytes(Utils.Json.Encode(result));
+            Response(ref response, ref data);
+        }
+    }
+
+
+    /// <summary>
+    /// 响应数据格式
+    /// </summary>
+    public struct ResponseData
+    {
+        [JsonProperty(PropertyName = "code")]
+        public int Code { get; set; }
+
+        [JsonProperty(PropertyName = "msg")]
+        public object Msg { get; set; }
+
+        [JsonProperty(PropertyName = "errors")]
+        public object Errors { get; set; }
+
+        [JsonProperty(PropertyName = "data")]
+        public object Data { get; set; }
+    }
+
+    public class AsyncHttpServer
+    {
+        private readonly HttpListener _listener;
+        private bool _isRunning;
+
+        /// <summary>
+        /// 中间件方法
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="response"></param>
+        /// <param name="result"></param>
+        /// <returns></returns>
+        public delegate bool Middleware(HttpListenerRequest request, HttpListenerResponse response);
+
+        /// <summary>
+        /// 接口方法
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="result"></param>
+        public delegate void HttpHandle(HttpListenerRequest request, HttpListenerResponse response);
+
+        /// <summary>
+        /// 中间件数组
+        /// </summary>
+        private readonly List<Middleware> MiddleWares = new List<Middleware>();
+
+        /// <summary>
+        /// 路由字典
+        /// </summary>
+        private readonly Dictionary<string, HttpHandler> Prefixes = new Dictionary<string, HttpHandler>();
+
+        /// <summary>
+        /// 默认接口
+        /// </summary>
+        private readonly HttpHandler DefaultHandler;
+        public readonly string Root;
+        public readonly int Port;
+        public readonly string Host;
+        public readonly string Scheme;
+        /// <summary>
+        /// 监听地址
+        /// </summary>
+        public readonly string Listener;
+
+        public AsyncHttpServer(string root, string host = "127.0.0.1", int port = 7000, string scheme = "http")
+        {
+            this.Root = root;
+            DefaultHandler = new HttpHandler(this, root);
+            this.Port = port;
+            this.Host = host;
+            this.Scheme = scheme;
+            Listener = $"{scheme}://{host}:{port}/";
+            _listener = new HttpListener();
+            _listener.Prefixes.Add(Listener);
+
+            Prefixes.Add("", DefaultHandler);
+
+        }
+
+
+        /// <summary>
+        /// 注册中间件
+        /// </summary>
+        /// <param name="func"></param>
+        public void RegisterMiddleware(Middleware func)
+        {
+            MiddleWares.Add(func);
+        }
+
+        public void RegisterPrefix(string prefix, HttpHandler handler)
+        {
+            Prefixes.Add(prefix, handler);
+        }
+
+        public async Task StartAsync()
+        {
+            _isRunning = true;
+            _listener.Start();
+            Console.WriteLine($"Server started on {string.Join(", ", _listener.Prefixes)}");
+
+            try
+            {
+                while (_isRunning)
+                {
+                    var context = await _listener.GetContextAsync();
+                    _ = Task.Factory.StartNew(() => ProcessRequest(context));
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Server error: {ex.Message}");
+            }
+        }
+
+        public void Stop()
+        {
+            _isRunning = false;
+            _listener.Stop();
+            _listener.Close();
+            Console.WriteLine("Server stopped");
+        }
+
+
+        private bool ProcessMiddlewares(HttpListenerContext context)
+        {
+            bool res = true;
+            // 中间件处理
+            foreach (var mw in MiddleWares)
+            {
+                if (!mw(context.Request, context.Response))
+                {
+                    context.Response.Close();
+                    res = false;
+                    break;
+                }
+            }
+            return res;
+        }
+
+
+
+        private void ProcessRequest(HttpListenerContext context)
+        {
+            if (!ProcessMiddlewares(context))
+            {
+                return;
+            }
+            var request = context.Request;
+            var response = context.Response;
+            try
+            {
+                Prefixes.TryGetValue(request.Url.AbsolutePath, out var r);
+                if (r == null)
+                {
+                    Prefixes.TryGetValue("", out r);
+                }
+                HttpHandle act;
+                if (r == null)
+                {
+                    response.StatusCode = 404;
+                    response.Close();
+                    act = DefaultHandler.Get;
+                }
+                else
+
+                {
+                    switch (request.HttpMethod.ToUpper())
+                    {
+                        case "POST":
+                            act = r.Post;
+                            break;
+                        case "PUT":
+                            act = r.Put;
+                            break;
+                        case "DELETE":
+                            act = r.Delete;
+                            break;
+                        case "PATCH":
+                            act = r.Patch;
+                            break;
+                        default:
+                            act = r.Get;
+                            break;
+                    }
+                }
+                act?.Invoke(request, response);
+            }
+            catch (Exception e)
+            {
+                Log.WriteLine(e.ToString());
+                response.StatusCode = 404;
+            }
+            finally
+            {
+                //response.Close();
+            }
+        }
+
+
+        public Dictionary<string, string> ParseForm(ref HttpListenerRequest request)
+        {
+            Dictionary<string, string> res = new Dictionary<string, string>();
+            string content;
+            using (Stream stream = request.InputStream)
+            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+            {
+                content = reader.ReadToEnd();
+            }
+            if (!string.IsNullOrEmpty(content))
+            {
+                foreach (var arg in content.Split('&'))
+                {
+                    var n = arg.IndexOf('=');
+                    if (n > 0)
+                    {
+                        res[arg.Substring(0, n)] = HttpUtility.UrlDecode(arg.Substring(n + 1));
+                    }
+                }
+            }
+            return res;
+        }
+
+
+    }
+}

+ 64 - 0
DaJiaoYan/Services/Instance.cs

@@ -0,0 +1,64 @@
+using DaJiaoYan.Utils;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace DaJiaoYan.Services
+{
+    internal class Instance
+    {
+        public static void Run()
+        {
+            Functions.UUIDBackgroundFunc();
+            FileExts.CheckDirectory(Variables.Const.TMP_PATH, true);
+            InitHttpServer();
+        }
+
+
+        public static void InitHttpServer()
+        {
+
+            AsyncHttpServer httpServer = new AsyncHttpServer(Variables.Const.TMP_PATH);
+
+            httpServer.RegisterMiddleware((HttpListenerRequest request, HttpListenerResponse response) =>
+            {
+                // cors middleware
+                string origin = request.Headers.Get("origin");
+                //string referer = request.Headers.Get("referer");
+
+                if (!string.IsNullOrEmpty(origin))
+                {
+                    var m = Regex.Match(origin, @"(https?:\/\/([^/]*\.dajiaoyan.com|dajiaoyan.com))", RegexOptions.IgnoreCase);
+                    if (m.Success)
+                    {
+                        response.Headers.Add("Access-Control-Allow-Credentials", "true");
+                        response.Headers.Add("Access-Control-Allow-Origin", origin);
+                    }
+                }
+                return true;
+            });
+
+            httpServer.RegisterMiddleware((HttpListenerRequest request, HttpListenerResponse response) =>
+            {
+                // options middleware
+                response.StatusCode = 200;
+                if (string.Equals(request.HttpMethod, "OPTIONS", System.StringComparison.OrdinalIgnoreCase))
+                {
+                    response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS, PUT, DELETE");
+                    response.Headers.Add("Access-Control-Allow-Headers", "AppName,AppVersion,Authorization,Platform");
+                    return false;
+                }
+                return true;
+            });
+
+            httpServer.RegisterPrefix("/ping", new PingHandler(httpServer));
+            httpServer.RegisterPrefix("/scanners", new ScannerHandler(httpServer));
+            httpServer.RegisterPrefix("/scan", new ScanHandler(httpServer));
+            httpServer.RegisterPrefix("/upload", new UploadHandler(httpServer));
+            new Thread(State => { _ = httpServer.StartAsync(); })
+            {
+                IsBackground = true
+            }.Start();
+        }
+    }
+}

+ 439 - 0
DaJiaoYan/Services/Scan.cs

@@ -0,0 +1,439 @@
+using DaJiaoYan.Models;
+using DaJiaoYan.Utils;
+using DaJiaoYan.Variables;
+using NTwain;
+using NTwain.Data;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using static DaJiaoYan.Models.ScanTask;
+
+namespace DaJiaoYan.Services
+{
+    internal class Scan
+    {
+        public delegate void DeleLoadDriver(bool ok);
+        public static DeleLoadDriver DelegateLoadDriver;
+
+        public delegate void DeleChangeScannerName(string name);
+        public static DeleChangeScannerName DelegateChangeScannerName;
+
+        public delegate void DeleChangeScannerStatus(string status, Color color);
+        public static DeleChangeScannerStatus DelegateChangeScannerStatus;
+
+
+
+        public static IntPtr OwnerFormHandle;
+        //private Form form;
+        //private IntPtr Control.Handle;
+
+
+        private static TwainSession _twain;
+
+        /// <summary>
+        /// twainDSM 是否已安装
+        /// </summary>
+        private static bool twainDSMInstalled = true;
+        /// <summary>
+        /// 图片序号
+        /// </summary>
+        private static int imgIndex = 0;
+
+        private static int preStatus = 0;
+
+        /// <summary>
+        /// 扫描仪状态
+        /// </summary>
+        private static ScannerState scannerState = ScannerState.Stop;
+
+        /// <summary>
+        /// 扫描仪当前状态
+        /// </summary>
+        public enum ScannerState { Stop, Running }
+        /// <summary>
+        /// 扫描任务
+        /// </summary>
+        private static ScanTask task;
+
+        public static ScannerState State { get { return scannerState; } }
+
+        /// <summary>
+        /// 根据图片后缀获取图片格式
+        /// </summary>
+        /// <param name="suffix"></param>
+        /// <returns></returns>
+        private static ImageFormat GetImageFormat(string suffix)
+        {
+            ImageFormat imageFormat;
+            switch (suffix.ToLower())
+            {
+                case "bmp":
+                    imageFormat = ImageFormat.Bmp;
+                    break;
+                case "jpg":
+                    imageFormat = ImageFormat.Jpeg;
+                    break;
+                case "jpeg":
+                    imageFormat = ImageFormat.Jpeg;
+                    break;
+                default:
+                    imageFormat = ImageFormat.Png;
+                    break;
+            }
+            return imageFormat;
+        }
+        #region 初始化扫描仪
+        /// <summary>
+        /// 初始化扫描仪
+        /// </summary>
+        public static void SetupTwain()
+        {
+            var appId = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetExecutingAssembly());
+            _twain = new TwainSession(appId);
+            _twain.StateChanged += (s, e) =>
+            {
+                if (preStatus >= 4 && _twain.State < 4)
+                {
+                    ChangeScannerState(ScannerState.Stop);
+                }
+                preStatus = _twain.State;
+            };
+            _twain.TransferError += (s, e) =>
+            {
+                // 扫描错误
+                //Services.Agent.ScanTaskError();
+                task.OnScannerError?.Invoke();
+                //Log.WriteLine("Got xfer error on thread " + Thread.CurrentThread.ManagedThreadId);
+            };
+            _twain.DataTransferred += (s, e) =>
+            {
+                // 文件扫描完成
+                //scannerState = ScannerState.Running;
+                ChangeScannerState(ScannerState.Running);
+                //Log.WriteLine("Transferred data event on thread " + Thread.CurrentThread.ManagedThreadId);
+
+                // example on getting ext image info
+                var infos = e.GetExtImageInfo(ExtendedImageInfo.Camera).Where(it => it.ReturnCode == ReturnCode.Success);
+                foreach (var it in infos)
+                {
+                    var values = it.ReadValues();
+                    //Log.WriteLine(string.Format("{0} = {1}", it.InfoID, values.FirstOrDefault()));
+                    break;
+                }
+
+                string name = string.Format("{0}_{1}.{2}", DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"), string.Format("{0:d7}", ++imgIndex), task.ImageSuffix);
+                string fileName = Path.Combine(task.Path, name);
+                if (e.NativeData != IntPtr.Zero)
+                {
+                    // 扫描流
+                    using (var stream = e.GetNativeImageStream())
+                    {
+                        if (stream != null)
+                        {
+                            using (Bitmap bmp = new Bitmap(stream))
+                            {
+                                bmp?.Save(fileName, GetImageFormat(task.ImageSuffix));
+                            }
+                        }
+                    }
+                }
+                else if (!string.IsNullOrEmpty(e.FileDataPath))
+                {
+                    using (Bitmap bmp = new Bitmap(e.FileDataPath))
+                    {
+                        bmp?.Save(fileName, GetImageFormat(task.ImageSuffix));
+                    }
+                    //var bmp = new Bitmap(e.FileDataPath);
+                    //;
+
+                    // 已存为文件
+                    //File.Copy(e.FileDataPath, fileName);
+                }
+
+                //Services.Agent.SendImage(fileName);
+                Task.Run(() =>
+                {
+                    //await Task.Delay(100);
+                    task.OnDeleScanFileCompleted?.Invoke(fileName, imgIndex);
+                });
+
+
+                if (_twain.State == 4)
+                {
+                    //scannerState = ScannerState.Stop;
+                    ChangeScannerState(ScannerState.Stop);
+                }
+            };
+            _twain.SourceDisabled += (s, e) =>
+            {
+                // 扫描任务完成
+
+                ChangeScannerState(ScannerState.Stop);
+
+                task.OnScanTaskCompleted?.Invoke();
+            };
+            _twain.TransferReady += (s, e) =>
+            {
+                // 扫描仪可用
+                //Log.WriteLine("Transferr ready event on thread " + Thread.CurrentThread.ManagedThreadId);
+
+            };
+
+            // either set sync context and don't worry about threads during events,
+            // or don't and use control.invoke during the events yourself
+            _twain.SynchronizationContext = SynchronizationContext.Current;
+            if (_twain.State < 3)
+            {
+                try
+                {
+
+                    // use this for internal msg loop
+                    _twain.Open();
+
+                }
+                catch
+                {
+                    twainDSMInstalled = false;
+                }
+            }
+        }
+
+        private static void Twain_DeviceEvent(object sender, DeviceEventArgs e)
+        {
+            throw new NotImplementedException();
+        }
+        #endregion
+
+        /// <summary>
+        /// 开始扫描
+        /// </summary>
+        /// <param name="scanTask"></param>
+        public static void Start(ScanTask scanTask)
+        {
+
+            void SendResult(bool status, string msg)
+            {
+                scanTask.OnScanBegin?.Invoke(status, msg);
+                ChangeScannerState(status ? ScannerState.Running : ScannerState.Stop);
+
+            }
+            task = scanTask;
+            if (!twainDSMInstalled)
+            {
+                SendResult(false, Const.ERR_NO_ANY_TWAIN_DRIVER);
+                Log.WriteLine("未加载扫描仪驱动!");
+                return;
+            }
+            else if (string.IsNullOrEmpty(task.Path))
+            {
+                SendResult(false, Const.ERR_NO_IMAGE_PATH);
+                return;
+            }
+            else
+            {
+                if (!Directory.Exists(scanTask.Path))
+                {
+                    Directory.CreateDirectory(scanTask.Path);
+                }
+                if (_twain.State < 3)
+                {
+                    if (_twain.Open() != ReturnCode.Success)
+                    {
+                        SendResult(false, Const.ERR_SCANNER_NOT_AVALIABLE);
+                        return;
+                    }
+                }
+                if (_twain.CurrentSource == null || !_twain.CurrentSource.Name.Equals(task.Scanner))
+                {
+                    if (_twain.OpenSource(task.Scanner) != ReturnCode.Success)
+                    {
+                        SendResult(false, Const.ERR_SCANNER_NOT_AVALIABLE);
+                        return;
+                    }
+                }
+
+                if (_twain.State != 4)
+                {
+                    SendResult(false, Const.ERR_SCANNER_NOT_AVALIABLE);
+                    return;
+                }
+                else
+                {
+                    //单双面(强制双面)
+                    //var duplexEnabled = BoolType.True;
+                    var ds = _twain.CurrentSource;
+                    //if (!ds.IsOpen)
+                    //{
+                    //    ds.Open();
+                    //}
+                    var dsc = ds.Capabilities;
+                    DelegateChangeScannerName?.Invoke(scanTask.Scanner);
+                    dsc.CapDuplexEnabled.SetValue(BoolType.True);
+
+
+                    //var pixelType = PixelType.RGB;
+                    if (task.Colorful)
+                    {
+                        //图片类型(强制彩色)
+                        dsc.ICapPixelType.SetValue(PixelType.RGB);
+                    }
+                    else
+                    {
+                        // 扫描灰度图
+                        dsc.ICapPixelType.SetValue(PixelType.Gray);
+                    }
+
+
+                    //旋转(强制不旋转)
+                    //var rotation = 0;
+                    dsc.ICapRotation.SetValue(0);
+
+                    //分辨率
+                    dsc.ICapXResolution.SetValue(task.ImageDpi);
+                    dsc.ICapYResolution.SetValue(task.ImageDpi);
+
+                    //使用Twain源的进度指示器? (当ShowUI == FALSE才有效)
+                    dsc.CapIndicators.SetValue(BoolType.False);
+                    // 自动尺寸
+                    dsc.ICapAutoSize.SetValue(AutoSize.Auto);
+                    // 自动旋转
+                    dsc.ICapAutomaticRotate.SetValue(BoolType.False);
+
+                    //dsc.CapDuplexEnabled.SetValue(BoolType.True);
+                    ////dsc.ICapImageFileFormat.SetValue(FileFormat.Png);
+                    //dsc.CapAutoScan.SetValue(BoolType.False);
+                    dsc.ICapXferMech.SetValue(XferMech.Native);
+                    ////_twain.CurrentSource.Capabilities.CapXferCount.SetValue(1);
+
+                    ////var pixelType = PixelType.RGB;
+                    //if (task.Colorful)
+                    //{
+                    //    //图片类型(强制彩色)
+                    //    dsc.ICapPixelType.SetValue(PixelType.RGB);
+                    //}
+                    //else
+                    //{
+                    //    // 扫描灰度图
+                    //    dsc.ICapPixelType.SetValue(PixelType.Gray);
+                    //}
+
+
+                    ////旋转(强制不旋转)
+                    ////var rotation = 0;
+                    //dsc.ICapRotation.SetValue(0);
+                    ////_twain.CurrentSource.Capabilities.If
+
+                    ////分辨率
+                    //dsc.ICapXResolution.SetValue(task.ImageDpi);
+                    //dsc.ICapYResolution.SetValue(task.ImageDpi);
+
+                    //dsc.CapIndicators.SetValue(BoolType.False);
+                    //// 自动尺寸
+                    //dsc.ICapAutoSize.SetValue(AutoSize.Auto);
+                    //// 自动旋转
+                    //dsc.ICapAutomaticRotate.SetValue(BoolType.False);
+                    // 连续扫描的张数
+                    if (task.CapXferCount > 0)
+                    {
+                        dsc.CapXferCount.SetValue(task.CapXferCount);
+                    }
+                    else
+                    {
+                        dsc.CapXferCount.SetValue(-1);
+                    }
+
+                    //dsc.ICapImageMerge.SetValue(ImageMerge.None);
+                    imgIndex = 0;
+                    //scannerState = ScannerState.Running;
+                    ChangeScannerState(ScannerState.Running);
+
+                    //使用Twain源的进度指示器? (当ShowUI == FALSE才有效)
+                    ds.Enable(SourceEnableMode.NoUI, false, OwnerFormHandle);
+
+                    //if (_twain.CurrentSource.Capabilities.CapUIControllable.IsSupported)//.SupportedCaps.Contains(CapabilityId.CapUIControllable))
+                    //{
+                    //    // hide scanner ui if possible
+                    //    _twain.CurrentSource.Enable(SourceEnableMode.ShowUI, false, OwnerFormHandle);
+                    //    //if (_twain.CurrentSource.Enable(SourceEnableMode.NoUI, true, OwnerFormHandle) == ReturnCode.Success)
+                    //    //{
+                    //    //    //btnAppendScan.Enabled = false;
+                    //    //}
+                    //}
+                    //else
+                    //{
+                    //    _twain.CurrentSource.Enable(SourceEnableMode.ShowUI, false, OwnerFormHandle);
+                    //    //if (_twain.CurrentSource.Enable(SourceEnableMode.ShowUI, false, OwnerFormHandle) == ReturnCode.Success)
+                    //    //{
+                    //    //    //btnOverWriteScan.Enabled = false;
+                    //    //}
+                    //}
+                    Log.WriteLine(Json.Encode("开始扫描!"));
+                    SendResult(true, "");
+                    return;
+                }
+            }
+
+        }
+
+        /// <summary>
+        /// 获取扫描仪资源
+        /// </summary>
+        /// <returns></returns>
+        public static ScannerList GetScannerList()
+        {
+            ScannerList result = new ScannerList();
+            if (twainDSMInstalled)
+            {
+                List<string> scannerSources = new List<string>();
+                if (_twain == null)
+                {
+                    SetupTwain();
+                }
+                if (_twain != null)
+                {
+                    foreach (var src in _twain)
+                    {
+                        scannerSources.Add(src.Name);
+                    }
+                    result.Resources = scannerSources.ToArray();
+                    if (_twain.CurrentSource != null)
+                    {
+                        result.Selected = _twain.CurrentSource.Name;
+                        DelegateChangeScannerName?.Invoke(result.Selected);
+                    }
+                    else
+                    {
+                        DelegateChangeScannerName?.Invoke("未选择扫描仪");
+                    }
+
+                }
+                DelegateLoadDriver?.Invoke(true);
+            }
+            else
+            {
+                DelegateLoadDriver?.Invoke(false);
+                Log.WriteLine("未能正确加载扫描仪驱动。");
+            }
+            return result;
+        }
+
+        private static void ChangeScannerState(ScannerState state)
+        {
+            scannerState = state;
+            if (state == ScannerState.Running)
+            {
+                DelegateChangeScannerStatus("扫描中", Color.Green);
+            }
+            else if (state == ScannerState.Stop)
+            {
+                DelegateChangeScannerStatus("未扫描", Color.Red);
+            }
+        }
+    }
+}

+ 100 - 0
DaJiaoYan/Services/UploadTask.cs

@@ -0,0 +1,100 @@
+using DaJiaoYan.Models;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Services
+{
+
+    internal class UploadTask
+    {
+        private static Dictionary<string, UploadTaskInfo> _infos = new Dictionary<string, UploadTaskInfo>();
+
+        public delegate void DeleNewUploadTask(string id);
+        public delegate void DeleIncreaseScanTask(string id, int cnt = 1);
+        public delegate void DeleIncreaseUploadTask(string id, int cnt = 1);
+        public delegate UploadTaskInfo DeleGetUploadTask(string id);
+        public delegate void DeleFinishUploadTask(string id);
+        public delegate void DeleFinishScanTask(string id);
+
+        public static DeleNewUploadTask deleNewUploadTask = NewUploadTask;
+        public static DeleIncreaseScanTask deleIncreaseScanTask = IncreaseScanTask;
+        public static DeleIncreaseUploadTask deleIncreaseUploadTask = IncreaseUploadTask;
+        public static DeleGetUploadTask deleGetUploadTask = GetUploadTaskInfo;
+        public static DeleFinishScanTask deleFinishUploadTask = FinishUploadTask;
+        public static DeleFinishScanTask deleFinishScanTask = FinishScanTask;
+
+
+
+        private static void NewUploadTask(string id)
+        {
+            lock (_infos)
+            {
+                if (!_infos.ContainsKey(id))
+                {
+                    _infos.Add(id, new UploadTaskInfo(id));
+                }
+            }
+        }
+
+        private static void IncreaseScanTask(string id, int cnt = 1)
+        {
+            lock (_infos)
+            {
+                if (_infos.ContainsKey(id))
+                {
+
+                    _infos[id].ScanCount += cnt;
+                }
+            }
+        }
+
+        private static void IncreaseUploadTask(string id, int cnt = 1)
+        {
+            lock (_infos)
+            {
+                if (_infos.ContainsKey(id))
+                {
+                    _infos[id].UploadCount += cnt;
+                }
+            }
+        }
+
+        private static UploadTaskInfo GetUploadTaskInfo(string id)
+        {
+            UploadTaskInfo info;
+            lock (_infos)
+            {
+                if (_infos.ContainsKey(id))
+                {
+                    info = _infos[id].Copy();
+                }
+                else
+                {
+                    info = new UploadTaskInfo(id);
+                }
+            }
+            return info;
+        }
+
+        private static void FinishUploadTask(string id)
+        {
+            lock (_infos)
+            {
+                if (_infos.ContainsKey(id))
+                {
+                    _infos[id].UploadFinished = 1;
+                }
+            }
+        }
+
+        private static void FinishScanTask(string id)
+        {
+            lock (_infos)
+            {
+                if (_infos.ContainsKey(id))
+                {
+                    _infos[id].ScanFinished = 1;
+                }
+            }
+        }
+    }
+}

+ 64 - 0
DaJiaoYan/Utils/ControlExts.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Windows.Forms;
+
+namespace DaJiaoYan.Utils
+{
+    internal class ControlExts
+    {
+
+        public static Form MessageBoxShowForm;
+        /// <summary>
+        /// 窗体操作代理
+        /// </summary>
+        /// <param name="form"></param>
+        /// <param name="method"></param>
+        public static void FormInvoke(Form form, Action method)
+        {
+            if (form.InvokeRequired)
+            {
+                try
+                {
+                    form.Invoke((EventHandler)delegate { method(); });
+                }
+                catch { }
+            }
+            else
+            {
+                try
+                {
+                    method();
+                }
+                catch { }
+
+            }
+        }
+
+        /// <summary>
+        /// 信息提示框
+        /// </summary>
+        /// <param name="text"></param>
+        /// <param name="caption"></param>
+        /// <param name="buttons"></param>
+        /// <param name="icon"></param>
+        /// <param name="defaultButton"></param>
+        /// <returns></returns>
+        public static DialogResult ShowMessageBox(string text, string caption = "提示", MessageBoxButtons buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
+        {
+            DialogResult act()
+            {
+                return MessageBox.Show(MessageBoxShowForm, text, caption, buttons, icon, defaultButton);
+            }
+
+            DialogResult result = DialogResult.None;
+            if (MessageBoxShowForm.InvokeRequired)
+            {
+                MessageBoxShowForm.Invoke((EventHandler)delegate { result = act(); });
+            }
+            else
+            {
+                result = act();
+            }
+            return result;
+        }
+    }
+}

+ 300 - 0
DaJiaoYan/Utils/FileExts.cs

@@ -0,0 +1,300 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace DaJiaoYan.Utils
+{
+    internal class FileExts
+    {
+
+
+        /// <summary>
+        /// 读取所有行
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="encoding"></param>
+        /// <returns></returns>
+        public static string[] ReadAllLines(string filename, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string[] txt = { };
+
+            if (File.Exists(filename))
+            {
+                txt = File.ReadAllLines(filename, encoding);
+            }
+
+            return txt;
+        }
+
+        /// <summary>
+        /// 读取所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="encoding"></param>
+        /// <returns></returns>
+        public static string ReadAllText(string filename, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string txt = string.Empty;
+
+            if (File.Exists(filename))
+            {
+                txt = File.ReadAllText(filename, encoding);
+            }
+
+            return txt;
+        }
+
+        /// <summary>
+        /// 写入所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void WriteAllText(string filename, string txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+
+            File.WriteAllText(filename, txt, encoding);
+
+        }
+
+        /// <summary>
+        /// 写入二进制值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="bytes"></param>
+        public static void WriteBytes(string filename, byte[] bytes)
+        {
+            if (bytes != null)
+            {
+                CheckDirectory(Path.GetDirectoryName(filename), true);
+
+                File.WriteAllBytes(filename, bytes);
+
+            }
+        }
+
+        /// <summary>
+        /// 读取所有二进制
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static byte[] ReadBytes(string filename)
+        {
+            byte[] txt = default;
+            if (File.Exists(filename))
+            {
+                txt = File.ReadAllBytes(filename);
+            }
+            return txt;
+        }
+
+        /// <summary>
+        /// 写入所有行
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void WriteAllLines(string filename, string[] txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            File.WriteAllLines(filename, txt, encoding);
+        }
+
+        /// <summary>
+        /// 添加所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void AppendAllText(string filename, string txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            File.AppendAllText(filename, txt, encoding);
+        }
+
+        /// <summary>
+        /// 检查目录是否存在,如果不存在,将根据条件创建或不创建
+        /// </summary>
+        /// <param name="p"></param>
+        /// <param name="create">如果目录不存在,是否创建</param>
+        /// <returns></returns>
+        public static bool CheckDirectory(string p, bool create)
+        {
+
+            bool res = false;
+            if (!string.IsNullOrEmpty(p))
+            {
+                res = Directory.Exists(p);
+
+                if (!res && create)
+                {
+                    Directory.CreateDirectory(p);
+                    res = true;
+                }
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 在文件管理器中打开文件夹并选择文件
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="failCallback"></param>
+        public static bool OpenFileInExplore(string filename, Action failCallback)
+        {
+            bool res = false;
+            try
+            {
+                //System.Diagnostics.Process.Start("Explorer.exe", $"/select, {filename}");
+                ExplorerFile(filename, failCallback);
+                res = true;
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+
+
+        /// <summary>
+        /// 打开路径并定位文件...对于@"h:\Bleacher Report - Hardaway with the safe call ??.mp4"这样的,explorer.exe /select,d:xxx不认,用API整它
+        /// </summary>
+        /// <param name="filePath">文件绝对路径</param>
+        [DllImport("shell32.dll", ExactSpelling = true)]
+        private static extern void ILFree(IntPtr pidlList);
+
+        [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+        private static extern IntPtr ILCreateFromPathW(string pszPath);
+
+        [DllImport("shell32.dll", ExactSpelling = true)]
+        private static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
+
+        private static void ExplorerFile(string filePath, Action failCallback)
+        {
+            if (!File.Exists(filePath) && !Directory.Exists(filePath))
+            {
+                failCallback?.Invoke();
+                return;
+            }
+
+
+            if (Directory.Exists(filePath))
+                Process.Start(@"explorer.exe", "/select,\"" + filePath + "\"");
+            else
+            {
+                IntPtr pidlList = ILCreateFromPathW(filePath);
+                if (pidlList != IntPtr.Zero)
+                {
+                    try
+                    {
+                        Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
+                    }
+                    finally
+                    {
+                        ILFree(pidlList);
+                    }
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// 获取文件夹下的图片,并按顺序排好
+        /// </summary>
+        /// <param name="p"></param>
+        /// <returns></returns>
+        public static List<string> GetImageFiles(string p)
+        {
+            List<string> files = new List<string>(), allFiles = new List<string>();
+            try
+            {
+                allFiles = new List<string>(Directory.GetFiles(p));
+            }
+            catch { }
+
+            files = allFiles.FindAll(x =>
+            {
+                return Variables.Vars.AvalibleImageSuffixes.Exists(y => { return string.Equals(y, System.IO.Path.GetExtension(x), StringComparison.OrdinalIgnoreCase); });
+            });
+            files.Sort((a, b) =>
+            {
+                if (a.Length == b.Length)
+                {
+                    return a.CompareTo(b);
+                }
+                else
+                {
+                    return a.Length.CompareTo(b.Length);
+                }
+            });
+
+            return files;
+        }
+
+        public static long GetHardDiskSpace(string str_HardDiskName)
+        {
+            long totalSize = 0;
+            str_HardDiskName += ":\\";
+            System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
+            foreach (System.IO.DriveInfo drive in drives)
+            {
+                if (string.Equals(drive.Name, str_HardDiskName, StringComparison.OrdinalIgnoreCase))
+                {
+                    totalSize = drive.TotalFreeSpace;
+                }
+            }
+            return totalSize;
+        }
+
+        public static string GetHardDiskFreeSpace(string str_HardDiskFreeSpace, FileSizeUnit targetUnit = FileSizeUnit.MB)
+        {
+            long totalSize = GetHardDiskSpace(str_HardDiskFreeSpace);
+            return Math.Floor(ToFileFormat(totalSize, targetUnit)).ToString() + Enum.GetName(typeof(FileSizeUnit), targetUnit);
+        }
+
+
+        /// <summary>
+        /// 根据指定的文件大小单位,对输入的文件大小(字节表示)进行转换。
+        /// </summary>
+        /// <param name="filesize">文件文件大小,单位为字节。</param>
+        /// <param name="targetUnit">目标单位。</param>
+        /// <returns></returns>
+        private static double ToFileFormat(long filesize, FileSizeUnit targetUnit = FileSizeUnit.MB)
+        {
+            double size;
+            switch (targetUnit)
+            {
+                case FileSizeUnit.KB: size = filesize / 1024.0; break;
+                case FileSizeUnit.MB: size = filesize / 1024.0 / 1024; break;
+                case FileSizeUnit.GB: size = filesize / 1024.0 / 1024 / 1024; break;
+                case FileSizeUnit.TB: size = filesize / 1024.0 / 1024 / 1024 / 1024; break;
+                case FileSizeUnit.PB: size = filesize / 1024.0 / 1024 / 1024 / 1024 / 1024; break;
+                default: size = filesize; break;
+            }
+            return size;
+        }
+
+        /// <summary>
+        /// 文件大小单位,包括从B至PB共六个单位。
+        /// </summary>
+        public enum FileSizeUnit
+        {
+            B,
+            KB,
+            MB,
+            GB,
+            TB,
+            PB
+        }
+    }
+}

+ 353 - 0
DaJiaoYan/Utils/Functions.cs

@@ -0,0 +1,353 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq.Expressions;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+
+namespace DaJiaoYan.Utils
+{
+    internal class Functions
+    {
+        private static readonly Queue<string> Pool = new Queue<string>();
+        private static readonly object _UUIDLocker = new object();
+
+
+
+
+        /// <summary>
+        /// 字符串生成MD5
+        /// </summary>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public static string GenMd5(string s)
+        {
+            //就是比string往后一直加要好的优化容器
+            StringBuilder sb = new StringBuilder();
+            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
+            {
+                //将输入字符串转换为字节数组并计算哈希。
+                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(s));
+
+                //X为     十六进制 X都是大写 x都为小写
+                //2为 每次都是两位数
+                //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。 
+                //遍历哈希数据的每个字节
+                //并将每个字符串格式化为十六进制字符串。
+                int length = data.Length;
+                for (int i = 0; i < length; i++)
+                {
+                    sb.Append(data[i].ToString("X2"));
+                }
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 获取文件的MD5码
+        /// </summary>
+        /// <param name="fileName">传入的文件名(含路径及后缀名)</param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public static string GenMd5HashFromFile(string fileName)
+        {
+            try
+            {
+                StringBuilder sb = new StringBuilder();
+
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                using (FileStream file = new FileStream(fileName, FileMode.Open))
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    //file.Close();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("X2"));
+                    }
+                }
+                return sb.ToString();
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message);
+            }
+        }
+
+
+        private static void UUIDInit()
+        {
+            lock (_UUIDLocker)
+            {
+                for (int i = 0; i < 10000; i++)
+                {
+                    Pool.Enqueue(Guid.NewGuid().ToString("N"));
+                    Thread.Sleep(1);
+                }
+            }
+        }
+
+        /// <summary>
+        /// UUID生成程序
+        /// </summary>
+        public static void UUIDBackgroundFunc()
+        {
+            new Thread(() =>
+            {
+                while (true)
+                {
+                    if (Pool.Count < 5000)
+                    {
+                        UUIDInit();
+                    }
+                    Thread.Sleep(1000);
+                }
+            })
+            {
+                IsBackground = true
+            }.Start();
+        }
+
+
+
+        /// <summary>
+        /// 生成UUID
+        /// </summary>
+        /// <returns></returns>
+        public static string GenUUID()
+        {
+            //if (Pool.Count <= 100)
+            //{
+            //    UUIDInit();
+            //}
+
+
+
+            return Pool.Dequeue();
+        }
+
+        /// <summary>
+        /// 深复制对象
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentException"></exception>
+        public static T Clone<T>(T source)
+        {
+            if (!typeof(T).IsSerializable)
+            {
+                throw new ArgumentException("The type must be serializable.", "source");
+            }
+
+            // Don't serialize a null object, simply return the default for that object
+            if (source == null)
+            {
+                return default;
+            }
+
+            IFormatter formatter = new BinaryFormatter();
+            //Stream stream = new MemoryStream();
+            using (Stream stream = new MemoryStream())
+            {
+                formatter.Serialize(stream, source);
+                stream.Seek(0, SeekOrigin.Begin);
+                return (T)formatter.Deserialize(stream);
+            }
+        }
+
+        /// <summary>
+        /// 返回字符串右侧指定数量的字符串
+        /// </summary>
+        /// <param name="txt"></param>
+        /// <param name="length"></param>
+        /// <returns></returns>
+        public static string RString(string txt, int length)
+        {
+            string res = string.Empty;
+            if (!string.IsNullOrEmpty(txt))
+            {
+                res = length > txt.Length ? txt : txt.Substring(txt.Length - length);
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 复制对象
+        /// </summary>
+        /// <typeparam name="TIn"></typeparam>
+        /// <typeparam name="TOut"></typeparam>
+        public static class CopyTo<TIn, TOut>
+        {
+
+            private static readonly Func<TIn, TOut> cache = GetFunc();
+            private static Func<TIn, TOut> GetFunc()
+            {
+                ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
+                List<MemberBinding> memberBindingList = new List<MemberBinding>();
+
+                foreach (var item in typeof(TOut).GetProperties())
+                {
+                    if (!item.CanWrite)
+                        continue;
+
+                    MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
+                    MemberBinding memberBinding = Expression.Bind(item, property);
+                    memberBindingList.Add(memberBinding);
+                }
+
+                MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
+                Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
+
+                return lambda.Compile();
+            }
+
+            public static TOut Trans(TIn tIn)
+            {
+                return cache(tIn);
+            }
+
+        }
+
+
+        /// <summary>  
+        /// 获取时间戳  13位
+        /// </summary>  
+        /// <returns></returns>  
+        public static long GetTimeStamp()
+        {
+            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
+            return Convert.ToInt64(ts.TotalSeconds * 1000);
+        }
+
+        /// <summary>
+        /// 将时间戳转换为日期类型,并格式化
+        /// </summary>
+        /// <param name="longDateTime"></param>
+        /// <returns></returns>
+        public static string LongDateTimeToDateTimeString(string longDateTime)
+        {
+            //用来格式化long类型时间的,声明的变量
+            long unixDate;
+            DateTime start;
+            DateTime date;
+            //ENd
+
+            unixDate = long.Parse(longDateTime);
+            start = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+            date = start.AddMilliseconds(unixDate).ToLocalTime();
+
+            return date.ToString("yyyy-MM-dd HH:mm:ss");
+
+        }
+
+        /// <summary>
+        /// 获取制定路径文件的md5值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static string FileMd5(string filename)
+        {
+            string res = "";
+            try
+            {
+                using (FileStream file = new FileStream(filename, FileMode.Open))
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("x2"));
+                    }
+                    res = sb.ToString();
+                }
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+
+
+        /// <summary>
+        /// 上传的原始图片地址
+        /// </summary>
+        /// <param name="testId"></param>
+        /// <param name="campusId"></param>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static string GenUploadOriginImageName(int testId, int campusId, string filename, string batchId = null)
+        {
+            string res;
+            if (string.IsNullOrEmpty(batchId))
+            {
+                res = $"origin/{testId}/{campusId}/{Path.GetFileName(filename)}";
+            }
+            else
+            {
+                res = $"origin/{testId}/{campusId}/{batchId}/{Path.GetFileName(filename)}";
+            }
+            return res;
+        }
+
+        public static long GetHardDiskSpace(string str_HardDiskName)
+        {
+            long totalSize = 0;
+            str_HardDiskName += ":\\";
+            System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
+            foreach (System.IO.DriveInfo drive in drives)
+            {
+                if (string.Equals(drive.Name, str_HardDiskName, StringComparison.OrdinalIgnoreCase))
+                {
+                    totalSize = drive.TotalFreeSpace;
+                }
+            }
+            return totalSize;
+        }
+
+        public static string GetHardDiskFreeSpace(string str_HardDiskFreeSpace, FileSizeUnit targetUnit = FileSizeUnit.MB)
+        {
+            long totalSize = GetHardDiskSpace(str_HardDiskFreeSpace);
+            return Math.Floor(ToFileFormat(totalSize, targetUnit)).ToString() + Enum.GetName(typeof(FileSizeUnit), targetUnit);
+        }
+
+
+        /// <summary>
+        /// 根据指定的文件大小单位,对输入的文件大小(字节表示)进行转换。
+        /// </summary>
+        /// <param name="filesize">文件文件大小,单位为字节。</param>
+        /// <param name="targetUnit">目标单位。</param>
+        /// <returns></returns>
+        private static double ToFileFormat(long filesize, FileSizeUnit targetUnit = FileSizeUnit.MB)
+        {
+            double size;
+            switch (targetUnit)
+            {
+                case FileSizeUnit.KB: size = filesize / 1024.0; break;
+                case FileSizeUnit.MB: size = filesize / 1024.0 / 1024; break;
+                case FileSizeUnit.GB: size = filesize / 1024.0 / 1024 / 1024; break;
+                case FileSizeUnit.TB: size = filesize / 1024.0 / 1024 / 1024 / 1024; break;
+                case FileSizeUnit.PB: size = filesize / 1024.0 / 1024 / 1024 / 1024 / 1024; break;
+                default: size = filesize; break;
+            }
+            return size;
+        }
+
+        /// <summary>
+        /// 文件大小单位,包括从B至PB共六个单位。
+        /// </summary>
+        public enum FileSizeUnit
+        {
+            B,
+            KB,
+            MB,
+            GB,
+            TB,
+            PB
+        }
+    }
+}

+ 402 - 0
DaJiaoYan/Utils/Http.cs

@@ -0,0 +1,402 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace DaJiaoYan.Utils
+{
+    public static class Http
+    {
+        public struct Response
+        {
+            public HttpStatusCode code;
+            public HttpContent message;
+            public string url;
+        }
+
+        public struct Request
+        {
+            /// <summary>
+            /// 请求的网址
+            /// </summary>
+            public string uri;
+            /// <summary>
+            /// 头信息
+            /// </summary>
+            public Dictionary<string, object> headers;
+            /// <summary>
+            /// 请求的数据
+            /// </summary>
+            public Dictionary<string, object> data;
+            /// <summary>
+            /// 上传的文件
+            /// </summary>
+            public Dictionary<string, string> files;
+            /// <summary>
+            /// 上传的文件二进制流
+            /// </summary>
+            public Dictionary<string, byte[]> fileStream;
+            /// <summary>
+            /// 请求类型
+            /// </summary>
+            public RequestContentType contentType;
+            /// <summary>
+            /// 请求方式
+            /// </summary>
+            public Method method;
+
+            public void Init()
+            {
+                uri = "";
+                headers = new Dictionary<string, object>();
+                data = new Dictionary<string, object>();
+                files = new Dictionary<string, string>();
+                fileStream = new Dictionary<string, byte[]>();
+                contentType = RequestContentType.Default;
+                method = Method.Get;
+            }
+
+            public Request(string uri, Method method)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = null;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+
+            public Request(string uri, Method method, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, RequestContentType contentType, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                this.contentType = contentType;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files, Dictionary<string, byte[]> fileStream)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                this.fileStream = fileStream;
+                contentType = RequestContentType.Default;
+            }
+
+
+        }
+
+        /// <summary>
+        /// 请求内容类型
+        /// </summary>
+        public enum RequestContentType
+        {
+            Default, Json
+        }
+
+        /// <summary>
+        /// 请求方法
+        /// </summary>
+        public enum Method
+        {
+            Get, Post, Put, Delete, Patch
+        }
+
+        /// <summary>
+        /// 初始化请求头
+        /// </summary>
+        /// <param name="httpClient"></param>
+        /// <param name="headers"></param>
+        private static void InitClientHeaders(ref HttpClient httpClient, Dictionary<string, object> headers)
+        {
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    if (!string.IsNullOrEmpty(header.Key) && !string.IsNullOrEmpty(header.Value.ToString()))
+                    {
+                        httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToString());
+                    }
+
+                }
+            }
+        }
+
+        /// <summary>
+        /// 获取
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static async Task<HttpResponseMessage> Fetch(Request request)
+        {
+            HttpResponseMessage response;
+            try
+            {
+                switch (request.method)
+                {
+                    case Method.Post:
+                        response = await Post(request);
+                        break;
+                    case Method.Put:
+                        response = await Put(request);
+                        break;
+                    case Method.Patch:
+                        response = await Patch(request);
+                        break;
+                    case Method.Delete:
+                        response = await Delete(request);
+                        break;
+                    default:
+                        response = await Get(request);
+                        break;
+                }
+            }
+            catch (Exception ex)
+            {
+                Log.Error(ex);
+                response = new HttpResponseMessage();
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Get(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+                InitClientHeaders(ref client, request.headers);
+                if (request.data != null)
+                {
+                    url = string.Concat(url, "?");
+                    foreach (var item in request.data)
+                    {
+                        url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                    }
+                    url = url.Substring(0, url.Length - 1);
+                }
+                try
+                {
+                    response = await client.GetAsync(url);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+            return response;
+        }
+
+        private static HttpContent InitNonGetContent(Request request)
+        {
+            HttpContent content;
+
+            if (request.contentType == RequestContentType.Json)
+            {
+                // 发送json 数据
+                HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(request.data), Encoding.UTF8);
+                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+                content = httpContent;
+            }
+            else
+            {
+                // 普通的键值对提交
+                MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
+                if (request.files != null)
+                {
+                    foreach (var file in request.files)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes(file.Value)), file.Key, Path.GetFileName(file.Value));
+                    }
+                }
+                if (request.fileStream != null)
+                {
+                    foreach (var file in request.fileStream)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(file.Value), file.Key, file.Key);
+                    }
+                }
+
+                if (request.data != null)
+                {
+                    foreach (var d in request.data)
+                    {
+                        multipartFormDataContent.Add(new StringContent(d.Value.ToString(), Encoding.UTF8), d.Key);
+                    }
+                }
+                content = multipartFormDataContent;
+            }
+            return content;
+        }
+
+        public static async Task<HttpResponseMessage> Post(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PostAsync(request.uri, content);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Patch(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+                HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), request.uri);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    httpRequestMessage.Content = content;
+                    response = await client.SendAsync(httpRequestMessage);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+                httpRequestMessage.Dispose();
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Put(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PutAsync(request.uri, content);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Delete(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    if (request.data != null)
+                    {
+                        url = string.Concat(url, "?");
+                        foreach (var item in request.data)
+                        {
+                            url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                        }
+                    }
+                    response = await client.DeleteAsync(request.uri);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+    }
+}

+ 108 - 0
DaJiaoYan/Utils/ImageUtils.cs

@@ -0,0 +1,108 @@
+using OpenCvSharp;
+using OpenCvSharp.Extensions;
+using System;
+using System.Drawing;
+using System.IO;
+
+namespace DaJiaoYan.Utils
+{
+    internal class ImageUtils
+    {
+        internal static Mat Read(string filename, ImreadModes mode = ImreadModes.AnyColor)
+        {
+            var res = Cv2.ImRead(filename, mode);
+            if (res == null || res.Width < 1 || res.Height < 1)
+            {
+                // 判断图片异常
+                try
+                {
+                    Bitmap bitmap = new Bitmap(filename);
+                    res = ToMat(bitmap);
+                }
+                catch { }
+
+            }
+            return res;
+        }
+        /// <summary>
+        /// image 转 mat
+        /// </summary>
+        /// <param name="image"></param>
+        /// <returns></returns>
+        internal static Mat ToMat(Image image)
+        {
+            Mat mat = null;
+            if (image != null)
+            {
+                Bitmap bmp = new Bitmap(image);
+                mat = BitmapConverter.ToMat(bmp);
+                bmp.Dispose();
+            }
+            return mat;
+        }
+
+        /// <summary>
+        /// 从 stream 转 mat
+        /// </summary>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        internal static Mat ToMat(Stream stream)
+        {
+            Mat mat = null;
+            if (stream != null && stream.Length > 0)
+            {
+                mat = Mat.FromStream(stream, ImreadModes.AnyColor);
+            }
+            return mat;
+        }
+
+
+        /// <summary>
+        /// 转Base64
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        internal static string ToBase64(string filename)
+        {
+            string res = "";
+            try
+            {
+                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
+                {
+                    byte[] buffer = new byte[fs.Length];
+                    fs.Read(buffer, 0, (int)fs.Length);
+                    res = Convert.ToBase64String(buffer);
+                }
+            }
+            catch { }
+            return res;
+        }
+
+
+        internal static void Red2OtherColor(ref Mat img, Scalar scalar, int minThreshold = 10, int maxThreshold = 40)
+        {
+            Cv2.CvtColor(img, img, ColorConversionCodes.BGR2HSV);
+
+            Mat mask0 = new Mat(), mask1 = new Mat(), mask;
+
+            Cv2.InRange(img,
+                new Mat(1, 3, MatType.CV_32S, new Scalar(0, maxThreshold, maxThreshold)),
+                new Mat(1, 3, MatType.CV_32S, new Scalar(minThreshold, 255, 255)),
+                mask0);
+
+            Cv2.InRange(img,
+                new Mat(1, 3, MatType.CV_32S, new Scalar(150, maxThreshold, maxThreshold)),
+                new Mat(1, 3, MatType.CV_32S, new Scalar(180, 255, 255)),
+                mask1);
+
+            mask = mask0 + mask1;
+            Cv2.CvtColor(img, img, ColorConversionCodes.HSV2BGR);
+            img.SetTo(scalar, mask);
+            mask0?.Dispose();
+            mask1?.Dispose();
+            mask?.Dispose();
+        }
+
+    }
+
+}

+ 67 - 0
DaJiaoYan/Utils/Json.cs

@@ -0,0 +1,67 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.IO;
+using System.Text;
+
+namespace DaJiaoYan.Utils
+{
+    internal class Json
+    {
+        private static readonly JavaScriptDateTimeConverter JavaScriptDateTimeConverter = new JavaScriptDateTimeConverter();
+
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public static T Decode<T>(Stream stream)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(JavaScriptDateTimeConverter);//指定转化日期的格式
+            using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                return serializer.Deserialize<T>(reader);
+            }
+        }
+
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="txt"></param>
+        /// <returns></returns>
+        public static T Decode<T>(string txt)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(JavaScriptDateTimeConverter);//指定转化日期的格式
+            using (StringReader sr = new StringReader(txt))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                T res = default;
+                try
+                {
+                    res = serializer.Deserialize<T>(reader);
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);
+                }
+                return res;
+
+            }
+        }
+
+        /// <summary>
+        /// json格式编码
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static string Encode(object obj)
+        {
+            return JsonConvert.SerializeObject(obj, JavaScriptDateTimeConverter);
+        }
+    }
+}

+ 100 - 0
DaJiaoYan/Utils/Log.cs

@@ -0,0 +1,100 @@
+using DaJiaoYan.Variables;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace DaJiaoYan.Utils
+{
+    internal class Log
+    {
+        private readonly static object _lock = new object();
+
+        /// <summary>
+        /// 当前全部日志
+        /// </summary>
+        private static string LogAll = "";
+        /// <summary>
+        /// 临时日志
+        /// </summary>
+        private static string LogTmp = "";
+
+
+        /// <summary>
+        /// 日志处理的委托
+        /// </summary>
+        /// <param name="s"></param>
+        public delegate void SetLog(string s);
+        /// <summary>
+        /// 具体委托的实例
+        /// </summary>
+        //public static SetLog DoSetLog;
+
+        public static string Now()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+        }
+
+        private static string Today()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd");
+        }
+
+        /// <summary>
+        /// 记录错误
+        /// </summary>
+        /// <param name="exception"></param>
+        public static void Error(Exception exception)
+        {
+            WriteLine(exception.StackTrace, "error", force: true);
+        }
+
+        /// <summary>
+        /// 记录信息
+        /// </summary>
+        /// <param name="s"></param>
+        /// <param name="t"></param>
+        /// <param name="filename"></param>
+        public static void WriteLine(string s, string t = "log", string filename = null, bool force = false)
+        {
+            if (s != null)
+            {
+                string info = $"{Now()} [{t}] {s}\r\n";
+                Console.Write(info);
+                lock (_lock)
+                {
+                    LogAll = string.Concat(LogAll, info);
+                    LogTmp = string.Concat(LogTmp, info);
+                }
+                //#if DEBUG
+                //                DoSetLog?.Invoke(Variables.Variables.LogTmp);
+                //#endif
+                Save(force);
+            }
+        }
+
+        /// <summary>
+        /// 保存信息
+        /// </summary>
+        /// <param name="force"></param>
+        public static void Save(bool force = false, string filename = null)
+        {
+            if (force || LogTmp.Length > 30000)
+            {
+                if (string.IsNullOrEmpty(filename))
+                {
+                    filename = Today();
+                }
+                string txt;
+                lock (_lock)
+                {
+                    txt = LogTmp;
+                    LogTmp = "";
+                }
+                Task.Run(() =>
+                {
+                    FileExts.AppendAllText(Path.Combine(Const.LOG_DIR, $"{filename}.txt"), txt);
+                });
+            }
+        }
+    }
+}

+ 93 - 0
DaJiaoYan/Utils/ProcessExts.cs

@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace DaJiaoYan.Utils
+{
+    internal class ProcessExts
+    {
+        public static Process RunningInstance(ref Process target)
+        {
+            Process result = null;
+            Process[] processes = Process.GetProcessesByName(target.ProcessName);
+            //遍历与当前进程名称相同的进程列表  
+            foreach (Process process in processes)
+            {
+                //如果实例已经存在则忽略当前进程  
+                if (process.Id != target.Id)
+                {
+                    //保证要打开的进程同已经存在的进程来自同一文件路径
+                    if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\").Equals(target.MainModule.FileName))
+                    {
+                        //返回已经存在的进程
+                        result = process;
+                        break;
+                    }
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 获取运行的
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static Process RunningInstance(string filename)
+        {
+            Process result = null;
+            filename = filename.Replace("/", "\\");
+            Process[] processes = Process.GetProcesses();
+            foreach (Process process in processes)
+            {
+                try
+                {
+                    if (process != null && process.MainModule != null && process.MainModule.FileName.Replace("/", "\\").Equals(filename))
+                    {
+                        result = process;
+                        break;
+                    }
+                }
+                catch
+                {
+                }
+
+            }
+            return result;
+        }
+
+        public static List<Process> GetSubProcess(string parentName)
+        {
+            List<Process> result = new List<Process>();
+            Process[] processes = Process.GetProcesses();
+            int i = 0;
+            foreach (Process process in processes)
+            {
+                try
+                {
+                    Utils.Log.WriteLine($"{++i}:  {process.MainModule.ModuleName} / {process.MainModule.FileName}");
+                    //if (process != null && process.MainModule != null && process.MainModule.ModuleName.FileName.Replace("/", "\\").Equals(filename))
+                    //{
+                    //    result.Add(process);
+                    //    break;
+                    //}
+                }
+                catch
+                {
+
+                }
+
+            }
+            return result;
+        }
+
+
+        /// <summary>
+        /// 启动升级程序
+        /// </summary>
+        public static void RunUpdate()
+        {
+            Process.Start(Variables.Const.UPDATE_EXEC_FILENAME);
+        }
+    }
+}

+ 31 - 0
DaJiaoYan/Utils/Reg.cs

@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace DaJiaoYan.Utils
+{
+    public static class Reg
+    {
+        public const string NUMBER = "\\d";
+
+        public enum Type
+        {
+            Number
+        }
+
+        private static readonly Dictionary<Type, string> map = new Dictionary<Type, string>()
+        {
+            { Type.Number, NUMBER },
+        };
+
+        public static bool Match(string s, Type t, RegexOptions options = RegexOptions.IgnoreCase)
+        {
+            var m = Regex.Match(s, map[t], options);
+            return m.Success;
+        }
+        public static bool Match(string s, string t, RegexOptions options = RegexOptions.IgnoreCase)
+        {
+            var m = Regex.Match(s, t, options);
+            return m.Success;
+        }
+    }
+}

+ 68 - 0
DaJiaoYan/Variables/Const.cs

@@ -0,0 +1,68 @@
+using System.IO;
+
+namespace DaJiaoYan.Variables
+{
+    internal class Const
+    {
+        public const string APP_VERSION = "1.0.1";
+        public const string APP_NAME = "com.marking.pc";
+        public const string PLATFORM = "windows";
+        public const double APP_BUILD_VERSION = 20251030.01;
+        public static readonly string APP_DEBUG_BUILD_VERSION = $"版本:{APP_BUILD_VERSION}";
+        public const string APP_FORM_NAME = "大教研";
+        public const string APP_SHOW_NAME = "大教研扫描服务";
+
+#if DEBUG
+        public const string HTTP_HOST = "http://local.api.dajiaoyan.com/";
+#else
+        public const string HTTP_HOST = "https://api.dajiaoyan.com/";
+#endif
+        /// <summary>
+        /// 静态文件服务器基础地址
+        /// </summary>
+        public const string HTTP_STATIC_HOST = "https://static.yuansiwei.com/";
+        public const string OSS_PATH = "app/dajiaoyan/ocr";
+        public const string APP_BASE_FILENAME = "dajiaoyan.zip";
+
+        
+
+
+        /// <summary>
+        /// 在线更新文件
+        /// </summary>
+        public static readonly string HTTP_UPDATE_FILE = $"{HTTP_STATIC_HOST}{OSS_PATH}/{APP_BASE_FILENAME}";
+
+        /// <summary>
+        /// 应用启动地址
+        /// </summary>
+        public static readonly string APP_PATH = Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath);
+        /// <summary>
+        /// 日志目录
+        /// </summary>
+        public static readonly string LOG_DIR = Path.Combine(APP_PATH, "log");
+        /// <summary>
+        /// 临时文件夹
+        /// </summary>
+        public static readonly string TMP_PATH = Path.Combine(APP_PATH, "tmp");
+
+        public static readonly string ICON_FILE = "logo.ico";
+        /// <summary>
+        /// 升级程序路径
+        /// </summary>
+        public static readonly string UPDATE_EXEC_FILENAME = Path.Combine(APP_PATH, "update", "update.exe");
+
+        /// <summary>
+        /// 扫描分辨率
+        /// </summary>
+        public const int SCAN_DPI = 200;
+
+        public const string MSG_CAPTION_WARN = "警告";
+        public const string MSG_CAPTION_ATTENTION = "注意";
+        public const string MSG_CAPTION_ERROR = "错误";
+        public const string MSG_CAPTION_INFO = "消息";
+
+        public const string ERR_NO_ANY_TWAIN_DRIVER = "未安装twain类型的驱动";
+        public const string ERR_NO_IMAGE_PATH = "请选择扫描文件存放目录!";
+        public const string ERR_SCANNER_NOT_AVALIABLE = "当前选择的扫描仪无法使用!";
+    }
+}

+ 88 - 0
DaJiaoYan/Variables/Vars.cs

@@ -0,0 +1,88 @@
+using OpenCvSharp;
+using System.Collections.Generic;
+
+namespace DaJiaoYan.Variables
+{
+    internal class Vars
+    {
+        public delegate void DeleCounterChange(int i);
+        public static DeleCounterChange DelegateCounterChange;
+        private static readonly object CounterLocker = new object();
+
+        /// <summary>
+        /// 任务
+        /// </summary>
+        private static readonly Dictionary<string, bool> Tasks = new Dictionary<string, bool>();
+        private static readonly object TasksLocker = new object();
+
+        private static int _UploadedCounter = 0;
+
+        /// <summary>
+        /// 请求头
+        /// </summary>
+        public static Dictionary<string, object> RequestHeaders = new Dictionary<string, object>
+        {
+            {"AppName", Const.APP_NAME },
+            {"AppVersion", Const.APP_VERSION },
+            {"Platform", Const.PLATFORM },
+            {"Authorization", "" },
+        };
+
+        /// <summary>
+        /// 扫描仪列表
+        /// </summary>
+        public static string[] Scanners = null;
+        /// <summary>
+        /// 当前选择的扫描仪
+        /// </summary>
+        public static string ScannerSelected = null;
+
+        /// <summary>
+        /// 可处理图片类型
+        /// </summary>
+        public static readonly List<string> AvalibleImageSuffixes = new List<string> { ".png", ".jpg", ".jpeg" };
+
+        /// <summary>
+        /// opencv jpeg 保存配置
+        /// </summary>
+        public readonly static ImageEncodingParam IMAGE_SAVE_JPEG_PARAM = new ImageEncodingParam(ImwriteFlags.JpegQuality, 70);
+        /// <summary>
+        /// opencv png 保存配置
+        /// </summary>
+        public readonly static ImageEncodingParam IMAGE_SAVE_PNG_PARAM = new ImageEncodingParam(ImwriteFlags.PngCompression, 5);
+
+        public static void SetTaskStatus(string taskId, bool status)
+        {
+            lock (TasksLocker)
+            {
+                Tasks[taskId] = status;
+            }
+        }
+
+
+        public static void UploadedCounterIncrease(int i = 1)
+        {
+            int n = 0;
+            lock (CounterLocker)
+            {
+                n = _UploadedCounter += i;
+            }
+            DelegateCounterChange?.Invoke(n);
+        }
+
+
+        public static bool AllTaskFinished()
+        {
+            bool res = true;
+            foreach (var item in Tasks.Values)
+            {
+                if (!item)
+                {
+                    res = false;
+                    break;
+                }
+            }
+            return res;
+        }
+    }
+}

BIN
DaJiaoYan/dlls/TWAINDSM.dll


BIN
DaJiaoYan/dlls/TWAINDSM32.msm


BIN
DaJiaoYan/logo.ico


+ 19 - 0
DaJiaoYan/packages.config

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Aliyun.OSS.SDK" version="2.14.1" targetFramework="net472" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="9.0.10" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="13.0.4" targetFramework="net472" />
+  <package id="NTwain" version="3.7.5" targetFramework="net472" />
+  <package id="OpenCvSharp4" version="4.11.0.20250507" targetFramework="net472" />
+  <package id="OpenCvSharp4.Extensions" version="4.11.0.20250507" targetFramework="net472" />
+  <package id="OpenCvSharp4.runtime.win" version="4.11.0.20250507" targetFramework="net472" />
+  <package id="System.Buffers" version="4.6.1" targetFramework="net472" />
+  <package id="System.Drawing.Common" version="9.0.10" targetFramework="net472" />
+  <package id="System.IO.Pipelines" version="9.0.10" targetFramework="net472" />
+  <package id="System.Memory" version="4.6.3" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="9.0.10" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.6.1" targetFramework="net472" />
+</packages>

BIN
DaJiaoYan/resources/logo.ico


+ 6 - 0
ReleaseHelper/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+</configuration>

+ 243 - 0
ReleaseHelper/FormMain.Designer.cs

@@ -0,0 +1,243 @@
+namespace ReleaseHelper
+{
+    partial class FormMain
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.statusStrip1 = new System.Windows.Forms.StatusStrip();
+            this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
+            this.toolStripProgressBarRelease = new System.Windows.Forms.ToolStripProgressBar();
+            this.toolStripStatusLabelStepNow = new System.Windows.Forms.ToolStripStatusLabel();
+            this.button1 = new System.Windows.Forms.Button();
+            this.label10 = new System.Windows.Forms.Label();
+            this.label9 = new System.Windows.Forms.Label();
+            this.label8 = new System.Windows.Forms.Label();
+            this.label7 = new System.Windows.Forms.Label();
+            this.label6 = new System.Windows.Forms.Label();
+            this.label5 = new System.Windows.Forms.Label();
+            this.textBoxLocalFile = new System.Windows.Forms.TextBox();
+            this.textBoxOssPath = new System.Windows.Forms.TextBox();
+            this.textBoxLatestVersion = new System.Windows.Forms.TextBox();
+            this.textBoxNowVersion = new System.Windows.Forms.TextBox();
+            this.textBoxUsername = new System.Windows.Forms.TextBox();
+            this.textBoxPassword = new System.Windows.Forms.TextBox();
+            this.statusStrip1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // statusStrip1
+            // 
+            this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.toolStripStatusLabel1,
+            this.toolStripProgressBarRelease,
+            this.toolStripStatusLabelStepNow});
+            this.statusStrip1.Location = new System.Drawing.Point(0, 222);
+            this.statusStrip1.Name = "statusStrip1";
+            this.statusStrip1.Size = new System.Drawing.Size(772, 22);
+            this.statusStrip1.SizingGrip = false;
+            this.statusStrip1.TabIndex = 4;
+            this.statusStrip1.Text = "statusStrip1";
+            // 
+            // toolStripStatusLabel1
+            // 
+            this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
+            this.toolStripStatusLabel1.Size = new System.Drawing.Size(32, 17);
+            this.toolStripStatusLabel1.Text = "进度";
+            // 
+            // toolStripProgressBarRelease
+            // 
+            this.toolStripProgressBarRelease.Name = "toolStripProgressBarRelease";
+            this.toolStripProgressBarRelease.Size = new System.Drawing.Size(500, 16);
+            this.toolStripProgressBarRelease.Step = 1;
+            // 
+            // toolStripStatusLabelStepNow
+            // 
+            this.toolStripStatusLabelStepNow.Name = "toolStripStatusLabelStepNow";
+            this.toolStripStatusLabelStepNow.Size = new System.Drawing.Size(0, 17);
+            // 
+            // button1
+            // 
+            this.button1.Location = new System.Drawing.Point(118, 189);
+            this.button1.Name = "button1";
+            this.button1.Size = new System.Drawing.Size(75, 23);
+            this.button1.TabIndex = 17;
+            this.button1.Text = "更新上线";
+            this.button1.UseVisualStyleBackColor = true;
+            this.button1.Click += new System.EventHandler(this.button1_Click);
+            // 
+            // label10
+            // 
+            this.label10.AutoSize = true;
+            this.label10.Location = new System.Drawing.Point(57, 16);
+            this.label10.Name = "label10";
+            this.label10.Size = new System.Drawing.Size(53, 12);
+            this.label10.TabIndex = 11;
+            this.label10.Text = "本地文件";
+            // 
+            // label9
+            // 
+            this.label9.AutoSize = true;
+            this.label9.Location = new System.Drawing.Point(57, 43);
+            this.label9.Name = "label9";
+            this.label9.Size = new System.Drawing.Size(53, 12);
+            this.label9.TabIndex = 12;
+            this.label9.Text = "发布路径";
+            // 
+            // label8
+            // 
+            this.label8.AutoSize = true;
+            this.label8.Location = new System.Drawing.Point(9, 70);
+            this.label8.Name = "label8";
+            this.label8.Size = new System.Drawing.Size(101, 12);
+            this.label8.TabIndex = 13;
+            this.label8.Text = "最近一次更新版本";
+            // 
+            // label7
+            // 
+            this.label7.AutoSize = true;
+            this.label7.Location = new System.Drawing.Point(57, 97);
+            this.label7.Name = "label7";
+            this.label7.Size = new System.Drawing.Size(53, 12);
+            this.label7.TabIndex = 14;
+            this.label7.Text = "当前版本";
+            // 
+            // label6
+            // 
+            this.label6.AutoSize = true;
+            this.label6.Location = new System.Drawing.Point(45, 124);
+            this.label6.Name = "label6";
+            this.label6.Size = new System.Drawing.Size(65, 12);
+            this.label6.TabIndex = 15;
+            this.label6.Text = "更新管理员";
+            // 
+            // label5
+            // 
+            this.label5.AutoSize = true;
+            this.label5.Location = new System.Drawing.Point(21, 151);
+            this.label5.Name = "label5";
+            this.label5.Size = new System.Drawing.Size(89, 12);
+            this.label5.TabIndex = 16;
+            this.label5.Text = "更新管理员密码";
+            // 
+            // textBoxLocalFile
+            // 
+            this.textBoxLocalFile.Location = new System.Drawing.Point(118, 12);
+            this.textBoxLocalFile.Name = "textBoxLocalFile";
+            this.textBoxLocalFile.ReadOnly = true;
+            this.textBoxLocalFile.Size = new System.Drawing.Size(642, 21);
+            this.textBoxLocalFile.TabIndex = 5;
+            // 
+            // textBoxOssPath
+            // 
+            this.textBoxOssPath.Location = new System.Drawing.Point(118, 39);
+            this.textBoxOssPath.Name = "textBoxOssPath";
+            this.textBoxOssPath.ReadOnly = true;
+            this.textBoxOssPath.Size = new System.Drawing.Size(642, 21);
+            this.textBoxOssPath.TabIndex = 6;
+            // 
+            // textBoxLatestVersion
+            // 
+            this.textBoxLatestVersion.Location = new System.Drawing.Point(118, 66);
+            this.textBoxLatestVersion.Name = "textBoxLatestVersion";
+            this.textBoxLatestVersion.ReadOnly = true;
+            this.textBoxLatestVersion.Size = new System.Drawing.Size(642, 21);
+            this.textBoxLatestVersion.TabIndex = 7;
+            // 
+            // textBoxNowVersion
+            // 
+            this.textBoxNowVersion.Location = new System.Drawing.Point(118, 93);
+            this.textBoxNowVersion.MaxLength = 11;
+            this.textBoxNowVersion.Name = "textBoxNowVersion";
+            this.textBoxNowVersion.Size = new System.Drawing.Size(642, 21);
+            this.textBoxNowVersion.TabIndex = 8;
+            // 
+            // textBoxUsername
+            // 
+            this.textBoxUsername.Location = new System.Drawing.Point(118, 120);
+            this.textBoxUsername.Name = "textBoxUsername";
+            this.textBoxUsername.Size = new System.Drawing.Size(642, 21);
+            this.textBoxUsername.TabIndex = 9;
+            // 
+            // textBoxPassword
+            // 
+            this.textBoxPassword.Location = new System.Drawing.Point(118, 147);
+            this.textBoxPassword.Name = "textBoxPassword";
+            this.textBoxPassword.Size = new System.Drawing.Size(642, 21);
+            this.textBoxPassword.TabIndex = 10;
+            this.textBoxPassword.UseSystemPasswordChar = true;
+            // 
+            // FormMain
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(772, 244);
+            this.Controls.Add(this.button1);
+            this.Controls.Add(this.label10);
+            this.Controls.Add(this.label9);
+            this.Controls.Add(this.label8);
+            this.Controls.Add(this.label7);
+            this.Controls.Add(this.label6);
+            this.Controls.Add(this.label5);
+            this.Controls.Add(this.textBoxLocalFile);
+            this.Controls.Add(this.textBoxOssPath);
+            this.Controls.Add(this.textBoxLatestVersion);
+            this.Controls.Add(this.textBoxNowVersion);
+            this.Controls.Add(this.textBoxUsername);
+            this.Controls.Add(this.textBoxPassword);
+            this.Controls.Add(this.statusStrip1);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "FormMain";
+            this.Text = "大教研扫描应用发布助手";
+            this.statusStrip1.ResumeLayout(false);
+            this.statusStrip1.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.StatusStrip statusStrip1;
+        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
+        private System.Windows.Forms.ToolStripProgressBar toolStripProgressBarRelease;
+        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelStepNow;
+        private System.Windows.Forms.Button button1;
+        private System.Windows.Forms.Label label10;
+        private System.Windows.Forms.Label label9;
+        private System.Windows.Forms.Label label8;
+        private System.Windows.Forms.Label label7;
+        private System.Windows.Forms.Label label6;
+        private System.Windows.Forms.Label label5;
+        private System.Windows.Forms.TextBox textBoxLocalFile;
+        private System.Windows.Forms.TextBox textBoxOssPath;
+        private System.Windows.Forms.TextBox textBoxLatestVersion;
+        private System.Windows.Forms.TextBox textBoxNowVersion;
+        private System.Windows.Forms.TextBox textBoxUsername;
+        private System.Windows.Forms.TextBox textBoxPassword;
+    }
+}
+

+ 77 - 0
ReleaseHelper/FormMain.cs

@@ -0,0 +1,77 @@
+using ReleaseHelper.Services;
+using ReleaseHelper.Variables;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace ReleaseHelper
+{
+
+    public partial class FormMain : Form
+    {
+        public FormMain()
+        {
+            InitializeComponent();
+            textBoxLocalFile.Text = Const.TOBE_UPLOAD_LOCAL_FILE;
+            textBoxOssPath.Text = Const.OSS_PATH;
+            Task.Run(async () =>
+            {
+                var info = await Services.Release.GetLatestVersionInfo(Variables.Const.HTTP_UPDATE_FILE);
+                BeginInvoke(new Action(() =>
+                {
+                    textBoxNowVersion.Text = textBoxLatestVersion.Text = info.Version.ToString();
+                }));
+            });
+        }
+
+        private void button1_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrEmpty(textBoxNowVersion.Text) || string.IsNullOrEmpty(textBoxUsername.Text) || string.IsNullOrEmpty(textBoxPassword.Text))
+            {
+                MessageBox.Show("信息不全");
+            }
+            else
+            {
+                try
+                {
+                    Task.Run(async () =>
+                    {
+                        string res = await Release.ReleaseApplication(
+                            textBoxUsername.Text,
+                            textBoxPassword.Text,
+                            Convert.ToDouble(textBoxNowVersion.Text.Trim()),
+                            Const.TOBE_UPLOAD_LOCAL_FILE,
+                            Const.OSS_PATH,
+                            setProgressBar
+                        );
+                        if (string.IsNullOrEmpty(res))
+                        {
+                            res = "更新成功!";
+                        }
+                        MessageBox.Show(res);
+                    });
+
+                }
+                catch
+                {
+                }
+            }
+        }
+
+        private void setProgressBar(int max, int now)
+        {
+            Utils.ControlExts.FormInvoke(this, () => {
+                toolStripProgressBarRelease.Maximum = max;
+                toolStripProgressBarRelease.Minimum = 0;
+                toolStripProgressBarRelease.Value = now;
+                Console.WriteLine(now.ToString(), "/", max.ToString());
+            });
+        }
+    }
+}

+ 123 - 0
ReleaseHelper/FormMain.resx

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>

+ 31 - 0
ReleaseHelper/Models/AliyunAccessModel.cs

@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace ReleaseHelper.Models
+{
+
+    public class AliyunAccessModel
+    {
+        //public int Status { get; set; }
+        [JsonProperty(PropertyName = "endpoint")]
+        public string EndPoint { get; set; }
+
+        [JsonProperty(PropertyName = "access_key_id")]
+        public string AccessKeyId { get; set; }
+
+        [JsonProperty(PropertyName = "access_key_secret")]
+        public string AccessKeySecret { get; set; }
+
+        [JsonProperty(PropertyName = "security_token")]
+        public string SecurityToken { get; set; }
+
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        [JsonProperty(PropertyName = "expiration")]
+        public long Expiration { get; set; }
+
+        [JsonProperty(PropertyName = "bucket_name")]
+        public string BucketName { get; set; }
+    }
+
+}

+ 8 - 0
ReleaseHelper/Models/UpdateInfo.cs

@@ -0,0 +1,8 @@
+namespace ReleaseHelper.Models
+{
+    public struct UpdateInfo
+    {
+        public string MD5 { get; set; }
+        public double Version { get; set; }
+    }
+}

+ 123 - 0
ReleaseHelper/Models/UserInfo.cs

@@ -0,0 +1,123 @@
+namespace ReleaseHelper.Models
+{
+    public struct UserInfo
+    {
+        public struct Info
+        {
+            public int Id;
+            public string Name;
+        }
+
+        /// <summary>
+        /// 学校信息
+        /// </summary>
+        public struct SchoolInfo
+        {
+            public int Id;
+            public string Name;
+            public int[] GradeIds;
+        }
+
+        /// <summary>
+        /// 用户id
+        /// </summary>
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 用户token
+        /// </summary>
+        public string Token { get; set; }
+        /// <summary>
+        /// 用户名
+        /// </summary>
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 用户当前角色
+        /// </summary>
+        public int RoleId { get; set; }
+
+        /// <summary>
+        /// 用户组id
+        /// </summary>
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// 角色名称
+        /// </summary>
+        public string RoleName { get; set; }
+
+        /// <summary>
+        /// 用户当前学校
+        /// </summary>
+        public int SchoolId { get; set; }
+
+        public string SchoolName { get; set; }
+
+        /// <summary>
+        /// 用户当前校区
+        /// </summary>
+        public int CampusId { get; set; }
+
+        public string CampusName { get; set; }
+
+        /// <summary>
+        /// 是否已登录
+        /// </summary>
+        public bool Logined { get; set; }
+
+        /// <summary>
+        /// 用户组
+        /// </summary>
+        public Info[] UserGroups { get; set; }
+
+        /// <summary>
+        /// 学校
+        /// </summary>
+        public SchoolInfo[] Schools { get; set; }
+
+        /// <summary>
+        /// 所有校区
+        /// </summary>
+        //public List<Models.SystemUserGet.CampusInfo> Campuses { get; set; }
+
+        /// <summary>
+        /// 科目id
+        /// </summary>
+        public int[] SubjectIds { get; set; }
+
+        //public UserConfig LocalConfig { get; set; }
+
+        /// <summary>
+        /// 系统用户配置
+        /// </summary>
+        //public SystemUserGet SystemUserInfo { get; set; }
+
+        public UserInfo(string username)
+        {
+            Id = 0;
+            Token = "";
+            UserName = username;
+            RoleId = 0;
+            RoleName = "";
+            GroupId = 0;
+            SchoolId = 0;
+            SchoolName = "";
+            CampusId = 0;
+            CampusName = "";
+            SubjectIds = new int[0];
+            UserGroups = new Info[0];
+            Schools = new SchoolInfo[0];
+            //Campuses = new List<Models.SystemUserGet.CampusInfo>();
+            Logined = false;
+            //LocalConfig = new UserConfig();
+            //SystemUserInfo = null;
+
+            //Config.ScanTasks = new List<string>();
+            //Config.OcrTasks = new List<string>();
+        }
+
+
+
+    }
+}

+ 78 - 0
ReleaseHelper/Models/UserLoginPost.cs

@@ -0,0 +1,78 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace ReleaseHelper.Models
+{
+
+    /// <summary>
+    /// 登录请求响应
+    /// </summary>
+    public class UserLoginPost
+    {
+        //public class LoginResponseData
+        //{
+        public int Uid { get; set; }
+
+        [JsonProperty(PropertyName = "exp_time")]
+        public int ExpTime { get; set; }
+
+        public string Username { get; set; }
+
+        [JsonProperty(PropertyName = "real_name")]
+        public string RealName { get; set; }
+        public int Role { get; set; }
+        public int GroupId { get; set; }
+        public List<LoginResponseDataGroups> Groups { get; set; }
+        public string Mobile { get; set; }
+        public string Token { get; set; }
+
+        [JsonProperty(PropertyName = "bound_wx")]
+        public bool BoundedWechat { get; set; }
+
+        [JsonProperty(PropertyName = "grade_ids")]
+        public int[] GradeIds { get; set; }
+
+        [JsonProperty(PropertyName = "subject_ids")]
+        public int[] SubjectIds { get; set; }
+
+        [JsonProperty(PropertyName = "school_id")]
+        public int SchoolId { get; set; }
+
+        [JsonProperty(PropertyName = "campus_id")]
+        public int CampusId { get; set; }
+
+        [JsonProperty(PropertyName = "school_ids")]
+        public int[] SchoolIds { get; set; }
+
+        public LoginResponseDataSchools[] Schools { get; set; }
+
+
+        [JsonProperty(PropertyName = "campus_ids")]
+        public int[] CampusIds { get; set; }
+
+        //}
+
+        public class LoginResponseDataGroups
+        {
+            [JsonProperty(PropertyName = "group_id")]
+            public int GroupId { get; set; }
+
+            [JsonProperty(PropertyName = "group_name")]
+            public string GroupName { get; set; }
+        }
+
+        public class LoginResponseDataSchools
+        {
+            public int Id { get; set; }
+
+            public string Name { get; set; }
+
+            [JsonProperty(PropertyName = "grade_ids")]
+            public int[] GradeIds { get; set; }
+        }
+
+        //public LoginResponseData Data { get; set; }
+    }
+
+
+}

+ 22 - 0
ReleaseHelper/Program.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace ReleaseHelper
+{
+    internal static class Program
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new FormMain());
+        }
+    }
+}

+ 33 - 0
ReleaseHelper/Properties/AssemblyInfo.cs

@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("ReleaseHelper")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ReleaseHelper")]
+[assembly: AssemblyCopyright("Copyright ©  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("63a2943c-0563-403c-b146-67780cc2dc46")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
ReleaseHelper/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本: 4.0.30319.42000
+//
+//     对此文件的更改可能导致不正确的行为,如果
+//     重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ReleaseHelper.Properties
+{
+
+
+    /// <summary>
+    ///   强类型资源类,用于查找本地化字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   返回此类使用的缓存 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ReleaseHelper.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
ReleaseHelper/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
ReleaseHelper/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ReleaseHelper.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
ReleaseHelper/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 108 - 0
ReleaseHelper/ReleaseHelper.csproj

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{63A2943C-0563-403C-B146-67780CC2DC46}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>ReleaseHelper</RootNamespace>
+    <AssemblyName>ReleaseHelper</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Aliyun.OSS, Version=2.14.1.0, Culture=neutral, PublicKeyToken=0ad4175f0dac0b9b, processorArchitecture=MSIL">
+      <HintPath>..\packages\Aliyun.OSS.SDK.2.14.1\lib\net461\Aliyun.OSS.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FormMain.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="FormMain.Designer.cs">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Models\AliyunAccessModel.cs" />
+    <Compile Include="Models\UpdateInfo.cs" />
+    <Compile Include="Models\UserInfo.cs" />
+    <Compile Include="Models\UserLoginPost.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Services\AliyunOss.cs" />
+    <Compile Include="Services\Api.cs" />
+    <Compile Include="Services\Release.cs" />
+    <Compile Include="Utils\ControlExts.cs" />
+    <Compile Include="Utils\FileExtensions.cs" />
+    <Compile Include="Utils\Functions.cs" />
+    <Compile Include="Utils\Http.cs" />
+    <Compile Include="Utils\Json.cs" />
+    <Compile Include="Utils\Log.cs" />
+    <Compile Include="Utils\MutexExtensions.cs" />
+    <Compile Include="Utils\ProcessExtensions.cs" />
+    <Compile Include="Variables\Const.cs" />
+    <Compile Include="Variables\Variables.cs" />
+    <EmbeddedResource Include="FormMain.resx">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 163 - 0
ReleaseHelper/Services/AliyunOss.cs

@@ -0,0 +1,163 @@
+using Aliyun.OSS;
+using ReleaseHelper.Models;
+using ReleaseHelper.Utils;
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace ReleaseHelper.Services
+{
+    public static class AliyunOss
+    {
+        public delegate void DeleWriteLog(string txt);
+        public static DeleWriteLog WriteLog;
+
+        private static OssClient client = null;
+        private static AliyunAccessModel accessModel = null;
+        /// <summary>
+        /// 客户端锁
+        /// </summary>
+        private readonly static object ClientLock = new object();
+
+        //public static string EndPoint { get; set; }
+        //public static string AccessKeyId { get; set; }
+        //public static string AccessKeySecret { get; set; }
+        /// <summary>
+        /// 授权有效期临界值
+        /// </summary>
+        private const int EXP_THRESHOLD = 60 * 2 * 1000;
+
+        public static AliyunAccessModel Access
+        {
+            get
+            {
+                lock (ClientLock)
+                {
+                    if (accessModel == null || accessModel.Expiration - EXP_THRESHOLD < Functions.GetTimeStamp())
+                    {
+                        //lock (accessModel)
+                        //{
+                        accessModel = Api.GetAliyunAccess().Result;
+                        accessModel.Expiration = Functions.GetTimeStamp() + accessModel.Expiration * 1000;
+                        accessModel.EndPoint = Regex.Replace(accessModel.EndPoint, @"-internal", "", RegexOptions.IgnoreCase);
+                        //}
+                    }
+                }
+
+                return accessModel;
+            }
+        }
+
+        public static void ClearAccess()
+        {
+            lock (ClientLock)
+            {
+                accessModel = null;
+            }
+        }
+
+        private static bool AccessAvalible()
+        {
+            return !string.IsNullOrEmpty(Access?.EndPoint) && !string.IsNullOrEmpty(Access?.AccessKeyId) && !string.IsNullOrEmpty(Access?.AccessKeySecret);
+        }
+
+
+        private static OssClient Client
+        {
+            get
+            {
+                if (client == null && AccessAvalible())
+                {
+                    client = new OssClient(Access?.EndPoint, Access?.AccessKeyId, Access?.AccessKeySecret);
+                }
+                return client;
+            }
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="requestContent"></param>
+        /// <returns></returns>
+        public static bool SimpleUpload(string objectName, MemoryStream requestContent)
+        {
+            //// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
+            //var endpoint = "yourEndpoint";
+            //// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
+            //var accessKeyId = "yourAccessKeyId";
+            //var accessKeySecret = "yourAccessKeySecret";
+            //// 填写Bucket名称。
+            //var bucketName = "examplebucket";
+            //// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
+            //var objectName = "exampleobject.txt";
+            //// 填写字符串。
+            //var objectContent = "More than just cloud.";
+
+            // 创建OssClient实例。
+            //var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
+            bool res = false;
+            try
+            {
+                //byte[] binaryData = Encoding.ASCII.GetBytes(objectContent);
+                //MemoryStream requestContent = new MemoryStream(binaryData);
+                // 上传文件。
+                Client?.PutObject(Access?.BucketName, objectName, requestContent);
+                //Console.WriteLine("Put object succeeded");
+                res = true;
+            }
+            catch (Exception ex)
+            {
+                WriteLog?.Invoke($"Put object failed, {ex.Message}");
+                WriteLog?.Invoke(ex.StackTrace);
+                //Console.WriteLine("Put object failed, {0}", ex.Message);
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 上传
+        /// </summary>
+        /// <param name="bucketName"></param>
+        /// <param name="objectName"></param>
+        /// <param name="localFilename"></param>
+        /// <returns></returns>
+        public static bool SimpleUpload(string objectName, string localFilename)
+        {
+            bool res = false;
+            //PutObjectResult r;
+            try
+            {
+                //byte[] binaryData = Encoding.ASCII.GetBytes(objectContent);
+                //MemoryStream requestContent = new MemoryStream(binaryData);
+                for (int i = 0; i < 5; i++)
+                {
+                    using (PutObjectResult r = Client?.PutObject(Access?.BucketName, objectName, localFilename))
+                    {
+                        //r = t.
+                        if (r?.HttpStatusCode == System.Net.HttpStatusCode.OK)
+                        {
+                            //r?.Dispose();
+                            res = true;
+                            break;
+                        }
+                    }
+                }
+                // 上传文件。
+                //Console.WriteLine("Put object succeeded");
+            }
+            catch (Exception ex)
+            {
+                WriteLog?.Invoke($"Put object failed, {ex.Message}");
+                WriteLog?.Invoke(ex.StackTrace);
+                //Console.WriteLine("Put object failed, {0}", ex.Message);
+            }
+            return res;
+        }
+
+
+
+
+    }
+}

+ 252 - 0
ReleaseHelper/Services/Api.cs

@@ -0,0 +1,252 @@
+using Newtonsoft.Json;
+using ReleaseHelper.Models;
+using ReleaseHelper.Utils;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace ReleaseHelper.Services
+{
+    public static class Api
+    {
+        public delegate void DeleLogin(bool runAfterLoginFn);
+        public static DeleLogin LoginFn;
+
+
+        private const int PAGE_SIZE = 999999;
+        private const int PAGE_NO = 1;
+
+        /// <summary>
+        /// 登录
+        /// </summary>
+        /// <param name="username"></param>
+        /// <param name="password"></param>
+        /// <returns></returns>
+        public static async Task<UserLoginPost> Login(string username, string password)
+        {
+            UserLoginPost loginResponse = null;
+            var (code, response) = await Fetch(
+                new Http.Request(@"v1/user/login", Http.Method.Post,
+                new Dictionary<string, object> {
+                    { "username", username} ,
+                    { "password", password}
+                }
+                ));
+
+            if (!string.IsNullOrEmpty(response))
+            {
+                loginResponse = Json.Decode<UserLoginPost>(response);
+                Variables.Variables.RequestHeaders["Authorization"] = "Bearer " + loginResponse.Token;
+            }
+            return loginResponse;
+        }
+
+
+        /// <summary>
+        /// 获取静态文件流
+        /// </summary>
+        /// <param name="fileName"></param>
+        /// <returns></returns>
+        public static async Task<byte[]> GetStaticFileBytes(string fileName)
+        {
+            byte[] result = null;
+            fileName = Regex.Replace(fileName, @"^[\/\\]*", "");
+            long begin = Functions.GetTimeStamp(), end;
+            Http.Request request = new Http.Request($"{Variables.Const.HTTP_STATIC_HOST}{fileName}", Http.Method.Get);
+            end = Functions.GetTimeStamp();
+            Log.WriteLine($"request static file create http: {end - begin}");
+            begin = end;
+            using (HttpResponseMessage response = await Http.Fetch(request))
+            {
+                end = Functions.GetTimeStamp();
+                Log.WriteLine($"request static file fetch http: {end - begin}");
+                begin = end;
+                if (response != null && response.StatusCode == HttpStatusCode.OK)
+                {
+                    result = response.Content.ReadAsByteArrayAsync().Result;
+                    end = Functions.GetTimeStamp();
+                    Log.WriteLine($"request static file read byte: {end - begin}");
+                    begin = end;
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 获取升级信息
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<Models.UpdateInfo> GetUpdateInfo(string ossFileUri)
+        {
+            UpdateInfo info = new UpdateInfo();
+            try
+            {
+                string url = Regex.Replace(ossFileUri, "\\.[^.]{2,5}$", ".txt", RegexOptions.IgnoreCase);
+                HttpResponseMessage response = await Http.Fetch(new Http.Request(url, Http.Method.Get));
+                if (response.IsSuccessStatusCode)
+                {
+                    string txt = await response.Content.ReadAsStringAsync();
+                    info = Utils.Json.Decode<UpdateInfo>(txt);
+                }
+            }
+            catch
+            {
+
+            }
+            return info;
+        }
+
+        /// <summary>
+        /// 获取阿里云OSS STS 权限
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<Models.AliyunAccessModel> GetAliyunAccess()
+        {
+            var (code, response) = await Fetch(new Http.Request(@"v1/oss/authorize", Http.Method.Post, null));
+            return Json.Decode<Models.AliyunAccessModel>(response);
+            //return GetResponse<Models.AliyunAccessModel>(@"v1/oss/authorize", Http.Method.Post, null);
+        }
+
+        /// <summary>
+        /// 获取系统返回的内容
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="url"></param>
+        /// <param name="method"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private static async Task<T> GetResponse<T>(string url, Http.Method method, Dictionary<string, object> data)
+        {
+            T res = default;
+            var (code, response) = await Fetch(new Http.Request(url, method, data));
+            if (!string.IsNullOrEmpty(response))
+            {
+                var tmp = Json.Decode<Dictionary<string, object>>(response);
+                if (tmp != null && tmp.ContainsKey("result"))
+                {
+                    res = Json.Decode<T>(tmp["result"].ToString());
+                }
+            }
+            return res;
+        }
+
+        #region 私有方法
+        /// <summary>
+        /// 请求
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        private static async Task<(int, string)> Fetch(Http.Request request)
+        {
+            foreach (var item in Variables.Variables.RequestHeaders)
+            {
+                // 添加请求头
+                request.headers.Add(item.Key, item.Value);
+            }
+            // 请求地址
+            request.uri = string.Concat(Variables.Const.HTTP_HOST, request.uri);
+            string apiResponse = null;
+            int code = 200;
+            Dictionary<string, object> resp;
+            using (HttpResponseMessage response = await Http.Fetch(request))
+            {
+                if (response != null)
+                {
+                    switch (response.StatusCode)
+                    {
+                        case HttpStatusCode.OK:
+                            // 正常响应
+
+                            resp = JsonConvert.DeserializeObject<Dictionary<string, object>>(await response.Content.ReadAsStringAsync());
+                            //apiResponse = Json.Decode<ApiResponse>(response.Content.ReadAsStreamAsync().Result);
+                            code = Convert.ToInt32(resp["code"]);
+                            switch (code)
+                            {
+                                case 400:
+                                    Response400();
+                                    apiResponse = resp["errors"] != null ? resp["errors"].ToString() : "接口错误";
+                                    break;
+                                case 401:
+                                    Response401();
+                                    break;
+                                case 4000:
+                                    Response401();
+                                    break;
+                                case 404:
+                                    Response404();
+                                    break;
+                                case 500:
+                                    Response500();
+                                    apiResponse = resp["errors"].ToString();
+                                    break;
+                                case 502:
+                                    Response500();
+                                    break;
+                                //case 1001:
+                                //    apiResponse = resp["msg"].ToString();
+                                //break;
+                                default:
+                                    apiResponse = resp["data"] != null ? resp["data"].ToString() : "";
+                                    break;
+                            }
+                            break;
+                        case HttpStatusCode.NotFound:
+                            // 页面不存在
+                            Response404();
+                            break;
+                        case HttpStatusCode.Unauthorized:
+                            // 无权限
+                            Response401();
+                            break;
+                        case HttpStatusCode.BadRequest:
+                            // 页面错误
+                            Response400();
+                            break;
+                        default:
+                            // 其它错误
+                            Response500();
+                            break;
+                    }
+                }
+                else
+                {
+                    // 没有网络
+                }
+            }
+            //Log.WriteLine($"{request.uri}:{apiResponse}");
+            return (code, apiResponse);
+        }
+
+        private static void Response400()
+        {
+
+        }
+
+        private static void Response401()
+        {
+            //void act()
+            //{
+            //    Forms.Instances.FormLogin.ShowDialog(Forms.Instances.MainEntrance);
+            //    Forms.Instances.FormLogin.ReLogin();
+            //}
+            //Utils.ControlExtensions.FormInvoke(Forms.Instances.FormLogin, act);
+            LoginFn?.Invoke(false);
+            //Forms.Instances.FormLogin.ShowDialog(Forms.Instances.MainEntrance);
+            //Forms.Instances.FormLogin.ReLogin();
+        }
+
+        private static void Response404()
+        {
+
+        }
+
+        private static void Response500()
+        {
+
+        }
+        #endregion
+    }
+}

+ 86 - 0
ReleaseHelper/Services/Release.cs

@@ -0,0 +1,86 @@
+using ReleaseHelper.Models;
+using ReleaseHelper.Utils;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace ReleaseHelper.Services
+{
+    public static class Release
+    {
+        /// <summary>
+        /// 设置更新进度
+        /// </summary>
+        /// <param name="max"></param>
+        /// <param name="now"></param>
+        public delegate void DeleSetProgress(int max, int now);
+
+
+        public static async Task<string> ReleaseApplication(string username, string password, double version, 
+            string objFilename, string targetOssPath, DeleSetProgress deleSetProgress)
+        {
+            string res = "";
+            int max = 4, now=0;
+            List<string> localFilenames = new List<string>() { objFilename, GetUpdateInfoFilename(objFilename) };
+            // 生成版本信息文件
+            await SaveLocalUpdateInfo(objFilename, version);
+            deleSetProgress(max, ++now);
+            // 登录
+            await Login(username, password);
+            deleSetProgress(max, ++now);
+            // 上传相应文件到OSS服务器
+            foreach (string f in localFilenames)
+            {
+                if (!AliyunOss.SimpleUpload($"{targetOssPath}/{Path.GetFileName(f)}", f))
+                {
+                    res = $"{f} 更新失败!";
+                }
+                deleSetProgress(max, ++now);
+            }
+            // 退出登录
+            Logout();
+            return res;
+        }
+
+        //private static async Task
+        public static async Task<Models.UpdateInfo> GetLatestVersionInfo(string ossUri)
+        {
+            return await Api.GetUpdateInfo(ossUri);
+        }
+
+        private static async Task SaveLocalUpdateInfo(string objFilename, double version)
+        {
+            await Task.Run(() =>
+            {
+                string f = GetUpdateInfoFilename(objFilename);
+                UpdateInfo info = new UpdateInfo
+                {
+                    MD5 = FileExtensions.FileMd5(objFilename),
+                    Version = version
+                };
+                FileExtensions.WriteAllText(f, Json.Encode(info), Encoding.UTF8);
+            });
+        }
+
+        private static string GetUpdateInfoFilename(string ObjFilename)
+        {
+            return Regex.Replace(ObjFilename, "\\.[^.]{2,5}$", ".txt", RegexOptions.IgnoreCase);
+        }
+
+
+        private static async Task Login(string username, string password)
+        {
+            await Api.Login(username, password);
+            //await Api.GetAliyunAccess();
+        }
+
+        private static void Logout()
+        {
+            Variables.Variables.RequestHeaders["Authorization"] = "";
+            AliyunOss.ClearAccess();
+        }
+
+    }
+}

+ 64 - 0
ReleaseHelper/Utils/ControlExts.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Windows.Forms;
+
+namespace ReleaseHelper.Utils
+{
+    internal class ControlExts
+    {
+
+        public static Form MessageBoxShowForm;
+        /// <summary>
+        /// 窗体操作代理
+        /// </summary>
+        /// <param name="form"></param>
+        /// <param name="method"></param>
+        public static void FormInvoke(Form form, Action method)
+        {
+            if (form.InvokeRequired)
+            {
+                try
+                {
+                    form.Invoke((EventHandler)delegate { method(); });
+                }
+                catch { }
+            }
+            else
+            {
+                try
+                {
+                    method();
+                }
+                catch { }
+
+            }
+        }
+
+        /// <summary>
+        /// 信息提示框
+        /// </summary>
+        /// <param name="text"></param>
+        /// <param name="caption"></param>
+        /// <param name="buttons"></param>
+        /// <param name="icon"></param>
+        /// <param name="defaultButton"></param>
+        /// <returns></returns>
+        public static DialogResult ShowMessageBox(string text, string caption = "提示", MessageBoxButtons buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1)
+        {
+            DialogResult act()
+            {
+                return MessageBox.Show(MessageBoxShowForm, text, caption, buttons, icon, defaultButton);
+            }
+
+            DialogResult result = DialogResult.None;
+            if (MessageBoxShowForm.InvokeRequired)
+            {
+                MessageBoxShowForm.Invoke((EventHandler)delegate { result = act(); });
+            }
+            else
+            {
+                result = act();
+            }
+            return result;
+        }
+    }
+}

+ 227 - 0
ReleaseHelper/Utils/FileExtensions.cs

@@ -0,0 +1,227 @@
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ReleaseHelper.Utils
+{
+    public static class FileExtensions
+    {
+        /// <summary>
+        /// 生成文件的md5
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        private static string FilenameMutexKey(string filename)
+        {
+            return Functions.GenMd5(filename);
+        }
+
+        /// <summary>
+        /// 读取所有行
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="encoding"></param>
+        /// <returns></returns>
+        public static string[] ReadAllLines(string filename, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            string[] txt = { };
+            void act()
+            {
+                if (File.Exists(filename))
+                {
+                    txt = File.ReadAllLines(filename, encoding);
+                }
+            }
+            MutexExtensions.Exec(key, act);
+            return txt;
+        }
+
+        /// <summary>
+        /// 读取所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="encoding"></param>
+        /// <returns></returns>
+        public static string ReadAllText(string filename, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            string txt = string.Empty;
+            void act()
+            {
+                if (File.Exists(filename))
+                {
+                    txt = File.ReadAllText(filename, encoding);
+                }
+            }
+            MutexExtensions.Exec(key, act);
+            return txt;
+        }
+
+        /// <summary>
+        /// 写入所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void WriteAllText(string filename, string txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            void act()
+            {
+                File.WriteAllText(filename, txt, encoding);
+            }
+            MutexExtensions.Exec(key, act);
+        }
+
+        /// <summary>
+        /// 写入二进制值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="bytes"></param>
+        public static void WriteBytes(string filename, byte[] bytes)
+        {
+            if (bytes != null)
+            {
+                string key = FilenameMutexKey(filename);
+                CheckDirectory(Path.GetDirectoryName(filename), true);
+                void act()
+                {
+                    File.WriteAllBytes(filename, bytes);
+                }
+                MutexExtensions.Exec(key, act);
+            }
+        }
+
+        /// <summary>
+        /// 读取所有二进制
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static byte[] ReadBytes(string filename)
+        {
+            string key = FilenameMutexKey(filename);
+            byte[] txt = default;
+            void act()
+            {
+                if (File.Exists(filename))
+                {
+                    txt = File.ReadAllBytes(filename);
+                }
+            }
+            MutexExtensions.Exec(key, act);
+            return txt;
+        }
+
+        /// <summary>
+        /// 写入所有行
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void WriteAllLines(string filename, string[] txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            void act()
+            {
+                File.WriteAllLines(filename, txt, encoding);
+            }
+            MutexExtensions.Exec(key, act);
+        }
+
+        /// <summary>
+        /// 添加所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void AppendAllText(string filename, string txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            void act()
+            {
+                File.AppendAllText(filename, txt, encoding);
+            }
+            MutexExtensions.Exec(key, act);
+        }
+
+        /// <summary>
+        /// 检查目录是否存在,如果不存在,将根据条件创建或不创建
+        /// </summary>
+        /// <param name="p"></param>
+        /// <param name="create">如果目录不存在,是否创建</param>
+        /// <returns></returns>
+        public static bool CheckDirectory(string p, bool create)
+        {
+
+            bool res = false;
+            if (!string.IsNullOrEmpty(p))
+            {
+                res = Directory.Exists(p);
+
+                if (!res && create)
+                {
+                    Directory.CreateDirectory(p);
+                    res = true;
+                }
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 在文件管理器中打开文件夹并选择文件
+        /// </summary>
+        /// <param name="filename"></param>
+        public static bool OpenFileInExplore(string filename)
+        {
+            bool res = false;
+            try
+            {
+                System.Diagnostics.Process.Start("Explorer.exe", $"/select, {filename}");
+                res = true;
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 获取制定路径文件的md5值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static string FileMd5(string filename)
+        {
+            string res = "";
+            try
+            {
+                using (FileStream file = new FileStream(filename, FileMode.Open))
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("x2"));
+                    }
+                    res = sb.ToString();
+                }
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+    }
+}

+ 383 - 0
ReleaseHelper/Utils/Functions.cs

@@ -0,0 +1,383 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq.Expressions;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ReleaseHelper.Utils
+{
+    public static class Functions
+    {
+
+        //static byte[] Encrypt(string input)
+        //{
+        //    StringBuilder sb = new StringBuilder();
+        //    int i = 0;
+        //    int offset = Variables.Const.KEY[0];
+        //    foreach (char c in input)
+        //    {
+        //        var c2 = Convert.ToChar(c + Variables.Const.KEY[i++] + offset);
+        //        sb.Append(c2);
+        //    }
+        //    return Encoding.UTF8.GetBytes(sb.ToString());
+        //}
+
+        //static string Decrypt(byte[] input)
+        //{
+        //    StringBuilder sb = new StringBuilder();
+        //    int i = 0;
+        //    int offset = Variables.Const.KEY[0];
+        //    string s = Encoding.UTF8.GetString(input);
+        //    foreach (char b in s)
+        //    {
+        //        sb.Append(Convert.ToChar(b - offset - Variables.Const.KEY[i++]));
+        //    }
+        //    return sb.ToString();
+        //}
+
+        //public static string LoadUserPW(string n, byte[] pwd)
+        //{
+        //    return Decrypt(pwd).Substring($"{n}_".Length);
+        //}
+
+        //public static byte[] DumpUserPW(string n, string pwd)
+        //{
+        //    return Encrypt($"{n}_{pwd}");
+        //}
+
+
+        /// <summary>
+        /// 字符串生成MD5
+        /// </summary>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public static string GenMd5(string s)
+        {
+            //就是比string往后一直加要好的优化容器
+            StringBuilder sb = new StringBuilder();
+            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
+            {
+                //将输入字符串转换为字节数组并计算哈希。
+                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(s));
+
+                //X为     十六进制 X都是大写 x都为小写
+                //2为 每次都是两位数
+                //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。 
+                //遍历哈希数据的每个字节
+                //并将每个字符串格式化为十六进制字符串。
+                int length = data.Length;
+                for (int i = 0; i < length; i++)
+                {
+                    sb.Append(data[i].ToString("X2"));
+                }
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 获取文件的MD5码
+        /// </summary>
+        /// <param name="fileName">传入的文件名(含路径及后缀名)</param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public static string GenMd5HashFromFile(string fileName)
+        {
+            try
+            {
+                StringBuilder sb = new StringBuilder();
+
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                using (FileStream file = new FileStream(fileName, FileMode.Open))
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    //file.Close();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("X2"));
+                    }
+                }
+                return sb.ToString();
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 生成UUID
+        /// </summary>
+        /// <returns></returns>
+        public static string GenUUID()
+        {
+            return Guid.NewGuid().ToString("N");
+        }
+
+        /// <summary>
+        /// 深复制对象
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentException"></exception>
+        public static T Clone<T>(T source)
+        {
+            if (!typeof(T).IsSerializable)
+            {
+                throw new ArgumentException("The type must be serializable.", "source");
+            }
+
+            // Don't serialize a null object, simply return the default for that object
+            if (source == null)
+            {
+                return default;
+            }
+
+            IFormatter formatter = new BinaryFormatter();
+            //Stream stream = new MemoryStream();
+            using (Stream stream = new MemoryStream())
+            {
+                formatter.Serialize(stream, source);
+                stream.Seek(0, SeekOrigin.Begin);
+                return (T)formatter.Deserialize(stream);
+            }
+        }
+
+        /// <summary>
+        /// 生成任务id
+        /// </summary>
+        /// <param name="testId"></param>
+        /// <returns></returns>
+        public static string GenTaskId(int testId)
+        {
+            return GenTaskId(testId, Utils.Functions.GenUUID());
+        }
+
+        /// <summary>
+        /// 生成任务id
+        /// </summary>
+        /// <param name="testId"></param>
+        /// <param name="uuid"></param>
+        /// <returns></returns>
+        public static string GenTaskId(int testId, string uuid)
+        {
+            return $"{testId}_{uuid}";
+        }
+
+        /// <summary>
+        /// 返回字符串右侧指定数量的字符串
+        /// </summary>
+        /// <param name="txt"></param>
+        /// <param name="length"></param>
+        /// <returns></returns>
+        public static string RString(string txt, int length)
+        {
+            string res = string.Empty;
+            if (!string.IsNullOrEmpty(txt))
+            {
+                res = length > txt.Length ? txt : txt.Substring(txt.Length - length);
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 复制对象
+        /// </summary>
+        /// <typeparam name="TIn"></typeparam>
+        /// <typeparam name="TOut"></typeparam>
+        public static class CopyTo<TIn, TOut>
+        {
+
+            private static readonly Func<TIn, TOut> cache = GetFunc();
+            private static Func<TIn, TOut> GetFunc()
+            {
+                ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
+                List<MemberBinding> memberBindingList = new List<MemberBinding>();
+
+                foreach (var item in typeof(TOut).GetProperties())
+                {
+                    if (!item.CanWrite)
+                        continue;
+
+                    MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
+                    MemberBinding memberBinding = Expression.Bind(item, property);
+                    memberBindingList.Add(memberBinding);
+                }
+
+                MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
+                Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
+
+                return lambda.Compile();
+            }
+
+            public static TOut Trans(TIn tIn)
+            {
+                return cache(tIn);
+            }
+
+        }
+
+
+        /// <summary>  
+        /// 获取时间戳  13位
+        /// </summary>  
+        /// <returns></returns>  
+        public static long GetTimeStamp()
+        {
+            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
+            return Convert.ToInt64(ts.TotalSeconds * 1000);
+        }
+
+        /// <summary>
+        /// 将时间戳转换为日期类型,并格式化
+        /// </summary>
+        /// <param name="longDateTime"></param>
+        /// <returns></returns>
+        public static string LongDateTimeToDateTimeString(string longDateTime)
+        {
+            //用来格式化long类型时间的,声明的变量
+            long unixDate;
+            DateTime start;
+            DateTime date;
+            //ENd
+
+            unixDate = long.Parse(longDateTime);
+            start = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+            date = start.AddMilliseconds(unixDate).ToLocalTime();
+
+            return date.ToString("yyyy-MM-dd HH:mm:ss");
+
+        }
+
+        /// <summary>
+        /// 获取制定路径文件的md5值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static string FileMd5(string filename)
+        {
+            string res = "";
+            try
+            {
+                using (FileStream file = new FileStream(filename, FileMode.Open))
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("x2"));
+                    }
+                    res = sb.ToString();
+                }
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+
+        ///// <summary>
+        ///// 生成上传文件对象的名称
+        ///// </summary>
+        ///// <param name="subjectId">科目id</param>
+        ///// <param name="filename">文件名</param>
+        ///// <returns></returns>
+        //public static string GenUploadImageName(int subjectId, string filename)
+        //{
+        //    string fileMd5 = FileMd5(filename), objectName = string.Empty;
+
+        //    if (!string.IsNullOrEmpty(fileMd5))
+        //    {
+        //        // 保存的文件名
+        //        objectName = $"images/marking/{Variables.Variables.SubjectIdCode[subjectId]}/{fileMd5.Substring(0, 4)}/{fileMd5}{Path.GetExtension(filename)}";
+        //    }
+        //    return objectName;
+
+        //}
+
+
+        ///// <summary>
+        ///// 获得已使用的物理内存的大小,单位 (Byte),如果获取失败,返回 -1.
+        ///// </summary>
+        ///// <returns></returns>
+        //public static double GetTotalPhysicalMemory(FileSizeUnit targetUnit = FileSizeUnit.MB)
+        //{
+        //    long capacity = 0;
+        //    try
+        //    {
+        //        foreach (ManagementObject mo1 in new ManagementClass("Win32_PhysicalMemory").GetInstances().Cast<ManagementObject>())
+        //        {
+        //            capacity += long.Parse(mo1.Properties["Capacity"].Value.ToString());
+        //        }
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        capacity = -1;
+        //        Console.WriteLine(ex.Message);
+        //    }
+        //    return ToFileFormat(capacity, targetUnit);
+        //}
+
+
+        ///// <summary>
+        ///// 获得已使用的物理内存的大小,单位 (Byte),如果获取失败,返回 -1.
+        ///// </summary>
+        ///// <returns></returns>
+        //public static double GetAvailablePhysicalMemory(FileSizeUnit targetUnit = FileSizeUnit.MB)
+        //{
+        //    long capacity = 0;
+        //    try
+        //    {
+        //        foreach (ManagementObject mo1 in new ManagementClass("Win32_PerfFormattedData_PerfOS_Memory").GetInstances().Cast<ManagementObject>())
+        //        {
+        //            capacity += long.Parse(mo1.Properties["AvailableBytes"].Value.ToString());
+        //        }
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        capacity = -1;
+        //        Console.WriteLine(ex.Message);
+        //    }
+        //    return ToFileFormat(capacity, targetUnit);
+        //}
+
+        ///// <summary>
+        ///// 根据指定的文件大小单位,对输入的文件大小(字节表示)进行转换。
+        ///// </summary>
+        ///// <param name="filesize">文件文件大小,单位为字节。</param>
+        ///// <param name="targetUnit">目标单位。</param>
+        ///// <returns></returns>
+        //private static double ToFileFormat(long filesize, FileSizeUnit targetUnit = FileSizeUnit.MB)
+        //{
+        //    double size;
+        //    switch (targetUnit)
+        //    {
+        //        case FileSizeUnit.KB: size = filesize / 1024.0; break;
+        //        case FileSizeUnit.MB: size = filesize / 1024.0 / 1024; break;
+        //        case FileSizeUnit.GB: size = filesize / 1024.0 / 1024 / 1024; break;
+        //        case FileSizeUnit.TB: size = filesize / 1024.0 / 1024 / 1024 / 1024; break;
+        //        case FileSizeUnit.PB: size = filesize / 1024.0 / 1024 / 1024 / 1024 / 1024; break;
+        //        default: size = filesize; break;
+        //    }
+        //    return size;
+        //}
+
+        ///// <summary>
+        ///// 文件大小单位,包括从B至PB共六个单位。
+        ///// </summary>
+        //public enum FileSizeUnit
+        //{
+        //    B,
+        //    KB,
+        //    MB,
+        //    GB,
+        //    TB,
+        //    PB
+        //}
+    }
+}

+ 398 - 0
ReleaseHelper/Utils/Http.cs

@@ -0,0 +1,398 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace ReleaseHelper.Utils
+{
+    public static class Http
+    {
+        public struct Response
+        {
+            public HttpStatusCode code;
+            public HttpContent message;
+            public string url;
+        }
+
+        public struct Request
+        {
+            /// <summary>
+            /// 请求的网址
+            /// </summary>
+            public string uri;
+            /// <summary>
+            /// 头信息
+            /// </summary>
+            public Dictionary<string, object> headers;
+            /// <summary>
+            /// 请求的数据
+            /// </summary>
+            public Dictionary<string, object> data;
+            /// <summary>
+            /// 上传的文件
+            /// </summary>
+            public Dictionary<string, string> files;
+            /// <summary>
+            /// 上传的文件二进制流
+            /// </summary>
+            public Dictionary<string, byte[]> fileStream;
+            /// <summary>
+            /// 请求类型
+            /// </summary>
+            public RequestContentType contentType;
+            /// <summary>
+            /// 请求方式
+            /// </summary>
+            public Method method;
+
+            public void Init()
+            {
+                uri = "";
+                headers = new Dictionary<string, object>();
+                data = new Dictionary<string, object>();
+                files = new Dictionary<string, string>();
+                fileStream = new Dictionary<string, byte[]>();
+                contentType = RequestContentType.Default;
+                method = Method.Get;
+            }
+
+            public Request(string uri, Method method)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = null;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+
+            public Request(string uri, Method method, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, RequestContentType contentType, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                this.contentType = contentType;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files, Dictionary<string, byte[]> fileStream)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                this.fileStream = fileStream;
+                contentType = RequestContentType.Default;
+            }
+
+
+        }
+
+        /// <summary>
+        /// 请求内容类型
+        /// </summary>
+        public enum RequestContentType
+        {
+            Default, Json
+        }
+
+        /// <summary>
+        /// 请求方法
+        /// </summary>
+        public enum Method
+        {
+            Get, Post, Put, Delete, Patch
+        }
+
+        /// <summary>
+        /// 初始化请求头
+        /// </summary>
+        /// <param name="httpClient"></param>
+        /// <param name="headers"></param>
+        private static void InitClientHeaders(ref HttpClient httpClient, Dictionary<string, object> headers)
+        {
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    if (!string.IsNullOrEmpty(header.Key) && !string.IsNullOrEmpty(header.Value.ToString()))
+                    {
+                        httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToString());
+                    }
+
+                }
+            }
+        }
+
+        /// <summary>
+        /// 获取
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static async Task<HttpResponseMessage> Fetch(Request request)
+        {
+            HttpResponseMessage response;
+            switch (request.method)
+            {
+                case Method.Post:
+                    response = await Post(request);
+                    break;
+                case Method.Put:
+                    response = await Put(request);
+                    break;
+                case Method.Patch:
+                    response = await Patch(request);
+                    break;
+                case Method.Delete:
+                    response = await Delete(request);
+                    break;
+                default:
+                    response = await Get(request);
+                    break;
+            }
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Get(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+                InitClientHeaders(ref client, request.headers);
+                if (request.data != null)
+                {
+                    //client.DefaultRequestHeaders.Add()
+                    url = string.Concat(url, "?");
+                    foreach (var item in request.data)
+                    {
+                        url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                    }
+                    url = url.Substring(0, url.Length - 1);
+                }
+                try
+                {
+                    response = await client.GetAsync(url);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+            return response;
+        }
+
+        private static HttpContent InitNonGetContent(Request request)
+        {
+            HttpContent content = null;
+
+            if (request.contentType == RequestContentType.Json)
+            {
+                // 发送json 数据
+                HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(request.data), Encoding.UTF8);
+                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+                content = httpContent;
+            }
+            else
+            //if (request.files != null || request.fileStream != null)
+            {
+                // 普通的键值对提交
+                MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
+                //FormUrlEncodedContent formUrlEncodedContent = new FormUrlEncodedContent();
+                if (request.files != null)
+                {
+                    foreach (var file in request.files)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes(file.Value)), file.Key, Path.GetFileName(file.Value));
+                    }
+                }
+                if (request.fileStream != null)
+                {
+                    foreach (var file in request.fileStream)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(file.Value), file.Key, file.Key);
+                    }
+                }
+
+                if (request.data != null)
+                {
+                    foreach (var d in request.data)
+                    {
+                        multipartFormDataContent.Add(new StringContent(d.Value.ToString(), Encoding.UTF8), d.Key);
+                    }
+                }
+                content = multipartFormDataContent;
+            }
+            return content;
+        }
+
+        public static async Task<HttpResponseMessage> Post(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PostAsync(request.uri, content);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Patch(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+                HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), request.uri);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    httpRequestMessage.Content = content;
+                    response = await client.SendAsync(httpRequestMessage);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+                httpRequestMessage.Dispose();
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Put(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PutAsync(request.uri, content);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Delete(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    if (request.data != null)
+                    {
+                        url = string.Concat(url, "?");
+                        foreach (var item in request.data)
+                        {
+                            url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                        }
+                    }
+                    response = await client.DeleteAsync(request.uri);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+    }
+}

+ 67 - 0
ReleaseHelper/Utils/Json.cs

@@ -0,0 +1,67 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.IO;
+using System.Text;
+
+namespace ReleaseHelper.Utils
+{
+
+    public static class Json
+    {
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public static T Decode<T>(Stream stream)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(new JavaScriptDateTimeConverter());//指定转化日期的格式
+            using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                return serializer.Deserialize<T>(reader);
+            }
+        }
+
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="txt"></param>
+        /// <returns></returns>
+        public static T Decode<T>(string txt)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(new JavaScriptDateTimeConverter());//指定转化日期的格式
+            using (StringReader sr = new StringReader(txt))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                T res = default;
+                try
+                {
+                    res = serializer.Deserialize<T>(reader);
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);
+                }
+                return res;
+
+            }
+        }
+
+        /// <summary>
+        /// json格式编码
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static string Encode(object obj)
+        {
+            return JsonConvert.SerializeObject(obj, new JavaScriptDateTimeConverter());
+        }
+
+    }
+}

+ 105 - 0
ReleaseHelper/Utils/Log.cs

@@ -0,0 +1,105 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace ReleaseHelper.Utils
+{
+    public static class Log
+    {
+        private readonly static object _lock = new object();
+        private static string logAll = "";
+        private static string logTmp = "";
+        private static readonly string logPath = Path.Combine(Environment.CurrentDirectory, "log");
+
+        /// <summary>
+        /// 日志处理的委托
+        /// </summary>
+        /// <param name="s"></param>
+        public delegate void SetLog(string s);
+        /// <summary>
+        /// 具体委托的实例
+        /// </summary>
+        public static SetLog DoSetLog;
+
+        public static string Now()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+        }
+
+        private static string Today()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd");
+        }
+
+        /// <summary>
+        /// 记录错误
+        /// </summary>
+        /// <param name="exception"></param>
+        public static void Error(Exception exception)
+        {
+            WriteLine(exception.StackTrace, "error", force: true);
+        }
+
+        /// <summary>
+        /// 记录信息
+        /// </summary>
+        /// <param name="s"></param>
+        /// <param name="t"></param>
+        /// <param name="filename"></param>
+        public static void WriteLine(string s, string t = "log", string filename = null, bool force = false)
+        {
+            if (s != null)
+            {
+                string info = $"{Now()} [{t}] {s}\r\n";
+                Console.Write(info);
+                lock (_lock)
+                {
+                    logAll = string.Concat(logAll, info);
+                    logTmp = string.Concat(logTmp, info);
+                }
+#if DEBUG
+                DoSetLog?.Invoke(logTmp);
+#endif
+                //Forms.Instances.MainEntrance().SetLog(Variables.Variables.LogTmp);
+                Save(force);
+            }
+        }
+
+        /// <summary>
+        /// 保存信息
+        /// </summary>
+        /// <param name="force"></param>
+        public static void Save(bool force = false, string filename = null)
+        {
+            //lock (Variables.Variables.LogTmp)
+            //{
+            if (force || logTmp.Length > 30000)
+            {
+                //if (!File.Exists(Variables.Variables.AppLogPath))
+                //{
+                //    string p = Variables.Variables.AppLogPath;
+                //    if (!Directory.Exists(p))
+                //    {
+                //        Directory.CreateDirectory(Variables.Variables.AppLogPath);
+                //    }
+                //}
+                if (string.IsNullOrEmpty(filename))
+                {
+                    filename = Today();
+                }
+                //File.AppendAllText(Path.Combine(Variables.Variables.AppLogPath, $"{filename}.txt"), Variables.Variables.LogTmp, System.Text.Encoding.UTF8);
+                string txt;
+                lock (_lock)
+                {
+                    txt = logTmp;
+                    logTmp = "";
+                }
+                Task.Run(() =>
+                {
+                    FileExtensions.AppendAllText(Path.Combine(logPath, $"{filename}.txt"), txt);
+                });
+            }
+            //}
+        }
+    }
+}

+ 54 - 0
ReleaseHelper/Utils/MutexExtensions.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Threading;
+
+namespace ReleaseHelper.Utils
+{
+    public static class MutexExtensions
+    {
+        public delegate void DeleWriteLog(string txt);
+        public static DeleWriteLog WriteLog;
+
+        /// <summary>
+        /// 进程间互斥操作
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="action"></param>
+        /// <param name="recursive"></param>
+        private static void Exec(string key, Action action, bool recursive)
+        {
+            using (Mutex mutex = new Mutex(initiallyOwned: false, name: key))
+            {
+                try
+                {
+                    mutex.WaitOne();
+                    action();
+                }
+                catch (AbandonedMutexException ex)
+                {
+                    if (recursive)
+                    {
+                        WriteLog?.Invoke(ex.Message);
+                    }
+                    else
+                    {
+                        Exec(key, action, true);
+                    }
+                }
+                finally
+                {
+                    mutex.ReleaseMutex();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 进程间互斥操作
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="action"></param>
+        public static void Exec(string key, Action action)
+        {
+            Exec(key, action, false);
+        }
+    }
+}

+ 84 - 0
ReleaseHelper/Utils/ProcessExtensions.cs

@@ -0,0 +1,84 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace ReleaseHelper.Utils
+{
+    public static class ProcessExtensions
+    {
+        public static Process RunningInstance(ref Process target)
+        {
+            Process result = null;
+            Process[] processes = Process.GetProcessesByName(target.ProcessName);
+            //遍历与当前进程名称相同的进程列表  
+            foreach (Process process in processes)
+            {
+                //如果实例已经存在则忽略当前进程  
+                if (process.Id != target.Id)
+                {
+                    //保证要打开的进程同已经存在的进程来自同一文件路径
+                    if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\").Equals(target.MainModule.FileName))
+                    {
+                        //返回已经存在的进程
+                        result = process;
+                        break;
+                    }
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 获取运行的
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static Process RunningInstance(string filename)
+        {
+            Process result = null;
+            filename = filename.Replace("/", "\\");
+            Process[] processes = Process.GetProcesses();
+            foreach (Process process in processes)
+            {
+                try
+                {
+                    if (process != null && process.MainModule != null && process.MainModule.FileName.Replace("/", "\\").Equals(filename))
+                    {
+                        result = process;
+                        break;
+                    }
+                }
+                catch
+                {
+                }
+
+            }
+            return result;
+        }
+
+        public static List<Process> GetSubProcess(string parentName)
+        {
+            List<Process> result = new List<Process>();
+            Process[] processes = Process.GetProcesses();
+            int i = 0;
+            foreach (Process process in processes)
+            {
+                try
+                {
+                    Utils.Log.WriteLine($"{++i}:  {process.MainModule.ModuleName} / {process.MainModule.FileName}");
+                    //if (process != null && process.MainModule != null && process.MainModule.ModuleName.FileName.Replace("/", "\\").Equals(filename))
+                    //{
+                    //    result.Add(process);
+                    //    break;
+                    //}
+                }
+                catch
+                {
+
+                }
+
+            }
+            return result;
+        }
+    }
+}

+ 41 - 0
ReleaseHelper/Variables/Const.cs

@@ -0,0 +1,41 @@
+using System.IO;
+
+namespace ReleaseHelper.Variables
+{
+    public static class Const
+    {
+        public const string APP_VERSION = "3.0.1";
+        public const string APP_NAME = "com.marking.pc";
+        public const string PLATFORM = "windows";
+
+        public const string HTTP_HOST = "https://api.dajiaoyan.com/";
+
+        /// <summary>
+        /// 静态文件服务器基础地址
+        /// </summary>
+        public const string HTTP_STATIC_HOST = "https://static.yuansiwei.com/";
+        public const string OSS_PATH = "app/dajiaoyan/ocr";
+
+        public const string APP_BASE_FILENAME = "dajiaoyan.zip";
+
+        /// <summary>
+        /// 在线更新文件
+        /// </summary>
+        public static readonly string HTTP_UPDATE_FILE = $"{HTTP_STATIC_HOST}{OSS_PATH}/{APP_BASE_FILENAME}";
+
+        /// <summary>
+        /// 应用所在目录
+        /// </summary>
+        public static readonly string APP_PATH = Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath);
+        /// <summary>
+        /// 需要上传的本地文件
+        /// </summary>
+        public static readonly string TOBE_UPLOAD_LOCAL_FILE = Path.Combine(APP_PATH, APP_BASE_FILENAME);
+
+        /// <summary>
+        /// 日志目录
+        /// </summary>
+        public static readonly string LOG_DIR = Path.Combine(APP_PATH, "log");
+
+    }
+}

+ 24 - 0
ReleaseHelper/Variables/Variables.cs

@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.IO;
+//using yxq.Models.Http;
+
+namespace ReleaseHelper.Variables
+{
+    public static class Variables
+    {
+        /// <summary>
+        /// 日志路径
+        /// </summary>
+        public static readonly string AppLogPath = Path.Combine(Const.APP_PATH, Const.LOG_DIR);
+        /// <summary>
+        /// 请求头
+        /// </summary>
+        public static Dictionary<string, object> RequestHeaders = new Dictionary<string, object>
+        {
+            {"AppName", Const.APP_NAME },
+            {"AppVersion", Const.APP_VERSION },
+            {"Platform", Const.PLATFORM },
+            {"Authorization", "" },
+        };
+    }
+}

+ 5 - 0
ReleaseHelper/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Aliyun.OSS.SDK" version="2.14.1" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="13.0.4" targetFramework="net472" />
+</packages>

+ 6 - 0
Update/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+</configuration>

+ 77 - 0
Update/Form1.Designer.cs

@@ -0,0 +1,77 @@
+namespace Update
+{
+    partial class Form1
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
+            this.stepProgressLabel = new System.Windows.Forms.Label();
+            this.stepProgressBar = new System.Windows.Forms.ProgressBar();
+            this.SuspendLayout();
+            // 
+            // stepProgressLabel
+            // 
+            this.stepProgressLabel.AutoSize = true;
+            this.stepProgressLabel.Location = new System.Drawing.Point(12, 42);
+            this.stepProgressLabel.Name = "stepProgressLabel";
+            this.stepProgressLabel.Size = new System.Drawing.Size(53, 12);
+            this.stepProgressLabel.TabIndex = 3;
+            this.stepProgressLabel.Text = "检查更新";
+            // 
+            // stepProgressBar
+            // 
+            this.stepProgressBar.Location = new System.Drawing.Point(12, 12);
+            this.stepProgressBar.Name = "stepProgressBar";
+            this.stepProgressBar.Size = new System.Drawing.Size(400, 20);
+            this.stepProgressBar.Step = 1;
+            this.stepProgressBar.TabIndex = 2;
+            // 
+            // Form1
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(425, 69);
+            this.Controls.Add(this.stepProgressLabel);
+            this.Controls.Add(this.stepProgressBar);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MaximizeBox = false;
+            this.Name = "Form1";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "大教研升级程序";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label stepProgressLabel;
+        private System.Windows.Forms.ProgressBar stepProgressBar;
+    }
+}
+

+ 75 - 0
Update/Form1.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Update
+{
+    public partial class Form1 : Form
+    {
+        private readonly static string mainAppPath = Path.Combine(Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath), "..");
+        private readonly static string mainAppFile = Path.Combine(mainAppPath, "dajiaoyan.exe");
+        public Form1()
+        {
+            InitializeComponent();
+            Task.Factory.StartNew(async () =>
+            {
+                await Task.Delay(2000);
+                await Services.Update.UpdateApplication(
+                    "http://static.yuansiwei.com/app/dajiaoyan/ocr/dajiaoyan.zip",
+                    mainAppPath,
+                    UpdateProgress,
+                    AfterUpdate);
+            });
+        }
+
+        public void UpdateProgress(string step, int nowP, int maxP)
+        {
+            Task.Factory.StartNew(() =>
+            {
+                BeginInvoke(new Action(() =>
+                {
+                    stepProgressLabel.Text = step;
+                    stepProgressBar.Minimum = 0;
+                    stepProgressBar.Maximum = maxP;
+                    stepProgressBar.Value = nowP;
+                }));
+            });
+        }
+
+        public void AfterUpdate(bool ok, string info)
+        {
+            BeginInvoke(new Action(() =>
+            {
+                if (ok)
+                {
+                    MessageBox.Show(info, "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                    try
+                    {
+                        Task.Delay(2000);
+                        Process.Start(mainAppFile);
+                    }
+                    catch
+                    {
+
+                    }
+
+                }
+                else
+                {
+                    MessageBox.Show(info, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                }
+                Close();
+            }));
+        }
+
+    }
+}
+

+ 343 - 0
Update/Form1.resx

@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAEAQEEAAAEAGADwMgAAFgAAACgAAABAAAAAggAAAAEAGAAAAAAAyDIAABMLAAATCwAAAAAAAAAA
+        AAD/////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////889/557/zz4Dxw2Dut0Dut0Dut0Dut0Dut0Dut0Dut0D225//////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////557/yyXDrqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////++e/225/rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHssTD/////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////++e/zz4DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////44a/rqyDonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225/////////////77c/ssTDyyXD889//////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////++e/vvVDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHxw2D77c/onwHonwHonwHonwHonwH77c/////////////zz4DonwHo
+        nwHonwHxw2D889//////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////77c/rqyDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHyyXDzz4DvvVDonwHonwHonwH11Y/onwHonwHonwHonwHppRD/////////
+        ///////vvVDonwHonwHonwHonwHppRD11Y//////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////44a/ppRDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D////////xw2DonwHonwHonwHonwHonwHonwHo
+        nwHut0D////////////////rqyDonwHonwHonwHonwHonwHonwHut0D889//////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///225/onwHonwHonwHonwHonwHonwHonwHut0D++e/77c/ppRDonwHonwHxw2D////////xw2DonwHo
+        nwHonwHonwHonwHonwHyyXD////////////889/onwHonwHonwHonwHonwHonwHonwHonwHppRD44a//
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////225/onwHonwHonwHonwHonwHonwHonwHonwHppRD77c/////77c/ppRDonwHonwHv
+        vVD++e/////vvVDonwHonwHonwHonwHonwH225/////////////44a/onwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwH225//////////////////////////////////////////////////////////////
+        ///////////////////////////77c/onwHonwHonwHonwHonwHonwHssTDvvVDonwHonwHppRD77c//
+        ///77c/ppRDonwHonwHssTD++e/889/onwHonwHonwHonwHonwH889/////////////zz4DonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////////////////////////////////
+        ///////////////////////////////////////889/ppRDonwHonwHonwHonwHonwHrqyD++e/////x
+        w2DonwHonwHppRD77c/////77c/ppRDonwHonwHssTD889/onwHonwHonwHonwHppRD/////////////
+        ///ut0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////////////
+        ///////////////////////////////////////////////////////ssTDonwHonwHonwHonwHonwHo
+        nwHonwHssTD++e/////xw2DonwHonwHppRD77c/////77c/ppRDonwHonwHonwHonwHonwHonwHonwHv
+        vVD////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH77c//
+        ///////////////////////////////////////////////////////////////////zz4DonwHonwHo
+        nwHonwHonwHrqyDppRDonwHonwHssTD++e/////xw2DonwHonwHppRDzz4D225/zz4DonwHonwHonwHo
+        nwHonwHonwHonwHzz4D////////////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHrqyD++e/////////////////////////////////////////////////////////////8
+        89/ppRDonwHonwHonwHonwHrqyD++e/77c/ppRDonwHonwHssTD++e/////xw2DonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwH557/////////////225/onwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////
+        ///////////////xw2DonwHonwHonwHonwHonwHonwH225/////77c/ppRDonwHonwHssTD++e/////v
+        vVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH////////////////zz4DonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH557//////////////////////////
+        ///////////////////////////889/onwHonwHonwHonwHonwHonwHonwHonwH225/////77c/ppRDo
+        nwHonwHvvVDzz4DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD////////////////1
+        1Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////
+        ///////////////////////////////////////////xw2DonwHonwHonwHonwHssTDxw2DonwHonwHo
+        nwH225/////77c/ppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0Dut0D225//
+        ///////////////++e/xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwH225/////////////////////////////////////////////++e/onwHonwHonwHonwHonwH4
+        4a/////xw2DonwHonwHonwH225/////77c/onwHonwHonwHonwHonwHonwHonwHonwHut0D225/889//
+        ///////////////////////////////////////44a/ppRDonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHssTD////////////////////////////////////////////11Y/onwHo
+        nwHonwHonwHonwHssTD++e/////xw2DonwHonwHonwH225/77c/onwHonwHonwHonwHonwHonwHssTD7
+        7c/////////////////////////////////////////////////////////77c/ppRDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH557//////////////////////////////////
+        ///////ssTDonwHonwHonwHonwHonwHonwHssTD++e/////xw2DonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHxw2D////////////////////////////////////////////////////////////////////7
+        7c/ppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D/////////////////
+        ///////////////////++e/onwHonwHonwHonwHonwHppRDonwHonwHssTD++e/////xw2DonwHonwHo
+        nwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////////
+        ///////////////////44a/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD/
+        ///////////////////////////////////225/onwHonwHonwHonwHssTD77c/ppRDonwHonwHssTD+
+        +e/////xw2DonwHonwHonwHonwHonwHssTD/////////////////////////////////////////////
+        ///////////////////////////////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwH77c/////////////////////////////////yyXDonwHonwHonwHonwHxw2D////7
+        7c/ppRDonwHonwHssTD77c/44a/onwHonwHonwHonwHonwH889//////////////////////////////
+        ///////////////////////////////////////////////////////889/onwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwH11Y/////////////////////////////////ut0DonwHonwHo
+        nwHonwHonwH225/////77c/ppRDonwHonwHppRDssTDonwHonwHonwHonwHxw2D/////////////////
+        ///////////////////////////////////////////////////////////////////////////ut0Do
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD/////////////////////////////
+        ///rqyDonwHonwHonwHonwHonwHonwH225/////77c/ppRDonwHonwHonwHonwHonwHonwHonwH889//
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////11Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////////
+        ///////////////////onwHonwHonwHonwHonwHonwHonwHonwH225/////++e/ssTDonwHonwHonwHo
+        nwHonwHssTD/////////////////////////////////////////////////////////////////////
+        ///////////////////////////77c/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH////////////////////////////////onwHonwHonwHonwHppRDssTDonwHonwHonwH11Y/77c/z
+        z4DonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////////
+        ///////////////////////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH////////////////////////////////onwHonwHonwHonwHut0D++e/rqyDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////////////////////
+        ///////////////////////////////////////////////////////////////onwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwH////////////////////////////////onwHonwHonwHo
+        nwHonwHppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////
+        ///////////////////////////////////////////////////////////////////////////////u
+        t0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////////////////////////////
+        ///onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH/////////////
+        ///////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHu
+        t0D11Y/++e//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH////////////////////////////////onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyDx
+        w2Dzz4D557//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////77c/onwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH////////////////////////////////ppRDonwHonwHonwHonwHonwHssTDy
+        yXD225/77c//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////11Y/onwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHssTD////////////////////////////////vvVDonwHssTDy
+        yXD44a/++e//////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////////ssTDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD/////////////////////////////
+        ///////++e//////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH11Y//////////////
+        ///////////////////////////////////////////////////////////////////889/44a/yyXDs
+        sTDppRD889//////////////////////////////////////////////////////////////////////
+        ///////////////////////ut0DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH7
+        7c/////////////////////////////////////////////////////////////////77c/225/xw2Dr
+        qyDonwHonwHonwHonwHonwHut0D/////////////////////////////////////////////////////
+        ///////////////////////////////////11Y/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHppRD////////////////////////////////////////////////////77c/zz4Dut0Dp
+        pRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////////////////////////////////
+        ///////////////////////////////////////////////77c/ppRDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHxw2D/////////////////////////////////////////////////
+        ///ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////////////
+        ///////////////////////////////////////////////////////////77c/ppRDonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH77c//////////////////////////////////
+        ///////////////////zz4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHyyXD////////////////////////////////////////////////////////////////44a/ppRDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD/////////////////////
+        ///////////////////////////////////++e/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHssTD77c/////////////////////////////////////////////////8
+        89/vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH225//////
+        ///////////////////////////////////////////////////////zz4DonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0D/////////////////////////////////
+        ///////77c/xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHrqyD////////////////////////////////////////////////////////////////++e/rqyDo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyD/////////////////
+        ///////77c/yyXDut0DppRDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwH557//////////////////////////////////////////////////////////
+        ///////////557/onwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHxw2D/
+        ///////////////////////xw2DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHyyXD/////////////////////////////////////////////
+        ///////////////////////////////yyXDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwH225/////////////////////////ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHrqyD++e//////////////////////////////
+        ///////////////////////////////////////////////////ssTDonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwH77c/////////////////////++e/onwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD77c//////////////////
+        ///////////////////////////////////////////////////////////////////++e/ssTDonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD////////////////////////557/onwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwH44a//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////889/rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHut0D/////////////////////
+        ///zz4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwH225//////////////////////////////////////////////////////////////////////////
+        ///////////////////////////++e/ssTDonwHonwHonwHonwHonwHonwHonwHonwHonwHzz4D/////
+        ///////////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHppRD225//////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////++e/vvVDonwHonwHonwHonwHonwHonwHo
+        nwHonwH44a/////////////////////////rqyDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHrqyD77c//////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////////////////zz4DonwHo
+        nwHonwHonwHonwHonwHonwH889/////////////////////++e/onwHonwHonwHonwHonwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHvvVD++e//////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////77c/ut0DonwHonwHonwHonwHppRD////////////////////////557/onwHonwHonwHo
+        nwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRD44a//////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////225/rqyDonwHonwHut0D////////////////////////z
+        z4DonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHppRDzz4D++e//////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////////////////44a/xw2D77c//////////
+        ///////////////vvVDonwHonwHonwHonwHonwHonwHonwHonwHonwHonwHssTD11Y/++e//////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////rqyDonwHonwHonwHonwHonwHonwHppRDvvVD11Y/889//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ///////////////////////////////////////////////225/zz4Dzz4Dzz4D557/557/++e//////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        ////////////////////////////////////////////////////////////////////////////////
+        //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+</value>
+  </data>
+</root>

+ 22 - 0
Update/Program.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Update
+{
+    internal static class Program
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new Form1());
+        }
+    }
+}

+ 33 - 0
Update/Properties/AssemblyInfo.cs

@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("update")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("update")]
+[assembly: AssemblyCopyright("Copyright ©  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("ffe02c1f-83ba-4968-ab2c-e0e3c538fb91")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
Update/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本: 4.0.30319.42000
+//
+//     对此文件的更改可能导致不正确的行为,如果
+//     重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace update.Properties
+{
+
+
+    /// <summary>
+    ///   强类型资源类,用于查找本地化字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   返回此类使用的缓存 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("update.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
Update/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
Update/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace update.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
Update/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 266 - 0
Update/Services/Update.cs

@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Update.Utils;
+
+namespace Update.Services
+{
+    public static class Update
+    {
+
+        public delegate void DeleUpdateProgress(string step, int nowP, int maxP);
+        public delegate void DeleAfterUpdateProgress(bool ok, string error);
+        private static readonly string DEFAULT_DOWNLOAD_PATH = Path.Combine(Environment.CurrentDirectory, "tmp");
+        private static readonly List<string> IGNORE_PATHS = new List<string>()
+        {
+            "update"
+        };
+
+        public struct UpdateInfo
+        {
+            public string MD5 { get; set; }
+            public double Version { get; set; }
+        }
+
+        public static async Task UpdateApplication(string url, string targetPath, DeleUpdateProgress updateProgress, DeleAfterUpdateProgress afterUpdateProgress, string tmpPath = null)
+        {
+            await Task.Factory.StartNew(async () =>
+            {
+                int maxSteps = 7, step = 1;
+                if (string.IsNullOrEmpty(tmpPath))
+                {
+                    tmpPath = DEFAULT_DOWNLOAD_PATH;
+                }
+                string localFile = Path.Combine(tmpPath, Path.GetFileName(url));
+                string uncompressPath = Path.Combine(tmpPath, "tmp");
+                void clearCache()
+                {
+                    Task.Run(async () => { await ClearCache(uncompressPath, localFile); });
+                }
+                //await ClearTempPath(uncompressPath);
+                updateProgress("获取更新参数", step++, maxSteps);
+                UpdateInfo info = await GetUpdateInfo(url);
+                if (info.Version < 1 || string.IsNullOrEmpty(info.MD5))
+                {
+                    AfterUpdateProgressEvent(afterUpdateProgress, false, "获取更新参数失败!", clearCache);
+                    return;
+                }
+                updateProgress("下载", step++, maxSteps);
+                if (!await Download(url, localFile))
+                {
+                    AfterUpdateProgressEvent(afterUpdateProgress, false, "下载失败!", clearCache);
+                    return;
+                }
+                updateProgress("校验", step++, maxSteps);
+                if (!CheckFileMd5(localFile, info.MD5))
+                {
+                    AfterUpdateProgressEvent(afterUpdateProgress, false, "校验失败!", clearCache);
+                    return;
+                }
+                updateProgress("解压", step++, maxSteps);
+                if (!await UnCompress(localFile, uncompressPath))
+                {
+                    AfterUpdateProgressEvent(afterUpdateProgress, false, "解压失败!", clearCache);
+                    return;
+                }
+                updateProgress("更新", step++, maxSteps);
+                if (!await CopyDirectory(uncompressPath, targetPath))
+                {
+                    updateProgress("更新失败", step, maxSteps);
+                    AfterUpdateProgressEvent(afterUpdateProgress, false, "更新失败!", clearCache);
+                    return;
+                }
+                updateProgress("清理缓存", step++, maxSteps);
+                if (!await ClearCache(uncompressPath, localFile))
+                {
+                    updateProgress("清理缓存失败", step, maxSteps);
+                    AfterUpdateProgressEvent(afterUpdateProgress, true, "更新完成,清理缓存失败!", null);
+                }
+                else
+                {
+                    updateProgress("更新完成", step++, maxSteps);
+                    AfterUpdateProgressEvent(afterUpdateProgress, true, "更新完成!", null);
+                }
+            });
+        }
+
+        private static void AfterUpdateProgressEvent(DeleAfterUpdateProgress afterUpdateProgress, bool res, string error, Action task)
+        {
+            afterUpdateProgress.Invoke(res, error);
+            task?.Invoke();
+        }
+
+        private static async Task<UpdateInfo> GetUpdateInfo(string url)
+        {
+            UpdateInfo info = new UpdateInfo();
+            try
+            {
+                url = Regex.Replace(url, "\\.[^.]{2,5}$", ".txt", RegexOptions.IgnoreCase);
+                HttpResponseMessage response = await Http.Fetch(new Http.Request(url, Http.Method.Get));
+                if (response.IsSuccessStatusCode)
+                {
+                    string txt = await response.Content.ReadAsStringAsync();
+                    info = Utils.Json.Decode<UpdateInfo>(txt);
+                }
+            }
+            catch
+            {
+
+            }
+            return info;
+        }
+
+        private static bool CheckFileMd5(string localFileName, string md5)
+        {
+            return FileExtensions.FileMd5(localFileName) == md5;
+        }
+
+        private static async Task<bool> ClearTempPath(string targetPath)
+        {
+            try
+            {
+                await Task.Run(() =>
+                {
+                    DirectoryInfo di = new DirectoryInfo(targetPath);
+                    if (di.Exists)
+                    {
+                        di.Delete(true);
+                    }
+                    FileExtensions.CheckDirectory(targetPath, true);
+                });
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+
+        }
+
+
+        private static async Task<bool> Download(string url, string localFilename)
+        {
+            try
+            {
+                HttpResponseMessage response = await Utils.Http.Fetch(new Utils.Http.Request(url, Utils.Http.Method.Get));
+                if (response != null && response.IsSuccessStatusCode)
+                {
+                    var bytes = await response.Content.ReadAsByteArrayAsync();
+                    FileExtensions.WriteBytes(localFilename, bytes);
+                }
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+
+        }
+
+        private static async Task<bool> UnCompress(string localFilename, string targetPath)
+        {
+            bool r = false;
+            try
+            {
+                r = await Task.Run(async () =>
+                {
+                    bool res = await ClearTempPath(targetPath);
+                    if (res)
+                    {
+                        ZipFile.ExtractToDirectory(localFilename, targetPath);
+                        res = true;
+                    }
+                    return res;
+                });
+            }
+            catch
+            {
+            }
+            return r;
+        }
+
+        private static async Task<bool> CopyDirectory(string srcPath, string dstPath)
+        {
+            bool copyDirectory(string sp, string dp)
+            {
+                string np;
+                try
+                {
+                    DirectoryInfo dir = new DirectoryInfo(sp);
+                    FileSystemInfo[] fileinfo = dir.GetFileSystemInfos();  //获取目录下(不包含子目录)的文件和子目录
+                    foreach (FileSystemInfo i in fileinfo)
+                    {
+                        np = Path.Combine(dp, i.Name);
+                        if (i is DirectoryInfo)     //判断是否文件夹
+                        {
+                            if (!Directory.Exists(np))
+                            {
+                                Directory.CreateDirectory(np);   //目标目录下不存在此文件夹即创建子文件夹
+                            }
+                            if (!IsIgnoreDirectory(i.FullName, srcPath))
+                            {
+                                // 判断是否是忽略目录
+                                copyDirectory(i.FullName, np);    //递归调用复制子文件夹
+                            }
+                        }
+                        else
+                        {
+                            File.Copy(i.FullName, np, true);      //不是文件夹即复制文件,true表示可以覆盖同名文件
+                        }
+                    }
+                    return true;
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+            return await Task.Run(() => { return copyDirectory(srcPath, dstPath); });
+        }
+
+        private static bool IsIgnoreDirectory(string p, string srcRootPath)
+        {
+            bool res = false;
+            Uri a = new Uri(p), b;
+            foreach (string s in IGNORE_PATHS)
+            {
+                b = new Uri(Path.Combine(srcRootPath, s));
+                res = a == b;
+                if (res)
+                {
+                    break;
+                }
+            }
+            return res;
+        }
+
+
+        private static async Task<bool> ClearCache(string cachePath, string cacheUpdateFile)
+        {
+            bool res = false;
+            await Task.Run(() =>
+            {
+                try
+                {
+                    DirectoryInfo di = new DirectoryInfo(cachePath);
+                    if (di.Exists)
+                    {
+                        di.Delete(true);
+                    }
+                    if (File.Exists(cacheUpdateFile))
+                    {
+                        File.Delete(cacheUpdateFile);
+                    }
+                    res = true;
+                }
+                catch { }
+            });
+            return res;
+        }
+
+    }
+}

+ 102 - 0
Update/Update.csproj

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{FFE02C1F-83BA-4968-AB2C-E0E3C538FB91}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>update</RootNamespace>
+    <AssemblyName>update</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>none</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>logo.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.IO.Compression.FileSystem" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Form1.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Form1.Designer.cs">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Services\Update.cs" />
+    <Compile Include="Utils\FileExtensions.cs" />
+    <Compile Include="Utils\Functions.cs" />
+    <Compile Include="Utils\Http.cs" />
+    <Compile Include="Utils\Json.cs" />
+    <Compile Include="Utils\Log.cs" />
+    <Compile Include="Utils\MutexExtensions.cs" />
+    <EmbeddedResource Include="Form1.resx">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="logo.ico" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 110 - 0
Update/Utils/FileExtensions.cs

@@ -0,0 +1,110 @@
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Update.Utils
+{
+    public static class FileExtensions
+    {
+        /// <summary>
+        /// 生成文件的md5
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        private static string FilenameMutexKey(string filename)
+        {
+            return Functions.GenMd5(filename);
+        }
+
+        /// <summary>
+        /// 写入二进制值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="bytes"></param>
+        public static void WriteBytes(string filename, byte[] bytes)
+        {
+            if (bytes != null)
+            {
+                string key = FilenameMutexKey(filename);
+                CheckDirectory(Path.GetDirectoryName(filename), true);
+                void act()
+                {
+                    File.WriteAllBytes(filename, bytes);
+                }
+                MutexExtensions.Exec(key, act);
+            }
+        }
+
+
+        /// <summary>
+        /// 添加所有文本
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <param name="txt"></param>
+        /// <param name="encoding"></param>
+        public static void AppendAllText(string filename, string txt, Encoding encoding = null)
+        {
+            encoding = encoding ?? Encoding.UTF8;
+            string key = FilenameMutexKey(filename);
+            CheckDirectory(Path.GetDirectoryName(filename), true);
+            void act()
+            {
+                File.AppendAllText(filename, txt, encoding);
+            }
+            MutexExtensions.Exec(key, act);
+        }
+
+        /// <summary>
+        /// 检查目录是否存在,如果不存在,将根据条件创建或不创建
+        /// </summary>
+        /// <param name="p"></param>
+        /// <param name="create">如果目录不存在,是否创建</param>
+        /// <returns></returns>
+        public static bool CheckDirectory(string p, bool create)
+        {
+
+            bool res = false;
+            if (!string.IsNullOrEmpty(p))
+            {
+                res = Directory.Exists(p);
+
+                if (!res && create)
+                {
+                    Directory.CreateDirectory(p);
+                    res = true;
+                }
+            }
+            return res;
+        }
+
+
+        /// <summary>
+        /// 获取制定路径文件的md5值
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static string FileMd5(string filename)
+        {
+            string res = "";
+            try
+            {
+                using (FileStream file = new FileStream(filename, FileMode.Open))
+                using (MD5 md5 = new MD5CryptoServiceProvider())
+                {
+                    byte[] retVal = md5.ComputeHash(file);
+                    StringBuilder sb = new StringBuilder();
+                    for (int i = 0; i < retVal.Length; i++)
+                    {
+                        sb.Append(retVal[i].ToString("x2"));
+                    }
+                    res = sb.ToString();
+                }
+            }
+            catch
+            {
+
+            }
+            return res;
+        }
+    }
+}

+ 45 - 0
Update/Utils/Functions.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq.Expressions;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Update.Utils
+{
+    public static class Functions
+    {
+
+
+        /// <summary>
+        /// 字符串生成MD5
+        /// </summary>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public static string GenMd5(string s)
+        {
+            //就是比string往后一直加要好的优化容器
+            StringBuilder sb = new StringBuilder();
+            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
+            {
+                //将输入字符串转换为字节数组并计算哈希。
+                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(s));
+
+                //X为     十六进制 X都是大写 x都为小写
+                //2为 每次都是两位数
+                //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。 
+                //遍历哈希数据的每个字节
+                //并将每个字符串格式化为十六进制字符串。
+                int length = data.Length;
+                for (int i = 0; i < length; i++)
+                {
+                    sb.Append(data[i].ToString("X2"));
+                }
+            }
+            return sb.ToString();
+        }
+
+    }
+}

+ 398 - 0
Update/Utils/Http.cs

@@ -0,0 +1,398 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace Update.Utils
+{
+    public static class Http
+    {
+        public struct Response
+        {
+            public HttpStatusCode code;
+            public HttpContent message;
+            public string url;
+        }
+
+        public struct Request
+        {
+            /// <summary>
+            /// 请求的网址
+            /// </summary>
+            public string uri;
+            /// <summary>
+            /// 头信息
+            /// </summary>
+            public Dictionary<string, object> headers;
+            /// <summary>
+            /// 请求的数据
+            /// </summary>
+            public Dictionary<string, object> data;
+            /// <summary>
+            /// 上传的文件
+            /// </summary>
+            public Dictionary<string, string> files;
+            /// <summary>
+            /// 上传的文件二进制流
+            /// </summary>
+            public Dictionary<string, byte[]> fileStream;
+            /// <summary>
+            /// 请求类型
+            /// </summary>
+            public RequestContentType contentType;
+            /// <summary>
+            /// 请求方式
+            /// </summary>
+            public Method method;
+
+            public void Init()
+            {
+                uri = "";
+                headers = new Dictionary<string, object>();
+                data = new Dictionary<string, object>();
+                files = new Dictionary<string, string>();
+                fileStream = new Dictionary<string, byte[]>();
+                contentType = RequestContentType.Default;
+                method = Method.Get;
+            }
+
+            public Request(string uri, Method method)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = null;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+
+            public Request(string uri, Method method, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = new Dictionary<string, object>();
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, RequestContentType contentType, Dictionary<string, object> headers, Dictionary<string, object> data)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                files = null;
+                fileStream = null;
+                this.contentType = contentType;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                fileStream = null;
+                contentType = RequestContentType.Default;
+            }
+
+            public Request(string uri, Method method, Dictionary<string, object> headers, Dictionary<string, object> data, Dictionary<string, string> files, Dictionary<string, byte[]> fileStream)
+            {
+                this.uri = uri;
+                this.method = method;
+                this.headers = headers;
+                this.data = data;
+                this.files = files;
+                this.fileStream = fileStream;
+                contentType = RequestContentType.Default;
+            }
+
+
+        }
+
+        /// <summary>
+        /// 请求内容类型
+        /// </summary>
+        public enum RequestContentType
+        {
+            Default, Json
+        }
+
+        /// <summary>
+        /// 请求方法
+        /// </summary>
+        public enum Method
+        {
+            Get, Post, Put, Delete, Patch
+        }
+
+        /// <summary>
+        /// 初始化请求头
+        /// </summary>
+        /// <param name="httpClient"></param>
+        /// <param name="headers"></param>
+        private static void InitClientHeaders(ref HttpClient httpClient, Dictionary<string, object> headers)
+        {
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    if (!string.IsNullOrEmpty(header.Key) && !string.IsNullOrEmpty(header.Value.ToString()))
+                    {
+                        httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToString());
+                    }
+
+                }
+            }
+        }
+
+        /// <summary>
+        /// 获取
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static async Task<HttpResponseMessage> Fetch(Request request)
+        {
+            HttpResponseMessage response;
+            switch (request.method)
+            {
+                case Method.Post:
+                    response = await Post(request);
+                    break;
+                case Method.Put:
+                    response = await Put(request);
+                    break;
+                case Method.Patch:
+                    response = await Patch(request);
+                    break;
+                case Method.Delete:
+                    response = await Delete(request);
+                    break;
+                default:
+                    response = await Get(request);
+                    break;
+            }
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Get(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+                InitClientHeaders(ref client, request.headers);
+                if (request.data != null)
+                {
+                    //client.DefaultRequestHeaders.Add()
+                    url = string.Concat(url, "?");
+                    foreach (var item in request.data)
+                    {
+                        url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                    }
+                    url = url.Substring(0, url.Length - 1);
+                }
+                try
+                {
+                    response = await client.GetAsync(url);
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+            return response;
+        }
+
+        private static HttpContent InitNonGetContent(Request request)
+        {
+            HttpContent content = null;
+
+            if (request.contentType == RequestContentType.Json)
+            {
+                // 发送json 数据
+                HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(request.data), Encoding.UTF8);
+                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+                content = httpContent;
+            }
+            else
+            //if (request.files != null || request.fileStream != null)
+            {
+                // 普通的键值对提交
+                MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
+                //FormUrlEncodedContent formUrlEncodedContent = new FormUrlEncodedContent();
+                if (request.files != null)
+                {
+                    foreach (var file in request.files)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes(file.Value)), file.Key, Path.GetFileName(file.Value));
+                    }
+                }
+                if (request.fileStream != null)
+                {
+                    foreach (var file in request.fileStream)
+                    {
+                        multipartFormDataContent.Add(new ByteArrayContent(file.Value), file.Key, file.Key);
+                    }
+                }
+
+                if (request.data != null)
+                {
+                    foreach (var d in request.data)
+                    {
+                        multipartFormDataContent.Add(new StringContent(d.Value.ToString(), Encoding.UTF8), d.Key);
+                    }
+                }
+                content = multipartFormDataContent;
+            }
+            return content;
+        }
+
+        public static async Task<HttpResponseMessage> Post(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PostAsync(request.uri, content);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Patch(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+                HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), request.uri);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    httpRequestMessage.Content = content;
+                    response = await client.SendAsync(httpRequestMessage);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+                httpRequestMessage.Dispose();
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Put(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                HttpContent content = InitNonGetContent(request);
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    response = await client.PutAsync(request.uri, content);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+        public static async Task<HttpResponseMessage> Delete(Request request)
+        {
+            HttpResponseMessage response = null;
+            if (!string.IsNullOrEmpty(request.uri))
+            {
+                HttpClient client = new HttpClient();
+                string url = request.uri;
+
+                try
+                {
+                    InitClientHeaders(ref client, request.headers);
+                    if (request.data != null)
+                    {
+                        url = string.Concat(url, "?");
+                        foreach (var item in request.data)
+                        {
+                            url = string.Concat(url, $"{item.Key}={HttpUtility.UrlEncode(item.Value.ToString())}&");
+                        }
+                    }
+                    response = await client.DeleteAsync(request.uri);
+                    //client.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    Log.Error(ex);
+                }
+                finally
+                {
+                    client?.Dispose();
+                }
+            }
+
+            return response;
+        }
+
+    }
+}

+ 67 - 0
Update/Utils/Json.cs

@@ -0,0 +1,67 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.IO;
+using System.Text;
+
+namespace Update.Utils
+{
+
+    public static class Json
+    {
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public static T Decode<T>(Stream stream)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(new JavaScriptDateTimeConverter());//指定转化日期的格式
+            using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                return serializer.Deserialize<T>(reader);
+            }
+        }
+
+        /// <summary>
+        /// json格式解码
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="txt"></param>
+        /// <returns></returns>
+        public static T Decode<T>(string txt)
+        {
+            JsonSerializer serializer = new JsonSerializer();
+            serializer.Converters.Add(new JavaScriptDateTimeConverter());//指定转化日期的格式
+            using (StringReader sr = new StringReader(txt))
+            using (JsonReader reader = new JsonTextReader(sr))
+            {
+                T res = default;
+                try
+                {
+                    res = serializer.Deserialize<T>(reader);
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);
+                }
+                return res;
+
+            }
+        }
+
+        /// <summary>
+        /// json格式编码
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static string Encode(object obj)
+        {
+            return JsonConvert.SerializeObject(obj, new JavaScriptDateTimeConverter());
+        }
+
+    }
+}

+ 105 - 0
Update/Utils/Log.cs

@@ -0,0 +1,105 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Update.Utils
+{
+    public static class Log
+    {
+        private readonly static object _lock = new object();
+        private static string logAll = "";
+        private static string logTmp = "";
+        private static readonly string logPath = Path.Combine(Environment.CurrentDirectory, "log");
+
+        /// <summary>
+        /// 日志处理的委托
+        /// </summary>
+        /// <param name="s"></param>
+        public delegate void SetLog(string s);
+        /// <summary>
+        /// 具体委托的实例
+        /// </summary>
+        public static SetLog DoSetLog;
+
+        public static string Now()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+        }
+
+        private static string Today()
+        {
+            return DateTime.Now.ToString("yyyy-MM-dd");
+        }
+
+        /// <summary>
+        /// 记录错误
+        /// </summary>
+        /// <param name="exception"></param>
+        public static void Error(Exception exception)
+        {
+            WriteLine(exception.StackTrace, "error", force: true);
+        }
+
+        /// <summary>
+        /// 记录信息
+        /// </summary>
+        /// <param name="s"></param>
+        /// <param name="t"></param>
+        /// <param name="filename"></param>
+        public static void WriteLine(string s, string t = "log", string filename = null, bool force = false)
+        {
+            if (s != null)
+            {
+                string info = $"{Now()} [{t}] {s}\r\n";
+                Console.Write(info);
+                lock (_lock)
+                {
+                    logAll = string.Concat(logAll, info);
+                    logTmp = string.Concat(logTmp, info);
+                }
+#if DEBUG
+                DoSetLog?.Invoke(logTmp);
+#endif
+                //Forms.Instances.MainEntrance().SetLog(Variables.Variables.LogTmp);
+                Save(force);
+            }
+        }
+
+        /// <summary>
+        /// 保存信息
+        /// </summary>
+        /// <param name="force"></param>
+        public static void Save(bool force = false, string filename = null)
+        {
+            //lock (Variables.Variables.LogTmp)
+            //{
+            if (force || logTmp.Length > 30000)
+            {
+                //if (!File.Exists(Variables.Variables.AppLogPath))
+                //{
+                //    string p = Variables.Variables.AppLogPath;
+                //    if (!Directory.Exists(p))
+                //    {
+                //        Directory.CreateDirectory(Variables.Variables.AppLogPath);
+                //    }
+                //}
+                if (string.IsNullOrEmpty(filename))
+                {
+                    filename = Today();
+                }
+                //File.AppendAllText(Path.Combine(Variables.Variables.AppLogPath, $"{filename}.txt"), Variables.Variables.LogTmp, System.Text.Encoding.UTF8);
+                string txt;
+                lock (_lock)
+                {
+                    txt = logTmp;
+                    logTmp = "";
+                }
+                Task.Run(() =>
+                {
+                    FileExtensions.AppendAllText(Path.Combine(logPath, $"{filename}.txt"), txt);
+                });
+            }
+            //}
+        }
+    }
+}

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