Exemple #1
0
        /// <summary>
        /// 清理掉在给定的时间内未活跃的客户端
        /// </summary>
        /// <param name="seconds"></param>
        private void ClearDeath()
        {
            // 客户端每20秒ping一次服务端,所以如果2分钟未活跃可以视为不在线了
            int      seconds        = 120;
            DateTime activeOn       = DateTime.Now.AddSeconds(-seconds);
            DateTime doubleActiveOn = activeOn.AddSeconds(-seconds);
            Dictionary <string, TSession> toRemoves;

            lock (_locker) {
                toRemoves = _dicByWsSessionId.Values.Where(a => a != null && a.ActiveOn <= activeOn).ToDictionary(a => a.WsSessionId, a => a);
            }
            var toCloseWses = _sessions.Sessions.Where(a => toRemoves.ContainsKey(a.SessionId)).ToArray();

            foreach (var ws in toCloseWses)
            {
                try {
                    ws.CloseAsync(WsCloseCode.Normal, $"{seconds.ToString()}秒内未活跃");
                }
                catch {
                }
            }
            // 断开Ws连接时的OnClose事件中会移除已断开连接的会话,但OnClose事件是由WebSocket的库触发
            // 的不是由我触发的,暂时没有深究这个库是否确定会触发OnClose事件所以这里加了个防护逻辑
            foreach (var toRemove in toRemoves)
            {
                if (toRemove.Value.ActiveOn <= doubleActiveOn)
                {
                    RemoveByWsSessionId(toRemove.Key);
                }
            }
            if (toRemoves.Count > 0)
            {
                NTMinerConsole.UserWarn($"周期清理不活跃的{_sessionType.Name},清理了 {toRemoves.Count.ToString()}/{toRemoves.Count.ToString()} 条");
            }
        }
Exemple #2
0
 private void CreateProcessAsync()
 {
     Task.Factory.StartNew(() => {
         lock (_locker) {
             try {
                 // 清理除当前外的Temp/Kernel
                 Cleaner.Instance.Clear();
                 NTMinerConsole.UserOk("场地打扫完毕");
                 // 应用超频
                 if (NTMinerContext.Instance.GpuProfileSet.IsOverClockEnabled(MainCoin.GetId()))
                 {
                     NTMinerConsole.UserWarn("应用超频,如果CPU性能较差耗时可能超过1分钟,请耐心等待");
                     var cmd = new CoinOverClockCommand(coinId: MainCoin.GetId());
                     AddOnecePath <CoinOverClockDoneEvent>("超频完成后继续流程", LogEnum.DevConsole,
                                                           message => {
                         // pathId是唯一的,从而可以断定该消息一定是因为该命令而引发的
                         ContinueCreateProcess();
                     }, location: this.GetType(), pathId: cmd.MessageId);
                     // 超频是在另一个线程执行的,因为N卡超频当cpu性能非常差时较耗时
                     VirtualRoot.Execute(cmd);
                 }
                 else
                 {
                     ContinueCreateProcess();
                 }
             }
             catch (Exception e) {
                 Logger.ErrorDebugLine(e);
                 NTMinerConsole.UserFail("挖矿内核启动失败,请联系开发人员解决");
             }
         }
     });
 }
Exemple #3
0
 public WsServerNodeAddressSet(IWsServerNodeRedis wsServerNodeRedis) : base(wsServerNodeRedis)
 {
     VirtualRoot.BuildEventPath <Per5MinuteEvent>("清理掉离线的WsServer节点", LogEnum.None, this.GetType(), PathPriority.Normal, path: message => {
         wsServerNodeRedis.GetAllAddress().ContinueWith(t => {
             var offlines = t.Result.Where(a => IsOffline(a.Value, message.BornOn)).Select(a => a.Key).ToArray();
             if (offlines != null && offlines.Length != 0)
             {
                 wsServerNodeRedis.ClearAsync(offlines).ContinueWith(_ => {
                     NTMinerConsole.UserWarn($"清理了 {offlines.Length.ToString()} 条");
                 });
             }
         });
     });
 }
 public WsServerNodeAddressSet(IWsServerNodeRedis wsServerNodeRedis) : base(wsServerNodeRedis)
 {
     VirtualRoot.AddEventPath <Per10SecondEvent>("清理掉离线的WsServer节点", LogEnum.UserConsole, action: message => {
         wsServerNodeRedis.GetAllAddress().ContinueWith(t => {
             var offlines = GetOfflineAddress(t.Result);
             if (offlines != null && offlines.Count != 0)
             {
                 wsServerNodeRedis.ClearAsync(offlines).ContinueWith(_ => {
                     NTMinerConsole.UserWarn($"清理了 {offlines.Count} 条");
                 });
             }
         });
     }, this.GetType());
 }
Exemple #5
0
        private void ContinueCreateProcess()
        {
            Thread.Sleep(1000);
            if (this != NTMinerContext.Instance.LockedMineContext)
            {
                NTMinerConsole.UserWarn("结束开始挖矿");
                return;
            }

            // 执行文件书写器
            this.ExecuteFileWriters();

            // 分离命令名和参数
            GetCmdNameAndArguments(out string kernelExeFileFullName, out string arguments);
            // 这是不应该发生的,如果发生很可能是填写命令的时候拼写错误了
            if (!File.Exists(kernelExeFileFullName))
            {
                NTMinerConsole.UserError(kernelExeFileFullName + "文件不存在,可能是被杀软删除导致,请退出杀毒软件重试或者QQ群联系小编,解释:大部分挖矿内核会报毒,不是开源矿工的问题也不是杀软的问题,也不是挖矿内核的问题,是挖矿这件事情的问题,可能是挖矿符合了病毒的定义。");
            }
            if (this.KernelProcessType == KernelProcessType.Logfile)
            {
                arguments = arguments.Replace(NTKeyword.LogFileParameterName, this.LogFileFullName);
            }
            NTMinerConsole.UserOk($"\"{kernelExeFileFullName}\" {arguments}");
            NTMinerConsole.UserInfo($"有请内核上场");
            if (this != NTMinerContext.Instance.LockedMineContext)
            {
                NTMinerConsole.UserWarn("结束开始挖矿");
                return;
            }
            NTMinerConsole.InitOnece(isForce: true, initHide: !NTMinerContext.IsUiVisible);
            switch (this.KernelProcessType)
            {
            case KernelProcessType.Logfile:
                CreateLogfileProcess(kernelExeFileFullName, arguments);
                break;

            case KernelProcessType.Pip:
                CreatePipProcess(kernelExeFileFullName, arguments);
                break;

            default:
                throw new InvalidProgramException();
            }
            this.ProcessCreatedOn = DateTime.Now;
            KernelProcessDaemon();
            VirtualRoot.RaiseEvent(new MineStartedEvent(this));
        }
Exemple #6
0
        private string[] GetNodeAddresses(string[] nodeAddresses)
        {
            if (nodeAddresses == null || nodeAddresses.Length == 0)
            {
                NTMinerConsole.UserWarn("节点集为空");
                return(new string[0]);
            }
            string thisServerAddress = ServerRoot.HostConfig.ThisServerAddress;
            var    thisNode          = nodeAddresses.FirstOrDefault(a => a == thisServerAddress);

            if (thisNode == null)
            {
                NTMinerConsole.UserWarn($"未发现和本节点地址相同的节点,本节点地址为:{(string.IsNullOrEmpty(thisServerAddress) ? "无" : thisServerAddress)}");
            }
            return(nodeAddresses);
        }
        public ServerStateResponse GetServerState([FromBody] JsonFileVersionRequest request)
        {
            ServerStateResponse serverState = ServerStateResponse.Empty;

            if (request != null)
            {
                serverState = AppRoot.GetServerStateResponse(request.Key);
                if (request.ClientId != Guid.Empty)
                {
                    var clientData = AppRoot.ClientDataSet.GetByClientId(request.ClientId);
                    if (clientData != null && !string.IsNullOrEmpty(clientData.MACAddress))
                    {
                        serverState.NeedReClientId = request.MACAddress.All(a => !clientData.MACAddress.Contains(a));
                        NTMinerConsole.UserWarn($"重复的网卡地址:{string.Join(",", request.MACAddress)}");
                    }
                }
            }
            return(serverState);
        }
Exemple #8
0
        public Task <List <SpeedData> > GetAllAsync()
        {
            var       db        = _redis.RedisConn.GetDatabase();
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            return(db.HashGetAllAsync(_redisKeySpeedDataByClientId).ContinueWith(t => {
                stopwatch.Stop();
                long seconds = stopwatch.ElapsedMilliseconds / 1000;
                string text = $"{nameof(SpeedDataRedis)}的redis方法HashGetAllAsync耗时 {seconds.ToString()} 秒";
                // 从redis一次性加载几十兆数据没有什么问题,打印些统计信息出来以待将来有问题时容易发现
                if (seconds > 5)
                {
                    NTMinerConsole.UserWarn(text);
                }
                else
                {
                    NTMinerConsole.UserInfo(text);
                }
                stopwatch.Start();
                List <SpeedData> list = new List <SpeedData>();
                foreach (var item in t.Result)
                {
                    if (item.Value.HasValue)
                    {
                        SpeedData data = VirtualRoot.JsonSerializer.Deserialize <SpeedData>(item.Value);
                        if (data != null)
                        {
                            list.Add(data);
                        }
                    }
                }
                stopwatch.Stop();
                seconds = stopwatch.ElapsedMilliseconds / 1000;
                NTMinerConsole.UserInfo($"反序列化和装配耗时 {seconds.ToString()} 秒");
                return list;
            }));
        }
Exemple #9
0
 public void ShowWarn(string message, string header, int autoHideSeconds, bool toConsole = false)
 {
     if (toConsole)
     {
         NTMinerConsole.UserWarn(message);
     }
     UIThread.Execute(() => {
         var builder = NotificationMessageBuilder.CreateMessage(Manager);
         builder.Warning(header, message ?? string.Empty);
         if (autoHideSeconds > 0)
         {
             builder
             .Dismiss()
             .WithDelay(autoHideSeconds)
             .Queue();
         }
         else
         {
             builder
             .Dismiss().WithButton("知道了", null)
             .Queue();
         }
     });
 }
Exemple #10
0
        public MainWindow()
        {
            if (WpfUtil.IsInDesignMode)
            {
                return;
            }
            if (!NTMinerConsole.IsEnabled)
            {
                NTMinerConsole.Enable();
            }
            this.Vm          = new MainWindowViewModel();
            this.DataContext = Vm;
            this.MinHeight   = 430;
            this.MinWidth    = 640;
            this.Width       = AppRoot.MainWindowWidth;
            this.Height      = AppRoot.MainWindowHeight;
#if DEBUG
            NTStopwatch.Start();
#endif
            ConsoleWindow.Instance.MouseDown += (sender, e) => {
                MoveConsoleWindow();
            };
            SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
            this.Loaded += (sender, e) => {
                ConsoleTabItemTopBorder.Margin = new Thickness(0, ConsoleTabItem.ActualHeight - 1, 0, 0);
                MoveConsoleWindow();
                hwndSource = PresentationSource.FromVisual((Visual)sender) as HwndSource;
                hwndSource.AddHook(new HwndSourceHook(Win32Proc.WindowProc));
            };
            InitializeComponent();
            _leftDrawerGripWidth    = LeftDrawerGrip.Width;
            _btnOverClockBackground = BtnOverClock.Background;
            // 下面几行是为了看见设计视图
            this.ResizeCursors.Visibility = Visibility.Visible;
            this.HideLeftDrawerGrid();
            // 上面几行是为了看见设计视图

            DateTime lastGetServerMessageOn = DateTime.MinValue;
            // 切换了主界面上的Tab时
            this.MainTabControl.SelectionChanged += (sender, e) => {
                // 延迟创建,以加快主界面的启动
                #region
                var selectedItem = MainTabControl.SelectedItem;
                if (selectedItem == TabItemSpeedTable)
                {
                    if (SpeedTableContainer.Child == null)
                    {
                        SpeedTableContainer.Child = GetSpeedTable();
                    }
                }
                else if (selectedItem == TabItemMessage)
                {
                    if (MessagesContainer.Child == null)
                    {
                        MessagesContainer.Child = new Messages();
                    }
                }
                else if (selectedItem == TabItemToolbox)
                {
                    if (ToolboxContainer.Child == null)
                    {
                        ToolboxContainer.Child = new Toolbox();
                    }
                }
                else if (selectedItem == TabItemMinerProfileOption)
                {
                    if (MinerProfileOptionContainer.Child == null)
                    {
                        MinerProfileOptionContainer.Child = new MinerProfileOption();
                    }
                }
                RpcRoot.SetIsServerMessagesVisible(selectedItem == TabItemMessage);
                if (selectedItem == TabItemMessage)
                {
                    if (lastGetServerMessageOn.AddSeconds(10) < DateTime.Now)
                    {
                        lastGetServerMessageOn = DateTime.Now;
                        VirtualRoot.Execute(new LoadNewServerMessageCommand());
                    }
                }
                if (selectedItem == ConsoleTabItem)
                {
                    ConsoleTabItemTopBorder.Visibility = Visibility.Visible;
                }
                else
                {
                    ConsoleTabItemTopBorder.Visibility = Visibility.Collapsed;
                }
                #endregion
            };
            this.IsVisibleChanged += (sender, e) => {
                #region
                if (this.IsVisible)
                {
                    NTMinerContext.IsUiVisible = true;
                }
                else
                {
                    NTMinerContext.IsUiVisible = false;
                }
                MoveConsoleWindow();
                #endregion
            };
            this.StateChanged += (s, e) => {
                #region
                if (Vm.MinerProfile.IsShowInTaskbar)
                {
                    ShowInTaskbar = true;
                }
                else
                {
                    if (WindowState == WindowState.Minimized)
                    {
                        ShowInTaskbar = false;
                    }
                    else
                    {
                        ShowInTaskbar = true;
                    }
                }
                if (WindowState == WindowState.Maximized)
                {
                    ResizeCursors.Visibility = Visibility.Collapsed;
                }
                else
                {
                    ResizeCursors.Visibility = Visibility.Visible;
                }
                MoveConsoleWindow();
                #endregion
            };
            bool isLeftClosed = false;
            this.ConsoleRectangle.IsVisibleChanged += (sender, e) => {
                if (this.ConsoleRectangle.IsVisible)
                {
                    if (isLeftClosed != (LeftDrawerGrip.Width == _leftDrawerGripWidth))
                    {
                        ConsoleWindowFit();
                    }
                }
                else
                {
                    isLeftClosed = LeftDrawerGrip.Width == _leftDrawerGripWidth;
                }
            };
            this.ConsoleRectangle.SizeChanged += (s, e) => {
                MoveConsoleWindow();
            };
            if (this.Width < 860)
            {
                NTMinerConsole.UserWarn("左侧面板已折叠,可点击侧边的'开始挖矿'按钮展开。");
            }
            this.SizeChanged += (s, e) => {
                #region
                if (this.Width < 860)
                {
                    this.CloseLeftDrawer();
                    this.BtnAboutNTMiner.Visibility = Visibility.Collapsed;
                }
                else
                {
                    this.OpenLeftDrawer(isSizeChanged: true);
                    this.BtnAboutNTMiner.Visibility = Visibility.Visible;
                }
                if (!this.ConsoleRectangle.IsVisible)
                {
                    if (e.WidthChanged)
                    {
                        ConsoleWindow.Instance.Width = e.NewSize.Width;
                    }
                    if (e.HeightChanged)
                    {
                        ConsoleWindow.Instance.Height = e.NewSize.Height;
                    }
                }
                #endregion
            };
            NotiCenterWindow.Bind(this, ownerIsTopmost: true);
            this.LocationChanged += (sender, e) => {
                MoveConsoleWindow();
            };
            VirtualRoot.BuildCmdPath <TopmostCommand>(path: message => {
                UIThread.Execute(() => {
                    if (!this.Topmost)
                    {
                        this.Topmost = true;
                    }
                });
            }, this.GetType());
            VirtualRoot.BuildCmdPath <UnTopmostCommand>(path: message => {
                UIThread.Execute(() => {
                    if (this.Topmost)
                    {
                        this.Topmost = false;
                    }
                });
            }, this.GetType());
            VirtualRoot.BuildCmdPath <CloseMainWindowCommand>(path: message => {
                UIThread.Execute(() => {
                    if (message.IsAutoNoUi)
                    {
                        SwitchToNoUi();
                    }
                    else
                    {
                        this.Close();
                    }
                });
            }, location: this.GetType());
            this.BuildEventPath <Per1MinuteEvent>("挖矿中时自动切换为无界面模式", LogEnum.DevConsole,
                                                  path: message => {
                if (NTMinerContext.IsUiVisible && NTMinerContext.Instance.MinerProfile.IsAutoNoUi && NTMinerContext.Instance.IsMining)
                {
                    if (NTMinerContext.MainWindowRendedOn.AddMinutes(NTMinerContext.Instance.MinerProfile.AutoNoUiMinutes) < message.BornOn)
                    {
                        VirtualRoot.ThisLocalInfo(nameof(MainWindow), $"挖矿中界面展示{NTMinerContext.Instance.MinerProfile.AutoNoUiMinutes}分钟后自动切换为无界面模式,可在选项页调整配置");
                        VirtualRoot.Execute(new CloseMainWindowCommand(isAutoNoUi: true));
                    }
                }
            }, location: this.GetType());
#if DEBUG
            var elapsedMilliseconds = NTStopwatch.Stop();
            if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds)
            {
                NTMinerConsole.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
            }
#endif
        }
 public void WarnWriteLine(object message)
 {
     NTMinerConsole.UserWarn(message?.ToString());
     _log.Warn(message);
 }
Exemple #12
0
        public ClientDataSet(IMinerRedis minerRedis, ISpeedDataRedis speedDataRedis, IMinerClientMqSender mqSender) : base(isPull: false, getDatas: callback => {
            var getMinersTask = minerRedis.GetAllAsync();
            var getSpeedsTask = speedDataRedis.GetAllAsync();
            Task.WhenAll(getMinersTask, getSpeedsTask).ContinueWith(t => {
                NTMinerConsole.UserInfo($"从redis加载了 {getMinersTask.Result.Count} 条MinerData,和 {getSpeedsTask.Result.Count} 条SpeedData");
                var speedDatas = getSpeedsTask.Result;
                List <ClientData> clientDatas = new List <ClientData>();
                DateTime speedOn = DateTime.Now.AddMinutes(-3);
                foreach (var minerData in getMinersTask.Result)
                {
                    var clientData = ClientData.Create(minerData);
                    // 该属性没有持久化而只在内存中,启动时将该属性值视为当前日期的前一天的零时加上CreatedOn的时间,别处有个周期清理7天不活跃矿机的任务
                    clientData.MinerActiveOn = DateTime.Today.AddDays(-1) + minerData.CreatedOn.TimeOfDay;
                    clientDatas.Add(clientData);
                    var speedData = speedDatas.FirstOrDefault(a => a.ClientId == minerData.ClientId);
                    if (speedData != null && speedData.SpeedOn > speedOn)
                    {
                        clientData.Update(speedData, out bool _);
                    }
                }
                callback?.Invoke(clientDatas);
            });
        }) {
            _minerRedis     = minerRedis;
            _speedDataRedis = speedDataRedis;
            _mqSender       = mqSender;
            VirtualRoot.BuildEventPath <Per1MinuteEvent>("周期清理Redis中不活跃的来自挖矿端上报的算力记录", LogEnum.DevConsole, path: message => {
                DateTime time     = message.BornOn.AddSeconds(-130);
                var toRemoveSpeed = _dicByClientId.Where(a => a.Value.MinerActiveOn != DateTime.MinValue && a.Value.MinerActiveOn <= time).ToArray();
                _speedDataRedis.DeleteByClientIdsAsync(toRemoveSpeed.Select(a => a.Key).ToArray());

                // 删除一周没有活跃过的客户端
                time = message.BornOn.AddDays(-7);
                var toRemoveClient = _dicByObjectId.Where(a => a.Value.MinerActiveOn <= time).ToArray();
                foreach (var kv in toRemoveClient)
                {
                    base.RemoveByObjectId(kv.Key);
                }
            }, this.GetType());
            // 收到Mq消息之前一定已经初始化完成,因为Mq消费者在ClientSetInitedEvent事件之后才会创建
            VirtualRoot.BuildEventPath <SpeedDataMqMessage>("收到SpeedDataMq消息后更新ClientData内存", LogEnum.None, path: message => {
                if (message.AppId == ServerRoot.HostConfig.ThisServerAddress)
                {
                    return;
                }
                if (message.ClientId == Guid.Empty)
                {
                    return;
                }
                if (IsOldMqMessage(message.Timestamp))
                {
                    NTMinerConsole.UserOk(_safeIgnoreMessage);
                    return;
                }
                speedDataRedis.GetByClientIdAsync(message.ClientId).ContinueWith(t => {
                    ReportSpeed(t.Result.SpeedDto, message.MinerIp, isFromWsServerNode: true);
                });
            }, this.GetType());
            VirtualRoot.BuildEventPath <MinerClientWsOpenedMqMessage>("收到MinerClientWsOpenedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => {
                if (IsOldMqMessage(message.Timestamp))
                {
                    NTMinerConsole.UserOk(_safeIgnoreMessage);
                    return;
                }
                if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData))
                {
                    clientData.NetActiveOn = message.Timestamp;
                    clientData.IsOnline    = true;
                }
            }, this.GetType());
            VirtualRoot.BuildEventPath <MinerClientWsClosedMqMessage>("收到MinerClientWsClosedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => {
                if (IsOldMqMessage(message.Timestamp))
                {
                    NTMinerConsole.UserOk(_safeIgnoreMessage);
                    return;
                }
                if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData))
                {
                    clientData.NetActiveOn = message.Timestamp;
                    clientData.IsOnline    = false;
                }
            }, this.GetType());
            VirtualRoot.BuildEventPath <MinerClientWsBreathedMqMessage>("收到MinerClientWsBreathedMq消息后更新NetActiveOn", LogEnum.None, path: message => {
                if (IsOldMqMessage(message.Timestamp))
                {
                    NTMinerConsole.UserOk(_safeIgnoreMessage);
                    return;
                }
                if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData))
                {
                    clientData.NetActiveOn = message.Timestamp;
                    clientData.IsOnline    = true;
                }
            }, this.GetType());
            VirtualRoot.BuildCmdPath <ChangeMinerSignMqMessage>(path: message => {
                if (_dicByObjectId.TryGetValue(message.Data.Id, out ClientData clientData))
                {
                    clientData.Update(message.Data, out bool isChanged);
                    if (isChanged)
                    {
                        var minerData = MinerData.Create(clientData);
                        _minerRedis.SetAsync(minerData).ContinueWith(t => {
                            _mqSender.SendMinerSignChanged(minerData.Id);
                        });
                    }
                }
                else
                {
                    // 此时该矿机是第一次在服务端出现
                    NTMinerConsole.UserWarn("该矿机首次出现于WsServer:" + VirtualRoot.JsonSerializer.Serialize(message.Data));
                    clientData                    = ClientData.Create(MinerData.Create(message.Data));
                    clientData.NetActiveOn        = DateTime.Now;
                    clientData.IsOnline           = true;
                    clientData.IsOuterUserEnabled = true;
                    Add(clientData);
                }
            }, this.GetType(), LogEnum.None);
        }
Exemple #13
0
 public ServerMessageSet(string dbFileFullName, bool isServer)
 {
     if (string.IsNullOrEmpty(dbFileFullName))
     {
         throw new ArgumentNullException(nameof(dbFileFullName));
     }
     _connectionString = $"filename={dbFileFullName}";
     VirtualRoot.BuildCmdPath <LoadNewServerMessageCommand>(path: message => {
         if (!RpcRoot.IsServerMessagesVisible)
         {
             return;
         }
         DateTime localTimestamp = VirtualRoot.LocalServerMessageSetTimestamp;
         // 如果已知服务器端最新消息的时间戳不比本地已加载的最新消息新就不用加载了
         if (message.KnowServerMessageTimestamp <= Timestamp.GetTimestamp(localTimestamp))
         {
             return;
         }
         RpcRoot.OfficialServer.ServerMessageService.GetServerMessagesAsync(localTimestamp, (response, e) => {
             if (response.IsSuccess())
             {
                 if (response.Data.Count > 0)
                 {
                     VirtualRoot.Execute(new ReceiveServerMessageCommand(response.Data));
                 }
             }
             else
             {
                 if (e != null)
                 {
                     Logger.ErrorDebugLine(e);
                 }
                 else
                 {
                     VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
                 }
             }
         });
     }, location: this.GetType());
     VirtualRoot.BuildCmdPath <ReceiveServerMessageCommand>(path: message => {
         ReceiveServerMessage(message.Data);
     }, location: this.GetType());
     VirtualRoot.BuildCmdPath <AddOrUpdateServerMessageCommand>(path: message => {
         InitOnece();
         if (isServer)
         {
             #region Server
             ServerMessageData exist;
             List <ServerMessageData> toRemoves = new List <ServerMessageData>();
             ServerMessageData data             = null;
             lock (_linkedList) {
                 exist = _linkedList.FirstOrDefault(a => a.Id == message.Input.Id);
                 if (exist != null)
                 {
                     exist.Update(message.Input);
                     exist.Timestamp = DateTime.Now;
                     _linkedList.Remove(exist);
                     _linkedList.AddFirst(exist);
                 }
                 else
                 {
                     data           = new ServerMessageData().Update(message.Input);
                     data.Timestamp = DateTime.Now;
                     _linkedList.AddFirst(data);
                     while (_linkedList.Count > NTKeyword.ServerMessageSetCapacity)
                     {
                         toRemoves.Add(_linkedList.Last.Value);
                         _linkedList.RemoveLast();
                     }
                 }
             }
             if (exist != null)
             {
                 try {
                     using (LiteDatabase db = new LiteDatabase(_connectionString)) {
                         var col = db.GetCollection <ServerMessageData>();
                         col.Update(exist);
                     }
                 }
                 catch (Exception e) {
                     Logger.ErrorDebugLine(e);
                 }
             }
             else
             {
                 try {
                     using (LiteDatabase db = new LiteDatabase(_connectionString)) {
                         var col = db.GetCollection <ServerMessageData>();
                         if (toRemoves.Count != 0)
                         {
                             foreach (var item in toRemoves)
                             {
                                 col.Delete(item.Id);
                             }
                         }
                         col.Insert(data);
                     }
                 }
                 catch (Exception e) {
                     Logger.ErrorDebugLine(e);
                 }
             }
             #endregion
         }
         else
         {
             RpcRoot.OfficialServer.ServerMessageService.AddOrUpdateServerMessageAsync(new ServerMessageData().Update(message.Input), (response, ex) => {
                 if (response.IsSuccess())
                 {
                     VirtualRoot.Execute(new LoadNewServerMessageCommand());
                 }
                 else
                 {
                     VirtualRoot.Out.ShowError(response.ReadMessage(ex), autoHideSeconds: 4);
                 }
             });
         }
     }, location: this.GetType());
     VirtualRoot.BuildCmdPath <MarkDeleteServerMessageCommand>(path: message => {
         InitOnece();
         if (isServer)
         {
             #region Server
             ServerMessageData exist = null;
             lock (_linkedList) {
                 exist = _linkedList.FirstOrDefault(a => a.Id == message.EntityId);
                 if (exist != null)
                 {
                     exist.IsDeleted = true;
                     exist.Content   = string.Empty;
                     exist.Timestamp = DateTime.Now;
                     _linkedList.Remove(exist);
                     _linkedList.AddFirst(exist);
                 }
             }
             if (exist != null)
             {
                 try {
                     using (LiteDatabase db = new LiteDatabase(_connectionString)) {
                         var col = db.GetCollection <ServerMessageData>();
                         col.Update(exist);
                     }
                 }
                 catch (Exception e) {
                     Logger.ErrorDebugLine(e);
                 }
             }
             #endregion
         }
         else
         {
             RpcRoot.OfficialServer.ServerMessageService.MarkDeleteServerMessageAsync(message.EntityId, (response, ex) => {
                 if (response.IsSuccess())
                 {
                     VirtualRoot.Execute(new LoadNewServerMessageCommand());
                 }
                 else
                 {
                     VirtualRoot.Out.ShowError(response.ReadMessage(ex), autoHideSeconds: 4);
                 }
             });
         }
     }, location: this.GetType());
     VirtualRoot.BuildCmdPath <ClearServerMessagesCommand>(path: message => {
         InitOnece();
         if (isServer)
         {
             NTMinerConsole.UserWarn("服务端的消息不能清空");
             return;
         }
         try {
             using (LiteDatabase db = new LiteDatabase(_connectionString)) {
                 lock (_linkedList) {
                     _linkedList.Clear();
                 }
                 db.DropCollection(nameof(ServerMessageData));
             }
         }
         catch (Exception e) {
             Logger.ErrorDebugLine(e);
         }
         VirtualRoot.RaiseEvent(new ServerMessagesClearedEvent());
     }, location: this.GetType());
 }