Ở bài viết trước, chúng ta đã bước đầu làm quen với Ezyfox Server thông qua một ứng dụng nhỏ sử dụng giao thức WS, nơi mà client sẽ hoàn tất việc HANDSHAKE
và LOGIN
, đồng thời nhận một lời chào từ backend server và hiển thị lên trang web. Hôm nay chúng ta tiếp tục nâng cấp ứng dụng để hỗ trợ thêm tính năng realtime chat giữa hai người.
Demo (1 phút):
Code hoàn chỉnh vui lòng xem ở đây và đây. Còn bây giờ bắt đầu thôi!
Client sẽ gọi active-users
command và xử lý response từ Ezy Server; đồng thời gửi chat message đến server thông qua chat
command khi user bấm nút Send:
Để có thể chat cho một ai đó thì trước tiên ứng dụng (cụ thể là phía frontend) phải hiển thị được các cá nhân đang online trong hệ thống, từ đó user có thể chọn một ai đó để bắt đầu chat. Để làm được điều này, client sẽ gọi command active-users
đến server và server sẽ trả về một danh sách các user đang có mặt trong hệ thống.
Mình có giải thích bằng các comment //
, mọi người chú ý đọc để hiểu rõ
//sửa đoạn này (cụ thể là sau khi nhận được lời chào thì gửi command active-users) setupApp.addDataHandler("greet", function (app, data) { var message = data.message; alert(message); $('.connect').remove(); //Đoạn này là gửi command đây app.sendRequest('active-users', {}); setInterval(function () { app.sendRequest('active-users', {}); }, 1000); }); //thêm đoạn này (xử lý response của command active-users) setupApp.addDataHandler("active-users", function (app, data) { getAllActiveUser(data, app); setupClickOnAPersonAndSendBtn(app); }); //hàm này xử lý danh sách user đang online được trả về từ backend, cụ thể là render lên UI function getAllActiveUser(data, app) { var activeUsers = data['active-users'].filter(e => e.id !== app.client.me.id); var bigS = $('#result-panel'); bigS.removeAttr("hidden"); var onlineList = bigS.find('.list-group'); onlineList.empty(); activeUsers.forEach(e => { onlineList.append('<li><a data-username="' + e.name + '" class="list-group-item" id="' + e.id + '" href="javascript:void(0);">' + e.name + '<span hidden class="badge">!</span></a></li>') }); } //Khi user bấm vào một ai đó để bắt đầu chat thì hiển thị khung chat và setup nút gửi tin nhắn function setupClickOnAPersonAndSendBtn(app) { var bigChat = $('#chat-window-1'); $('.list-group-item').on("click", function (object) { bigChat.removeAttr('hidden'); var panelBody = bigChat.find('.panel-body'); panelBody.children().not(':first').remove(); var thiz = $(this); var id = thiz.attr('id'); var name = thiz.attr('data-username'); bigChat.find('#chat-title').text('Chat to ' + name) var btnChat = bigChat.find('#btn-chat'); btnChat.attr('data-user-id', id) bigChat.attr('data-user-id', id) //Đoạn này setup click event lên button Send btnChat.off('click'); btnChat.on('click', function () { var msg = bigChat.find('#btn-input').val(); if (msg && msg.length > 0) { //Dòng này gửi command chat lên server app.sendRequest('chat', {from: app.client.me.id, to: +id, content: msg}); //Thêm dingf chat lên UI nữa chứ var msgDiv = '<div class="row msg_container base_sent">' + ' <div class="col-md-10 col-xs-10">' + ' <div class="messages msg_sent">' + ' <p>' + msg + '</p>' + ' </div>' + ' </div>' + ' <div class="col-md-2 col-xs-2 avatar">' + ' <img src="http://www.bitrebels.com/wp-content/uploads/2011/02/Original-Facebook-Geek-Profile-Avatar-1.jpg"' + ' class=" img-responsive ">' + ' </div>' + '</div>'; panelBody.append(msgDiv); } }) }); }
Thêm logic xử lý active-users
command từ client gửi lên:
@EzyPrototype
@EzyRequestListener("active-users")
public class ActiveUsersRequestHandler extends AbstractClientRequestHandler { protected void execute() throws EzyBadRequestException { responseFactory .newObjectResponse() .command("active-users") .param("active-users", appContext.getApp().getUserManager().getUserList().stream().map(UserResponse::new).collect(Collectors.toList())) .session(session) .execute(); }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EzyObjectBinding(read = false)
public class UserResponse { private Long id; private Integer zoneId; private String name; private Boolean active; public UserResponse(EzyUser ezyUser) { this(ezyUser.getId(), ezyUser.getZoneId(), ezyUser.getName(), Boolean.TRUE); } }
Server tiếp tục nhận command chat
và gửi message về đúng người nhận:
Command chat
mà client gửi lên có cấu trúc như sau (xem lại đoạn javascript app.sendRequest('chat', {from: app.client.me.id, to: +id, content: msg});
):
@Data
@NoArgsConstructor
@AllArgsConstructor
@EzyObjectBinding
public class ChatRequest { private Long from; private Long to; private String content; }
Chúng ta cần một handler để gửi đoạn message này đến đúng người nhận (dựa vào trường to
):
@EzyRequestController
@Setter
public class ChatRequestHandler extends EzyLoggable { @EzyAutoBind("appResponseFactory") protected EzyResponseFactory responseFactory; @EzyDoHandle("chat") public void secureChat(EzyUser user, EzyAppContext appContext, ChatRequest request) { responseFactory.newObjectResponse() .encrypted() .command("chat") .param("chat", new ChatResponse(request)) //Dòng này sẽ quyết định response sẽ được gửi đến user nào .user(appContext.getApp().getUserManager().getUser(request.getTo())) .execute(); }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EzyObjectBinding
public class ChatResponse { private Long from; private Long to; private String content; public ChatResponse(ChatRequest request) { this(request.getFrom(), request.getTo(), request.getContent()); }
}
Đến đây chúng ta thấy có 2 cách để viết một handler. Một cách là sử dụng @EzyRequestListener("command")
, cách kia là sử dụng @EzyRequestController
cùng với @EzyDoHandle("command")
. Cách thứ 2, chúng ta có quyền thêm các params như EzyUser
, EzyAppContext
, ChatRequest
vào method signature, trong khi cách thứ nhất thì không (chúng ta phải inject chúng vào nếu muốn sử dụng). Tùy trường hợp các bạn chọn cách tối ưu nhé.
Nhớ sửa đoạn này ở AppEntry
:
@Override protected String[] getScanablePackages() { return new String[] { "com.ezyfoxserver.app", "com.ezyfoxserver.event.handler", "com.ezyfoxserver.service", /*Ezyfox Server sẽ scan luôn package com.ezyfoxserver.dto*/ "com.ezyfoxserver.dto", }; }
Một lần nữa, các bạn chú ý đọc comment giải thích code nhé!
Client sẽ nhận message qua command chat
và render lên UI:
setupApp.addDataHandler("chat", function (app, data) { var chat = data.chat; var message = chat.content; console.log(message) var from = chat.from; var bigChat = $('#chat-window-1'); var id = bigChat.attr('data-user-id'); if (id && +id === from) { console.log('equal') var msgDiv = '<div class="row msg_container base_receive">' + ' <div class="col-md-2 col-xs-2 avatar">' + ' <img src="http://www.bitrebels.com/wp-content/uploads/2011/02/Original-Facebook-Geek-Profile-Avatar-1.jpg"' + ' class=" img-responsive ">' + ' </div>' + ' <div class="col-md-10 col-xs-10">' + ' <div class="messages msg_receive">' + ' <p>' + message + '</p>' + ' </div>' + ' </div>' + '</div>'; //Dòng này render message content lên UI này bigChat.find('.panel-body').append(msgDiv); } else { //ĐOẠN NÀY CHƯA CẦN NÊN TẠM COMMENT LẠI // var bigS = $('#result-panel'); // var onlineList = bigS.find('.list-group-item'); // console.log(onlineList.find('#' + from)); } });
Tổng kết:
Đây là 1 ứng dụng chat realtime đơn giản với mục đích học tập và khám phá framework, do đó tính năng chỉ giới hạn ở mức chạy được. Các tính năng chưa support như load tin nhắn cũ, thông báo tin nhắn mới đến bằng icon sẽ được hoàn thiện sau. Code hoàn chỉnh vui lòng xem ở đây và đây.
Hẹn gặp lại các bạn trong loại bài tiếp theo!