本篇我们讲一下Server端,主要是监控各单位在线情况以前消息分发。
我这人在写博客之前总是喜欢讲故事,就说那个TDD,DDD,其实这两个概念我在10年前我就理解过,我也实践过。当时领域驱动这个概念高大上,当时EF刚出来,还是ObjectDBContext。当时理解领域就是一堆可以实现某个完整业务逻辑的单元UnitOfWork。当时做远程教育平台的时候,有80张表,当时映射为EF实体。分了几个领域,即多个edmx文件,每个edmx文件包含一个完整的业务逻辑db实体映射。设置一对一,多对一,0或1对多。感觉这就是领域驱动设计,后来又看了一本领域驱动设计的书,说是需要一个领域专家,这个专家不需要懂技术,但是必须对这块业务非常熟悉。看了看代码,也就是一些理论,娘希匹。最后我干脆也不专门去研究什么DDD,按照自己的想法来,做的快乐顺手就行了。结合最近公司狂推的unittest,一个开发在Scrum环境下,两周就要release一次,plan meeting需要一天或半天吧,需求确认需要半天吧,再减去release和daily meeting以及retrospective meeting的时间,你说有多少时间写unit test。电商后台业务非常复杂,长年累月下来代码也比较乱,耦合度很高。这时仅仅是测试一个api就需要经过几十个business logic check。可想而知花费的时间,权衡一下,算求了吧。
话说当年我也是喜欢什么依赖注入解耦,什么Spring.Net,微软的Unity我都用。可是后来到了外企,我发现根本没人用这些玩意,我也就随之丢弃了这些东西。可是经过了十年,现在又分享什么autofac,我一脸疑惑。不是依赖注入一直都在那里么,为何近期又觉得这个东西好了呢。真的是因人而异,我记得之前去一家公司有个项目经理是从艾默生出来的,人家自己写了一套IOC框架,什么IL中间语言写的麻溜。当时好佩服,我连Opcodes是什么都不知道。废话这么多,我还是要写博客,持续学习。
先看一下图,无图无真相嘛。
当没有部门在线时,监控状态如下:
当有部门在线时,监控状态如下,我们发现保洁部在线。
当我们停止server端服务后,客户端保洁部会检测到服务端停止工作,就会显示连接失败。
当我们再次启动服务后,客户端保洁部就会连接成功。
那么接下来就到我们的重点部分,代码实现。
界面上,我们用了一个ListView控件用来展示各个部门以及在线状态,首先先看一下这个部门列表的展示。
private async void FrmMsgPublish_Load(object sender, EventArgs e) { this.InitUnit(); var messageServer = await MessageManageBiz.Instance.GetMessageServer(); if (messageServer == null) { this.ShowMessage("消息服务器地址信息未配置,请先进行配置!"); this.Close(); return; } this.ServerURI = string.Join(":", messageServer.IPAddress, messageServer.Port); this.ServerURI = string.Concat("http://", ServerURI); }
在form加载的时候,我们初始化了各部门列表数据,获取了ServerURI,这个Server URI是配置在我们的数据库中的,由后台信息共享系统进行维护。
private async void InitUnit() { var unitEntityList = await UnitManageBiz.Instance.GetUnitInfoByName(); ListViewItem lvItem = null; this.lv_Unit.BeginUpdate(); foreach (var unitEntity in unitEntityList) { lvItem = new ListViewItem(); lvItem.ImageIndex = 0; lvItem.Text = unitEntity.Name; lvItem.Tag = unitEntity; lvItem.SubItems.Add(new ListViewItem.ListViewSubItem { Text = "离线" }); this.lv_Unit.Items.Add(lvItem); } this.lv_Unit.EndUpdate(); }
上面部分代码就是初始化部门ListView,初始状态都是离线。
接着我们看一下这个MessageServer的配置,先看Biz层。
public async Task<MessageServerEntity> GetMessageServer() { return await MessageManageDAL.Instance.GetMessageServer(); }
再看DAL层
public async Task<MessageServerEntity> GetMessageServer() { var sqlScript = DBScriptManager.GetScript(this.GetType().Name, "GetMessageServer"); return await dapperHelper.QuerySingle<MessageServerEntity>(sqlScript); }
拿到脚本后,用dapper的querySinlge方法进行查询返回一个实体。
OK,拿到这个后,我们就可以启动Server了,为什么这个一定要配置起来,因为你的server有可能在阿里云和数据库在一起,有可能在腾讯云上单独放着。所以配置起来比较灵活,我们在本地测试也容易修改。
private void btn_StartServer_Click(object sender, EventArgs e) { this.btn_StartServer.Enabled = false; Task.Run(() => StartServer()); }
开启一个task启动Server
private void StartServer() { try { SignalR = WebApp.Start(ServerURI); this.Invoke(new Action(() => { this.lab_State.Text = "服务启动成功!"; this.lab_State.ForeColor = Color.Green; })); } catch (Exception ex) { this.ShowMessage(string.Concat("服务启动失败!", ServerURI)); this.Invoke((Action)(() => this.btn_StartServer.Enabled = true)); return; } }
这里我们启动监听配置的ServerURI,因为当前启动的task线程和UI不在一个线程上,所以需要用this.invoke委托的方式来更改UI线程上的控件属性。
同时在Server端我们还需要定义ASP.NET SignalR hub用于监听客户端响应。
class Startup { public void Configuration(IAppBuilder app) { app.UseCors(CorsOptions.AllowAll); app.MapSignalR(); } } public class MessageHub : Hub { public void Send(string messageTitle, string messageContent, string currentUnitName, List<string> departmentName) { Clients.Others.addMessage(messageTitle, messageContent, currentUnitName, departmentName); } public override Task OnConnected() { Program.MainForm.RefreshUnitListViewState(Uri.UnescapeDataString(Context.Headers["UnitName"]), RemoteConnectionState.Connected); return base.OnConnected(); } public override Task OnDisconnected(bool x) { Program.MainForm.RefreshUnitListViewState(Uri.UnescapeDataString(Context.Headers["UnitName"]), RemoteConnectionState.Disconnected); return base.OnDisconnected(true); } }
上面有三个方法,第一个是我们自己定义接收客户端消息的方法,主要是消息标题,消息内容,哪个单位发的消息,发给哪些单位。然后
消息发送出去以后其他单位判断如果最后一个参数中包含自己部门,就会在他们单位的电视屏幕上显示这个消息,转成语音播报。
在OnConnected方法中(上线),我们会找到对应的部门将其刷新成绿色。在Disconnected方法中(掉线),我们会把对应的部门刷成红色。
public void RefreshUnitListViewState(string connectionId, RemoteConnectionState state) { if (string.IsNullOrEmpty(connectionId)) return; this.Invoke((Action)(() => { lv_Unit.BeginUpdate(); foreach (ListViewItem lvItem in lv_Unit.Items) { if (lvItem.Text.Equals(connectionId, StringComparison.CurrentCultureIgnoreCase)) { if (state == RemoteConnectionState.Disconnected) { lvItem.ImageIndex = 0; lvItem.SubItems[1].Text = "离线"; lvItem.SubItems[1].ForeColor = Color.Red; lvItem.SubItems[0].ForeColor = Color.Red; } else { lvItem.ImageIndex = 1; lvItem.SubItems[1].Text = "在线"; lvItem.SubItems[1].ForeColor = Color.Green; lvItem.SubItems[0].ForeColor = Color.Green; } } } lv_Unit.EndUpdate(); })); }
OK,今天关于服务端就说到这里,明天讲客户端。
匿名游客
匿名游客