1. 程式人生 > >BeetleX之快速構建Web多房間聊天室

BeetleX之快速構建Web多房間聊天室

其實構建一個Web多房間聊天室也並不是什麼困難的技術,藉助於websocket就可以輕鬆實現多使用者線上實時通訊互動;在這裡主要介紹一下在BeetleXBeetleXjs的支援下如何讓這個功能實現的更簡單和高效。接下來通過使用BeetleX來一步步講解Web多房間聊天室的具體實現。

資訊邏輯

既然是多房間聊天室那它具備兩個主要元素,分別是使用者和房間;下面通過類來描述這兩個元素:

使用者

    public class User
    {
        public string Name { get; set; }

        public string Address { get; set; }

        [JsonIgnore]
        public ISession Session { get; set; }

        [JsonIgnore]
        public Room Room { get; set; }

        public void Send(BeetleX.FastHttpApi.WebSockets.DataFrame frame)
        {
            frame.Send(Session);
        }

        public void Exit()
        {
            Room?.Exit(this);
        }
    }

資訊描述比較簡單主要包括資訊用:名稱,會話和房間;涉及的行為有傳送資訊和退出房間。

房間

    public class Room
    {

        public string Name { get; set; }

        public List<User> Users { get; private set; } = new List<User>();

        public HttpApiServer HttpServer { get; set; }

        public void Send(Command cmd)
        {
            cmd.Room = Name;
            var frame = HttpServer.CreateDataFrame(cmd);
            lock (Users)
            {
                foreach (var item in Users)
                    item.Send(frame);
            }

        }

        public User[] GetOnlines()
        {
            lock (Users)
                return Users.ToArray();
        }

        public void Enter(User user)
        {
            if (user == null)
                return;
            if (user.Room != this)
            {
                user.Room?.Exit(user);
                lock (Users)
                    Users.Add(user);
                user.Room = this;
                Command quit = new Command { Type = "enter",Message=$"enter room", User = user };
                Send(quit);
            }
        }

        public void Exit(User user)
        {
            if (user == null)
                return;
            lock (Users)
                Users.Remove(user);
            user.Room = null;
            Command quit = new Command { Type = "quit", Message = $"exit room", User = user };
            Send(quit);
        }
    }

房間資訊主要包括名稱和使用者資訊,具體行為有進房間,出房間和向房間傳送資訊。

服務邏輯

有了邏輯資訊那就需要把這個資訊通過介面的服務方式提供給外部訪問操作,接下來定義一個簡單的控制器類來描述相關介面服務行為

    [BeetleX.FastHttpApi.Controller]
    public class Home : BeetleX.FastHttpApi.IController
    {
        [BeetleX.FastHttpApi.NotAction]
        public void Init(HttpApiServer server, string path)
        {
            for (int i = 0; i < 10; i++)
            {
                string key = $"{i:00}";
                mRooms[key] = new Room { Name = key, HttpServer = server };
            }
            server.HttpDisconnect += (o, e) =>
            {
                GetUser(e.Session)?.Exit();
            };
        }
        private ConcurrentDictionary<string, Room> mRooms 
        = new ConcurrentDictionary<string, Room>(StringComparer.OrdinalIgnoreCase);
        public object Rooms()
        {
            return from a in mRooms.Values orderby a.Name select new {a.Name};
        }
        public void Enter(string room, IHttpContext context)
        {
            User user = GetUser(context.Session);
            mRooms.TryGetValue(room, out Room result);
            result?.Enter(user);
        }
        public void Talk(string message, IHttpContext context)
        {
            if (!string.IsNullOrEmpty(message))
            {
                var user = GetUser(context.Session);
                Command cmd = new Command { Type = "talk", Message = message, User = user };
                user?.Room?.Send(cmd);
            }
        }
        public void Login(string name, IHttpContext context)
        {
            User user = new User();
            user.Name = name;
            user.Session = context.Session;
            user.Address = context.Request.RemoteIPAddress;
            SetUser(context.Session, user);
        }
        private User GetUser(ISession session)
        {
            return (User)session["__user"];
        }

        private void SetUser(ISession session, User user)
        {
            session["__user"] = user;
        }
    }

Init方法

用於初始化房間資訊,並繫結連線斷開事件,如果使用者斷開了則執行使用者退出房間。

Login方法

登陸到使用者中

Rooms方法

獲取所有房間資訊

Enter方法

使用者進入房間

Talk

使用者向房間內傳送一條訊息

啟動服務

當功能邏輯寫好後,接下來的工作就是讓這些介面部署到websocket服務中,部署的程式碼比較簡單:

    class Program
    {
        static void Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.UseBeetlexHttp(o =>
                    {
                        o.LogToConsole = true;
                        o.ManageApiEnabled = false;
                        o.Port = 80;
                        o.SetDebug();
                        o.LogLevel = BeetleX.EventArgs.LogType.Warring;
                    },
                    typeof(Program).Assembly);
                });
            builder.Build().Run();
        }
    }

當服務部署後就可以專心去做前端實現的工作。

Web前端

為了更方便地和Beetlex服務整合,因此也單獨針對性地封裝了相應的javascript元件;除了自有封裝的javascript還涉及到vuejs的使用。通過以上元件整合前端的程式碼相比服務端來說就更簡單了,詳細程式碼如下:

<body>
    <div id="page">
        <page-header> </page-header>
        <div class="container" style="margin-top:110px;">
            <div class="row">
                <ul style="list-style:none;">
                    <li v-for="item in messages" class="message">
                        <h4>
                            <span class="label label-success">[{{item.Room}}]</span>
                            <span class="label label-info">{{item.User.Name}}</span>
                            <span class="label label-default">{{new Date()}}</span>
                        </h4>
                        <div style="padding-left:20px;">
                            {{item.Message}}
                        </div>
                    </li>
                </ul>
            </div>
        </div>
        <page-footer :status="loginStatus" @login="onLogin($event)"
                     @talk="onTalk($event)" @select="onSelectRoom($event)" :rooms="getRooms.result">
        </page-footer>
    </div>
    <script>
        beetlex.websocket.receive = function (r) {
            page.messages.push(r);
        };
        beetlex.websocket.disconnect = function () {
            page.loginStatus = false;
        };
        beetlex.useWebsocket();
        var login = new beetlexAction("/Login");
        var getRooms = new beetlexAction('/Rooms', null, []);
        var enterRoom = new beetlexAction('/Enter');
        var talk = new beetlexAction('/Talk');
        login.requested = function (r) {
            page.loginStatus = true;
        };
        var model = {
            getRooms: getRooms,
            loginStatus: false,
            login: login,
            talk: talk,
            enterRoom: enterRoom,
            messages: [],
        };
        var page = new Vue({
            el: '#page',
            data: model,
            methods: {
                onSelectRoom: function (r) {
                    // alert(r);
                    this.enterRoom.post({ room: r });
                },
                onLogin: function (r) {
                    this.login.post({ name: r });
                },
                onTalk: function (msg) {
                    talk.post({ message: msg });
                },
            },
        });
        getRooms.get();
    </script>
</body>

beetlex

是針對httpwebsocket封裝的功能類,它自動相容這兩種請求;在預設情況是http請求,呼叫useWebsocket後所有請求都優先使用websocket;當websocket不可用的情況組會自動切回到http.

beetlexAction

用於描述一個請求,分別提供了postget方法;當在websocket下這兩個方法的處理方式一樣。

執行效果

演示地址

http://chat.ikende.com

程式碼地址

https://github.com/IKende/BeetleX-Samples/tree/master/WebSocket.