2018年11月30日 01:15
原创作品,转载时请务必以超链接形式标明文章原始出处,否则将追究法律责任。

本篇我们讲一下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是什么都不知道。废话这么多,我还是要写博客,持续学习。


先看一下图,无图无真相嘛。

当没有部门在线时,监控状态如下:

image.png

当有部门在线时,监控状态如下,我们发现保洁部在线。

image.png

当我们停止server端服务后,客户端保洁部会检测到服务端停止工作,就会显示连接失败。

image.png

当我们再次启动服务后,客户端保洁部就会连接成功。

image.png


那么接下来就到我们的重点部分,代码实现。

界面上,我们用了一个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,今天关于服务端就说到这里,明天讲客户端。

发表评论
匿名  
用户评论

匿名游客

2021年10月12日 19:23
这个玩意写的好不好,你说了算

匿名游客

2020年05月26日 22:57
我看这个写的不错,给你个好评,哈哈哈哈