ntminer 4 年之前
父节点
当前提交
3cd755ca7d
共有 100 个文件被更改,包括 940 次插入1686 次删除
  1. 41 5
      NTMiner.sln
  2. 0 3
      src/AppModels/AppModels.csproj
  3. 2 2
      src/AppModels/AppRoot.cs
  4. 2 10
      src/AppModels/AppStatic.cs
  5. 0 1
      src/AppModels/MinerStudio/IMinerStudioService.cs
  6. 0 17
      src/AppModels/MinerStudio/Impl/LocalMinerStudioService.cs
  7. 0 6
      src/AppModels/MinerStudio/Impl/ServerMinerStudioService.cs
  8. 0 6
      src/AppModels/MinerStudio/Messages.cs
  9. 0 6
      src/AppModels/MinerStudio/MinerStudioRoot.cs
  10. 0 35
      src/AppModels/MinerStudio/MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs
  11. 0 4
      src/AppModels/MinerStudio/MinerStudioService.cs
  12. 0 236
      src/AppModels/MinerStudio/Vms/ChartViewModel.cs
  13. 0 56
      src/AppModels/MinerStudio/Vms/ChartsWindowViewModel.cs
  14. 0 21
      src/AppModels/MinerStudio/Vms/CoinSnapshotDataViewModel.cs
  15. 66 15
      src/AppModels/MinerStudio/Vms/MinerClientsWindowViewModel.cs
  16. 15 4
      src/AppModels/Vms/MinerProfileViewModel.cs
  17. 0 5
      src/AppViews0/AppViewFactory.cs
  18. 0 11
      src/AppViews0/AppViews0.csproj
  19. 0 372
      src/AppViews0/MinerStudio/Views/ChartsWindow.xaml
  20. 0 199
      src/AppViews0/MinerStudio/Views/ChartsWindow.xaml.cs
  21. 0 2
      src/AppViews0/MinerStudio/Views/Design/ChartsWindowViewModel.xaml
  22. 2 2
      src/AppViews0/MinerStudio/Views/MinerClientsWindow.xaml
  23. 3 0
      src/AppViews0/MinerStudio/Views/Ucs/MinerClients.xaml
  24. 3 0
      src/AppViews0/Views/ConsoleWindow.xaml
  25. 5 0
      src/AppViews0/Views/ConsoleWindow.xaml.cs
  26. 1 2
      src/AppViews0/Views/Ucs/InnerProperty.xaml
  27. 0 20
      src/AppViews0/Views/Ucs/MainMenu.xaml
  28. 1 1
      src/AppViews0/Views/Ucs/StateBar.xaml
  29. 1 0
      src/AppViews0/Views/Ucs/WalletEdit.xaml.cs
  30. 1 1
      src/BlankWindow/GlowWindow.xaml.cs
  31. 3 3
      src/CalcConfigUpdater/CalcConfigUpdater.csproj
  32. 13 18
      src/MinerClient/App.xaml.cs
  33. 0 2
      src/NTMiner.Controllers/IClientDataBinaryController`1.cs
  34. 0 10
      src/NTMiner.Controllers/ICoinSnapshotController.cs
  35. 0 9
      src/NTMiner.Controllers/IWsServerNodeController.cs
  36. 0 1
      src/NTMiner.Controllers/NTMiner.Controllers.csproj
  37. 7 2
      src/NTMinerClient/Core/Impl/CalcConfigSet.cs
  38. 0 6
      src/NTMinerClient/Core/Impl/CoinSnapshotSet.cs
  39. 1 1
      src/NTMinerClient/Mine/Cleaner.cs
  40. 1 1
      src/NTMinerClient/Mine/MineContext.cs
  41. 0 15
      src/NTMinerClient/MinerClientTempPath.cs
  42. 0 7
      src/NTMinerClient/NTMinerClient.csproj
  43. 267 201
      src/NTMinerDaemon/Ws/AbstractWsClient.cs
  44. 0 98
      src/NTMinerDataSchemas/Core/MinerServer/ClientData.cs
  45. 0 8
      src/NTMinerDataSchemas/Core/MinerServer/CoinSnapshotData.cs
  46. 4 1
      src/NTMinerDataSchemas/Core/MinerServer/QueryClientsResponse.cs
  47. 3 0
      src/NTMinerDataSchemas/NTKeyword.cs
  48. 1 0
      src/NTMinerDataSchemas/Ws/WsClientState.cs
  49. 1 1
      src/NTMinerHub/Hub/IMessagePathHub.cs
  50. 82 3
      src/NTMinerHub/Hub/MessagePathHub.cs
  51. 0 89
      src/NTMinerHub/Hub/MessagePath`1.cs
  52. 0 2
      src/NTMinerHub/NTMinerHub.csproj
  53. 3 3
      src/NTMinerLogging/ILoggingService.cs
  54. 3 3
      src/NTMinerLogging/Impl/Log4NetLoggingService.cs
  55. 3 3
      src/NTMinerLogging/Logger.cs
  56. 0 2
      src/NTMinerRpcClient/NTMinerRpcClient.csproj
  57. 0 1
      src/NTMinerRpcClient/RpcRoot.cs
  58. 0 39
      src/NTMinerRpcClient/Services/Official/ClientDataBinaryService.cs
  59. 0 27
      src/NTMinerRpcClient/Services/Official/CoinSnapshotService.cs
  60. 0 21
      src/NTMinerRpcClient/Services/Official/WsServerNodeService.cs
  61. 0 2
      src/NTMinerRpcClient/Services/OfficialServices.cs
  62. 7 8
      src/NTMinerServer/Core/Impl/ReadonlyUserSet.cs
  63. 2 2
      src/NTMinerServer/Core/Impl/WsServerNodeAddressSetBase.cs
  64. 36 1
      src/NTMinerServer/Core/Messages.cs
  65. 22 0
      src/NTMinerServer/Core/Mq/MinerClientMqBodyUtil.cs
  66. 3 3
      src/NTMinerServer/Core/Mq/Senders/Impl/WsServerNodeMqSender.cs
  67. 5 5
      src/NTMinerServer/Core/Redis/Impl/ReadOnlyMinerRedis.cs
  68. 5 5
      src/NTMinerServer/Core/Redis/Impl/ReadOnlyUserRedis.cs
  69. 4 4
      src/NTMinerServer/Core/Redis/Impl/ReadOnlyWsServerNodeRedis.cs
  70. 7 7
      src/NTMinerServer/Core/Redis/Impl/SpeedDataRedis.cs
  71. 1 1
      src/NTMinerServer/IMqRedis.cs
  72. 8 0
      src/NTMinerServer/MqKeyword.cs
  73. 5 5
      src/NTMinerServer/MqRedis.cs
  74. 6 11
      src/NTMinerServer/NTMinerServer.csproj
  75. 0 1
      src/NTMinerServer/packages.config
  76. 19 0
      src/NTMinerlib/Messages.cs
  77. 27 0
      src/NTMinerlib/TempPath.cs
  78. 0 7
      src/NTMinerlib/VirtualRoot.cs
  79. 3 3
      src/NTMinerlib/VirtualRoot.partials.Hub.cs
  80. 0 0
      src/ServerCommon/Core/ICaptchaSet.cs
  81. 0 0
      src/ServerCommon/Core/IHostConfig.cs
  82. 0 0
      src/ServerCommon/Core/IReadonlyUserSet.cs
  83. 0 0
      src/ServerCommon/Core/IUserAppSettingSet.cs
  84. 0 0
      src/ServerCommon/Core/IUserMineWorkSet.cs
  85. 0 0
      src/ServerCommon/Core/IUserMinerGroupSet.cs
  86. 0 0
      src/ServerCommon/Core/IUserSet.cs
  87. 0 0
      src/ServerCommon/Core/Impl/HostConfigData.cs
  88. 10 0
      src/ServerCommon/IpSet/IRemoteIpSet.cs
  89. 35 0
      src/ServerCommon/IpSet/Impl/RemoteIpSet.cs
  90. 24 0
      src/ServerCommon/IpSet/RemoteIpEntry.cs
  91. 0 0
      src/ServerCommon/MinerSignExtensions.cs
  92. 36 0
      src/ServerCommon/Properties/AssemblyInfo.cs
  93. 78 0
      src/ServerCommon/ServerCommon.csproj
  94. 10 1
      src/ServerCommon/ServerRoot.cs
  95. 4 0
      src/ServerCommon/packages.config
  96. 二进制
      src/ThirdPartyDlls/websocket-sharp.dll
  97. 20 0
      src/UnitTests/EncodingTests.cs
  98. 8 1
      src/UnitTests/EnumTests.cs
  99. 7 0
      src/UnitTests/GuidTests.cs
  100. 13 0
      src/UnitTests/NameValueCollectionTests.cs

+ 41 - 5
NTMiner.sln

@@ -102,7 +102,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{DC5626
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerServer", "src\NTMinerServer\NTMinerServer.csproj", "{CB2619B7-3F59-41B7-A562-4A3F117822CE}"
 EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserApp", "UserApp", "{2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiServer", "src\WebApiServer\WebApiServer.csproj", "{E7B88637-7704-4701-98E0-7875BB6E8C98}"
 EndProject
@@ -110,6 +110,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WsServer", "src\WsServer\Ws
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E0B0D173-418C-49D0-9018-99BC6C526CF5}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerCommon", "src\ServerCommon\ServerCommon.csproj", "{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Official", "Official", "{3A26E27F-79C5-44D5-B25F-307A6EFE0636}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{103F58A8-D0E9-405D-8FF0-DD600FCA2282}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserServer", "src\UserServer\UserServer.csproj", "{7141D542-E321-406D-A242-B857255F79F6}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -432,6 +440,30 @@ Global
 		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x64.Build.0 = Release|Any CPU
 		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.ActiveCfg = Release|Any CPU
 		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.Build.0 = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|x64.Build.0 = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Debug|x86.Build.0 = Debug|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|x64.ActiveCfg = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|x64.Build.0 = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|x86.ActiveCfg = Release|Any CPU
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}.Release|x86.Build.0 = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|x64.Build.0 = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Debug|x86.Build.0 = Debug|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|x64.ActiveCfg = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|x64.Build.0 = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|x86.ActiveCfg = Release|Any CPU
+		{7141D542-E321-406D-A242-B857255F79F6}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -443,7 +475,7 @@ Global
 		{DD8E010E-D5E9-47CF-9CB4-8DC6E13D483D} = {3E8B6DD0-5039-4E01-899B-2FB28372E66E}
 		{3426F677-5026-4BB1-A0B2-DD5D9BD70D07} = {6F255C76-FE02-4484-8368-DD7899E06FDC}
 		{B3667589-D9B1-489E-AB4F-D1EC06A40A9D} = {6F255C76-FE02-4484-8368-DD7899E06FDC}
-		{5646CADA-FEAF-4991-ABDA-DBEBCD54D792} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
+		{5646CADA-FEAF-4991-ABDA-DBEBCD54D792} = {103F58A8-D0E9-405D-8FF0-DD600FCA2282}
 		{6ED1F43A-B43F-4BE4-80DD-49297CF3DC4F} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
 		{526C022A-3C86-4998-A8F1-F5860F183C7C} = {531D065C-4FA2-4B12-9475-4F61C4BFD974}
 		{128B59A2-FA1A-479C-9136-7EB866B96FB4} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
@@ -460,11 +492,15 @@ Global
 		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5} = {E0B0D173-418C-49D0-9018-99BC6C526CF5}
 		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680} = {E0B0D173-418C-49D0-9018-99BC6C526CF5}
 		{DC562605-2F35-4D5C-B576-829D8654399D} = {E32C4009-7A81-4A60-B561-286D8A0C5E06}
-		{CB2619B7-3F59-41B7-A562-4A3F117822CE} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE} = {3A26E27F-79C5-44D5-B25F-307A6EFE0636}
 		{2C8C5FC2-8B81-4228-8EBD-6B491216FBC1} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
-		{E7B88637-7704-4701-98E0-7875BB6E8C98} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
-		{637E9AD8-6410-419B-8FEE-2528130F871A} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
+		{E7B88637-7704-4701-98E0-7875BB6E8C98} = {103F58A8-D0E9-405D-8FF0-DD600FCA2282}
+		{637E9AD8-6410-419B-8FEE-2528130F871A} = {103F58A8-D0E9-405D-8FF0-DD600FCA2282}
 		{E0B0D173-418C-49D0-9018-99BC6C526CF5} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
+		{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{3A26E27F-79C5-44D5-B25F-307A6EFE0636} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{103F58A8-D0E9-405D-8FF0-DD600FCA2282} = {3A26E27F-79C5-44D5-B25F-307A6EFE0636}
+		{7141D542-E321-406D-A242-B857255F79F6} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {A5D14B0A-73FF-46E8-87F3-7E83FFC1DBDD}

+ 0 - 3
src/AppModels/AppModels.csproj

@@ -59,7 +59,6 @@
     <Compile Include="AppRoot.partials.CoinProfileViewModels.cs" />
     <Compile Include="MinerStudio\Impl\EmptyWsClient.cs" />
     <Compile Include="MinerStudio\MinerStudioService.cs" />
-    <Compile Include="MinerStudio\MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs" />
     <Compile Include="AppRoot.partials.CoinViewModels.cs" />
     <Compile Include="MinerStudio\MinerStudioRoot.partials.ColumnsShowViewModels.cs" />
     <Compile Include="AppRoot.partials.DriveSetViewModel.cs" />
@@ -119,8 +118,6 @@
     <Compile Include="MinerStudio\Vms\CalcConfigViewModel.cs" />
     <Compile Include="MinerStudio\Vms\CalcConfigViewModels.cs" />
     <Compile Include="Vms\CalcViewModel.cs" />
-    <Compile Include="MinerStudio\Vms\ChartsWindowViewModel.cs" />
-    <Compile Include="MinerStudio\Vms\ChartViewModel.cs" />
     <Compile Include="Vms\CoinGroupViewModel.cs" />
     <Compile Include="Vms\CoinIncomeViewModel.cs" />
     <Compile Include="Vms\CoinKernelProfileViewModel.cs" />

+ 2 - 2
src/AppModels/AppRoot.cs

@@ -58,8 +58,8 @@ namespace NTMiner {
         #region MainWindowHeight MainWindowWidth
         public static double MainWindowHeight {
             get {
-                if (SystemParameters.WorkArea.Size.Height >= 620) {
-                    return 620;
+                if (SystemParameters.WorkArea.Size.Height >= 630) {
+                    return 630;
                 }
                 else if (SystemParameters.WorkArea.Size.Height >= 520) {
                     return 520;

+ 2 - 10
src/AppModels/AppStatic.cs

@@ -123,7 +123,7 @@ namespace NTMiner {
         public static string LogsDirFullName {
             get {
                 if (ClientAppType.IsMinerClient) {
-                    return MinerClientTempPath.TempLogsDirFullName.Replace(TempPath.TempDirFullName, NTKeyword.TempDirParameterName);
+                    return TempPath.LogsDirFullName.Replace(TempPath.TempDirFullName, NTKeyword.TempDirParameterName);
                 }
                 return HomePath.HomeLogsDirFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName);
             }
@@ -681,12 +681,7 @@ namespace NTMiner {
             VirtualRoot.Execute(new ShowAboutPageCommand());
         });
         public static ICommand ShowSpeedChart { get; private set; } = new DelegateCommand(() => {
-            if (ClientAppType.IsMinerClient) {
-                VirtualRoot.Execute(new ShowSpeedChartsCommand());
-            }
-            else {
-                VirtualRoot.Execute(new ShowChartsWindowCommand());
-            }
+            VirtualRoot.Execute(new ShowSpeedChartsCommand());
         });
         public static ICommand ShowNTMinerUpdaterConfig { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowNTMinerUpdaterConfigCommand());
@@ -703,9 +698,6 @@ namespace NTMiner {
             VirtualRoot.Execute(new UnTopmostCommand());
             Process.Start(url);
         });
-        public static ICommand ShowMinerClients { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowMinerClientsWindowCommand(isToggle: false));
-        });
         public static ICommand ShowCalcConfig { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowCalcConfigCommand());
         });

+ 0 - 1
src/AppModels/MinerStudio/IMinerStudioService.cs

@@ -5,7 +5,6 @@ using System.Collections.Generic;
 
 namespace NTMiner.MinerStudio {
     public interface IMinerStudioService {
-        void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback);
         void QueryClientsAsync(QueryClientsRequest query);
         void UpdateClientAsync(string objectId, string propertyName, object value, Action<ResponseBase, Exception> callback);
         void UpdateClientsAsync(string propertyName, Dictionary<string, object> values, Action<ResponseBase, Exception> callback);

+ 0 - 17
src/AppModels/MinerStudio/Impl/LocalMinerStudioService.cs

@@ -17,11 +17,9 @@ namespace NTMiner.MinerStudio.Impl {
         private readonly string _daemonControllerName = RpcRoot.GetControllerName<INTMinerDaemonController>();
 
         private readonly IClientDataSet _clientDataSet;
-        private readonly ICoinSnapshotSet _coinSnapshotSet;
 
         public LocalMinerStudioService() {
             _clientDataSet = new ClientDataSet();
-            _coinSnapshotSet = new CoinSnapshotSet(_clientDataSet);
         }
 
         #region AddClientsAsync
@@ -98,21 +96,6 @@ namespace NTMiner.MinerStudio.Impl {
         }
         #endregion
 
-        #region GetLatestSnapshotsAsync
-        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
-            try {
-                List<CoinSnapshotData> data = _coinSnapshotSet.GetLatestSnapshots(
-                    limit,
-                    out int totalMiningCount,
-                    out int totalOnlineCount) ?? new List<CoinSnapshotData>();
-                callback?.Invoke(GetCoinSnapshotsResponse.Ok(data, totalMiningCount, totalOnlineCount), null);
-            }
-            catch (Exception e) {
-                callback?.Invoke(ResponseBase.ServerError<GetCoinSnapshotsResponse>(e.Message), e);
-            }
-        }
-        #endregion
-
         #region EnableRemoteDesktopAsync
         public void EnableRemoteDesktopAsync(IMinerData client) {
             RpcRoot.JsonRpc.PostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.EnableRemoteDesktop), null, null, timeountMilliseconds: 3000);

+ 0 - 6
src/AppModels/MinerStudio/Impl/ServerMinerStudioService.cs

@@ -41,12 +41,6 @@ namespace NTMiner.MinerStudio.Impl {
         }
         #endregion
 
-        #region GetLatestSnapshotsAsync
-        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
-            RpcRoot.OfficialServer.CoinSnapshotService.GetLatestSnapshotsAsync(limit, callback);
-        }
-        #endregion
-
         #region EnableRemoteDesktopAsync
         public void EnableRemoteDesktopAsync(IMinerData client) {
             if (!MinerStudioRoot.WsClient.IsOpen) {

+ 0 - 6
src/AppModels/MinerStudio/Messages.cs

@@ -44,12 +44,6 @@ namespace NTMiner.MinerStudio {
         }
     }
 
-    [MessageType(description: "打开群控算力图表窗口")]
-    public class ShowChartsWindowCommand : Cmd {
-        public ShowChartsWindowCommand() {
-        }
-    }
-
     [MessageType(description: "打开列分组页面")]
     public class ShowColumnsShowPageCommand : Cmd {
         public ShowColumnsShowPageCommand() {

+ 0 - 6
src/AppModels/MinerStudio/MinerStudioRoot.cs

@@ -28,12 +28,6 @@ namespace NTMiner.MinerStudio {
             }
         }
 
-        public static CoinSnapshotDataViewModels CoinSnapshotDataVms {
-            get {
-                return CoinSnapshotDataViewModels.Instance;
-            }
-        }
-
         public static ColumnsShowViewModels ColumnsShowVms {
             get {
                 return ColumnsShowViewModels.Instance;

+ 0 - 35
src/AppModels/MinerStudio/MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs

@@ -1,35 +0,0 @@
-using NTMiner.Core.MinerServer;
-using NTMiner.MinerStudio.Vms;
-using NTMiner.Vms;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace NTMiner.MinerStudio {
-    public static partial class MinerStudioRoot {
-        public class CoinSnapshotDataViewModels : ViewModelBase {
-            public static CoinSnapshotDataViewModels Instance { get; private set; } = new CoinSnapshotDataViewModels();
-
-            private readonly Dictionary<string, CoinSnapshotDataViewModel> _dicByCoinCode = new Dictionary<string, CoinSnapshotDataViewModel>(StringComparer.OrdinalIgnoreCase);
-
-            private CoinSnapshotDataViewModels() {
-                if (WpfUtil.IsInDesignMode) {
-                    return;
-                }
-                foreach (var coinVm in AppRoot.CoinVms.AllCoins) {
-                    _dicByCoinCode.Add(coinVm.Code, new CoinSnapshotDataViewModel(CoinSnapshotData.CreateEmpty(coinVm.Code)));
-                }
-            }
-
-            public bool TryGetSnapshotDataVm(string coinCode, out CoinSnapshotDataViewModel vm) {
-                return _dicByCoinCode.TryGetValue(coinCode, out vm);
-            }
-
-            public List<CoinSnapshotDataViewModel> CoinSnapshotDataVms {
-                get {
-                    return _dicByCoinCode.Values.ToList();
-                }
-            }
-        }
-    }
-}

+ 0 - 4
src/AppModels/MinerStudio/MinerStudioService.cs

@@ -24,10 +24,6 @@ namespace NTMiner.MinerStudio {
             }
         }
 
-        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
-            Service.GetLatestSnapshotsAsync(limit, callback);
-        }
-
         public void QueryClientsAsync(QueryClientsRequest query) {
             Service.QueryClientsAsync(query);
         }

+ 0 - 236
src/AppModels/MinerStudio/Vms/ChartViewModel.cs

@@ -1,236 +0,0 @@
-using LiveCharts;
-using LiveCharts.Configurations;
-using LiveCharts.Wpf;
-using NTMiner.Core;
-using NTMiner.Vms;
-using System;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-
-namespace NTMiner.MinerStudio.Vms {
-    public class ChartViewModel : ViewModelBase {
-        private SeriesCollection _series;
-        private AxesCollection _axisY;
-        private AxesCollection _axisX;
-
-        private readonly CoinViewModel _coinVm;
-
-        public ICommand Hide { get; private set; }
-        private readonly ChartValues<MeasureModel> _rejectValues;
-        private readonly ChartValues<MeasureModel> _acceptValues;
-        public ChartViewModel(CoinViewModel coinVm) {
-            this.Hide = new DelegateCommand(() => {
-                this.IsShow = false;
-            });
-            _coinVm = coinVm;
-            MinerStudioRoot.CoinSnapshotDataVms.TryGetSnapshotDataVm(coinVm.Code, out _snapshotDataVm);
-            var mapper = Mappers.Xy<MeasureModel>()
-                .X(model => model.DateTime.Ticks)   //use DateTime.Ticks as X
-                .Y(model => model.Value);           //use the value property as Y
-
-            //lets save the mapper globally.
-            Charting.For<MeasureModel>(mapper);
-
-            string DateTimeFormatter(double value) => new DateTime((long)value).ToString("HH:mm");
-            string SpeedFormatter(double value) => value.ToUnitSpeedText();
-            //AxisStep forces the distance between each separator in the X axis
-            double axisStep = TimeSpan.FromMinutes(1).Ticks;
-            //AxisUnit forces lets the axis know that we are plotting Minutes
-            //this is not always necessary, but it can prevent wrong labeling
-            double axisUnit = TimeSpan.TicksPerMinute;
-            var axisYSpeed = new Axis() {
-                LabelFormatter = SpeedFormatter,
-                MinValue = 0,
-                Separator = new Separator(),
-                Foreground = WpfUtil.AxisForeground,
-                FontSize = 13,
-                Position = AxisPosition.RightTop
-            };
-            var axisYOnlineCount = new Axis() {
-                LabelFormatter = value => Math.Round(value, 0) + "miner",
-                Separator = new Separator(),
-                Foreground = WpfUtil.AxisForeground,
-                MinValue = 0,
-                FontSize = 11
-            };
-            var axisYShareCount = new Axis() {
-                LabelFormatter = value => Math.Round(value, 0) + "share",
-                Separator = new Separator(),
-                Foreground = WpfUtil.AxisForeground,
-                MinValue = 0,
-                FontSize = 11,
-                Position = AxisPosition.RightTop
-            };
-            this._axisY = new AxesCollection {
-                axisYOnlineCount, axisYSpeed, axisYShareCount
-            };
-            DateTime now = DateTime.Now;
-            this._axisX = new AxesCollection() {
-                new Axis() {
-                    LabelFormatter = DateTimeFormatter,
-                    MaxValue = now.Ticks,
-                    MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerContext.SpeedHistoryLengthByMinute).Ticks,
-                    Unit=axisUnit,
-                    Separator = new Separator() {
-                        Step = axisStep
-                    },
-                    Foreground = WpfUtil.AxisForeground,
-                    FontSize = 12,
-                }
-            };
-            LineSeries mainCoinSpeedLs = new LineSeries {
-                Title = "speed",
-                DataLabels = false,
-                PointGeometrySize = 0,
-                StrokeThickness = 1,
-                ScalesYAt = 1,
-                Values = new ChartValues<MeasureModel>()
-            };
-            LineSeries onlineCountLs = new LineSeries {
-                Title = "onlineCount",
-                DataLabels = false,
-                PointGeometrySize = 0,
-                StrokeThickness = 1,
-                ScalesYAt = 0,
-                Fill = WpfUtil.TransparentBrush,
-                Stroke = OnlineColor,
-                Values = new ChartValues<MeasureModel>()
-            };
-            LineSeries miningCountLs = new LineSeries {
-                Title = "miningCount",
-                DataLabels = false,
-                PointGeometrySize = 0,
-                StrokeThickness = 1,
-                ScalesYAt = 0,
-                Fill = WpfUtil.TransparentBrush,
-                Stroke = MiningColor,
-                Values = new ChartValues<MeasureModel>()
-            };
-            _rejectValues = new ChartValues<MeasureModel>();
-            _acceptValues = new ChartValues<MeasureModel>();
-            StackedColumnSeries rejectScs = new StackedColumnSeries {
-                Title = "rejectShare",
-                Values = _rejectValues,
-                DataLabels = false,
-                ScalesYAt = 2,
-                MaxColumnWidth = 7
-            };
-            StackedColumnSeries acceptScs = new StackedColumnSeries {
-                Title = "acceptShare",
-                Values = _acceptValues,
-                DataLabels = false,
-                ScalesYAt = 2,
-                MaxColumnWidth = 7
-            };
-            this._series = new SeriesCollection() {
-                mainCoinSpeedLs, rejectScs, acceptScs, miningCountLs, onlineCountLs
-            };
-        }
-
-        private bool _isFirst = true;
-        private bool _isShow;
-        public bool IsShow {
-            get {
-                if (!_isFirst) {
-                    return _isShow;
-                }
-                _isFirst = false;
-                string key = $"ChartVm.IsShow.{this.CoinVm.Code}";
-                if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(key, out IAppSetting _appSetting)) {
-                    _isShow = (bool)_appSetting.Value;
-                }
-                else {
-                    _isShow = false;
-                }
-                return _isShow;
-            }
-            set {
-                _isFirst = false;
-                _isShow = value;
-                OnPropertyChanged(nameof(IsShow)); 
-                string key = $"ChartVm.IsShow.{CoinVm.Code}";
-                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
-                    Key = key,
-                    Value = _isShow
-                }));
-            }
-        }
-
-        private CoinSnapshotDataViewModel _snapshotDataVm;
-        public CoinSnapshotDataViewModel SnapshotDataVm {
-            get {
-                return _snapshotDataVm;
-            }
-        }
-
-        public SolidColorBrush MiningColor {
-            get {
-                return WpfUtil.GreenBrush;
-            }
-        }
-
-        public SolidColorBrush OnlineColor {
-            get {
-                return WpfUtil.BlackBrush;
-            }
-        }
-
-        public CoinViewModel CoinVm {
-            get {
-                return _coinVm;
-            }
-        }
-
-        public SeriesCollection Series {
-            get => _series;
-            private set {
-                if (_series != value) {
-                    _series = value;
-                    OnPropertyChanged(nameof(Series));
-                }
-            }
-        }
-
-        public AxesCollection AxisY {
-            get => _axisY;
-            private set {
-                if (_axisY != value) {
-                    _axisY = value;
-                    OnPropertyChanged(nameof(AxisY));
-                }
-            }
-        }
-
-        public AxesCollection AxisX {
-            get {
-                return _axisX;
-            }
-            private set {
-                if (_axisX != value) {
-                    _axisX = value;
-                    OnPropertyChanged(nameof(AxisX));
-                }
-            }
-        }
-
-        public void SetAxisLimits(DateTime now) {
-            AxisX[0].MaxValue = now.Ticks;
-            AxisX[0].MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerContext.SpeedHistoryLengthByMinute).Ticks;
-            double maxAcceptValue = 0;
-            double maxRejectValue = 0;
-            if (_acceptValues != null && _acceptValues.Count != 0) {
-                maxAcceptValue = _acceptValues.Max(a => a.Value);
-            }
-            if (_rejectValues != null && _rejectValues.Count != 0) {
-                maxRejectValue = _rejectValues.Max(a => a.Value);
-            }
-            double maxShareValue = Math.Max(maxRejectValue, maxAcceptValue);
-            // 不能为0
-            if (maxShareValue < 1) {
-                maxShareValue = 1;
-            }
-            AxisY[2].MaxValue = maxShareValue * 3;
-        }
-    }
-}

+ 0 - 56
src/AppModels/MinerStudio/Vms/ChartsWindowViewModel.cs

@@ -1,56 +0,0 @@
-using NTMiner.Vms;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace NTMiner.MinerStudio.Vms {
-    public class ChartsWindowViewModel : ViewModelBase {
-        private List<ChartViewModel> _chartVms;
-        private int _totalMiningCount;
-        private int _totalOnlineCount;
-
-        public ChartsWindowViewModel() {
-            _chartVms = new List<ChartViewModel>();
-            foreach (var coinVm in AppRoot.CoinVms.AllCoins.OrderBy(a => a.Code)) {
-                _chartVms.Add(new ChartViewModel(coinVm));
-            }
-        }
-
-        public bool IsShowAll {
-            get {
-                return ChartVms.All(a => a.IsShow);
-            }
-            set {
-                foreach (var item in ChartVms) {
-                    item.IsShow = value;
-                }
-                OnPropertyChanged(nameof(IsShowAll));
-            }
-        }
-
-        public int TotalMiningCount {
-            get => _totalMiningCount;
-            set {
-                if (_totalMiningCount != value) {
-                    _totalMiningCount = value;
-                    OnPropertyChanged(nameof(TotalMiningCount));
-                }
-            }
-        }
-
-        public int TotalOnlineCount {
-            get => _totalOnlineCount;
-            set {
-                if (_totalOnlineCount != value) {
-                    _totalOnlineCount = value;
-                    OnPropertyChanged(nameof(TotalOnlineCount));
-                }
-            }
-        }
-
-        public List<ChartViewModel> ChartVms {
-            get {
-                return _chartVms;
-            }
-        }
-    }
-}

+ 0 - 21
src/AppModels/MinerStudio/Vms/CoinSnapshotDataViewModel.cs

@@ -18,8 +18,6 @@ namespace NTMiner.MinerStudio.Vms {
             this.Speed = data.Speed;
             this.MainCoinMiningCount = data.MainCoinMiningCount;
             this.MainCoinOnlineCount = data.MainCoinOnlineCount;
-            this.DualCoinMiningCount = data.DualCoinMiningCount;
-            this.DualCoinOnlineCount = data.DualCoinOnlineCount;
             this.Timestamp = data.Timestamp;
         }
 
@@ -89,25 +87,6 @@ namespace NTMiner.MinerStudio.Vms {
             }
         }
 
-        public int DualCoinOnlineCount {
-            get => _data.DualCoinOnlineCount;
-            set {
-                if (_data.DualCoinOnlineCount != value) {
-                    _data.DualCoinOnlineCount = value;
-                    OnPropertyChanged(nameof(DualCoinOnlineCount));
-                }
-            }
-        }
-        public int DualCoinMiningCount {
-            get => _data.DualCoinMiningCount;
-            set {
-                if (_data.DualCoinMiningCount != value) {
-                    _data.DualCoinMiningCount = value;
-                    OnPropertyChanged(nameof(DualCoinMiningCount));
-                }
-            }
-        }
-
         public DateTime Timestamp {
             get => _data.Timestamp;
             set {

+ 66 - 15
src/AppModels/MinerStudio/Vms/MinerClientsWindowViewModel.cs

@@ -16,7 +16,7 @@ namespace NTMiner.MinerStudio.Vms {
     public class MinerClientsWindowViewModel : ViewModelBase, IWsStateViewModel {
         public static MinerClientsWindowViewModel Instance { get; private set; } = new MinerClientsWindowViewModel();
 
-        private List<CoinSnapshotViewModel> _coinSnapshotVms = null;
+        private readonly List<CoinSnapshotViewModel> _coinSnapshotVms = null;
         private CoinSnapshotViewModel _coinSnapshotVm = CoinSnapshotViewModel.PleaseSelect;
         private ColumnsShowViewModel _columnsShow;
         private int _countDown = 10;
@@ -24,7 +24,6 @@ namespace NTMiner.MinerStudio.Vms {
         private MinerClientViewModel _currentMinerClient;
         private MinerClientViewModel[] _selectedMinerClients = new MinerClientViewModel[0];
         private int _pageIndex = 1;
-        private int _pageSize = 20;
         private int _total;
         private EnumItem<MineStatus> _mineStatusEnumItem;
         private string _minerIp;
@@ -40,7 +39,7 @@ namespace NTMiner.MinerStudio.Vms {
         private int _frozenColumnCount = 8;
         private uint _minTemp = 40;
         private int _rejectPercent = 10;
-        private Dictionary<ClientDataSortField, SortDirection> _sortDirection = new Dictionary<ClientDataSortField, SortDirection> {
+        private readonly Dictionary<ClientDataSortField, SortDirection> _sortDirection = new Dictionary<ClientDataSortField, SortDirection> {
             [ClientDataSortField.MinerName] = SortDirection.Ascending,
             [ClientDataSortField.MainCoinSpeed] = SortDirection.Ascending,
             [ClientDataSortField.CpuTemperature] = SortDirection.Descending,
@@ -67,6 +66,7 @@ namespace NTMiner.MinerStudio.Vms {
         private bool _isLoading = false;
         private double _lodingIconAngle;
         private ClientDataSortField _lastSortField = ClientDataSortField.MinerName;
+        private string _wsServerIp;
         private readonly Dictionary<ClientDataSortField, SortDirection> _lastSortDirection;
 
         public bool IsEnableVirtualization {
@@ -113,6 +113,42 @@ namespace NTMiner.MinerStudio.Vms {
             }
         }
 
+        public GridLength MinerListHeight {
+            get {
+                if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(nameof(MinerListHeight), out IAppSetting appSetting) && appSetting.Value != null) {
+                    if (double.TryParse(appSetting.Value.ToString(), out double value)) {
+                        return new GridLength(value, GridUnitType.Star);
+                    }
+                }
+                return new GridLength(3, GridUnitType.Star);
+            }
+            set {
+                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                    Key = nameof(MinerListHeight),
+                    Value = value.Value.ToString()
+                }));
+                OnPropertyChanged(nameof(MinerListHeight));
+            }
+        }
+
+        public GridLength MinerDetailsHeight {
+            get {
+                if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(nameof(MinerDetailsHeight), out IAppSetting appSetting) && appSetting.Value != null) {
+                    if (double.TryParse(appSetting.Value.ToString(), out double value)) {
+                        return new GridLength(value, GridUnitType.Star);
+                    }
+                }
+                return new GridLength(5, GridUnitType.Star);
+            }
+            set {
+                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                    Key = nameof(MinerDetailsHeight),
+                    Value = value.Value.ToString()
+                }));
+                OnPropertyChanged(nameof(MinerDetailsHeight));
+            }
+        }
+
         #region QueryMinerClients
         public void QueryMinerClients(bool isAuto = false) {
             if (!isAuto) {
@@ -449,7 +485,6 @@ namespace NTMiner.MinerStudio.Vms {
                                 VirtualRoot.Out.ShowError("删除矿机失败:" + response.ReadMessage(e), autoHideSeconds: 4, toConsole: true);
                             }
                             else {
-                                // TODO:考虑是否应该只刷新选中的矿机以消减网络流量
                                 QueryMinerClients();
                             }
                         });
@@ -791,7 +826,7 @@ namespace NTMiner.MinerStudio.Vms {
             });
             this.SwitchService = new DelegateCommand(() => {
                 if (RpcRoot.IsInnerNet) {
-                    MinerStudio.MinerStudioRoot.Login(() => {
+                    MinerStudioRoot.Login(() => {
                         MinerStudioRoot.WsClient.OpenOrCloseWs(isResetFailCount: true);
                         RpcRoot.SetIsOuterNet(true);
                     }, RpcRoot.OfficialServerAddress);
@@ -814,6 +849,7 @@ namespace NTMiner.MinerStudio.Vms {
             VirtualRoot.BuildCmdPath<RefreshWsStateCommand>(message => {
                 #region
                 if (message.WsClientState != null) {
+                    this.WsServerIp = message.WsClientState.WsServerIp;
                     this.IsWsOnline = message.WsClientState.Status == WsClientStatus.Open;
                     if (message.WsClientState.ToOut) {
                         VirtualRoot.Out.ShowWarn(message.WsClientState.Description, autoHideSeconds: 3);
@@ -872,7 +908,6 @@ namespace NTMiner.MinerStudio.Vms {
                 if (_isWsOnline != value) {
                     _isWsOnline = value;
                     OnPropertyChanged(nameof(IsWsOnline));
-                    OnPropertyChanged(nameof(WsStateText));
                     OnPropertyChanged(nameof(WsNextTrySecondsDelayVisible));
                 }
             }
@@ -881,12 +916,12 @@ namespace NTMiner.MinerStudio.Vms {
         public string WsDescription {
             get {
                 if (string.IsNullOrEmpty(RpcRoot.RpcUser.LoginName)) {
-                    return "未登录";
+                    return $"未登录 {WsServerIp}";
                 }
                 if (string.IsNullOrEmpty(_wsDescription)) {
-                    return WsStateText;
+                    return $"{WsStateText} {WsServerIp}";
                 }
-                return _wsDescription;
+                return $"{_wsDescription} {WsServerIp}";
             }
             set {
                 if (_wsDescription != value) {
@@ -1011,6 +1046,14 @@ namespace NTMiner.MinerStudio.Vms {
             }
         }
 
+        public string WsServerIp {
+            get => _wsServerIp;
+            set {
+                _wsServerIp = value;
+                OnPropertyChanged(nameof(WsServerIp));
+            }
+        }
+
         public MainMenuViewModel MainMenu {
             get {
                 return MainMenuViewModel.Instance;
@@ -1199,13 +1242,21 @@ namespace NTMiner.MinerStudio.Vms {
         }
 
         public int PageSize {
-            get => _pageSize;
-            set {
-                if (_pageSize != value) {
-                    _pageSize = value;
-                    OnPropertyChanged(nameof(PageSize));
-                    this.PageIndex = 1;
+            get {
+                if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting("MinerList" + nameof(PageSize), out IAppSetting appSetting) && appSetting.Value != null) {
+                    if (int.TryParse(appSetting.Value.ToString(), out int value)) {
+                        return value;
+                    }
                 }
+                return 20;
+            }
+            set {
+                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                    Key = "MinerList" + nameof(PageSize),
+                    Value = value.ToString()
+                }));
+                OnPropertyChanged(nameof(PageSize));
+                this.PageIndex = 1;
             }
         }
 

+ 15 - 4
src/AppModels/Vms/MinerProfileViewModel.cs

@@ -22,6 +22,7 @@ namespace NTMiner.Vms {
         private DateTime _wsLastTryOn;
         private bool _isConnecting;
         private double _wsRetryIconAngle;
+        private string _wsServerIp;
 
         public ICommand Up { get; private set; }
         public ICommand Down { get; private set; }
@@ -64,6 +65,7 @@ namespace NTMiner.Vms {
                 VirtualRoot.BuildCmdPath<RefreshWsStateCommand>(message => {
                     #region
                     if (message.WsClientState != null) {
+                        this.WsServerIp = message.WsClientState.WsServerIp;
                         this.IsWsOnline = message.WsClientState.Status == WsClientStatus.Open;
                         if (message.WsClientState.ToOut) {
                             VirtualRoot.Out.ShowWarn(message.WsClientState.Description, autoHideSeconds: 3);
@@ -87,7 +89,7 @@ namespace NTMiner.Vms {
                         if (WsNextTrySecondsDelay > 0) {
                             WsNextTrySecondsDelay--;
                         }
-                        else if(WsLastTryOn == DateTime.MinValue) {
+                        else if (WsLastTryOn == DateTime.MinValue) {
                             this.RefreshWsDaemonState();
                         }
                         OnPropertyChanged(nameof(WsLastTryOnText));
@@ -157,8 +159,9 @@ namespace NTMiner.Vms {
         public void RefreshWsDaemonState() {
             RpcRoot.Client.NTMinerDaemonService.GetWsDaemonStateAsync((WsClientState state, Exception e) => {
                 if (state != null) {
-                    this.IsWsOnline = state.Status == WsClientStatus.Open;
                     this.WsDescription = state.Description;
+                    this.WsServerIp = state.WsServerIp;
+                    this.IsWsOnline = state.Status == WsClientStatus.Open;
                     if (state.NextTrySecondsDelay > 0) {
                         this.WsNextTrySecondsDelay = state.NextTrySecondsDelay;
                     }
@@ -203,6 +206,14 @@ namespace NTMiner.Vms {
             }
         }
 
+        public string WsServerIp {
+            get => _wsServerIp;
+            set {
+                _wsServerIp = value;
+                OnPropertyChanged(nameof(WsServerIp));
+            }
+        }
+
         public int WsNextTrySecondsDelay {
             get {
                 if (_wsNextTrySecondsDelay < 0) {
@@ -303,9 +314,9 @@ namespace NTMiner.Vms {
         public string WsStateText {
             get {
                 if (IsWsOnline) {
-                    return "连接服务器成功";
+                    return $"连接服务器成功 {WsServerIp}";
                 }
-                return "离线";
+                return $"离线 {WsServerIp}";
             }
         }
 

+ 0 - 5
src/AppViews0/AppViewFactory.cs

@@ -261,11 +261,6 @@ namespace NTMiner {
                     MinerStudioUcs.MinerClientFinderConfig.ShowWindow();
                 });
             }, location: location);
-            VirtualRoot.BuildCmdPath<ShowChartsWindowCommand>(path: message => {
-                UIThread.Execute(() => {
-                    MinerStudioViews.ChartsWindow.ShowWindow();
-                });
-            }, location: location);
             VirtualRoot.BuildCmdPath<ShowOverClockDataPageCommand>(path: message => {
                 UIThread.Execute(() => {
                     MinerStudioUcs.OverClockDataPage.ShowWindow();

+ 0 - 11
src/AppViews0/AppViews0.csproj

@@ -103,9 +103,6 @@
     <Compile Include="Views\ConsoleWindow.xaml.cs">
       <DependentUpon>ConsoleWindow.xaml</DependentUpon>
     </Compile>
-    <Compile Include="MinerStudio\Views\ChartsWindow.xaml.cs">
-      <DependentUpon>ChartsWindow.xaml</DependentUpon>
-    </Compile>
     <Compile Include="MinerStudio\Views\Ucs\LocalIpConfig.xaml.cs">
       <DependentUpon>LocalIpConfig.xaml</DependentUpon>
     </Compile>
@@ -434,10 +431,6 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
-    <Page Include="MinerStudio\Views\Design\ChartsWindowViewModel.xaml">
-      <Generator>MSBuild:Compile</Generator>
-      <SubType>Designer</SubType>
-    </Page>
     <Page Include="MinerStudio\Views\Design\ColumnsShowViewModel.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -454,10 +447,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="MinerStudio\Views\ChartsWindow.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="MinerStudio\Views\Design\ChangePasswordViewModel.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>

+ 0 - 372
src/AppViews0/MinerStudio/Views/ChartsWindow.xaml

@@ -1,372 +0,0 @@
-<blankWindow:BlankWindow 
-    x:Class="NTMiner.MinerStudio.Views.ChartsWindow"
-	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-	xmlns:blankWindow="http://NTMiner.com"
-	xmlns:controls="clr-namespace:NTMiner.Controls;assembly=NTMinerWpf"
-	xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
-	xmlns:sys="clr-namespace:System;assembly=mscorlib"
-	xmlns:local="clr-namespace:NTMiner.Views"
-	xmlns:uc0="clr-namespace:NTMiner.Views.Ucs"
-	xmlns:uc="clr-namespace:NTMiner.MinerStudio.Views.Ucs"
-	xmlns:vm="clr-namespace:NTMiner.MinerStudio.Vms;assembly=AppModels"
-	xmlns:app="clr-namespace:NTMiner;assembly=AppModels"
-	Background="White"
-	WindowState="Maximized"
-	WindowStartupLocation="CenterScreen"
-	mc:Ignorable="d"
-	Title="{x:Static app:AppStatic.AppName}" 
-    d:DataContext="{d:DesignData Source=Design/ChartsWindowViewModel.xaml}"
-    d:DesignHeight="900" d:DesignWidth="1720">
-	<Grid>
-		<Grid.RowDefinitions>
-			<RowDefinition Height="30"></RowDefinition>
-			<RowDefinition Height="*"></RowDefinition>
-		</Grid.RowDefinitions>
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="300"></ColumnDefinition>
-            <ColumnDefinition Width="*"></ColumnDefinition>
-        </Grid.ColumnDefinitions>
-		<!--上标题栏-->
-        <Border Grid.ColumnSpan="2">
-            <Grid>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="Auto"></ColumnDefinition>
-                    <ColumnDefinition Width="*"></ColumnDefinition>
-                    <ColumnDefinition Width="Auto"></ColumnDefinition>
-                </Grid.ColumnDefinitions>
-                <WrapPanel Grid.Column="0" Margin="4" VerticalAlignment="Center">
-                    <Menu Background="Transparent">
-                        <MenuItem Width="22">
-                            <MenuItem.Icon>
-                                <Path 
-								    Width="18"
-								    Height="18"
-								    Data="{StaticResource Icon_SpeedChart}"
-								    Fill="Black"
-								    Stretch="Fill" />
-                            </MenuItem.Icon>
-                            <MenuItem 
-                                Header="打开家目录"
-                                Command="{x:Static app:AppStatic.OpenDir}" 
-                                CommandParameter="{x:Static app:AppStatic.HomeDir}">
-                                <MenuItem.Icon>
-                                    <Path
-						                Width="16"
-						                Height="16"
-						                Data="{StaticResource Icon_Home}"
-						                Fill="{StaticResource BtnBackground}"
-						                Stretch="Fill" />
-                                </MenuItem.Icon>
-                            </MenuItem>
-                            <MenuItem 
-                                Header="打开临时目录" 
-                                Command="{x:Static app:AppStatic.OpenDir}" 
-                                CommandParameter="{x:Static app:AppStatic.TempDir}">
-                                <MenuItem.Icon>
-                                    <Path
-						                Width="16"
-						                Height="16"
-						                Data="{StaticResource Icon_Dir}"
-						                Fill="{StaticResource BtnBackground}"
-						                Stretch="Fill" />
-                                </MenuItem.Icon>
-                            </MenuItem>
-                            <MenuItem Header="图标" Command="{x:Static app:AppStatic.ShowIcons}">
-                                <MenuItem.Icon>
-                                    <Path
-							            Width="16"
-							            Height="16"
-							            Data="{StaticResource Icon_Icon}"
-							            Fill="{StaticResource BtnBackground}"
-							            Stretch="Fill" />
-                                </MenuItem.Icon>
-                            </MenuItem>
-                        </MenuItem>
-                    </Menu>
-                    <controls:KbLinkButton
-                        Margin="2 0 0 0"
-					    ToolTip="关于开源矿工"
-                        BorderThickness="0"
-                        VerticalAlignment="Center"
-					    Command="{x:Static app:AppStatic.ShowAbout}"
-					    KbDisplayLine="False"
-					    Background="Transparent"
-					    Foreground="Black">
-                        <WrapPanel>
-                            <TextBlock
-							    Margin="0"
-							    HorizontalAlignment="Left"
-							    VerticalAlignment="Center"
-							    FontSize="15"
-							    Text="{x:Static app:AppStatic.AppName}" />
-                            <TextBlock
-							    Margin="0"
-							    HorizontalAlignment="Left"
-							    VerticalAlignment="Center"
-							    FontSize="15"
-							    Text=" - 算力图" />
-                        </WrapPanel>
-                    </controls:KbLinkButton>
-                </WrapPanel>
-                <WrapPanel Grid.Column="2">
-                    <WrapPanel Margin="0" VerticalAlignment="Top">
-                        <controls:KbButton 
-					        ToolTip="矿机雷达"
-					        Command="{x:Static app:AppStatic.OpenMinerClientFinder}" 
-					        Background="Transparent" 
-					        BorderThickness="0"
-					        Margin="0 0 12 0"
-					        CornerRadius="2">
-                            <Path 
-						        Width="18"
-						        Height="18"
-						        Data="{StaticResource Icon_MinerClientFinder}"
-						        Fill="Black"
-						        Stretch="Fill" />
-                        </controls:KbButton>
-                        <controls:KbButton 
-						    ToolTip="收益计算器"
-						    Command="{x:Static app:AppStatic.ShowCalc}" 
-						    Background="Transparent" 
-						    BorderThickness="0"
-						    Margin="0 0 12 0"
-						    CornerRadius="2">
-                            <Path 
-							    Width="18"
-							    Height="18"
-							    Data="{StaticResource Icon_Calc}"
-							    Fill="Black"
-							    Stretch="Fill" />
-                        </controls:KbButton>
-                        <controls:KbButton 
-                            Width="20"
-                            Height="20"
-						    ToolTip="矿机列表"
-						    Command="{x:Static app:AppStatic.ShowMinerClients}" 
-						    Background="Transparent" 
-						    BorderThickness="0"
-						    Margin="0 0 8 0"
-						    CornerRadius="2">
-                            <Image Source="/NTMinerWpf;component/Styles/Images/cc32.png">
-                            </Image>
-                        </controls:KbButton>
-                        <uc0:MainMenu>
-                        </uc0:MainMenu>
-                        <controls:KbSystemMinButton KbSystemButtonForeground="Black" />
-                        <controls:KbSystemMaxButton KbSystemButtonForeground="Black" />
-                        <controls:KbSystemCloseButton KbSystemButtonForeground="Black" />
-                    </WrapPanel>
-                </WrapPanel>
-            </Grid>
-        </Border>
-        <TextBlock x:Name="TbUcName" Foreground="Black" Visibility="{x:Static app:AppStatic.IsDevModeVisible}" HorizontalAlignment="Center"></TextBlock>
-        <Grid Grid.Row="1" Grid.Column="0">
-            <DataGrid 
-                RowHeight="30"
-			    ItemsSource="{Binding ChartVms}" 
-                BorderBrush="{StaticResource LightLineColor}"
-			    BorderThickness="0 1 1 0">
-                <DataGrid.Resources>
-                    <controls:BindingProxy x:Key="proxy" Data="{Binding}" />
-                </DataGrid.Resources>
-                <DataGrid.Columns>
-                    <DataGridTemplateColumn IsReadOnly="True">
-                        <DataGridTemplateColumn.Header>
-                            <CheckBox 
-					            FocusVisualStyle="{x:Null}"
-                                IsChecked="{Binding Data.IsShowAll, Source={StaticResource proxy}}"
-					            VerticalAlignment="Center">
-                            </CheckBox>
-                        </DataGridTemplateColumn.Header>
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <CheckBox 
-								    FocusVisualStyle="{x:Null}"
-                                    HorizontalAlignment="Center"
-								    VerticalContentAlignment="Bottom" 
-								    IsChecked="{Binding IsShow,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
-                                    Click="IsShowCheckBox_Click"
-								    ToolTip="显示算力图" 
-								    VerticalAlignment="Center">
-                                </CheckBox>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                    <DataGridTemplateColumn Width="30" IsReadOnly="True">
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <Image Width="26" Height="26" Source="{Binding CoinVm.IconImageSource}">
-                                </Image>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                    <DataGridTextColumn Width="50" Binding="{Binding CoinVm.Code}" IsReadOnly="True">
-                    </DataGridTextColumn>
-                    <DataGridTemplateColumn Width="50" IsReadOnly="True">
-                        <DataGridTemplateColumn.Header>
-                            <TextBlock 
-					            Text="{Binding Data.TotalMiningCount,Source={StaticResource proxy}}" 
-					            ToolTip="总挖矿数" 
-					            Foreground="Green"></TextBlock>
-                        </DataGridTemplateColumn.Header>
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <WrapPanel>
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityInvertConverter}}">
-                                        <TextBlock 
-                                            Width="40" 
-                                            Foreground="{Binding MiningColor}" 
-                                            ToolTip="挖矿数" 
-                                            Visibility="{Binding SnapshotDataVm.MainCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-                                            Text="{Binding SnapshotDataVm.MainCoinMiningCount}"></TextBlock>
-                                    </Border>
-
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityConverter}}">
-                                        <TextBlock 
-                                            Width="40" 
-                                            Foreground="{Binding MiningColor}" 
-                                            ToolTip="挖矿数" 
-                                            Visibility="{Binding SnapshotDataVm.DualCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-                                            Text="{Binding SnapshotDataVm.DualCoinMiningCount}"></TextBlock>
-                                    </Border>
-                                </WrapPanel>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                    <DataGridTemplateColumn Width="50" IsReadOnly="True">
-                        <DataGridTemplateColumn.Header>
-                            <TextBlock Text="{Binding Data.TotalOnlineCount,Source={StaticResource proxy}}" ToolTip="总在线数"></TextBlock>
-                        </DataGridTemplateColumn.Header>
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <WrapPanel>
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityInvertConverter}}">
-                                        <TextBlock 
-									        Foreground="{Binding OnlineColor}"
-									        ToolTip="在线数" 
-                                            Visibility="{Binding SnapshotDataVm.MainCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-									        Text="{Binding SnapshotDataVm.MainCoinOnlineCount}">
-                                        </TextBlock>
-                                    </Border>
-
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityConverter}}">
-                                        <TextBlock 
-									        Foreground="{Binding OnlineColor}"
-									        ToolTip="在线数" 
-                                            Visibility="{Binding SnapshotDataVm.DualCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-									        Text="{Binding SnapshotDataVm.DualCoinOnlineCount}">
-                                        </TextBlock>
-                                    </Border>
-                                </WrapPanel>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                    <DataGridTemplateColumn MinWidth="50" Width="*" IsReadOnly="True">
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <WrapPanel>
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityInvertConverter}}">
-                                        <TextBlock 
-                                            Visibility="{Binding SnapshotDataVm.MainCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-									        Text="{Binding SnapshotDataVm.SpeedText}">
-                                        </TextBlock>
-                                    </Border>
-
-                                    <Border VerticalAlignment="Center" Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityConverter}}">
-                                        <TextBlock 
-                                            Visibility="{Binding SnapshotDataVm.DualCoinOnlineCount,Converter={StaticResource IntToHiddenConverter}}"
-									        Text="{Binding SnapshotDataVm.SpeedText}">
-                                        </TextBlock>
-                                    </Border>
-                                </WrapPanel>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                </DataGrid.Columns>
-            </DataGrid>
-            <TextBlock Grid.Row="2" Visibility="{Binding ChartVms, Converter={StaticResource NoRecordVisibilityConverter}}" Foreground="Red" VerticalAlignment="Center" HorizontalAlignment="Center">没有记录</TextBlock>
-        </Grid>
-        <ScrollViewer 
-			Grid.Row="1" 
-            Grid.Column="1"
-			Background="White"
-			VerticalScrollBarVisibility="Visible"
-			PreviewMouseDown="ScrollViewer_PreviewMouseDown">
-			<Viewbox Stretch="Uniform" VerticalAlignment="Top">
-				<ItemsControl
-					Width="1140"
-					ItemsSource="{Binding ChartVms}"
-					BorderThickness="0"
-					MouseDown="MetroWindow_MouseDown"
-					ScrollViewer.HorizontalScrollBarVisibility="Disabled">
-					<ItemsControl.ItemsPanel>
-						<ItemsPanelTemplate>
-							<WrapPanel>
-							</WrapPanel>
-						</ItemsPanelTemplate>
-					</ItemsControl.ItemsPanel>
-					<ItemsControl.ItemTemplate>
-						<DataTemplate>
-							<Border Height="170" Width="570" Background="White" Visibility="{Binding IsShow, Converter={StaticResource BoolToVisibilityConverter}}">
-								<Border.ContextMenu>
-									<ContextMenu>
-										<MenuItem Header="不展示" Command="{Binding Hide}">
-										</MenuItem>
-									</ContextMenu>
-								</Border.ContextMenu>
-								<Grid Margin="5 0 5 10">
-									<lvc:CartesianChart									
-										DisableAnimations="True"                                            
-										Hoverable="False" 
-										DataTooltip="{x:Null}"
-										Series="{Binding Series}"
-										AxisY="{Binding AxisY}"
-										AxisX="{Binding AxisX}">
-									</lvc:CartesianChart>
-									<Grid Margin="40 9 70 0">
-										<Grid.ColumnDefinitions>
-											<ColumnDefinition Width="*"></ColumnDefinition>
-											<ColumnDefinition Width="Auto"></ColumnDefinition>
-										</Grid.ColumnDefinitions>
-										<StackPanel>
-											<WrapPanel Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityInvertConverter}}">
-                                                <Image Width="20" Height="20" Margin="10 -4 0 0" Source="{Binding CoinVm.IconImageSource}">
-                                                </Image>
-                                                <TextBlock FontSize="14" FontWeight="Bold" Foreground="{Binding MiningColor}" ToolTip="挖矿数" Text="{Binding SnapshotDataVm.MainCoinMiningCount}" Margin="4 0 0 0"></TextBlock>
-												<TextBlock FontSize="14" FontWeight="Bold">/</TextBlock>
-												<TextBlock 
-													FontSize="14" FontWeight="Bold" 
-													Foreground="{Binding OnlineColor}"
-													ToolTip="在线数" 
-													Text="{Binding SnapshotDataVm.MainCoinOnlineCount}">
-												</TextBlock>
-											</WrapPanel>
-
-											<WrapPanel Visibility="{Binding CoinVm.JustAsDualCoin, Converter={StaticResource BoolToVisibilityConverter}}">
-												<TextBlock FontSize="14" FontWeight="Bold" Foreground="{Binding MiningColor}" ToolTip="挖矿数" Text="{Binding SnapshotDataVm.DualCoinMiningCount}" Margin="4 0 0 0"></TextBlock>
-												<TextBlock FontSize="14" FontWeight="Bold">/</TextBlock>
-												<TextBlock 
-													FontSize="14" FontWeight="Bold" 
-													Foreground="{Binding OnlineColor}"
-													ToolTip="在线数" 
-													Text="{Binding SnapshotDataVm.DualCoinOnlineCount}">
-												</TextBlock>
-											</WrapPanel>
-										</StackPanel>
-										<WrapPanel Grid.Column="1" Margin="0 0 50 0">
-											<TextBlock FontSize="14" FontWeight="Bold" Text="{Binding CoinVm.Code}" Margin="4 0 0 0"></TextBlock>
-											<TextBlock FontSize="14" FontWeight="Bold" ToolTip="当前算力" Text="{Binding SnapshotDataVm.SpeedText}" Margin="8 0 0 0"></TextBlock>
-										</WrapPanel>
-									</Grid>
-								</Grid>
-							</Border>
-						</DataTemplate>
-					</ItemsControl.ItemTemplate>
-				</ItemsControl>
-			</Viewbox>
-        </ScrollViewer>
-    </Grid>
-</blankWindow:BlankWindow>

+ 0 - 199
src/AppViews0/MinerStudio/Views/ChartsWindow.xaml.cs

@@ -1,199 +0,0 @@
-using LiveCharts;
-using NTMiner.Core.MinerServer;
-using NTMiner.MinerStudio.Vms;
-using NTMiner.Views;
-using NTMiner.Vms;
-using System;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-
-namespace NTMiner.MinerStudio.Views {
-    public partial class ChartsWindow : BlankWindow {
-        private static ChartsWindow _sWindow = null;
-        public static void ShowWindow() {
-            if (_sWindow == null) {
-                _sWindow = new ChartsWindow();
-            }
-            _sWindow.Show();
-            if (_sWindow.WindowState == WindowState.Minimized) {
-                _sWindow.WindowState = WindowState.Normal;
-            }
-            _sWindow.Activate();
-        }
-
-        public ChartsWindowViewModel Vm { get; private set; }
-
-        private ChartsWindow() {
-            if (WpfUtil.IsInDesignMode) {
-                return;
-            }
-            this.Vm = new ChartsWindowViewModel();
-            this.DataContext = this.Vm;
-            Width = SystemParameters.FullPrimaryScreenWidth * 0.95;
-            Height = SystemParameters.FullPrimaryScreenHeight * 0.95;
-            InitializeComponent();
-            this.TbUcName.Text = nameof(ChartsWindow);
-            NotiCenterWindow.Bind(this);
-            #region 总算力
-            this.BuildEventPath<Per10SecondEvent>("周期刷新总算力图", LogEnum.DevConsole,
-                path: message => {
-                    RefreshTotalSpeedChart(limit: 1);
-                }, location: this.GetType());
-            RefreshTotalSpeedChart(limit: 60);
-            #endregion
-        }
-
-        protected override void OnClosed(EventArgs e) {
-            _sWindow = null;
-            base.OnClosed(e);
-        }
-
-        #region 刷新总算力图表
-        private void RefreshTotalSpeedChart(int limit) {
-            MinerStudioService.Instance.GetLatestSnapshotsAsync(
-                limit,
-                (response, exception) => {
-                    if (response == null) {
-                        return;
-                    }
-
-                    if (!response.IsSuccess()) {
-                        VirtualRoot.Out.ShowError(response.ReadMessage(exception), autoHideSeconds: 4);
-                        return;
-                    }
-                    UIThread.Execute(() => {
-                        bool isOnlyOne = limit == 1;
-                        Vm.TotalMiningCount = response.TotalMiningCount;
-                        Vm.TotalOnlineCount = response.TotalOnlineCount;
-                        foreach (var chartVm in Vm.ChartVms) {
-                            var list = response.Data.Where(a => a.CoinCode == chartVm.CoinVm.Code).ToList();
-                            if (list.Count == 0) {
-                                list.Add(new CoinSnapshotData {
-                                    CoinCode = chartVm.CoinVm.Code,
-                                    DualCoinOnlineCount = 0,
-                                    DualCoinMiningCount = 0,
-                                    MainCoinOnlineCount = 0,
-                                    MainCoinMiningCount = 0,
-                                    RejectShareDelta = 0,
-                                    ShareDelta = 0,
-                                    Speed = 0,
-                                    Timestamp = DateTime.Now.AddSeconds(-5)
-                                });
-                            }
-                            CoinSnapshotData one = null;
-                            if (isOnlyOne) {
-                                one = list.Last();
-                            }
-                            else {
-                                list = list.OrderBy(a => a.Timestamp).ToList();
-                            }
-                            CoinSnapshotData latestData = list.Last();
-                            chartVm.SnapshotDataVm.Update(latestData);
-                            foreach (var riser in chartVm.Series) {
-                                if (riser.Title == "speed") {
-                                    if (list.Count > 0) {
-                                        if (isOnlyOne) {
-                                            riser.Values.Add(new MeasureModel() {
-                                                DateTime = one.Timestamp,
-                                                Value = one.Speed
-                                            });
-                                        }
-                                        else {
-                                            foreach (var item in list) {
-                                                riser.Values.Add(new MeasureModel() {
-                                                    DateTime = item.Timestamp,
-                                                    Value = item.Speed
-                                                });
-                                            }
-                                        }
-                                    }
-                                }
-                                else if (riser.Title == "onlineCount") {
-                                    if (isOnlyOne) {
-                                        riser.Values.Add(new MeasureModel() {
-                                            DateTime = one.Timestamp,
-                                            Value = one.MainCoinOnlineCount + one.DualCoinOnlineCount
-                                        });
-                                    }
-                                    else {
-                                        foreach (var item in list) {
-                                            riser.Values.Add(new MeasureModel() {
-                                                DateTime = item.Timestamp,
-                                                Value = item.MainCoinOnlineCount + item.DualCoinOnlineCount
-                                            });
-                                        }
-                                    }
-                                }
-                                else if (riser.Title == "miningCount") {
-                                    if (isOnlyOne) {
-                                        riser.Values.Add(new MeasureModel() {
-                                            DateTime = one.Timestamp,
-                                            Value = one.MainCoinMiningCount + one.DualCoinMiningCount
-                                        });
-                                    }
-                                    else {
-                                        foreach (var item in list) {
-                                            riser.Values.Add(new MeasureModel() {
-                                                DateTime = item.Timestamp,
-                                                Value = item.MainCoinMiningCount + item.DualCoinMiningCount
-                                            });
-                                        }
-                                    }
-                                }
-                                else if (riser.Title == "rejectShare") {
-                                    if (isOnlyOne) {
-                                        riser.Values.Add(new MeasureModel() {
-                                            DateTime = one.Timestamp,
-                                            Value = one.RejectShareDelta
-                                        });
-                                    }
-                                    else {
-                                        foreach (var item in list) {
-                                            riser.Values.Add(new MeasureModel() {
-                                                DateTime = item.Timestamp,
-                                                Value = item.RejectShareDelta
-                                            });
-                                        }
-                                    }
-                                }
-                                else if (riser.Title == "acceptShare") {
-                                    if (isOnlyOne) {
-                                        riser.Values.Add(new MeasureModel() {
-                                            DateTime = one.Timestamp,
-                                            Value = one.ShareDelta
-                                        });
-                                    }
-                                    else {
-                                        foreach (var item in list) {
-                                            riser.Values.Add(new MeasureModel() {
-                                                DateTime = item.Timestamp,
-                                                Value = item.ShareDelta
-                                            });
-                                        }
-                                    }
-                                }
-                            }
-                            DateTime now = DateTime.Now.AddSeconds(10);
-                            foreach (var riser in chartVm.Series) {
-                                IChartValues valuesTotal = riser.Values;
-                                if (valuesTotal.Count > 0 && ((MeasureModel)valuesTotal[0]).DateTime.AddMinutes(NTMinerContext.SpeedHistoryLengthByMinute) < now) {
-                                    valuesTotal.RemoveAt(0);
-                                }
-                            }
-                            chartVm.SetAxisLimits(now);
-                        }
-                    });
-                });
-        }
-        #endregion
-        
-        private void ScrollViewer_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
-            WpfUtil.ScrollViewer_PreviewMouseDown(sender, e);
-        }
-
-        private void IsShowCheckBox_Click(object sender, RoutedEventArgs e) {
-            Vm.OnPropertyChanged(nameof(Vm.IsShowAll));
-        }
-    }
-}

+ 0 - 2
src/AppViews0/MinerStudio/Views/Design/ChartsWindowViewModel.xaml

@@ -1,2 +0,0 @@
-<vm:ChartsWindowViewModel xmlns:vm="clr-namespace:NTMiner.MinerStudio.Vms;assembly=AppModels">
-</vm:ChartsWindowViewModel>

+ 2 - 2
src/AppViews0/MinerStudio/Views/MinerClientsWindow.xaml

@@ -210,8 +210,8 @@
                 </TabItem.Header>
                 <Grid>
                     <Grid.RowDefinitions>
-                        <RowDefinition Height="3*" MinHeight="200"></RowDefinition>
-                        <RowDefinition Height="5*" MinHeight="300"></RowDefinition>
+                        <RowDefinition Height="{Binding MinerListHeight,Mode=TwoWay}" MinHeight="200"></RowDefinition>
+                        <RowDefinition Height="{Binding MinerDetailsHeight,Mode=TwoWay}" MinHeight="300"></RowDefinition>
                     </Grid.RowDefinitions>
                     <Border Margin="0 0 0 -2" Background="White" VerticalAlignment="Bottom" Height="10"></Border>
                     <Grid Margin="0 0 0 6" Background="{StaticResource ToolbarBackground}">

+ 3 - 0
src/AppViews0/MinerStudio/Views/Ucs/MinerClients.xaml

@@ -337,6 +337,7 @@
             </DataGridTemplateColumn>
             <!--LocalIp-->
             <DataGridTemplateColumn 
+                Width="156"
 				Visibility="{Binding Data.ColumnsShow.LocalIp, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConverter}}"
 				IsReadOnly="True">
                 <DataGridTemplateColumn.Header>
@@ -385,6 +386,7 @@
             </DataGridTemplateColumn>
             <!--MinerIp-->
             <DataGridTemplateColumn 
+                Width="140"
 				Visibility="{Binding Data.ColumnsShow.MinerIp, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConverter}}"
 				IsReadOnly="True">
                 <DataGridTemplateColumn.Header>
@@ -419,6 +421,7 @@
             </DataGridTemplateColumn>
             <!--MACAddress-->
             <DataGridTemplateColumn 
+                Width="130"
 				Visibility="{Binding Data.ColumnsShow.MACAddress,Source={StaticResource proxy},Converter={StaticResource BoolToVisibilityConverter}}"
 				IsReadOnly="True">
                 <DataGridTemplateColumn.Header>

+ 3 - 0
src/AppViews0/Views/ConsoleWindow.xaml

@@ -15,5 +15,8 @@
     Title="Windows控制台窗口">
     <!--窗口不能透明否则在远程桌面时无法显现-->
     <Grid>
+        <TextBlock x:Name="TbDescription" TextWrapping="Wrap" Foreground="Gray" FontSize="14" Visibility="Collapsed">
+            别紧张,点一下就好了。这个黑色窗口应该处在主界面的下面,透过主界面的一个透明区域才会被看到,但在某些Windows下躲在主界面下面的这个控制台窗口偶尔会跑到上面来,这是Windows的一个BUG会在将来的Windows中修复。
+        </TextBlock>
     </Grid>
 </Window>

+ 5 - 0
src/AppViews0/Views/ConsoleWindow.xaml.cs

@@ -21,6 +21,11 @@ namespace NTMiner.Views {
                 hwndSource = PresentationSource.FromVisual((Visual)sender) as HwndSource;
                 hwndSource.AddHook(new HwndSourceHook(Win32Proc.WindowProc));
             };
+            2.SecondsDelay().ContinueWith(t => {
+                UIThread.Execute(() => {
+                    this.TbDescription.Visibility = Visibility.Visible;
+                });
+            });
         }
 
         protected override void OnClosed(EventArgs e) {

+ 1 - 2
src/AppViews0/Views/Ucs/InnerProperty.xaml

@@ -78,8 +78,7 @@
 
             <WrapPanel>
                 <TextBlock Style="{StaticResource LblTb}" Text="服务端配置文件名"></TextBlock>
-                <TextBlock Text="{x:Static app:AppStatic.ServerJsonFileFullName}"></TextBlock>
-                <TextBlock Margin="10 0 0 0" ToolTip="版本时间戳" Text="{Binding ServerJsonVersion}"></TextBlock>
+                <TextBlock Text="{x:Static app:AppStatic.ServerJsonFileFullName}" ToolTip="{Binding ServerJsonVersion}"></TextBlock>
             </WrapPanel>
 
             <WrapPanel Visibility="{x:Static app:AppStatic.IsDevModeVisible}">

+ 0 - 20
src/AppViews0/Views/Ucs/MainMenu.xaml

@@ -55,16 +55,6 @@
 						Stretch="Fill" />
                 </MenuItem.Icon>
             </MenuItem>
-            <MenuItem Width="26" Height="26" ToolTip="算力图" Command="{x:Static app:AppStatic.ShowSpeedChart}" Visibility="{Binding IsMinerStudioLocalOrOuterAdminVisible}">
-                <MenuItem.Icon>
-                    <Path 
-						Height="14"
-						Width="14"
-						Data="{StaticResource Icon_SpeedChart}"
-						Fill="{Binding TopItemForeground}"
-						Stretch="Fill" />
-                </MenuItem.Icon>
-            </MenuItem>
             <MenuItem Width="26" Height="26" ToolTip="收益计算器" Command="{x:Static app:AppStatic.ShowCalc}">
                 <MenuItem.Icon>
                     <Path 
@@ -381,16 +371,6 @@
 						Fill="{Binding TopItemForeground}"
 						Stretch="Fill" />
 				</MenuItem.Icon>
-				<MenuItem Header="矿工列表" Command="{x:Static app:AppStatic.ShowMinerClients}">
-					<MenuItem.Icon>
-						<Path
-							Width="16"
-							Height="16"
-							Data="{StaticResource Icon_Miner}"
-							Fill="{StaticResource BtnBackground}"
-							Stretch="Fill" />
-					</MenuItem.Icon>
-				</MenuItem>
 				<MenuItem Header="币种" Command="{x:Static app:AppStatic.ShowCoins}">
 					<MenuItem.Icon>
 						<Path

+ 1 - 1
src/AppViews0/Views/Ucs/StateBar.xaml

@@ -167,7 +167,7 @@
 					</controls:KbLinkButton>
 					<controls:KbButton 
 						HorizontalContentAlignment="Left"
-						Width="220" ToolTip="收益计算器"
+						Width="220" ToolTip="您挖的币在矿池,矿池都有支付限额,比如限额0.1个ETH,就是说矿池每天支付一次但只支付挖够了0.1个ETH的矿工。您可点击前面的 “查看收益” 查看您挖了多少币了。"
 						Padding="2 0" Cursor="Hand" Background="Transparent"
 						Command="{x:Static app:AppStatic.ShowCalc}"
 						CommandParameter="{Binding MinerProfile.CoinVm}">

+ 1 - 0
src/AppViews0/Views/Ucs/WalletEdit.xaml.cs

@@ -10,6 +10,7 @@ namespace NTMiner.Views.Ucs {
                 Width = 520,
                 IconName = "Icon_Wallet",
                 IsMaskTheParent = true,
+                FooterVisible = System.Windows.Visibility.Collapsed,
                 CloseVisible = System.Windows.Visibility.Visible
             }, ucFactory: (window) =>
             {

+ 1 - 1
src/BlankWindow/GlowWindow.xaml.cs

@@ -160,7 +160,7 @@ namespace NTMiner {
 
         private void Owner_StateChanged(object sender, EventArgs e) {
             Window window = (Window)sender;
-            if (window.WindowState == WindowState.Normal) {
+            if (window.WindowState != WindowState.Minimized) {
                 this.Show();
             }
             else {

+ 3 - 3
src/CalcConfigUpdater/CalcConfigUpdater.csproj

@@ -80,9 +80,9 @@
       <Project>{c7108d8f-eb73-4ae3-916f-be817ede37af}</Project>
       <Name>NTMinerRpcClient</Name>
     </ProjectReference>
-    <ProjectReference Include="..\NTMinerServer\NTMinerServer.csproj">
-      <Project>{cb2619b7-3f59-41b7-a562-4a3f117822ce}</Project>
-      <Name>NTMinerServer</Name>
+    <ProjectReference Include="..\ServerCommon\ServerCommon.csproj">
+      <Project>{e12eefdc-66e9-4b7d-a036-fc1d4962eb04}</Project>
+      <Name>ServerCommon</Name>
     </ProjectReference>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

+ 13 - 18
src/MinerClient/App.xaml.cs

@@ -24,7 +24,7 @@ namespace NTMiner {
                 NTMinerConsole.Disable();
             }
             VirtualRoot.SetOut(NotiCenterWindowViewModel.Instance);
-            Logger.SetDir(MinerClientTempPath.TempLogsDirFullName);
+            Logger.SetDir(TempPath.LogsDirFullName);
             WpfUtil.Init();
             AppUtil.Init(this);
             AppUtil.IsHotKeyEnabled = true;
@@ -178,23 +178,18 @@ namespace NTMiner {
         private void BuildPaths() {
             VirtualRoot.BuildCmdPath<MinerClientActionCommand>(path: message => {
                 #region
-                try {
-                    switch (message.ActionType) {
-                        case MinerClientActionType.SwitchRadeonGpuOn:
-                            VirtualRoot.Execute(new SwitchRadeonGpuCommand(on: true));
-                            break;
-                        case MinerClientActionType.SwitchRadeonGpuOff:
-                            VirtualRoot.Execute(new SwitchRadeonGpuCommand(on: false));
-                            break;
-                        case MinerClientActionType.BlockWAU:
-                            VirtualRoot.Execute(new BlockWAUCommand());
-                            break;
-                        default:
-                            break;
-                    }
-                }
-                catch (Exception e) {
-                    Logger.ErrorDebugLine(e);
+                switch (message.ActionType) {
+                    case MinerClientActionType.SwitchRadeonGpuOn:
+                        VirtualRoot.Execute(new SwitchRadeonGpuCommand(on: true));
+                        break;
+                    case MinerClientActionType.SwitchRadeonGpuOff:
+                        VirtualRoot.Execute(new SwitchRadeonGpuCommand(on: false));
+                        break;
+                    case MinerClientActionType.BlockWAU:
+                        VirtualRoot.Execute(new BlockWAUCommand());
+                        break;
+                    default:
+                        break;
                 }
                 #endregion
             }, location: this.GetType());

+ 0 - 2
src/NTMiner.Controllers/IClientDataBinaryController`1.cs

@@ -7,7 +7,5 @@ namespace NTMiner.Controllers {
         /// 需签名
         /// </summary>
         T1 QueryClients(QueryClientsRequest request);
-
-        T1 QueryClientsForWs(QueryClientsForWsRequest request);
     }
 }

+ 0 - 10
src/NTMiner.Controllers/ICoinSnapshotController.cs

@@ -1,10 +0,0 @@
-using NTMiner.Core.MinerServer;
-
-namespace NTMiner.Controllers {
-    public interface ICoinSnapshotController {
-        /// <summary>
-        /// 需签名
-        /// </summary>
-        GetCoinSnapshotsResponse LatestSnapshots(GetCoinSnapshotsRequest request);
-    }
-}

+ 0 - 9
src/NTMiner.Controllers/IWsServerNodeController.cs

@@ -1,16 +1,7 @@
 using NTMiner.ServerNode;
-using System.Collections.Generic;
 
 namespace NTMiner.Controllers {
     public interface IWsServerNodeController {
-        /// <summary>
-        /// 需签名
-        /// </summary>
-        DataResponse<List<WsServerNodeState>> Nodes(object request);
-        /// <summary>
-        /// 需签名
-        /// </summary>
-        DataResponse<string[]> NodeAddresses(object request);
         DataResponse<string> GetNodeAddress(GetWsServerNodeAddressRequest request);
     }
 }

+ 0 - 1
src/NTMiner.Controllers/NTMiner.Controllers.csproj

@@ -37,7 +37,6 @@
     <Compile Include="ICalcConfigBinaryController`1.cs" />
     <Compile Include="ICaptchaController`1.cs" />
     <Compile Include="IClientDataBinaryController`1.cs" />
-    <Compile Include="ICoinSnapshotController.cs" />
     <Compile Include="IGpuNameController.cs" />
     <Compile Include="IMinerClientController.cs" />
     <Compile Include="IReportBinaryController.cs" />

+ 7 - 2
src/NTMinerClient/Core/Impl/CalcConfigSet.cs

@@ -26,13 +26,18 @@ namespace NTMiner.Core.Impl {
             if ((_initedOn == DateTime.MinValue || NTMinerContext.IsUiVisible || ClientAppType.IsMinerStudio) && (forceRefresh || _initedOn.AddMinutes(10) < now)) {
                 _initedOn = now;
                 RpcRoot.OfficialServer.CalcConfigService.GetCalcConfigsAsync(data => {
-                    Init(data);
-                    VirtualRoot.RaiseEvent(new CalcConfigSetInitedEvent());
+                    if (data != null && data.Count != 0) {
+                        Init(data);
+                        VirtualRoot.RaiseEvent(new CalcConfigSetInitedEvent());
+                    }
                 });
             }
         }
 
         private void Init(List<CalcConfigData> data) {
+            if (data == null || data.Count == 0) {
+                return;
+            }
             var list = _root.ServerContext.CoinSet.AsEnumerable().OrderBy(a => a.Code).Select(a => new CalcConfigData {
                 CoinCode = a.Code,
                 CreatedOn = DateTime.Now,

+ 0 - 6
src/NTMinerClient/Core/Impl/CoinSnapshotSet.cs

@@ -1,6 +0,0 @@
-namespace NTMiner.Core.Impl {
-    public class CoinSnapshotSet : CoinSnapshotSetBase, ICoinSnapshotSet {
-        public CoinSnapshotSet(IClientDataSetBase clientSet) : base(isPull: true, clientSet) {
-        }
-    }
-}

+ 1 - 1
src/NTMinerClient/Mine/Cleaner.cs

@@ -102,7 +102,7 @@ namespace NTMiner.Mine {
         private void ClearLogs() {
             try {
                 List<string> toRemoves = new List<string>();
-                foreach (var file in Directory.GetFiles(MinerClientTempPath.TempLogsDirFullName)) {
+                foreach (var file in Directory.GetFiles(TempPath.LogsDirFullName)) {
                     FileInfo fileInfo = new FileInfo(file);
                     if (fileInfo.LastWriteTime.AddDays(7) < DateTime.Now) {
                         toRemoves.Add(file);

+ 1 - 1
src/NTMinerClient/Mine/MineContext.cs

@@ -73,7 +73,7 @@ namespace NTMiner.Mine {
                 this.KernelProcessType = KernelProcessType.Pip;
                 logFileName = $"{this.Kernel.Code}_pip_{DateTime.Now.Ticks.ToString()}.log";
             }
-            this.LogFileFullName = Path.Combine(MinerClientTempPath.TempLogsDirFullName, logFileName);
+            this.LogFileFullName = Path.Combine(TempPath.LogsDirFullName, logFileName);
         }
 
         public void Start(bool isRestart) {

+ 0 - 15
src/NTMinerClient/MinerClientTempPath.cs

@@ -76,21 +76,6 @@ namespace NTMiner {
             }
         }
 
-        private static bool _sIsFirstCallTempLogsDirFullName = true;
-        public static string TempLogsDirFullName {
-            get {
-                string dirFullName = Path.Combine(TempPath.TempDirFullName, NTKeyword.LogsDirName);
-                if (_sIsFirstCallTempLogsDirFullName) {
-                    if (!Directory.Exists(dirFullName)) {
-                        Directory.CreateDirectory(dirFullName);
-                    }
-                    _sIsFirstCallTempLogsDirFullName = false;
-                }
-
-                return dirFullName;
-            }
-        }
-
         private static bool _sIsFirstCallToolsDirFullName = true;
         /// <summary>
         /// 在临时目录。因为工具并非常用的程序文件,在用户第一次使用时才会下载。

+ 0 - 7
src/NTMinerClient/NTMinerClient.csproj

@@ -86,15 +86,9 @@
     <Compile Include="..\WebApiServer\Core\IClientDataSetBase.cs">
       <Link>Core\IClientDataSetBase.cs</Link>
     </Compile>
-    <Compile Include="..\WebApiServer\Core\ICoinSnapshotSet.cs">
-      <Link>Core\ICoinSnapshotSet.cs</Link>
-    </Compile>
     <Compile Include="..\WebApiServer\Core\Impl\ClientDataSetBase.cs">
       <Link>Core\Impl\ClientDataSetBase.cs</Link>
     </Compile>
-    <Compile Include="..\WebApiServer\Core\Impl\CoinSnapshotSetBase.cs">
-      <Link>Core\Impl\CoinSnapshotSetBase.cs</Link>
-    </Compile>
     <Compile Include="ComputerRoot.cs" />
     <Compile Include="Core\AverageSpeed.cs" />
     <Compile Include="Core\CoinExtensions.cs" />
@@ -139,7 +133,6 @@
     <Compile Include="Core\Impl\LocalMessageSet.cs" />
     <Compile Include="Core\IClientDataSet.cs" />
     <Compile Include="Core\Impl\ClientDataSet.cs" />
-    <Compile Include="Core\Impl\CoinSnapshotSet.cs" />
     <Compile Include="Core\MinerStudio\MinerStudioPath.cs" />
     <Compile Include="Mine\Cleaner.cs" />
     <Compile Include="Mine\DualMineContext.cs" />

+ 267 - 201
src/NTMinerDaemon/Ws/AbstractWsClient.cs

@@ -12,10 +12,37 @@ namespace NTMiner.Ws {
         private string _closeReason = string.Empty;
         private string _closeCode = string.Empty;
         private WebSocket _ws = null;
-        private bool _isOuterUserEnabled;
+        private string _wsServerIp = string.Empty;
+        private void NeedReWebSocket() {
+            _ws = null;
+        }
+        private bool IsOuterUserEnabled {
+            get {
+                switch (_appType) {
+                    case NTMinerAppType.MinerClient:
+                        return NTMinerRegistry.GetIsOuterUserEnabled();
+                    case NTMinerAppType.MinerStudio:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        }
+        private string OuterUserId {
+            get {
+                switch (_appType) {
+                    case NTMinerAppType.MinerClient:
+                        return NTMinerRegistry.GetOuterUserId();
+                    case NTMinerAppType.MinerStudio:
+                        return RpcRoot.RpcUser.LoginName;
+                    default:
+                        return string.Empty;
+                }
+            }
+        }
         // 用来判断用户是否改变了outerUserId,如果改变了则需要关闭连接使用新用户重新连接,该值根据_appType的不同而不同,
         // MinerClient时_outerUserId是来自注册表的OuterUserId,MinerStudio时_outerUserId是RpcRoot.RpcUser.LoginName
-        private string _outerUserId = string.Empty;
+        private string _preOuterUserId = string.Empty;
         private AESPassword _aesPassword;
 
         #region ctor
@@ -38,30 +65,27 @@ namespace NTMiner.Ws {
                     }
                 }, typeof(VirtualRoot));
             VirtualRoot.BuildEventPath<Per20SecondEvent>("周期Ws Ping", LogEnum.None, path: message => {
-                try {
-                    if (_ws != null && _ws.ReadyState == WebSocketState.Open) {
-                        // 或者_ws.IsAlive,因为_ws.IsAlive内部也是一个Ping,所以用Ping从而显式化这里有个网络请求
-                        Task.Factory.StartNew(() => {
-                            if (!_ws.Ping()) {
-                                _ws.CloseAsync(CloseStatusCode.Away);
-                            }
-                        });
-                    }
-                }
-                catch (Exception e) {
-                    Logger.ErrorDebugLine(e);
+                if (_ws != null && _ws.ReadyState == WebSocketState.Open) {
+                    // 或者_ws.IsAlive,因为_ws.IsAlive内部也是一个Ping,所以用Ping从而显式化这里有个网络请求
+                    Task.Factory.StartNew(() => {
+                        if (!_ws.Ping()) {
+                            _ws.CloseAsync(CloseStatusCode.Away);
+                        }
+                    });
                 }
             }, typeof(VirtualRoot));
             VirtualRoot.BuildEventPath<AppExitEvent>("退出程序时关闭Ws连接", LogEnum.DevConsole, path: message => {
                 _ws?.CloseAsync(CloseStatusCode.Normal, "客户端程序退出");
             }, this.GetType());
+            /// 1,进程启动后第一次连接时;
+            NeedReWebSocket();
             OpenOrCloseWs();
         }
         #endregion
 
         public bool IsOpen {
             get {
-                if (!_isOuterUserEnabled) {
+                if (!IsOuterUserEnabled) {
                     return false;
                 }
                 return _ws != null && _ws.ReadyState == WebSocketState.Open;
@@ -75,85 +99,54 @@ namespace NTMiner.Ws {
         protected abstract bool TryGetHandler(string messageType, out Action<Action<WsMessage>, WsMessage> handler);
         protected abstract void UpdateWsStateAsync(string description, bool toOut);
 
-        #region SendAsync
-        public void SendAsync(WsMessage message) {
-            if (!IsOpen) {
-                return;
-            }
-            switch (_appType) {
-                case NTMinerAppType.MinerClient:
-                    _ws.SendAsync(message.ToBytes(), null);
-                    break;
-                case NTMinerAppType.MinerStudio:
-                    _ws.SendAsync(message.SignToBytes(RpcRoot.RpcUser.Password), null);
-                    break;
-                default:
-                    break;
-            }
-        }
-        #endregion
-
         #region StartOrCloseWs
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="isResetFailCount">
+        /// 当由于用户行为执行该方法时传true,用户行为有:点击界面上的重连按钮、由内网切换到外网时的登录
+        /// </param>
         public void OpenOrCloseWs(bool isResetFailCount = false) {
             try {
                 if (isResetFailCount) {
                     ResetFailCount();
                 }
-                switch (_appType) {
-                    case NTMinerAppType.MinerClient:
-                        _isOuterUserEnabled = NTMinerRegistry.GetIsOuterUserEnabled();
-                        break;
-                    case NTMinerAppType.MinerStudio:
-                        _isOuterUserEnabled = true;
-                        break;
-                    default:
-                        _isOuterUserEnabled = false;
-                        break;
-                }
-                string outerUserId;
-                switch (_appType) {
-                    case NTMinerAppType.MinerClient:
-                        outerUserId = NTMinerRegistry.GetOuterUserId();
-                        break;
-                    case NTMinerAppType.MinerStudio:
-                        outerUserId = RpcRoot.RpcUser.LoginName;
-                        break;
-                    default:
-                        outerUserId = string.Empty;
-                        break;
-                }
-                if (!_isOuterUserEnabled) {
+                if (!IsOuterUserEnabled) {
                     ResetFailCount();
                     if (_ws != null && _ws.ReadyState == WebSocketState.Open) {
                         _ws.CloseAsync(CloseStatusCode.Normal, "外网群控已禁用");
                     }
                     return;
                 }
+                string outerUserId = OuterUserId;
                 if (string.IsNullOrEmpty(outerUserId)) {
                     ResetFailCount();
                     _ws?.CloseAsync(CloseStatusCode.Normal, "未填写用户");
                     return;
                 }
-                bool isUserIdChanged = _outerUserId != outerUserId;
+                bool isUserIdChanged = _preOuterUserId != outerUserId;
                 if (isUserIdChanged) {
-                    _outerUserId = outerUserId;
+                    _preOuterUserId = outerUserId;
                 }
-                // 1,进程启动后第一次连接时
                 if (_ws == null) {
-                    NewWebSocket(webSocket => _ws = webSocket);
+                    NewWebSocket();
                 }
                 else if (isUserIdChanged) {
                     ResetFailCount();
                     if (_ws.ReadyState == WebSocketState.Open) {
-                        // 因为旧的用户名密码成连接过且用户名或密码变更了,那么关闭连接即可,无需立即再次连接因为一定连接不成功因为用户名密码不再正确。
                         string why = string.Empty;
                         if (isUserIdChanged) {
                             why = ",因为用户变更";
                         }
+                        // 因为连接处在打开中说明用户名正确,现在用户名变更了则极有可能用户名不再正确所以没必要立即做
+                        // 因用户名变更而需要的重连操作。另外如果是用户名是由一个正确的用户名变更为另一个正确的用户名
+                        // 的话应该立即重连,但由于不知道新用户名是否正确所以还是等下个周期自动重连吧这里就不立即重连了,
+                        // 只需关闭当前连接即可。
                         _ws.CloseAsync(CloseStatusCode.Normal, $"关闭连接{why}");
                     }
                     else {
-                        // 因为旧用户名密码没有成功连接过,说明旧用户名密码不正确,而现在用户名密码变更了,有可能变更正确了所以尝试连接一次。
+                        // 因为旧用户名密码没有成功连接过,说明旧用户名密码不正确,而现在用户名密码变更了,
+                        // 有可能变更正确了所以立即尝试连接一次。
                         ConnectAsync(_ws);
                     }
                 }
@@ -167,7 +160,7 @@ namespace NTMiner.Ws {
         }
         #endregion
 
-        #region GetWsDaemonState
+        #region GetState
         public WsClientState GetState() {
             var ws = _ws;
             string description = $"{_closeCode} {_closeReason}";
@@ -181,6 +174,7 @@ namespace NTMiner.Ws {
             var state = new WsClientState {
                 Status = status,
                 Description = description,
+                WsServerIp = _wsServerIp,
                 NextTrySecondsDelay = NextTrySecondsDelay,
                 LastTryOn = LastTryOn
             };
@@ -188,8 +182,27 @@ namespace NTMiner.Ws {
         }
         #endregion
 
+        #region SendAsync
+        public void SendAsync(WsMessage message) {
+            if (!IsOpen) {
+                return;
+            }
+            switch (_appType) {
+                case NTMinerAppType.MinerClient:
+                    _ws.SendAsync(message.ToBytes(), null);
+                    break;
+                case NTMinerAppType.MinerStudio:
+                    _ws.SendAsync(message.SignToBytes(RpcRoot.RpcUser.Password), null);
+                    break;
+                default:
+                    break;
+            }
+        }
+        #endregion
+
         #region 私有方法
         #region NewWebSocket
+        private readonly object _wsLocker = new object();
         /// <summary>
         /// 这是一次彻底的重连,重新获取服务器地址的重连,以下情况下才会调用该方法:
         /// 1,进程启动后第一次连接时;
@@ -197,159 +210,209 @@ namespace NTMiner.Ws {
         /// 3,收到服务器的WsMessage.ReGetServerAddress类型的消息时;
         /// </summary>
         /// <returns></returns>
-        private void NewWebSocket(Action<WebSocket> callback) {
-            RpcRoot.OfficialServer.WsServerNodeService.GetNodeAddressAsync(NTMinerRegistry.GetClientId(_appType), _outerUserId, (response, ex) => {
+        private void NewWebSocket() {
+            if (_ws != null) {
+                return;
+            }
+            RpcRoot.OfficialServer.WsServerNodeService.GetNodeAddressAsync(NTMinerRegistry.GetClientId(_appType), _preOuterUserId, (response, ex) => {
+                if (_ws == null) {
+                    lock (_wsLocker) {
+                        if (_ws == null) {
+                            LastTryOn = DateTime.Now;
+                            _wsServerIp = string.Empty;
+
+                            #region 网络错误或没有获取到WsServer节点时退出
+                            if (!response.IsSuccess()) {
+                                // 没有获取到该矿机的WsServer分片节点,递增失败次数以增大重试延迟周期
+                                IncreaseFailCount();
+                                UpdateNextTrySecondsDelay();
+                                string description;
+                                if (response == null || response.ReasonPhrase == null) {
+                                    description = "网络错误";
+                                }
+                                else {
+                                    description = response.ReadMessage(ex);
+                                }
+                                UpdateWsStateAsync(description, toOut: false);
+                                // 退出
+                                return;
+                            }
+                            string server = response.Data;
+                            if (string.IsNullOrEmpty(server)) {
+                                // 没有获取到该矿机的WsServer分片节点,递增失败次数以增大重试延迟周期
+                                IncreaseFailCount();
+                                UpdateNextTrySecondsDelay();
+                                UpdateWsStateAsync("服务器不在线", toOut: false);
+                                // 退出
+                                return;
+                            }
+                            _wsServerIp = server;
+                            #endregion
+
+                            var ws = new WebSocket($"ws://{server}/{_appType.GetName()}") {
+                                Compression = CompressionMethod.Deflate
+                            };
+                            ws.Log.File = TempPath.WebSocketSharpMinerClientLogFileFullName;
+                            ws.Log.Output = WebSocketSharpOutput;
+                            ws.OnOpen += new EventHandler(Ws_OnOpen);
+                            ws.OnMessage += new EventHandler<MessageEventArgs>(Ws_OnMessage);
+                            ws.OnError += new EventHandler<ErrorEventArgs>(Ws_OnError);
+                            ws.OnClose += new EventHandler<CloseEventArgs>(Ws_OnClose);
+
+                            ConnectAsync(ws);
+                            _ws = ws;
+                        }
+                    }
+                }
+            });
+        }
+        #endregion
+
+        private static void WebSocketSharpOutput(LogData data, string path) {
+            Console.WriteLine(data.Message);
+            if (path != null && path.Length > 0) {
+                using (var writer = new System.IO.StreamWriter(path, true))
+                using (var syncWriter = System.IO.TextWriter.Synchronized(writer)) {
+                    syncWriter.WriteLine(data.ToString());
+                }
+            }
+        }
+
+        private void Ws_OnClose(object sender, CloseEventArgs e) {
+            WebSocket ws = (WebSocket)sender;
+            if (_ws != ws) {
+                return;
+            }
+            #region
+            try {
                 LastTryOn = DateTime.Now;
-                if (!response.IsSuccess()) {
+                CloseStatusCode closeStatus = CloseStatusCode.Undefined;
+                if (Enum.IsDefined(typeof(CloseStatusCode), e.Code)) {
+                    closeStatus = (CloseStatusCode)e.Code;
+                    _closeCode = closeStatus.GetName();
+                }
+                else {
+                    _closeCode = e.Code.ToString();
+                    Logger.InfoDebugLine($"意外的Ws关闭码:{_closeCode}");
+                }
+                if (closeStatus == CloseStatusCode.ProtocolError || closeStatus == CloseStatusCode.Undefined) {
+                    // 协议错误导致连接关闭,递增失败次数以增大重试延迟周期
+                    IncreaseFailCount();
+                    // WebSocket协议并无定义状态码用于表达握手阶段身份验证失败的情况,阅读WebSocketSharp的源码发现验证不通过包含于ProtocolError中,
+                    // 姑且将ProtocolError视为用户名密码验证不通过,因为ProtocolError包含的其它失败情况在编程正确下不会发送。
+                    _closeReason = "登录失败";
+                }
+                else if (closeStatus == CloseStatusCode.Away) {
+                    // 服务器ping不通时和服务器关闭进程时,递增失败次数以增大重试延迟周期
+                    IncreaseFailCount();
+                    _closeReason = "失去连接,请稍后重试";
+                    // 可能是因为服务器节点不存在导致的失败,所以下一次进行重新获取服务器地址的全新连接
+                    // 2,连不上服务器时
+                    NeedReWebSocket();
+                }
+                else if (closeStatus == CloseStatusCode.Abnormal) {
+                    // 服务器或本机网络不可用时,递增失败次数以增大重试延迟周期
                     IncreaseFailCount();
-                    UpdateNextTrySecondsDelay();
-                    string description;
-                    if (response == null || response.ReasonPhrase == null) {
-                        description = "网络错误";
+                    _closeReason = "网络错误";
+                    // 可能是因为服务器节点不存在导致的失败,所以下一次进行重新获取服务器地址的全新连接
+                    // 2,连不上服务器时
+                    NeedReWebSocket();
+                }
+                else if (closeStatus == CloseStatusCode.Normal) {
+                    _closeReason = e.Reason;
+                    if (e.Reason == WsMessage.ReGetServerAddress) {
+                        _closeReason = "服务器通知客户端重连";
+                        // 3,收到服务器的WsMessage.ReGetServerAddress类型的消息时
+                        NeedReWebSocket();
+                        // 立即重连以防止在上一个连接关闭之前重连成功从而在界面上留下错误顺序的消息
+                        NewWebSocket();
                     }
                     else {
-                        description = response.ReadMessage(ex);
+                        // 正常关闭时,递增失败次数以增大重试延迟周期
+                        IncreaseFailCount();
                     }
-                    UpdateWsStateAsync(description, toOut: false);
-                    callback?.Invoke(null);
-                    // 退出
-                    return;
                 }
-                string server = response.Data;
-                if (string.IsNullOrEmpty(server)) {
-                    IncreaseFailCount();
-                    UpdateNextTrySecondsDelay();
-                    UpdateWsStateAsync("服务器不在线", toOut: false);
-                    callback?.Invoke(null);
-                    // 退出
+            }
+            catch {
+            }
+            UpdateNextTrySecondsDelay();
+            UpdateWsStateAsync($"{_closeCode} {_closeReason}", toOut: false);
+            #endregion
+        }
+
+        private void Ws_OnError(object sender, ErrorEventArgs e) {
+            NTMinerConsole.DevError(e.Message);
+        }
+
+        private void Ws_OnMessage(object sender, MessageEventArgs e) {
+            WebSocket ws = (WebSocket)sender;
+            if (_ws != ws) {
+                return;
+            }
+            #region
+            if (e.IsPing) {
+                return;
+            }
+            WsMessage message = null;
+            if (e.IsBinary) {
+                message = VirtualRoot.BinarySerializer.Deserialize<WsMessage>(e.RawData);
+            }
+            else {// 此时一定IsText,因为取值只有IsBinary、IsPing、IsText这三种
+                if (string.IsNullOrEmpty(e.Data) || e.Data[0] != '{' || e.Data[e.Data.Length - 1] != '}') {
                     return;
                 }
-
-                var ws = new WebSocket($"ws://{server}/{_appType.GetName()}");
-                ws.OnOpen += (sender, e) => {
-                    ResetFailCount();
-                    UpdateWsStateAsync("连接服务器成功", toOut: false);
-                };
-                ws.OnMessage += (sender, e) => {
-                    if (_ws != ws) {
+                message = VirtualRoot.JsonSerializer.Deserialize<WsMessage>(e.Data);
+            }
+            if (message == null) {
+                return;
+            }
+            switch (_appType) {
+                case NTMinerAppType.MinerClient:
+                    if (message.Type == WsMessage.UpdateAESPassword) {
+                        if (message.TryGetData(out AESPassword aesPassword)) {
+                            aesPassword.Password = Cryptography.RSAUtil.DecryptString(aesPassword.Password, aesPassword.PublicKey);
+                            _aesPassword = aesPassword;
+                        }
                         return;
                     }
-                    #region
-                    if (e.IsPing) {
+                    if (_aesPassword == null) {
                         return;
                     }
-                    WsMessage message = null;
-                    if (e.IsBinary) {
-                        message = VirtualRoot.BinarySerializer.Deserialize<WsMessage>(e.RawData);
-                    }
-                    else {// 此时一定IsText,因为取值只有IsBinary、IsPing、IsText这三种
-                        if (string.IsNullOrEmpty(e.Data) || e.Data[0] != '{' || e.Data[e.Data.Length - 1] != '}') {
-                            return;
-                        }
-                        message = VirtualRoot.JsonSerializer.Deserialize<WsMessage>(e.Data);
-                    }
-                    if (message == null) {
+                    if (message.Sign != message.CalcSign(_aesPassword.Password)) {
+                        UpdateWsStateAsync(description: "来自群控的消息签名错误", toOut: true);
                         return;
                     }
-                    switch (_appType) {
-                        case NTMinerAppType.MinerClient:
-                            if (message.Type == WsMessage.UpdateAESPassword) {
-                                if (message.TryGetData(out AESPassword aesPassword)) {
-                                    aesPassword.Password = Cryptography.RSAUtil.DecryptString(aesPassword.Password, aesPassword.PublicKey);
-                                    _aesPassword = aesPassword;
-                                }
-                                return;
-                            }
-                            if (_aesPassword == null) {
-                                return;
-                            }
-                            if (message.Sign != message.CalcSign(_aesPassword.Password)) {
-                                UpdateWsStateAsync(description: "来自群控的消息签名错误", toOut: true);
-                                return;
-                            }
-                            break;
-                        case NTMinerAppType.MinerStudio:
-                            if (message.Type == WsMessage.ReLogin) {
-                                UpdateWsStateAsync(description: "用户密码已变更,请重新登录", toOut: true);
-                                return;
-                            }
-                            if (message.Sign != message.CalcSign(RpcRoot.RpcUser.Password)) {
-                                UpdateWsStateAsync(description: "来自群控的消息签名错误", toOut: true);
-                                return;
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                    if (message.Type == WsMessage.ReGetServerAddress) {
-                        ws.CloseAsync(CloseStatusCode.Normal, WsMessage.ReGetServerAddress);
+                    break;
+                case NTMinerAppType.MinerStudio:
+                    if (message.Type == WsMessage.ReLogin) {
+                        UpdateWsStateAsync(description: "用户密码已变更,请重新登录", toOut: true);
                         return;
                     }
-                    if (TryGetHandler(message.Type, out Action<Action<WsMessage>, WsMessage> handler)) {
-                        handler.Invoke(SendAsync, message);
-                    }
-                    else {
-                        NTMinerConsole.DevWarn(() => $"OnMessage Received InvalidType {e.Data}");
-                    }
-                    #endregion
-                };
-                ws.OnError += (sender, e) => {
-                    NTMinerConsole.DevError(e.Message);
-                };
-                ws.OnClose += (sender, e) => {
-                    if (_ws != ws) {
+                    if (message.Sign != message.CalcSign(RpcRoot.RpcUser.Password)) {
+                        UpdateWsStateAsync(description: "来自群控的消息签名错误", toOut: true);
                         return;
                     }
-                    #region
-                    try {
-                        LastTryOn = DateTime.Now;
-                        _closeCode = e.Code.ToString();
-                        CloseStatusCode closeStatus = (CloseStatusCode)e.Code;
-                        _closeCode = closeStatus.GetName();
-                        if (closeStatus == CloseStatusCode.ProtocolError) {
-                            IncreaseFailCount();
-                            // WebSocket协议并无定义状态码用于表达握手阶段身份验证失败的情况,阅读WebSocketSharp的源码发现验证不通过包含于ProtocolError中,
-                            // 姑且将ProtocolError视为用户名密码验证不通过,因为ProtocolError包含的其它失败情况在编程正确下不会发送。
-                            _closeReason = "登录失败";
-                        }
-                        else if (closeStatus == CloseStatusCode.Away) { // 服务器ping不通时和服务器关闭进程时都是Away
-                            IncreaseFailCount();
-                            _closeReason = "失去连接,请稍后重试";
-                            // 可能是因为服务器节点不存在导致的失败,所以下一次进行重新获取服务器地址的全新连接
-                            // 2,连不上服务器时
-                            _ws = null;
-                        }
-                        else if (closeStatus == CloseStatusCode.Abnormal) { // 服务器或本机网络不可用时尝试连接时的类型是Abnormal
-                            IncreaseFailCount();
-                            _closeReason = "网络错误";
-                            // 可能是因为服务器节点不存在导致的失败,所以下一次进行重新获取服务器地址的全新连接
-                            // 2,连不上服务器时
-                            _ws = null;
-                        }
-                        else if (closeStatus == CloseStatusCode.Normal) {
-                            _closeReason = e.Reason;
-                            if (e.Reason == WsMessage.ReGetServerAddress) {
-                                _closeReason = "服务器通知客户端重连";
-                                // 放在这里重连以防止在上一个连接关闭之前重连成功从而在界面上留下错误顺序的消息
-                                // 3,收到服务器的WsMessage.ReGetServerAddress类型的消息时
-                                NewWebSocket(webSocket => _ws = webSocket);
-                            }
-                            else {
-                                IncreaseFailCount();
-                            }
-                        }
-                    }
-                    catch {
-                    }
-                    UpdateNextTrySecondsDelay();
-                    UpdateWsStateAsync($"{_closeCode} {_closeReason}", toOut: false);
-                    #endregion
-                };
-                ConnectAsync(ws);
-                callback?.Invoke(ws);
-            });
+                    break;
+                default:
+                    break;
+            }
+            if (message.Type == WsMessage.ReGetServerAddress) {
+                ws.CloseAsync(CloseStatusCode.Normal, WsMessage.ReGetServerAddress);
+                return;
+            }
+            if (TryGetHandler(message.Type, out Action<Action<WsMessage>, WsMessage> handler)) {
+                handler.Invoke(SendAsync, message);
+            }
+            else {
+                NTMinerConsole.DevWarn(() => $"OnMessage Received InvalidType {e.Data}");
+            }
+            #endregion
+        }
+
+        private void Ws_OnOpen(object sender, EventArgs e) {
+            ResetFailCount();
+            UpdateWsStateAsync("连接服务器成功", toOut: false);
         }
-        #endregion
 
         #region ConnectAsync
         private readonly FieldInfo _retryCountForConnectFieldInfo = typeof(WebSocket).GetField("_retryCountForConnect", BindingFlags.Instance | BindingFlags.NonPublic);
@@ -364,9 +427,9 @@ namespace NTMiner.Ws {
             _retryCountForConnectFieldInfo.SetValue(ws, 1);
             WsUserName userName = new WsUserName {
                 ClientType = _appType,
-                ClientVersion = EntryAssemblyInfo.CurrentVersionStr, 
-                ClientId = NTMinerRegistry.GetClientId(_appType), 
-                UserId = _outerUserId,
+                ClientVersion = EntryAssemblyInfo.CurrentVersionStr,
+                ClientId = NTMinerRegistry.GetClientId(_appType),
+                UserId = _preOuterUserId,
                 IsBinarySupported = true
             };
             string userNameJson = VirtualRoot.JsonSerializer.Serialize(userName);
@@ -389,6 +452,9 @@ namespace NTMiner.Ws {
             NextTrySecondsDelay = -1;
         }
 
+        /// <summary>
+        /// 每失败一次,重试延迟周期增加10秒钟
+        /// </summary>
         private void IncreaseFailCount() {
             _failConnCount++;
         }

+ 0 - 98
src/NTMinerDataSchemas/Core/MinerServer/ClientData.cs

@@ -717,104 +717,6 @@ namespace NTMiner.Core.MinerServer {
             #endregion
         }
 
-        #region GetMainCoinShareDelta
-        public int GetMainCoinShareDelta(bool isPull) {
-            if (_preMainCoinShare == 0) {
-                return 0;
-            }
-            if (this.IsMining == false || string.IsNullOrEmpty(this.MainCoinCode)) {
-                return 0;
-            }
-            if (isPull) {
-                if (this._preUpdateOn.AddSeconds(5) > DateTime.Now) {
-                    return 0;
-                }
-            }
-            else if (this._preUpdateOn.AddSeconds(115) > DateTime.Now) {
-                return 0;
-            }
-
-            int delta = this.MainCoinTotalShare - _preMainCoinShare;
-            if (delta < 0) {
-                delta = 0;
-            }
-            return delta;
-        }
-        #endregion
-
-        #region GetDualCoinShareDelta
-        public int GetDualCoinShareDelta(bool isPull) {
-            if (_preDualCoinShare == 0) {
-                return 0;
-            }
-            if (this.IsMining == false || string.IsNullOrEmpty(this.DualCoinCode)) {
-                return 0;
-            }
-            if (isPull) {
-                if (this._preUpdateOn.AddSeconds(5) > DateTime.Now) {
-                    return 0;
-                }
-            }
-            else if (this._preUpdateOn.AddSeconds(115) > DateTime.Now) {
-                return 0;
-            }
-
-            int delta = this.DualCoinTotalShare - _preDualCoinShare;
-            if (delta < 0) {
-                delta = 0;
-            }
-            return delta;
-        }
-        #endregion
-
-        #region GetMainCoinRejectShareDelta
-        public int GetMainCoinRejectShareDelta(bool isPull) {
-            if (_preMainCoinRejectShare == 0) {
-                return 0;
-            }
-            if (this.IsMining == false || string.IsNullOrEmpty(this.MainCoinCode)) {
-                return 0;
-            }
-            if (isPull && this._preUpdateOn.AddSeconds(5) > DateTime.Now) {
-                return 0;
-            }
-            else if (this._preUpdateOn.AddSeconds(115) > DateTime.Now) {
-                return 0;
-            }
-
-            int delta = this.MainCoinRejectShare - _preMainCoinRejectShare;
-            if (delta < 0) {
-                delta = 0;
-            }
-            return delta;
-        }
-        #endregion
-
-        #region GetDualCoinRejectShareDelta
-        public int GetDualCoinRejectShareDelta(bool isPull) {
-            if (_preDualCoinRejectShare == 0) {
-                return 0;
-            }
-            if (this.IsMining == false || string.IsNullOrEmpty(this.DualCoinCode)) {
-                return 0;
-            }
-            if (isPull) {
-                if (this._preUpdateOn.AddSeconds(5) > DateTime.Now) {
-                    return 0;
-                }
-            }
-            else if (this._preUpdateOn.AddSeconds(115) > DateTime.Now) {
-                return 0;
-            }
-
-            int delta = this.DualCoinRejectShare - _preDualCoinRejectShare;
-            if (delta < 0) {
-                delta = 0;
-            }
-            return delta;
-        }
-        #endregion
-
         public string Id { get; set; }
 
         /// <summary>

+ 0 - 8
src/NTMinerDataSchemas/Core/MinerServer/CoinSnapshotData.cs

@@ -8,10 +8,6 @@ namespace NTMiner.Core.MinerServer {
                 Id = string.Empty,
                 CoinCode = coinCode,
                 Speed = 0,
-                RejectShareDelta = 0,
-                ShareDelta = 0,
-                DualCoinMiningCount = 0,
-                DualCoinOnlineCount = 0,
                 MainCoinMiningCount = 0,
                 MainCoinOnlineCount = 0,
                 Timestamp = DateTime.MinValue
@@ -26,12 +22,8 @@ namespace NTMiner.Core.MinerServer {
         public string Id { get; set; }
         public string CoinCode { get; set; }
         public double Speed { get; set; }
-        public int ShareDelta { get; set; }
-        public int RejectShareDelta { get; set; }
         public int MainCoinOnlineCount { get; set; }
         public int MainCoinMiningCount { get; set; }
-        public int DualCoinOnlineCount { get; set; }
-        public int DualCoinMiningCount { get; set; }
         public DateTime Timestamp { get; set; }
     }
 }

+ 4 - 1
src/NTMinerDataSchemas/Core/MinerServer/QueryClientsResponse.cs

@@ -7,7 +7,10 @@ namespace NTMiner.Core.MinerServer {
             this.LatestSnapshots = new CoinSnapshotData[0];
         }
 
-        public static QueryClientsResponse Ok(List<ClientData> data, int total, CoinSnapshotData[] latestSnapshots, int totalMiningCount, int totalOnlineCount) {
+        public static QueryClientsResponse Ok(
+            List<ClientData> data, int total, 
+            CoinSnapshotData[] latestSnapshots, 
+            int totalMiningCount, int totalOnlineCount) {
             return new QueryClientsResponse() {
                 StateCode = 200,
                 ReasonPhrase = "Ok",

+ 3 - 0
src/NTMinerDataSchemas/NTKeyword.cs

@@ -15,6 +15,7 @@ namespace NTMiner {
         public const string VersionBuild = Version + "." + _build;
         public const string ManJiTag = "蛮吉";
         public const string ManXiaoManTag = "蛮小满";
+        public const string WoLiuDao = "涡流岛";
         public const string Copyright = "Copyright ©  NTMiner";
         public const string Company = "开源矿工";
 
@@ -23,6 +24,8 @@ namespace NTMiner {
         public const string RootConfigFileName = "home.config";
 
         public const string LogsDirName = "Logs";
+        public const string WebSocketSharpMinerClientLogFileName = "websocket-sharp-minerclient.log";
+        public const string WebSocketSharpMinerStudioLogFileName = "websocket-sharp-minerstudio.log";
         public const string ServerJsonFileName = "server.json";
         public const string LocalJsonFileName = "local.json";
         public const string GpuProfilesFileName = "gpuProfiles.json";

+ 1 - 0
src/NTMinerDataSchemas/Ws/WsClientState.cs

@@ -9,6 +9,7 @@ namespace NTMiner.Ws {
 
         public WsClientStatus Status { get; set; }
         public string Description { get; set; }
+        public string WsServerIp { get; set; }
         public int NextTrySecondsDelay { get; set; }
         public DateTime LastTryOn { get; set; }
         public bool ToOut { get; set; }

+ 1 - 1
src/NTMinerHub/Hub/IMessagePathHub.cs

@@ -8,7 +8,7 @@ namespace NTMiner.Hub {
     public interface IMessagePathHub {
         void Route<TMessage>(TMessage message) where TMessage : IMessage;
 
-        void AddPath<TMessage>(MessagePath<TMessage> path);
+        IMessagePathId AddPath<TMessage>(Type location, string description, LogEnum logType, Action<TMessage> action, PathId pathId, int viaTimesLimit = -1);
 
         void RemovePath(IMessagePathId pathId);
 

+ 82 - 3
src/NTMinerHub/Hub/MessagePathHub.cs

@@ -1,7 +1,11 @@
 namespace NTMiner.Hub {
     using System;
     using System.Collections.Generic;
+#if DEBUG
+    using System.ComponentModel;
+#endif
     using System.Linq;
+    using System.Threading;
 
     public class MessagePathHub : IMessagePathHub {
         #region 内部类
@@ -98,6 +102,79 @@
                 }
             }
         }
+
+#if DEBUG
+        public class MessagePath<TMessage> : IMessagePathId, INotifyPropertyChanged {
+#else
+    public class MessagePath<TMessage> : IMessagePathId {
+#endif
+            private readonly Action<TMessage> _path;
+            private bool _isEnabled;
+            private int _viaTimesLimit;
+
+#if DEBUG
+            public event PropertyChangedEventHandler PropertyChanged;
+#endif
+
+            internal MessagePath(Type location, string description, LogEnum logType, Action<TMessage> action, PathId pathId, int viaTimesLimit) {
+                if (viaTimesLimit == 0) {
+                    throw new InvalidProgramException("消息路径的viaTimesLimit不能为0,可以为负数表示不限制通过次数或为正数表示限定通过次数,但不能为0");
+                }
+                _isEnabled = true;
+                MessageType = typeof(TMessage);
+                Location = location;
+                Path = $"{location.FullName}[{MessageType.FullName}]";
+                Description = description;
+                LogType = logType;
+                _path = action;
+                PathId = pathId;
+                _viaTimesLimit = viaTimesLimit;
+                CreatedOn = DateTime.Now;
+            }
+
+            public int ViaTimesLimit {
+                get => _viaTimesLimit;
+                private set {
+                    _viaTimesLimit = value;
+                }
+            }
+
+            internal void DecreaseViaTimesLimit(Action<IMessagePathId> onDownToZero) {
+                int newValue = Interlocked.Decrement(ref _viaTimesLimit);
+                if (newValue == 0) {
+                    onDownToZero?.Invoke(this);
+                }
+#if DEBUG
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViaTimesLimit)));
+#endif
+            }
+
+            public PathId PathId { get; private set; }
+            public DateTime CreatedOn { get; private set; }
+            public Type MessageType { get; private set; }
+            public Type Location { get; private set; }
+            public string Path { get; private set; }
+            public LogEnum LogType { get; private set; }
+            public string Description { get; private set; }
+            public bool IsEnabled {
+                get => _isEnabled;
+                set {
+                    _isEnabled = value;
+#if DEBUG
+                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEnabled)));
+#endif
+                }
+            }
+
+            public void Go(TMessage message) {
+                try {
+                    _path?.Invoke(message);
+                }
+                catch (Exception e) {
+                    Logger.ErrorDebugLine(Path + ":" + e.Message, e);
+                }
+            }
+        }
         #endregion
 
         private readonly MessagePathSetSet PathSetSet = new MessagePathSetSet();
@@ -179,12 +256,14 @@
             }
         }
 
-        public void AddPath<TMessage>(MessagePath<TMessage> path) {
-            if (path == null) {
-                throw new ArgumentNullException(nameof(path));
+        public IMessagePathId AddPath<TMessage>(Type location, string description, LogEnum logType, Action<TMessage> action, PathId pathId, int viaTimesLimit = -1) {
+            if (action == null) {
+                throw new ArgumentNullException(nameof(action));
             }
+            MessagePath<TMessage> path = new MessagePath<TMessage>(location, description, logType, action, pathId, viaTimesLimit);
             PathSetSet.GetMessagePathSet<TMessage>().AddMessagePath(path);
             PathAdded?.Invoke(path);
+            return path;
         }
 
         public void RemovePath(IMessagePathId pathId) {

+ 0 - 89
src/NTMinerHub/Hub/MessagePath`1.cs

@@ -1,89 +0,0 @@
-using System;
-using System.Threading;
-#if DEBUG
-using System.ComponentModel;
-#endif
-
-namespace NTMiner.Hub {
-#if DEBUG
-    public class MessagePath<TMessage> : IMessagePathId, INotifyPropertyChanged {
-#else
-    public class MessagePath<TMessage> : IMessagePathId {
-#endif
-        private readonly Action<TMessage> _path;
-        private bool _isEnabled;
-        private int _viaTimesLimit;
-
-#if DEBUG
-        public event PropertyChangedEventHandler PropertyChanged;
-#endif
-
-        public static MessagePath<TMessage> AddMessagePath(IMessagePathHub hub, Type location, string description, LogEnum logType, Action<TMessage> action, PathId pathId, int viaTimesLimit = -1) {
-            if (action == null) {
-                throw new ArgumentNullException(nameof(action));
-            }
-            MessagePath<TMessage> path = new MessagePath<TMessage>(location, description, logType, action, pathId, viaTimesLimit);
-            hub.AddPath(path);
-            return path;
-        }
-
-        private MessagePath(Type location, string description, LogEnum logType, Action<TMessage> action, PathId pathId, int viaTimesLimit) {
-            if (viaTimesLimit == 0) {
-                throw new InvalidProgramException("消息路径的viaTimesLimit不能为0,可以为负数表示不限制通过次数或为正数表示限定通过次数,但不能为0");
-            }
-            _isEnabled = true;
-            MessageType = typeof(TMessage);
-            Location = location;
-            Path = $"{location.FullName}[{MessageType.FullName}]";
-            Description = description;
-            LogType = logType;
-            _path = action;
-            PathId = pathId;
-            _viaTimesLimit = viaTimesLimit;
-            CreatedOn = DateTime.Now;
-        }
-
-        public int ViaTimesLimit {
-            get => _viaTimesLimit;
-            private set {
-                _viaTimesLimit = value;
-            }
-        }
-
-        internal void DecreaseViaTimesLimit(Action<IMessagePathId> onDownToZero) {
-            int newValue = Interlocked.Decrement(ref _viaTimesLimit);
-            if (newValue == 0) {
-                onDownToZero?.Invoke(this);
-            }
-#if DEBUG
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViaTimesLimit)));
-#endif
-        }
-
-        public PathId PathId { get; private set; }
-        public DateTime CreatedOn { get; private set; }
-        public Type MessageType { get; private set; }
-        public Type Location { get; private set; }
-        public string Path { get; private set; }
-        public LogEnum LogType { get; private set; }
-        public string Description { get; private set; }
-        public bool IsEnabled {
-            get => _isEnabled;
-            set {
-                _isEnabled = value;
-#if DEBUG
-                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEnabled)));
-#endif
-            }
-        }
-
-        public void Go(TMessage message) {
-            try {
-                _path?.Invoke(message);
-            }
-            catch (Exception e) {
-                Logger.ErrorDebugLine(Path + ":" + e.Message, e);
-            }
-        }
-    }
-}

+ 0 - 2
src/NTMinerHub/NTMinerHub.csproj

@@ -31,13 +31,11 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="Microsoft.CSharp" />
     <Reference Include="System" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="AnonymousMessagePath.cs" />
     <Compile Include="Hub\Cmd.cs" />
-    <Compile Include="Hub\MessagePath`1.cs" />
     <Compile Include="Hub\SourcedEvent`1.cs" />
     <Compile Include="Hub\EventBase.cs" />
     <Compile Include="Hub\ICmd.cs" />

+ 3 - 3
src/NTMinerLogging/ILoggingService.cs

@@ -10,8 +10,8 @@ namespace NTMiner {
         void ErrorDebugLine(object message);
         void ErrorDebugLine(object message, Exception exception);
 
-        void OkWriteLine(object message);
-        void WarnWriteLine(object message);
-        void ErrorWriteLine(object message);
+        void OkUserLine(object message);
+        void WarnUserLine(object message);
+        void ErrorUserLine(object message);
     }
 }

+ 3 - 3
src/NTMinerLogging/Impl/Log4NetLoggingService.cs

@@ -84,17 +84,17 @@ $@"<log4net>
             _log.Error(message, exception);
         }
 
-        public void OkWriteLine(object message) {
+        public void OkUserLine(object message) {
             NTMinerConsole.UserOk(message?.ToString());
             _log.Info(message);
         }
 
-        public void WarnWriteLine(object message) {
+        public void WarnUserLine(object message) {
             NTMinerConsole.UserWarn(message?.ToString());
             _log.Warn(message);
         }
 
-        public void ErrorWriteLine(object message) {
+        public void ErrorUserLine(object message) {
             NTMinerConsole.UserError(message?.ToString());
             _log.Warn(message);
         }

+ 3 - 3
src/NTMinerLogging/Logger.cs

@@ -78,19 +78,19 @@ namespace NTMiner {
             if (!_isEnabled) {
                 return;
             }
-            _logger.Value.OkWriteLine(message);
+            _logger.Value.OkUserLine(message);
         }
         public static void WarnWriteLine(object message) {
             if (!_isEnabled) {
                 return;
             }
-            _logger.Value.WarnWriteLine(message);
+            _logger.Value.WarnUserLine(message);
         }
         public static void ErrorWriteLine(object message) {
             if (!_isEnabled) {
                 return;
             }
-            _logger.Value.ErrorWriteLine(message);
+            _logger.Value.ErrorUserLine(message);
         }
     }
 }

+ 0 - 2
src/NTMinerRpcClient/NTMinerRpcClient.csproj

@@ -75,8 +75,6 @@
     <Compile Include="Services\Client\NTMinerDaemonService.cs" />
     <Compile Include="IKernelOutputKeywordSet.cs" />
     <Compile Include="Impl\KernelOutputKeywordSet.cs" />
-    <Compile Include="Services\Official\ClientDataBinaryService.cs" />
-    <Compile Include="Services\Official\CoinSnapshotService.cs" />
     <Compile Include="Services\Official\FileUrlService.cs" />
     <Compile Include="Services\Official\GpuNameService.cs" />
     <Compile Include="Services\Official\NTMinerWalletService.cs" />

+ 0 - 1
src/NTMinerRpcClient/RpcRoot.cs

@@ -10,7 +10,6 @@ namespace NTMiner {
         public static readonly IJsonRpcHelper JsonRpc = new JsonRpcHelper();
 
         static RpcRoot() {
-            System.Net.ServicePointManager.DefaultConnectionLimit = 5;
         }
 
         public static string OfficialServerHost { get; private set; }

+ 0 - 39
src/NTMinerRpcClient/Services/Official/ClientDataBinaryService.cs

@@ -1,39 +0,0 @@
-using NTMiner.Controllers;
-using NTMiner.Core.MinerServer;
-using System;
-using System.Net.Http;
-
-namespace NTMiner.Services.Official {
-    public class ClientDataBinaryService {
-        private readonly string _controllerName = RpcRoot.GetControllerName<IClientDataBinaryController<HttpResponseMessage>>();
-
-        internal ClientDataBinaryService() {
-        }
-
-        #region QueryClientsAsync
-        public void QueryClientsAsync(QueryClientsRequest query, Action<QueryClientsResponse, Exception> callback) {
-            RpcRoot.JsonRequestBinaryResponseRpcHelper.SignPostAsync(
-                RpcRoot.OfficialServerHost, 
-                RpcRoot.OfficialServerPort, 
-                _controllerName, 
-                nameof(IClientDataBinaryController<HttpResponseMessage>.QueryClients), 
-                data: query, 
-                callback,
-                timeountMilliseconds: 3000);
-        }
-        #endregion
-
-        #region QueryClientsForWsAsync
-        public void QueryClientsForWsAsync(QueryClientsForWsRequest query, Action<QueryClientsResponse, Exception> callback) {
-            RpcRoot.JsonRequestBinaryResponseRpcHelper.SignPostAsync(
-                RpcRoot.OfficialServerHost, 
-                RpcRoot.OfficialServerPort, 
-                _controllerName, 
-                nameof(IClientDataBinaryController<HttpResponseMessage>.QueryClientsForWs), 
-                data: query, 
-                callback,
-                timeountMilliseconds: 3000);
-        }
-        #endregion
-    }
-}

+ 0 - 27
src/NTMinerRpcClient/Services/Official/CoinSnapshotService.cs

@@ -1,27 +0,0 @@
-using NTMiner.Controllers;
-using NTMiner.Core.MinerServer;
-using System;
-
-namespace NTMiner.Services.Official {
-    public class CoinSnapshotService {
-        private readonly string _controllerName = RpcRoot.GetControllerName<ICoinSnapshotController>();
-
-        internal CoinSnapshotService() {
-        }
-
-        #region GetLatestSnapshotsAsync
-        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
-            GetCoinSnapshotsRequest request = new GetCoinSnapshotsRequest {
-                Limit = limit
-            };
-            RpcRoot.JsonRpc.SignPostAsync(
-                RpcRoot.OfficialServerHost, 
-                RpcRoot.OfficialServerPort, 
-                _controllerName, 
-                nameof(ICoinSnapshotController.LatestSnapshots), 
-                data: request, 
-                callback);
-        }
-        #endregion
-    }
-}

+ 0 - 21
src/NTMinerRpcClient/Services/Official/WsServerNodeService.cs

@@ -1,7 +1,6 @@
 using NTMiner.Controllers;
 using NTMiner.ServerNode;
 using System;
-using System.Collections.Generic;
 
 namespace NTMiner.Services.Official {
     public class WsServerNodeService {
@@ -10,26 +9,6 @@ namespace NTMiner.Services.Official {
         internal WsServerNodeService() {
         }
 
-        public void GetNodesAsync(Action<DataResponse<List<WsServerNodeState>>, Exception> callback) {
-            RpcRoot.JsonRpc.SignPostAsync(
-                RpcRoot.OfficialServerHost, 
-                RpcRoot.OfficialServerPort, 
-                _controllerName, 
-                nameof(IWsServerNodeController.Nodes), 
-                new object(), 
-                callback);
-        }
-
-        public void GetNodeAddressesAsync(Action<DataResponse<string[]>, Exception> callback) {
-            RpcRoot.JsonRpc.SignPostAsync(
-                RpcRoot.OfficialServerHost, 
-                RpcRoot.OfficialServerPort, 
-                _controllerName, 
-                nameof(IWsServerNodeController.NodeAddresses), 
-                new object(), 
-                callback);
-        }
-
         public void GetNodeAddressAsync(Guid clientId, string outerUserId, Action<DataResponse<string>, Exception> callback) {
             var data = new GetWsServerNodeAddressRequest {
                 ClientId = clientId,

+ 0 - 2
src/NTMinerRpcClient/Services/OfficialServices.cs

@@ -11,7 +11,6 @@
         public readonly NTMinerWalletService NTMinerWalletService = new NTMinerWalletService();
         public readonly KernelOutputKeywordService KernelOutputKeywordService = new KernelOutputKeywordService();
         public readonly CalcConfigService CalcConfigService = new CalcConfigService();
-        public readonly CoinSnapshotService CoinSnapshotService = new CoinSnapshotService();
         public readonly ServerMessageService ServerMessageService = new ServerMessageService();
         public readonly ReportService ReportService = new ReportService();
         public readonly ReportBinaryService ReportBinaryService = new ReportBinaryService();
@@ -19,7 +18,6 @@
         public readonly AppSettingService AppSettingService = new AppSettingService();
         public readonly UserAppSettingService UserAppSettingService = new UserAppSettingService();
         public readonly ClientDataService ClientDataService = new ClientDataService();
-        public readonly ClientDataBinaryService ClientDataBinaryService = new ClientDataBinaryService();
         public readonly UserMinerGroupService UserMinerGroupService = new UserMinerGroupService();
         public readonly UserMineWorkService UserMineWorkService = new UserMineWorkService();
         public readonly GpuNameService GpuNameService = new GpuNameService();

+ 7 - 8
src/NTMinerServer/Core/Impl/ReadonlyUserSet.cs

@@ -6,7 +6,6 @@ using System.Linq;
 
 namespace NTMiner.Core.Impl {
     public class ReadOnlyUserSet : IReadOnlyUserSet {
-        protected const string _safeIgnoreMessage = "该消息发生的时间早于本节点启动时间1分钟,安全忽略";
         protected readonly Dictionary<string, UserData> _dicByLoginName = new Dictionary<string, UserData>(StringComparer.OrdinalIgnoreCase);
         private DateTime _initedOn = DateTime.MinValue;
         public bool IsReadied {
@@ -31,7 +30,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserAddedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 userRedis.GetByLoginNameAsync(message.LoginName).ContinueWith(t => {
@@ -48,7 +47,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserUpdatedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 userRedis.GetByLoginNameAsync(message.LoginName).ContinueWith(t => {
@@ -65,7 +64,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserRemovedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 _dicByLoginName.Remove(message.LoginName);
@@ -78,7 +77,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserEnabledMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 if (_dicByLoginName.TryGetValue(message.LoginName, out UserData userData)) {
@@ -93,7 +92,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserDisabledMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 if (_dicByLoginName.TryGetValue(message.LoginName, out UserData userData)) {
@@ -108,7 +107,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserPasswordChangedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 userRedis.GetByLoginNameAsync(message.LoginName).ContinueWith(t => {
@@ -125,7 +124,7 @@ namespace NTMiner.Core.Impl {
                     return;
                 }
                 if (IsOldMqMessage(message.Timestamp)) {
-                    NTMinerConsole.UserOk(_safeIgnoreMessage);
+                    NTMinerConsole.UserOk(nameof(UserRSAKeyUpdatedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage);
                     return;
                 }
                 if (_dicByLoginName.TryGetValue(message.LoginName, out UserData userData)) {

+ 2 - 2
src/NTMinerServer/Core/Impl/WsServerNodeAddressSetBase.cs

@@ -16,13 +16,13 @@ namespace NTMiner.Core.Impl {
                 Init();
             }, this.GetType());
             // 收到Mq消息之前一定已经初始化完成,因为Mq消费者在WsServerNodeAddressSetInitedEvent事件之后才会创建
-            VirtualRoot.BuildEventPath<WsServerNodeRemovedMqMessage>("收到移除了服务器节点Mq消息后敲响打扫时间到的铃声", LogEnum.UserConsole, path: message => {
+            VirtualRoot.BuildEventPath<WsServerNodeRemovedMqMessage>("收到移除了服务器节点Mq消息后刷新节点列表", LogEnum.UserConsole, path: message => {
                 if (message.AppId == ServerRoot.HostConfig.ThisServerAddress) {
                     return;
                 }
                 Init();
             }, this.GetType());
-            VirtualRoot.BuildEventPath<WsServerNodeAddedMqMessage>("收到添加了服务器节点Mq消息后敲响打扫时间到的铃声", LogEnum.UserConsole, path: message => {
+            VirtualRoot.BuildEventPath<WsServerNodeAddedMqMessage>("收到添加了服务器节点Mq消息后刷新节点列表", LogEnum.UserConsole, path: message => {
                 if (message.AppId == ServerRoot.HostConfig.ThisServerAddress) {
                     return;
                 }

+ 36 - 1
src/NTMinerServer/Core/Messages.cs

@@ -1,6 +1,7 @@
 using NTMiner.Core.Daemon;
 using NTMiner.Core.MinerClient;
 using NTMiner.Core.MinerServer;
+using NTMiner.Cryptography;
 using NTMiner.Hub;
 using NTMiner.VirtualMemory;
 using System;
@@ -102,7 +103,7 @@ namespace NTMiner.Core {
         public string AppId { get; private set; }
         public string LoginName { get; private set; }
         public DateTime Timestamp { get; private set; }
-        public Cryptography.RSAKey Key { get; private set; }
+        public RSAKey Key { get; private set; }
     }
 
     [MessageType(description: "收到了UserRSAKeyUpdated Mq消息后")]
@@ -183,6 +184,40 @@ namespace NTMiner.Core {
         public MinerSign Data { get; private set; }
     }
 
+    [MessageType(description: "收到了QueryClientsForWs Mq消息后,该消息是个命令")]
+    public class QueryClientsForWsMqMessage : Cmd {
+        public QueryClientsForWsMqMessage(string appId, DateTime timestamp, string loginName, string sessionId, QueryClientsForWsRequest query) {
+            this.AppId = appId;
+            this.Timestamp = timestamp;
+            this.LoginName = loginName;
+            this.SessionId = sessionId;
+            this.Query = query;
+        }
+
+        public string AppId { get; private set; }
+        public DateTime Timestamp { get; private set; }
+        public string LoginName { get; private set; }
+        public string SessionId { get; private set; }
+        public QueryClientsForWsRequest Query { get; private set; }
+    }
+
+    [MessageType(description: "收到了QueryClientsForWsResponse Mq消息后,该消息是个命令")]
+    public class QueryClientsForWsResponseMqMessage : EventBase {
+        public QueryClientsForWsResponseMqMessage(string appId, DateTime timestamp, string loginName, string sessionId, QueryClientsResponse response) {
+            this.AppId = appId;
+            this.Timestamp = timestamp;
+            this.LoginName = loginName;
+            this.SessionId = sessionId;
+            this.Response = response;
+        }
+
+        public string AppId { get; private set; }
+        public DateTime Timestamp { get; private set; }
+        public string LoginName { get; private set; }
+        public string SessionId { get; private set; }
+        public QueryClientsResponse Response { get; private set; }
+    }
+
     [MessageType(description: "收到了MinerDataAdded Mq消息后")]
     public class MinerDataAddedMqMessage : MinerDataMqMessage {
         public MinerDataAddedMqMessage(string appId, string minerId, DateTime timestamp) : base(appId, minerId, timestamp) {

+ 22 - 0
src/NTMinerServer/Core/Mq/MinerClientMqBodyUtil.cs

@@ -39,5 +39,27 @@ namespace NTMiner.Core.Mq {
             return VirtualRoot.JsonSerializer.Deserialize<MinerSign>(json);
         }
         #endregion
+
+        #region QueryClientsForWsRequest
+        public static byte[] GetQueryClientsForWsMqSendBody(QueryClientsForWsRequest request) {
+            return Encoding.UTF8.GetBytes(VirtualRoot.JsonSerializer.Serialize(request));
+        }
+
+        public static QueryClientsForWsRequest GetQueryClientsForWsMqReceiveBody(byte[] body) {
+            string json = Encoding.UTF8.GetString(body);
+            return VirtualRoot.JsonSerializer.Deserialize<QueryClientsForWsRequest>(json);
+        }
+        #endregion
+
+        #region QueryClientsResponse
+        public static byte[] GetQueryClientsResponseMqSendBody(QueryClientsResponse response) {
+            return Encoding.UTF8.GetBytes(VirtualRoot.JsonSerializer.Serialize(response));
+        }
+
+        public static QueryClientsResponse GetQueryClientsResponseMqReceiveBody(byte[] body) {
+            string json = Encoding.UTF8.GetString(body);
+            return VirtualRoot.JsonSerializer.Deserialize<QueryClientsResponse>(json);
+        }
+        #endregion
     }
 }

+ 3 - 3
src/NTMinerServer/Core/Mq/Senders/Impl/WsServerNodeMqSender.cs

@@ -2,8 +2,8 @@
 
 namespace NTMiner.Core.Mq.Senders.Impl {
     public class WsServerNodeMqSender : IWsServerNodeMqSender {
-        private readonly IServerConnection _serverConnection;
-        public WsServerNodeMqSender(IServerConnection serverConnection) {
+        private readonly IMqRedis _serverConnection;
+        public WsServerNodeMqSender(IMqRedis serverConnection) {
             _serverConnection = serverConnection;
         }
 
@@ -32,7 +32,7 @@ namespace NTMiner.Core.Mq.Senders.Impl {
         private IBasicProperties CreateBasicProperties() {
             var basicProperties = _serverConnection.MqChannel.CreateBasicProperties();
             basicProperties.Persistent = false;
-            basicProperties.Expiration = "36000000"; // 36秒,单位是微秒(1微秒是10的负6次方秒)
+            basicProperties.Expiration = MqKeyword.Expiration36sec;
             basicProperties.AppId = ServerRoot.HostConfig.ThisServerAddress;
 
             return basicProperties;

+ 5 - 5
src/NTMinerServer/Core/Redis/Impl/ReadOnlyMinerRedis.cs

@@ -6,13 +6,13 @@ namespace NTMiner.Core.Redis.Impl {
     public class ReadOnlyMinerRedis : IReadOnlyMinerRedis {
         protected const string _redisKeyMinerById = "miners.MinerById";// 根据Id索引Miner对象的json
 
-        protected readonly IServerConnection _serverConnection;
-        public ReadOnlyMinerRedis(IServerConnection serverConnection) {
-            _serverConnection = serverConnection;
+        protected readonly IMqRedis _redis;
+        public ReadOnlyMinerRedis(IMqRedis redis) {
+            _redis = redis;
         }
 
         public Task<List<MinerData>> GetAllAsync() {
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAllAsync(_redisKeyMinerById).ContinueWith(t => {
                 List<MinerData> list = new List<MinerData>();
                 foreach (var item in t.Result) {
@@ -31,7 +31,7 @@ namespace NTMiner.Core.Redis.Impl {
             if (string.IsNullOrEmpty(minerId)) {
                 return Task.FromResult<MinerData>(null);
             }
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAsync(_redisKeyMinerById, minerId).ContinueWith(t => {
                 if (t.Result.HasValue) {
                     return VirtualRoot.JsonSerializer.Deserialize<MinerData>(t.Result);

+ 5 - 5
src/NTMinerServer/Core/Redis/Impl/ReadOnlyUserRedis.cs

@@ -6,13 +6,13 @@ namespace NTMiner.Core.Redis.Impl {
     public class ReadOnlyUserRedis : IReadOnlyUserRedis {
         protected const string _redisKeyUserByLoginName = "users.UserByLoginName";// 根据LoginName索引User对象的json
 
-        protected readonly IServerConnection _serverConnection;
-        public ReadOnlyUserRedis(IServerConnection serverConnection) {
-            _serverConnection = serverConnection;
+        protected readonly IMqRedis _redis;
+        public ReadOnlyUserRedis(IMqRedis redis) {
+            _redis = redis;
         }
 
         public Task<List<UserData>> GetAllAsync() {
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAllAsync(_redisKeyUserByLoginName).ContinueWith(t => {
                 List<UserData> list = new List<UserData>();
                 foreach (var item in t.Result) {
@@ -31,7 +31,7 @@ namespace NTMiner.Core.Redis.Impl {
             if (string.IsNullOrEmpty(loginName)) {
                 return Task.FromResult<UserData>(null);
             }
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAsync(_redisKeyUserByLoginName, loginName).ContinueWith(t => {
                 if (t.Result.HasValue) {
                     return VirtualRoot.JsonSerializer.Deserialize<UserData>(t.Result);

+ 4 - 4
src/NTMinerServer/Core/Redis/Impl/ReadOnlyWsServerNodeRedis.cs

@@ -7,13 +7,13 @@ namespace NTMiner.Core.Redis.Impl {
         protected const string _redisKeyWsServerNodeByAddress = "wsServerNodes.WsServerNodeByAddress";// 根据Address索引WsServerNodeState对象的json
         protected const string _redisKeyWsServerNodeAddress = "wsServerNodes.Address";
 
-        protected readonly IServerConnection _serverConnection;
-        public ReadOnlyWsServerNodeRedis(IServerConnection serverConnection) {
-            _serverConnection = serverConnection;
+        protected readonly IMqRedis _redis;
+        public ReadOnlyWsServerNodeRedis(IMqRedis redis) {
+            _redis = redis;
         }
 
         public Task<Dictionary<string, DateTime>> GetAllAddress() {
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAllAsync(_redisKeyWsServerNodeAddress).ContinueWith(t => {
                 Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();
                 foreach (var item in t.Result) {

+ 7 - 7
src/NTMinerServer/Core/Redis/Impl/SpeedDataRedis.cs

@@ -8,13 +8,13 @@ namespace NTMiner.Core.Redis.Impl {
     public class SpeedDataRedis : ISpeedDataRedis {
         protected const string _redisKeySpeedDataByClientId = "speedDatas.SpeedDataByClientId";// 根据ClientId索引SpeedData对象的json
 
-        protected readonly IServerConnection _serverConnection;
-        public SpeedDataRedis(IServerConnection serverConnection) {
-            _serverConnection = serverConnection;
+        protected readonly IMqRedis _redis;
+        public SpeedDataRedis(IMqRedis redis) {
+            _redis = redis;
         }
 
         public Task<List<SpeedData>> GetAllAsync() {
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             Stopwatch stopwatch = new Stopwatch();
             stopwatch.Start();
             return db.HashGetAllAsync(_redisKeySpeedDataByClientId).ContinueWith(t => {
@@ -49,7 +49,7 @@ namespace NTMiner.Core.Redis.Impl {
             if (clientId == Guid.Empty) {
                 return Task.FromResult<SpeedData>(null);
             }
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashGetAsync(_redisKeySpeedDataByClientId, clientId.ToString()).ContinueWith(t => {
                 if (t.Result.HasValue) {
                     return VirtualRoot.JsonSerializer.Deserialize<SpeedData>(t.Result);
@@ -64,7 +64,7 @@ namespace NTMiner.Core.Redis.Impl {
             if (speedData == null || speedData.ClientId == Guid.Empty) {
                 return TaskEx.CompletedTask;
             }
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashSetAsync(_redisKeySpeedDataByClientId, speedData.ClientId.ToString(), VirtualRoot.JsonSerializer.Serialize(speedData));
         }
 
@@ -76,7 +76,7 @@ namespace NTMiner.Core.Redis.Impl {
             for (int i = 0; i < clientIds.Length; i++) {
                 values[i] = clientIds[i].ToString();
             }
-            var db = _serverConnection.RedisConn.GetDatabase();
+            var db = _redis.RedisConn.GetDatabase();
             return db.HashDeleteAsync(_redisKeySpeedDataByClientId, values);
         }
     }

+ 1 - 1
src/NTMinerServer/IServerConnection.cs → src/NTMinerServer/IMqRedis.cs

@@ -2,7 +2,7 @@
 using StackExchange.Redis;
 
 namespace NTMiner {
-    public interface IServerConnection {
+    public interface IMqRedis {
         ConnectionMultiplexer RedisConn { get; }
         IModel MqChannel { get; }
     }

+ 8 - 0
src/NTMinerServer/MqKeyword.cs

@@ -2,13 +2,19 @@
 
 namespace NTMiner {
     public static class MqKeyword {
+        public const string SafeIgnoreMessage = "该消息发生的时间早于本节点启动时间1分钟,安全忽略";
         public const string DurableQueueEndsWith = ".durable";
+        public const string Expiration36sec = "36000000"; // 36秒,单位是微秒(1微秒是10的负6次方秒)
+        public const string Expiration60sec = "60000000";
 
         public const string NTMinerExchange = "ntminer";
 
         public const string LoginNameHeaderName = "loginName";
+        public const string SessionIdHeaderName = "sessionId";
         public const string MinerIpHeaderName = "minerIp";
 
+        // TODO:因为一个节点只对应两个队列,一个不持久队列和一个持久队列,消息通过路由键分类。
+        // 通过Mq的管理后台是看不出每个路由键的消息速率的,所以考虑统计一下每个路由键消息的速率。
         public const string UserAddedRoutingKey = "UserAdded";
         public const string UserUpdatedRoutingKey = "UserUpdated";
         public const string UserRemovedRoutingKey = "UserRemoved";
@@ -30,6 +36,8 @@ namespace NTMiner {
         public const string MinerSignChangedRoutingKey = "MinerSignChanged";
         public const string ChangeMinerSignRoutingKey = "ChangeMinerSign";
 
+        public const string QueryClientsForWsRoutingKey = "QueryClientsForWs";
+        public const string QueryClientsForWsResponseRoutingKey = "QueryClientsForWsResponse{0}";
         public const string SpeedRoutingKey = WsMessage.Speed;
     }
 }

+ 5 - 5
src/NTMinerServer/ServerConnection.cs → src/NTMinerServer/MqRedis.cs

@@ -7,7 +7,7 @@ using System.Linq;
 using System.Threading.Tasks;
 
 namespace NTMiner {
-    public class ServerConnection : IServerConnection {
+    public class MqRedis : IMqRedis {
         /// <summary>
         /// 内部完成redis连接的创建和mq交换器、队列的声明以及mq消费者的启动,队列和交换器的绑定由启动的消费者负责。
         /// (mq消费者的启动是异步的,不会立即启动,而是在满足了后续的条件后才会启动)。
@@ -15,7 +15,7 @@ namespace NTMiner {
         /// <param name="serverAppType"></param>
         /// <param name="mqMessagePaths"></param>
         /// <returns></returns>
-        public static bool Create(ServerAppType serverAppType, AbstractMqMessagePath[] mqMessagePaths, out IServerConnection serverConfig) {
+        public static bool Create(ServerAppType serverAppType, AbstractMqMessagePath[] mqMessagePaths, out IMqRedis serverConfig) {
             string mqClientTypeName = serverAppType.GetName();
             serverConfig = null;
             ConnectionMultiplexer redisConn;
@@ -27,7 +27,7 @@ namespace NTMiner {
                 Logger.ErrorDebugLine(e);
                 return false;
             }
-            IConnection mqConn;// TODO:需要一个机制在连接关闭时重新连接,因为AutomaticRecovery也会失败
+            IConnection mqConn;
             try {
                 var factory = new ConnectionFactory {
                     HostName = ServerRoot.HostConfig.MqHostName,
@@ -49,7 +49,7 @@ namespace NTMiner {
 
             StartConsumer(channel, mqMessagePaths);
 
-            serverConfig = new ServerConnection(redisConn, channel);
+            serverConfig = new MqRedis(redisConn, channel);
             return true;
         }
 
@@ -109,7 +109,7 @@ namespace NTMiner {
             });
         }
 
-        private ServerConnection(ConnectionMultiplexer redisConn, IModel channel) {
+        private MqRedis(ConnectionMultiplexer redisConn, IModel channel) {
             this.RedisConn = redisConn;
             this.MqChannel = channel;
         }

+ 6 - 11
src/NTMinerServer/NTMinerServer.csproj

@@ -34,10 +34,6 @@
     <Reference Include="LiteDB, Version=4.1.4.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
       <HintPath>..\..\packages\LiteDB.4.1.4\lib\net40\LiteDB.dll</HintPath>
     </Reference>
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="Microsoft.Diagnostics.Tracing.EventSource, Version=1.1.28.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-      <HintPath>..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll</HintPath>
-    </Reference>
     <Reference Include="RabbitMQ.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=89e7d7c5feba84ce, processorArchitecture=MSIL">
       <HintPath>..\..\packages\RabbitMQ.Client.5.2.0\lib\net451\RabbitMQ.Client.dll</HintPath>
     </Reference>
@@ -62,8 +58,6 @@
     <Compile Include="Core\Redis\IReadOnlyWsServerNodeRedis.cs" />
     <Compile Include="Core\Redis\ISpeedDataRedis.cs" />
     <Compile Include="ServerAppType.cs" />
-    <Compile Include="ServerRoot.cs" />
-    <Compile Include="MinerSignExtensions.cs" />
     <Compile Include="Core\Mq\MinerClientMqBodyUtil.cs" />
     <Compile Include="Core\Mq\OperationMqBodyUtil.cs" />
     <Compile Include="Core\Mq\UserMqBodyUtil.cs" />
@@ -71,20 +65,17 @@
     <Compile Include="Core\Mq\MqMessagePaths\AbstractMqMessagePath.cs" />
     <Compile Include="Core\Mq\MqMessagePaths\BasicPropertiesExtensions.cs" />
     <Compile Include="Core\ShardingHasher.cs" />
-    <Compile Include="Core\Impl\HostConfigData.cs" />
     <Compile Include="Core\Impl\ReadOnlyUserSet.cs" />
     <Compile Include="Core\Redis\Impl\ReadOnlyMinerRedis.cs" />
-    <Compile Include="Core\IReadonlyUserSet.cs" />
     <Compile Include="Core\Redis\IReadOnlyMinerRedis.cs" />
-    <Compile Include="Core\IHostConfig.cs" />
     <Compile Include="Core\Redis\Impl\ReadOnlyUserRedis.cs" />
-    <Compile Include="IServerConnection.cs" />
+    <Compile Include="IMqRedis.cs" />
     <Compile Include="MqKeyword.cs" />
     <Compile Include="Core\Mq\MqMessagePaths\IMqMessagePath.cs" />
     <Compile Include="Core\Messages.cs" />
     <Compile Include="Core\Redis\IReadOnlyUserRedis.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="ServerConnection.cs" />
+    <Compile Include="MqRedis.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\NTMinerDataSchemas\NTMinerDataSchemas.csproj">
@@ -103,6 +94,10 @@
       <Project>{f5091b28-5bb6-4446-9b97-02b37125e340}</Project>
       <Name>NTMinerLogging</Name>
     </ProjectReference>
+    <ProjectReference Include="..\ServerCommon\ServerCommon.csproj">
+      <Project>{e12eefdc-66e9-4b7d-a036-fc1d4962eb04}</Project>
+      <Name>ServerCommon</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <None Include="Core\Mq\README.md" />

+ 0 - 1
src/NTMinerServer/packages.config

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="LiteDB" version="4.1.4" targetFramework="net452" />
-  <package id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.28" targetFramework="net452" />
   <package id="RabbitMQ.Client" version="5.2.0" targetFramework="net452" />
   <package id="StackExchange.Redis" version="1.2.6" targetFramework="net452" />
 </packages>

+ 19 - 0
src/NTMinerlib/Messages.cs

@@ -4,6 +4,7 @@ using NTMiner.Hub;
 using NTMiner.User;
 using System;
 using System.Collections.Generic;
+using System.Net;
 
 namespace NTMiner {
     #region abstract
@@ -40,6 +41,24 @@ namespace NTMiner {
         public T Event { get; private set; }
     }
 
+    [MessageType(description: "WebSocket服务的TcpListener Accept了一个客户端")]
+    public class WsTcpClientAcceptedEvent : EventBase {
+        public WsTcpClientAcceptedEvent(IPAddress remoteIp) {
+            this.RemoteIp = remoteIp;
+        }
+
+        public IPAddress RemoteIp { get; private set; }
+    }
+
+    [MessageType(description: "WebApi服务收到请求时")]
+    public class WebApiRequestEvent : EventBase {
+        public WebApiRequestEvent(IPAddress remoteIp) {
+            this.RemoteIp = remoteIp;
+        }
+
+        public IPAddress RemoteIp { get; private set; }
+    }
+
     [MessageType(description: "关闭窗口")]
     public class CloseWindowCommand : Cmd {
         public CloseWindowCommand(Guid id) : base(id) { }

+ 27 - 0
src/NTMinerlib/TempPath.cs

@@ -4,5 +4,32 @@ using System.IO;
 namespace NTMiner {
     public static class TempPath {
         public static readonly string TempDirFullName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), NTKeyword.TempDirName);
+
+        private static bool _sIsFirstCallLogsDirFullName = true;
+        public static string LogsDirFullName {
+            get {
+                string dirFullName = Path.Combine(TempDirFullName, NTKeyword.LogsDirName);
+                if (_sIsFirstCallLogsDirFullName) {
+                    if (!Directory.Exists(dirFullName)) {
+                        Directory.CreateDirectory(dirFullName);
+                    }
+                    _sIsFirstCallLogsDirFullName = false;
+                }
+
+                return dirFullName;
+            }
+        }
+
+        public static string WebSocketSharpMinerClientLogFileFullName {
+            get {
+                return Path.Combine(LogsDirFullName, NTKeyword.WebSocketSharpMinerClientLogFileName);
+            }
+        }
+
+        public static string WebSocketSharpMinerStudioLogFileFullName {
+            get {
+                return Path.Combine(LogsDirFullName, NTKeyword.WebSocketSharpMinerStudioLogFileName);
+            }
+        }
     }
 }

+ 0 - 7
src/NTMinerlib/VirtualRoot.cs

@@ -391,8 +391,6 @@ namespace NTMiner {
                     miningCount++;
                     mainCoinSnapshotData.MainCoinMiningCount += 1;
                     mainCoinSnapshotData.Speed += clientData.MainCoinSpeed;
-                    mainCoinSnapshotData.ShareDelta += clientData.GetMainCoinShareDelta(isPull);
-                    mainCoinSnapshotData.RejectShareDelta += clientData.GetMainCoinRejectShareDelta(isPull);
                 }
 
                 mainCoinSnapshotData.MainCoinOnlineCount += 1;
@@ -407,13 +405,8 @@ namespace NTMiner {
                     }
 
                     if (clientData.IsMining) {
-                        dualCoinSnapshotData.DualCoinMiningCount += 1;
                         dualCoinSnapshotData.Speed += clientData.DualCoinSpeed;
-                        dualCoinSnapshotData.ShareDelta += clientData.GetDualCoinShareDelta(isPull);
-                        dualCoinSnapshotData.RejectShareDelta += clientData.GetDualCoinRejectShareDelta(isPull);
                     }
-
-                    dualCoinSnapshotData.DualCoinOnlineCount += 1;
                 }
             }
 

+ 3 - 3
src/NTMinerlib/VirtualRoot.partials.Hub.cs

@@ -35,7 +35,7 @@ namespace NTMiner {
         /// 修建消息的运动路径
         /// </summary>
         public static IMessagePathId BuildMessagePath<TMessage>(string description, LogEnum logType, Action<TMessage> path, Type location) {
-            return MessagePath<TMessage>.AddMessagePath(MessageHub, location, description, logType, path, pathId: PathId.Empty);
+            return MessageHub.AddPath(location, description, logType, path, pathId: PathId.Empty);
         }
 
         /// <summary>
@@ -43,14 +43,14 @@ namespace NTMiner {
         /// 注意该路径具有特定的路径标识pathId,pathId可以看作是路径的形状,只有和该路径的形状相同的消息才能通过路径。
         /// </summary>
         public static IMessagePathId BuildOnecePath<TMessage>(string description, LogEnum logType, Action<TMessage> path, PathId pathId, Type location) {
-            return MessagePath<TMessage>.AddMessagePath(MessageHub, location, description, logType, path, pathId, viaTimesLimit: 1);
+            return MessageHub.AddPath(location, description, logType, path, pathId, viaTimesLimit: 1);
         }
 
         /// <summary>
         /// 消息通过路径指定的次数后路径即消失
         /// </summary>
         public static IMessagePathId BuildViaTimesLimitPath<TMessage>(string description, LogEnum logType, Action<TMessage> path, int viaTimesLimit, Type location) {
-            return MessagePath<TMessage>.AddMessagePath(MessageHub, location, description, logType, path, pathId: PathId.Empty, viaTimesLimit: viaTimesLimit);
+            return MessageHub.AddPath(location, description, logType, path, pathId: PathId.Empty, viaTimesLimit: viaTimesLimit);
         }
 
         public static void BuildCmdPath<TCmd>(Action<TCmd> path, Type location, LogEnum logType = LogEnum.DevConsole)

+ 0 - 0
src/WebApiServer/Core/ICaptchaSet.cs → src/ServerCommon/Core/ICaptchaSet.cs


+ 0 - 0
src/NTMinerServer/Core/IHostConfig.cs → src/ServerCommon/Core/IHostConfig.cs


+ 0 - 0
src/NTMinerServer/Core/IReadonlyUserSet.cs → src/ServerCommon/Core/IReadonlyUserSet.cs


+ 0 - 0
src/WebApiServer/Core/IUserAppSettingSet.cs → src/ServerCommon/Core/IUserAppSettingSet.cs


+ 0 - 0
src/WebApiServer/Core/IUserMineWorkSet.cs → src/ServerCommon/Core/IUserMineWorkSet.cs


+ 0 - 0
src/WebApiServer/Core/IUserMinerGroupSet.cs → src/ServerCommon/Core/IUserMinerGroupSet.cs


+ 0 - 0
src/WebApiServer/Core/IUserSet.cs → src/ServerCommon/Core/IUserSet.cs


+ 0 - 0
src/NTMinerServer/Core/Impl/HostConfigData.cs → src/ServerCommon/Core/Impl/HostConfigData.cs


+ 10 - 0
src/ServerCommon/IpSet/IRemoteIpSet.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NTMiner.IpSet {
+    public interface IRemoteIpSet {
+    }
+}

+ 35 - 0
src/ServerCommon/IpSet/Impl/RemoteIpSet.cs

@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Net;
+
+namespace NTMiner.IpSet.Impl {
+    public class RemoteIpSet : IRemoteIpSet {
+        private readonly Dictionary<IPAddress, RemoteIpEntry> _dicByIp = new Dictionary<IPAddress, RemoteIpEntry>();
+
+        public RemoteIpSet() {
+            VirtualRoot.BuildEventPath<WsTcpClientAcceptedEvent>("收集Ws客户端IP和端口", LogEnum.None, path: message => {
+                if (_dicByIp.TryGetValue(message.RemoteIp, out RemoteIpEntry entry)) {
+                    entry.IncActionTimes();
+                }
+                else {
+                    entry = new RemoteIpEntry(message.RemoteIp);
+                    entry.IncActionTimes();
+                    _dicByIp.Add(message.RemoteIp, entry);
+                }
+            }, this.GetType());
+            VirtualRoot.BuildEventPath<WebApiRequestEvent>("收集WebApi客户端IP和端口", LogEnum.None, path: message => {
+                if (_dicByIp.TryGetValue(message.RemoteIp, out RemoteIpEntry entry)) {
+                    entry.IncActionTimes();
+                }
+                else {
+                    entry = new RemoteIpEntry(message.RemoteIp);
+                    entry.IncActionTimes();
+                    _dicByIp.Add(message.RemoteIp, entry);
+                }
+            }, this.GetType());
+
+            VirtualRoot.BuildEventPath<Per10SecondEvent>("周期找出恶意IP封掉", LogEnum.None, path: message => {
+                // TODO:阿里云AuthorizeSecurityGroup
+            }, this.GetType());
+        }
+    }
+}

+ 24 - 0
src/ServerCommon/IpSet/RemoteIpEntry.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace NTMiner.IpSet {
+    public class RemoteIpEntry {
+        public RemoteIpEntry(IPAddress remoteIp) {
+            this.RemoteIp = remoteIp;
+            this.DateTimes = new Queue<DateTime>();
+        }
+
+        public IPAddress RemoteIp { get; private set; }
+        public int ActionTimes { get; private set; }
+        public Queue<DateTime> DateTimes { get; private set; }
+
+        public void IncActionTimes() {
+            this.ActionTimes++;
+            this.DateTimes.Enqueue(DateTime.Now);
+            while (this.DateTimes.Count > 10) {
+                this.DateTimes.Dequeue();
+            }
+        }
+    }
+}

+ 0 - 0
src/NTMinerServer/MinerSignExtensions.cs → src/ServerCommon/MinerSignExtensions.cs


+ 36 - 0
src/ServerCommon/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("ServerCommon")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ServerCommon")]
+[assembly: AssemblyCopyright("Copyright ©  2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("e12eefdc-66e9-4b7d-a036-fc1d4962eb04")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 78 - 0
src/ServerCommon/ServerCommon.csproj

@@ -0,0 +1,78 @@
+<?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>{E12EEFDC-66E9-4B7D-A036-FC1D4962EB04}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>NTMiner</RootNamespace>
+    <AssemblyName>ServerCommon</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <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' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="LiteDB, Version=4.1.4.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\LiteDB.4.1.4\lib\net40\LiteDB.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Core\ICaptchaSet.cs" />
+    <Compile Include="Core\IHostConfig.cs" />
+    <Compile Include="Core\Impl\HostConfigData.cs" />
+    <Compile Include="Core\IUserAppSettingSet.cs" />
+    <Compile Include="Core\IUserMinerGroupSet.cs" />
+    <Compile Include="Core\IUserMineWorkSet.cs" />
+    <Compile Include="Core\IUserSet.cs" />
+    <Compile Include="IpSet\Impl\RemoteIpSet.cs" />
+    <Compile Include="IpSet\IRemoteIpSet.cs" />
+    <Compile Include="IpSet\RemoteIpEntry.cs" />
+    <Compile Include="Core\IReadonlyUserSet.cs" />
+    <Compile Include="MinerSignExtensions.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ServerRoot.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\NTMinerDataSchemas\NTMinerDataSchemas.csproj">
+      <Project>{defc7387-f9fa-4651-a281-6612efb7fbb6}</Project>
+      <Name>NTMinerDataSchemas</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\NTMinerHub\NTMinerHub.csproj">
+      <Project>{e17f278a-e393-403e-9d37-e371036d7a02}</Project>
+      <Name>NTMinerHub</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\NTMinerlib\NTMinerlib.csproj">
+      <Project>{5d0f2719-83fd-40fc-8f65-85ebb891bc13}</Project>
+      <Name>NTMinerlib</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\NTMinerLogging\NTMinerLogging.csproj">
+      <Project>{f5091b28-5bb6-4446-9b97-02b37125e340}</Project>
+      <Name>NTMinerLogging</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 10 - 1
src/NTMinerServer/ServerRoot.cs → src/ServerCommon/ServerRoot.cs

@@ -1,12 +1,21 @@
 using LiteDB;
 using NTMiner.Core;
 using NTMiner.Core.Impl;
+using NTMiner.IpSet;
+using NTMiner.IpSet.Impl;
 using System.IO;
 
 namespace NTMiner {
     public static class ServerRoot {
+        public static readonly IRemoteIpSet _remoteEndPointSet = new RemoteIpSet();
+        public static IRemoteIpSet RemoteEndPointSet {
+            get {
+                return _remoteEndPointSet;
+            }
+        }
+
         static ServerRoot() {
-            System.Net.ServicePointManager.DefaultConnectionLimit = 200;
+            System.Net.ServicePointManager.DefaultConnectionLimit = 512;
         }
 
         private static IHostConfig _hostConfig;

+ 4 - 0
src/ServerCommon/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="LiteDB" version="4.1.4" targetFramework="net452" />
+</packages>

二进制
src/ThirdPartyDlls/websocket-sharp.dll


+ 20 - 0
src/UnitTests/EncodingTests.cs

@@ -0,0 +1,20 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Text;
+
+namespace NTMiner {
+    [TestClass]
+    public class EncodingTests {
+        [TestMethod]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void Test1() {
+            _ = Encoding.UTF8.GetString(null);
+        }
+
+        [TestMethod]
+        public void Test2() {
+            string s = Encoding.UTF8.GetString(new byte[0]);
+            Assert.AreEqual(string.Empty, s);
+        }
+    }
+}

+ 8 - 1
src/UnitTests/EnumTests.cs

@@ -1,10 +1,17 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
-using NTMiner;
 using System;
 
 namespace NTMiner {
     [TestClass]
     public class EnumTests {
+        [TestMethod]
+        public void IsDefinedTest() {
+            Assert.IsTrue(Enum.IsDefined(typeof(NTMinerAppType), NTMinerAppType.MinerClient.ToString()));
+            Assert.IsTrue(Enum.IsDefined(typeof(NTMinerAppType), NTMinerAppType.MinerClient.GetName()));
+            Assert.IsTrue(Enum.IsDefined(typeof(NTMinerAppType), 0));
+            Assert.IsFalse(Enum.IsDefined(typeof(NTMinerAppType), 1000));
+        }
+
         [TestMethod]
         public void ToStringTest() {
             Assert.AreEqual(nameof(NTMinerAppType.MinerClient), NTMinerAppType.MinerClient.ToString());

+ 7 - 0
src/UnitTests/GuidTests.cs

@@ -12,6 +12,13 @@ namespace NTMiner {
                 hashCode = -hashCode;
             }
             Assert.AreEqual(501827256, hashCode);
+            string s = guid.ToString("N");
+            Assert.AreEqual(s, "0a3bf32d9d2d4a399c4675d6ac73065c");
+            guid = Guid.Parse(s); hashCode = guid.GetHashCode();
+            if (hashCode < 0) {
+                hashCode = -hashCode;
+            }
+            Assert.AreEqual(501827256, hashCode);
         }
     }
 }

+ 13 - 0
src/UnitTests/NameValueCollectionTests.cs

@@ -0,0 +1,13 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Specialized;
+
+namespace NTMiner {
+    [TestClass]
+    public class NameValueCollectionTests {
+        [TestMethod]
+        public void Test1() {
+            var headers = new NameValueCollection();
+            Assert.IsNull(headers["aaa"]);
+        }
+    }
+}

部分文件因为文件数量过多而无法显示