Comprehensive Guide to Chat Server Business Logic

This document outlines the various business logic operations handled by a chat server, including essential functionalities like heartbeats, user registration, login, friend management, profile updates, group operations, and real-time messaging.

Data Packet Structure

All data packets are prefixed with a business id followed by a sequence number, m_seq.

Heartbeat

The heartbeat mechanism is used to verify the online status of connected clients. It requires no additional data beyond the standard packet structure.


std::string outgoingBuffer;
yt::BinaryWriteStream3 stream(&outgoingBuffer);
stream.Write(MSG_TYPE_HEARTBEAT); // Assuming MSG_TYPE_HEARTBEAT is defined elsewhere
stream.Write(sequenceNumber);
std::string emptyData;
stream.Write(emptyData.c_str(), emptyData.length());
stream.Flush();

// LOG_INFO << "Responding to client: cmd=" << MSG_TYPE_HEARTBEAT << ", sessionId=" << sessionID;
Send(outgoingBuffer);
   

User Registration

User registration involves validating incoming JSON data for format and required fields (username, nickname, password). If valid, the system checks for existing usernames. If a username is unique, a new user is added to the UserManager; otherwise, a registration failure message is returned.


void ClientSession::HandleRegistration(const std::string& jsonData, const std::shared_ptr& connection)
{
   // Example JSON: { "username": "13917043329", "nickname" : "balloon", "password" : "123" }
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON format: " << jsonData << ", sessionId = " << sessionID << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["username"].isString() || !rootObject["nickname"].isString() || !rootObject["password"].isString()) {
       // LOG_WARN << "Missing required fields in JSON: " << jsonData << ", sessionId = " << sessionID << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   User newUser;
   newUser.username = rootObject["username"].asString();
   newUser.nickname = rootObject["nickname"].asString();
   newUser.password = rootObject["password"].asString(); // Password should ideally be hashed

   std::string responseMessage;
   User existingUser;
   existingUser.userId = 0; // Initialize with a non-valid user ID
   if (Singleton<usermanager>::GetInstance().FindUserByUsername(newUser.username, existingUser)) {
       responseMessage = "{\"code\": 101, \"msg\": \"Username already registered\"}";
   } else {
       if (!Singleton<usermanager>::GetInstance().AddUser(newUser)) {
           responseMessage = "{\"code\": 100, \"msg\": \"Registration failed\"}";
       } else {
           responseMessage = "{\"code\": 0, \"msg\": \"ok\"}";
       }
   }

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_REGISTER); // Assuming MSG_TYPE_REGISTER is defined
   stream.Write(sequenceNumber);
   stream.Write(responseMessage.c_str(), responseMessage.length());
   stream.Flush();

   // LOG_INFO << "Registration response: cmd=" << MSG_TYPE_REGISTER << ", userId=" << newUser.userId << ", data=" << responseMessage;
   Send(outgoingBuffer);
}
   </usermanager></usermanager>

User Login

Login proceeds by validating the incoming JSON. If valid, credentials (username, password) are extracted. The system queries the UserManager for the username. If the user doesn't exist, a error is returned. If the user exists, the password is compared. Mismatched passwords result in an error. Successful logins return user information and trigger the push of cached messages and online status notifications to friends.


void ClientSession::HandleLogin(const std::string& jsonData, const std::shared_ptr& connection)
{
   // Example JSON: {"username": "13917043329", "password": "123", "clientType": 1, "status": 1}
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for login: " << jsonData << ", sessionId = " << sessionID << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["username"].isString() || !rootObject["password"].isString() || !rootObject["clientType"].isInt() || !rootObject["status"].isInt()) {
       // LOG_WARN << "Invalid login JSON fields: " << jsonData << ", sessionId = " << sessionID << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   std::string username = rootObject["username"].asString();
   std::string password = rootObject["password"].asString();
   std::ostringstream responseStream;
   User authenticatedUser;
   authenticatedUser.userId = 0;

   if (!Singleton<usermanager>::GetInstance().FindUserByUsername(username, authenticatedUser)) {
       responseStream << "{\"code\": 102, \"msg\": \"User not registered\"}";
   } else {
       if (authenticatedUser.password != password) { // Password comparison should use hashing
           responseStream << "{\"code\": 103, \"msg\": \"Incorrect username or password\"}";
       } else {
           // Store session-specific user info
           sessionUserInfo.userId = authenticatedUser.userId;
           sessionUserInfo.username = username;
           sessionUserInfo.nickname = authenticatedUser.nickname;
           sessionUserInfo.password = password; // Should not be stored here
           sessionUserInfo.clientType = rootObject["clientType"].asInt();
           sessionUserInfo.status = rootObject["status"].asInt();

           // Construct success response with user details
           responseStream << "{\"code\": 0, \"msg\": \"ok\", \"userId\": " << sessionUserInfo.userId << ",\"username\":\"" << authenticatedUser.username << "\", \"nickname\":\""
                          << authenticatedUser.nickname << "\", \"faceType\": " << authenticatedUser.faceType << ", \"customFace\":\"" << authenticatedUser.customFace << "\", \"gender\":" << authenticatedUser.gender
                          << ", \"birthday\":" << authenticatedUser.birthday << ", \"signature\":\"" << authenticatedUser.signature << "\", \"address\": \"" << authenticatedUser.address
                          << "\", \"phoneNumber\": \"" << authenticatedUser.phoneNumber << "\", \"email\":\"" << authenticatedUser.email << "\"}";
       }
   }

   // Send login response
   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_LOGIN); // Assuming MSG_TYPE_LOGIN is defined
   stream.Write(sequenceNumber);
   stream.Write(responseStream.str().c_str(), responseStream.str().length());
   stream.Flush();

   // LOG_INFO << "Login response: cmd=" << MSG_TYPE_LOGIN << ", data=" << responseStream.str() << ", userId=" << sessionUserInfo.userId;
   Send(outgoingBuffer);

   // Push cached notifications
   std::list<notifymsgcache> notificationCaches;
   Singleton<msgcachemanager>::GetInstance().GetNotifications(sessionUserInfo.userId, notificationCaches);
   for (const auto& cacheEntry : notificationCaches) {
       Send(cacheEntry.notificationMessage);
   }

   // Push cached chat messages
   std::list<chatmsgcache> chatCaches;
   Singleton<msgcachemanager>::GetInstance().GetChatMessages(sessionUserInfo.userId, chatCaches);
   for (const auto& cacheEntry : chatCaches) {
       Send(cacheEntry.chatMessage);
   }

   // Notify friends about online status
   std::list<user> friends;
   Singleton<usermanager>::GetInstance().GetFriends(sessionUserInfo.userId, friends);
   IMServer& imServer = Singleton<imserver>::GetInstance();
   for (const auto& friendInfo : friends) {
       std::shared_ptr<clientsession> friendSession;
       if (imServer.FindSessionByUserId(friendSession, friendInfo.userId)) {
           friendSession->NotifyUserStatusChange(sessionUserInfo.userId, ONLINE_STATUS_ONLINE);
       }
   }
}
   </clientsession></imserver></usermanager></user></msgcachemanager></chatmsgcache></msgcachemanager></notifymsgcache></usermanager>

Get Friend List

This operation retrieves a user's friends from the UserManager. Each friend's information is formatted into a JSON object and sent back to the client.


void ClientSession::HandleGetFriendList()
{
   std::list<user> friendList;
   Singleton<usermanager>::GetInstance().GetFriends(sessionUserInfo.userId, friendList);

   std::string friendInfoJsonArray;
   IMServer& imServer = Singleton<imserver>::GetInstance();

   for (const auto& friendUser : friendList) {
       bool isOnline = imServer.IsUserOnline(friendUser.userId);
       // Construct JSON for each friend
       // Example: {"userId": 1,"username":"qqq", "nickname":"qqq", "faceType": 0, "customFace":"", "gender":0, "birthday":19900101, "signature":", "address": "", "phoneNumber": "", "email":", "clientType": 1, "status":1}
       std::ostringstream friendDetailStream;
       friendDetailStream << "{\"userId\": " << friendUser.userId << ",\"username\":\"" << friendUser.username << "\", \"nickname\":\"" << friendUser.nickname
                          << "\", \"faceType\": " << friendUser.faceType << ", \"customFace\":\"" << friendUser.customFace << "\", \"gender\":" << friendUser.gender
                          << ", \"birthday\":" << friendUser.birthday << ", \"signature\":\"" << friendUser.signature << "\", \"address\": \"" << friendUser.address
                          << "\", \"phoneNumber\": \"" << friendUser.phoneNumber << "\", \"email\":\"" << friendUser.email << "\", \"clientType\": 1, \"status\":"
                          << (isOnline ? 1 : 0) << "}";

       friendInfoJsonArray += friendDetailStream.str();
       friendInfoJsonArray += ",";
   }

   // Remove trailing comma if any
   if (!friendInfoJsonArray.empty()) {
       friendInfoJsonArray.pop_back();
   }

   std::ostringstream responseStream;
   responseStream << "{\"code\": 0, \"msg\": \"ok\", \"friends\":[" << friendInfoJsonArray << "]}";

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_GETFRIENDLIST); // Assuming MSG_TYPE_GETFRIENDLIST is defined
   stream.Write(sequenceNumber);
   stream.Write(responseStream.str().c_str(), responseStream.str().length());
   stream.Flush();

   // LOG_INFO << "Get friend list response: cmd=" << MSG_TYPE_GETFRIENDLIST << ", data=" << responseStream.str() << ", userId=" << sessionUserInfo.userId;
   Send(outgoingBuffer);
}
   </imserver></usermanager></user>

Find User

This function searches for a user by username in the UserManager. If found, user details are returned; otherwise, an empty list or not-found indicator is sent.


void ClientSession::HandleFindUser(const std::string& jsonData, const std::shared_ptr& connection)
{
   // Example JSON: { "searchType": 1, "username" : "zhangyl" }
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for find user: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["searchType"].isInt() || !rootObject["username"].isString()) {
       // LOG_WARN << "Invalid find user JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   std::string searchUsername = rootObject["username"].asString();
   std::string responseMessage;

   // Currently supports finding a single user
   User foundUser;
   if (!Singleton<usermanager>::GetInstance().FindUserByUsername(searchUsername, foundUser)) {
       responseMessage = "{ \"code\": 0, \"msg\": \"ok\", \"users\": [] }";
   } else {
       // Simplified user info for search result
       char userInfoBuffer[256] = { 0 };
       snprintf(userInfoBuffer, sizeof(userInfoBuffer), "{ \"code\": 0, \"msg\": \"ok\", \"users\": [{\"userId\": %d, \"username\": \"%s\", \"nickname\": \"%s\", \"faceType\":%d}] }",
                foundUser.userId, foundUser.username.c_str(), foundUser.nickname.c_str(), foundUser.faceType);
       responseMessage = userInfoBuffer;
   }

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_FINDUSER); // Assuming MSG_TYPE_FINDUSER is defined
   stream.Write(sequenceNumber);
   stream.Write(responseMessage.c_str(), responseMessage.length());
   stream.Flush();

   // LOG_INFO << "Find user response: cmd=" << MSG_TYPE_FINDUSER << ", data=" << responseMessage << ", userId=" << sessionUserInfo.userId;
   Send(outgoingBuffer);
}
   </usermanager>

Friend Operations

Handles actions like adding, removing, or responding to friend requests. It differentiates between user operations and group operations (e.g., joining/leaving a group) based on the target ID. For friend requests, it establishes relationships and notifies relevant parties. Offline users' messages are cached.


void ClientSession::HandleFriendOperation(const std::string& jsonData, const std::shared_ptr& connection)
{
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for friend operation: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["type"].isInt() || !rootObject["targetUserId"].isInt()) {
       // LOG_WARN << "Invalid friend operation JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   int operationType = rootObject["type"].asInt();
   int32_t targetUserId = rootObject["targetUserId"].asInt();

   // Group operations (targetUserId >= GROUP_ID_BOUNDARY)
   if (targetUserId >= GROUP_ID_BOUNDARY) { // Assuming GROUP_ID_BOUNDARY is defined
       if (operationType == OPERATION_LEAVE_GROUP) { // Assuming OPERATION_LEAVE_GROUP is defined (e.g., 4)
           RemoveUserFromGroup(connection, targetUserId);
           return;
       }
       // Add to group (auto-accept)
       AddUserToGroup(targetUserId, connection);
       return;
   }

   // User operations
   char operationDataBuffer[256] = { 0 };

   // Remove friend
   if (operationType == OPERATION_REMOVE_FRIEND) { // Assuming OPERATION_REMOVE_FRIEND is defined (e.g., 4)
       RemoveFriend(connection, targetUserId);
       return;
   }

   // Send friend request
   if (operationType == OPERATION_SEND_REQUEST) { // Assuming OPERATION_SEND_REQUEST is defined (e.g., 1)
       // Example: {"targetUserId": 9, "type": 2, "fromUserId": "currentUserId", "fromUsername": "currentUsername"}
       snprintf(operationDataBuffer, sizeof(operationDataBuffer), "{\"targetUserId\":%d, \"type\":%d, \"fromUserId\": %d, \"fromUsername\": \"%s\"}",
                targetUserId, OPERATION_RECEIVE_REQUEST, sessionUserInfo.userId, sessionUserInfo.username.c_str()); // Assuming OPERATION_RECEIVE_REQUEST is defined (e.g., 2)
   }
   // Respond to friend request
   else if (operationType == OPERATION_RESPOND_REQUEST) { // Assuming OPERATION_RESPOND_REQUEST is defined (e.g., 3)
       if (!rootObject["accept"].isInt()) {
           // LOG_WARN << "Invalid JSON for accept field: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
           return;
       }

       int acceptStatus = rootObject["accept"].asInt();
       if (acceptStatus == 1) { // Accepted
           int smallerId = sessionUserInfo.userId;
           int greaterId = targetUserId;
           if (smallerId > greaterId) {
               std::swap(smallerId, greaterId);
           }
           if (!Singleton<usermanager>::GetInstance().EstablishFriendship(smallerId, greaterId)) {
               // LOG_ERROR << "Failed to establish friendship: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
               return;
           }
       }

       // Notify current user of the outcome
       snprintf(operationDataBuffer, sizeof(operationDataBuffer), "{\"targetUserId\": %d, \"type\": %d, \"fromUserId\": %d, \"fromUsername\": \"%s\", \"accept\": %d}",
                targetUserId, OPERATION_RESPOND_REQUEST, sessionUserInfo.userId, sessionUserInfo.username.c_str(), acceptStatus);

       // Notify the target user
       std::string outgoingBufferSelf;
       yt::BinaryWriteStream3 streamSelf(&outgoingBufferSelf);
       streamSelf.Write(MSG_TYPE_FRIENDOPERATION); // Assuming MSG_TYPE_FRIENDOPERATION is defined
       streamSelf.Write(sequenceNumber);
       streamSelf.Write(operationDataBuffer, strlen(operationDataBuffer));
       streamSelf.Flush();
       Send(outgoingBufferSelf);

       // LOG_INFO << "Friend request response sent to self: cmd=" << MSG_TYPE_FRIENDOPERATION << ", data=" << operationDataBuffer << ", userId=" << sessionUserInfo.userId;
   }

   // Send response to the target user
   std::string outgoingBufferTarget;
   yt::BinaryWriteStream3 streamTarget(&outgoingBufferTarget);
   streamTarget.Write(MSG_TYPE_FRIENDOPERATION);
   streamTarget.Write(sequenceNumber);
   streamTarget.Write(operationDataBuffer, strlen(operationDataBuffer));
   streamTarget.Flush();

   // Cache message if target is offline
   std::shared_ptr<clientsession> targetSession;
   if (!Singleton<imserver>::GetInstance().FindSessionByUserId(targetSession, targetUserId)) {
       Singleton<msgcachemanager>::GetInstance().AddNotificationCache(targetUserId, outgoingBufferTarget);
       // LOG_INFO << "Target user " << targetUserId << " offline, caching notification message.";
       return;
   }

   targetSession->Send(outgoingBufferTarget);
   // LOG_INFO << "Friend operation sent to target userId: " << targetUserId << ", data=" << operationDataBuffer;
}

// Helper function for removing a friend or leaving a group
void ClientSession::RemoveFriend(const std::shared_ptr& connection, int32_t friendOrGroupId) {
   int32_t smallerId = friendOrGroupId;
   int32_t greaterId = sessionUserInfo.userId;
   if (smallerId > greaterId) {
       std::swap(smallerId, greaterId);
   }

   if (!Singleton<usermanager>::GetInstance().ReleaseFriendship(smallerId, greaterId)) {
       // LOG_ERROR << "Failed to remove friend/group member. friendId: " << friendOrGroupId << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   User friendInfo;
   if (!Singleton<usermanager>::GetInstance().FindUserById(friendOrGroupId, friendInfo)) {
       // LOG_ERROR << "Error retrieving friend info for removal. friendId: " << friendOrGroupId << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   // Notify the user initiating the removal
   char removalData[256] = { 0 };
   snprintf(removalData, sizeof(removalData), "{\"targetUserId\":%d, \"type\":%d, \"fromUserId\": %d, \"fromUsername\": \"%s\"}",
            friendOrGroupId, OPERATION_REMOVE_NOTIFICATION, sessionUserInfo.userId, sessionUserInfo.username.c_str()); // Assuming OPERATION_REMOVE_NOTIFICATION is defined (e.g., 5)

   std::string outgoingBufferSelf;
   yt::BinaryWriteStream3 streamSelf(&outgoingBufferSelf);
   streamSelf.Write(MSG_TYPE_FRIENDOPERATION);
   streamSelf.Write(sequenceNumber);
   streamSelf.Write(removalData, strlen(removalData));
   streamSelf.Flush();
   Send(outgoingBufferSelf);
   // LOG_INFO << "Sent friend removal notification to self: cmd=" << MSG_TYPE_FRIENDOPERATION << ", data=" << removalData << ", userId=" << sessionUserInfo.userId;

   // If it's a user and not a group
   if (friendOrGroupId < GROUP_ID_BOUNDARY) {
       // Notify the removed friend if they are online
       std::shared_ptr<clientsession> targetSession;
       if (Singleton<imserver>::GetInstance().FindSessionByUserId(targetSession, friendOrGroupId)) {
           memset(removalData, 0, sizeof(removalData));
           snprintf(removalData, sizeof(removalData), "{\"targetUserId\":%d, \"type\":%d, \"fromUserId\": %d, \"fromUsername\": \"%s\"}",
                    sessionUserInfo.userId, OPERATION_REMOVE_NOTIFICATION, friendOrGroupId, friendInfo.username.c_str());

           outgoingBufferSelf.clear();
           streamSelf.Clear();
           streamSelf.Write(MSG_TYPE_FRIENDOPERATION);
           streamSelf.Write(sequenceNumber);
           streamSelf.Write(removalData, strlen(removalData));
           streamSelf.Flush();
           targetSession->Send(outgoingBufferSelf);
           // LOG_INFO << "Sent friend removal notification to friend: cmd=" << MSG_TYPE_FRIENDOPERATION << ", data=" << removalData << ", userId=" << friendOrGroupId;
       }
       return;
   }

   // If it was a group, notify other group members about the change
   std::list<user> groupMembers;
   Singleton<usermanager>::GetInstance().GetGroupMembers(friendOrGroupId, groupMembers);
   IMServer& imServer = Singleton<imserver>::GetInstance();
   for (const auto& member : groupMembers) {
       std::shared_ptr<clientsession> memberSession;
       if (imServer.FindSessionByUserId(memberSession, member.userId)) {
           memberSession->NotifyUserStatusChange(friendOrGroupId, USER_STATUS_GROUP_UPDATED); // Assuming USER_STATUS_GROUP_UPDATED is defined
       }
   }
}

// Helper function for joining a group
void ClientSession::AddUserToGroup(int32_t groupId, const std::shared_ptr& connection) {
   if (!Singleton<usermanager>::GetInstance().EstablishFriendship(sessionUserInfo.userId, groupId)) {
       // LOG_ERROR << "Failed to join group. groupId: " << groupId << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   User groupInfo;
   if (!Singleton<usermanager>::GetInstance().FindUserById(groupId, groupInfo)) { // Assuming group info can be retrieved similarly to user info
       // LOG_ERROR << "Failed to retrieve group info. groupId: " << groupId << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   // Notify the user they joined the group
   char joinNotification[256] = { 0 };
   snprintf(joinNotification, sizeof(joinNotification), "{\"targetUserId\": %d, \"type\": %d, \"fromUserId\": %d, \"fromUsername\": \"%s\", \"accept\": 1}", // Assuming accept value 1 for joining
            groupInfo.userId, OPERATION_RESPOND_REQUEST, sessionUserInfo.userId, sessionUserInfo.username.c_str());

   std::string outgoingBufferSelf;
   yt::BinaryWriteStream3 streamSelf(&outgoingBufferSelf);
   streamSelf.Write(MSG_TYPE_FRIENDOPERATION);
   streamSelf.Write(sequenceNumber);
   streamSelf.Write(joinNotification, strlen(joinNotification));
   streamSelf.Flush();
   Send(outgoingBufferSelf);
   // LOG_INFO << "Sent group join confirmation to self: cmd=" << MSG_TYPE_FRIENDOPERATION << ", data=" << joinNotification << ", userId=" << sessionUserInfo.userId;

   // Notify other group members about the new member
   std::list<user> groupMembers;
   Singleton<usermanager>::GetInstance().GetGroupMembers(groupId, groupMembers);
   IMServer& imServer = Singleton<imserver>::GetInstance();
   for (const auto& member : groupMembers) {
       std::shared_ptr<clientsession> memberSession;
       if (imServer.FindSessionByUserId(memberSession, member.userId)) {
           memberSession->NotifyUserStatusChange(groupId, USER_STATUS_GROUP_UPDATED); // Notify group update
       }
   }
}
   </clientsession></imserver></usermanager></user></usermanager></usermanager></clientsession></imserver></usermanager></user></imserver></clientsession></usermanager></usermanager></msgcachemanager></imserver></clientsession></usermanager>

Update User Information

Allows users to modify their profile details (nickname, avatar, signature, etc.). The UserManager is updated, and online friends are notified of the changes.


void ClientSession::HandleUpdateUserInfo(const std::string& jsonData, const std::shared_ptr& connection)
{
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for update user info: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   // Validate required fields for update
   if (!rootObject["nickname"].isString() || !rootObject["faceType"].isInt() ||
       !rootObject["customFace"].isString() || !rootObject["gender"].isInt() ||
       !rootObject["birthday"].isInt() || !rootObject["signature"].isString() ||
       !rootObject["address"].isString() || !rootObject["phoneNumber"].isString() ||
       !rootObject["email"].isString()) {
       // LOG_WARN << "Invalid update user info JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   User updatedInfo;
   updatedInfo.nickname = rootObject["nickname"].asString();
   updatedInfo.faceType = rootObject["faceType"].asInt();
   updatedInfo.customFace = rootObject["customFace"].asString();
   updatedInfo.gender = rootObject["gender"].asInt();
   updatedInfo.birthday = rootObject["birthday"].asInt();
   updatedInfo.signature = rootObject["signature"].asString();
   updatedInfo.address = rootObject["address"].asString();
   updatedInfo.phoneNumber = rootObject["phoneNumber"].asString();
   updatedInfo.email = rootObject["email"].asString();

   std::ostringstream responseStream;
   if (!Singleton<usermanager>::GetInstance().UpdateUserInfo(sessionUserInfo.userId, updatedInfo)) {
       responseStream << "{ \"code\": 104, \"msg\": \"Failed to update user information\" }";
   } else {
       // Update session info if necessary
       sessionUserInfo.nickname = updatedInfo.nickname;
       sessionUserInfo.faceType = updatedInfo.faceType;
       // ... update other relevant session fields

       // Construct success response with updated user details
       responseStream << "{\"code\": 0, \"msg\": \"ok\", \"userId\": " << sessionUserInfo.userId << ",\"username\":\"" << sessionUserInfo.username
                      << "\", \"nickname\":\"" << updatedInfo.nickname
                      << "\", \"faceType\": " << updatedInfo.faceType << ", \"customFace\":\"" << updatedInfo.customFace
                      << "\", \"gender\":" << updatedInfo.gender
                      << ", \"birthday\":" << updatedInfo.birthday << ", \"signature\":\"" << updatedInfo.signature << "\", \"address\": \"" << updatedInfo.address
                      << "\", \"phoneNumber\": \"" << updatedInfo.phoneNumber << "\", \"email\":\""
                      << updatedInfo.email << "\"}";
   }

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_UPDATEUSERINFO); // Assuming MSG_TYPE_UPDATEUSERINFO is defined
   stream.Write(sequenceNumber);
   stream.Write(responseStream.str().c_str(), responseStream.str().length());
   stream.Flush();

   Send(outgoingBuffer);
   // LOG_INFO << "Update user info response: cmd=" << MSG_TYPE_UPDATEUSERINFO << ", data=" << responseStream.str() << ", userId=" << sessionUserInfo.userId;

   // Notify online friends about the profile change
   std::list<user> friends;
   Singleton<usermanager>::GetInstance().GetFriends(sessionUserInfo.userId, friends);
   IMServer& imServer = Singleton<imserver>::GetInstance();
   for (const auto& friendInfo : friends) {
       std::shared_ptr<clientsession> friendSession;
       if (imServer.FindSessionByUserId(friendSession, friendInfo.userId)) {
           friendSession->NotifyUserStatusChange(sessionUserInfo.userId, USER_STATUS_PROFILE_UPDATED); // Assuming USER_STATUS_PROFILE_UPDATED is defined
       }
   }
}
   </clientsession></imserver></usermanager></user></usermanager>

Modify Password

Handles password changes. It verifies the old password before allowing the new password to be set via the UserManager.


void ClientSession::HandleModifyPassword(const std::string& jsonData, const std::shared_ptr& connection)
{
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for password modification: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["oldPassword"].isString() || !rootObject["newPassword"].isString()) {
       // LOG_WARN << "Invalid password modification JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   std::string oldPassword = rootObject["oldPassword"].asString();
   std::string newPassword = rootObject["newPassword"].asString();

   std::string responseMessage;
   User currentUser;
   if (!Singleton<usermanager>::GetInstance().FindUserById(sessionUserInfo.userId, currentUser)) {
       // LOG_ERROR << "Failed to retrieve user for password modification. userId: " << sessionUserInfo.userId << ", data: " << jsonData << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (currentUser.password != oldPassword) { // Use hashed password comparison
       responseMessage = "{\"code\": 103, \"msg\": \"Incorrect old password\"}";
   } else {
       if (!Singleton<usermanager>::GetInstance().ModifyUserPassword(sessionUserInfo.userId, newPassword)) {
           responseMessage = "{\"code\": 105, \"msg\": \"Password modification error\"}";
           // LOG_ERROR << "Password modification failed. userId: " << sessionUserInfo.userId << ", data: " << jsonData << ", client: " << connection->peerAddress().toIpPort();
       } else {
           responseMessage = "{\"code\": 0, \"msg\": \"ok\"}";
           // Update session password if needed, but consider security implications
           // sessionUserInfo.password = newPassword;
       }
   }

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_MODIFYPASSWORD); // Assuming MSG_TYPE_MODIFYPASSWORD is defined
   stream.Write(sequenceNumber);
   stream.Write(responseMessage.c_str(), responseMessage.length());
   stream.Flush();

   Send(outgoingBuffer);
   // LOG_INFO << "Password modification response: cmd=" << MSG_TYPE_MODIFYPASSWORD << ", data=" << responseMessage << ", userId=" << sessionUserInfo.userId;
}
   </usermanager></usermanager>

Create Group

Allows users to create new groups. The group name is parsed, and the group is added via UserManager. The creator is automatically added to the group, and confirmation messages are sent.


void ClientSession::HandleCreateGroup(const std::string& jsonData, const std::shared_ptr& connection)
{
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for group creation: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["groupName"].isString()) {
       // LOG_WARN << "Invalid group creation JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   std::ostringstream responseStream;
   std::string groupName = rootObject["groupName"].asString();
   int32_t newGroupId;

   if (!Singleton<usermanager>::GetInstance().AddGroup(groupName, sessionUserInfo.userId, newGroupId)) {
       // LOG_WARN << "Failed to add group. Data: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       responseStream << "{ \"code\": 106, \"msg\" : \"Group creation failed\"}";
   } else {
       // Creator automatically joins the group
       if (!Singleton<usermanager>::GetInstance().EstablishFriendship(sessionUserInfo.userId, newGroupId)) {
           // LOG_ERROR << "Failed to add creator to new group. Data: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
           // Potentially rollback group creation or handle error
       }
       responseStream << "{\"code\": 0, \"msg\": \"ok\", \"groupId\":" << newGroupId << ", \"groupName\": \"" << groupName << "\"}";
   }

   // Respond with group creation success
   std::string outgoingBufferCreate;
   yt::BinaryWriteStream3 streamCreate(&outgoingBufferCreate);
   streamCreate.Write(MSG_TYPE_CREATEGROUP); // Assuming MSG_TYPE_CREATEGROUP is defined
   streamCreate.Write(sequenceNumber);
   streamCreate.Write(responseStream.str().c_str(), responseStream.str().length());
   streamCreate.Flush();
   Send(outgoingBufferCreate);
   // LOG_INFO << "Group creation response: cmd=" << MSG_TYPE_CREATEGROUP << ", data=" << responseStream.str() << ", userId=" << sessionUserInfo.userId;

   // Respond with joining group success (type 3 for friend operation, accept value 1 for join)
   char joinNotification[256] = { 0 };
   snprintf(joinNotification, sizeof(joinNotification), "{\"targetUserId\": %d, \"type\": %d, \"fromUserId\": %d, \"fromUsername\": \"%s\", \"accept\": 1}",
            newGroupId, OPERATION_RESPOND_REQUEST, sessionUserInfo.userId, sessionUserInfo.username.c_str());

   std::string outgoingBufferJoin;
   yt::BinaryWriteStream3 streamJoin(&outgoingBufferJoin);
   streamJoin.Write(MSG_TYPE_FRIENDOPERATION); // Re-using friend operation type for join notification
   streamJoin.Write(sequenceNumber);
   streamJoin.Write(joinNotification, strlen(joinNotification));
   streamJoin.Flush();
   Send(outgoingBufferJoin);
   // LOG_INFO << "Sent group join notification: cmd=" << MSG_TYPE_FRIENDOPERATION << ", data=" << joinNotification << ", userId=" << sessionUserInfo.userId;
}
   </usermanager></usermanager>

Get Group Members

Similar to retrieving friend lists, this fetches members of a specified group from the UserManager.


void ClientSession::HandleGetGroupMembers(const std::string& jsonData, const std::shared_ptr& connection)
{
   // Example JSON: {"groupId": groupID}
   Json::Reader jsonParser;
   Json::Value rootObject;
   if (!jsonParser.parse(jsonData, rootObject)) {
       // LOG_WARN << "Invalid JSON for get group members: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   if (!rootObject["groupId"].isInt()) {
       // LOG_WARN << "Invalid get group members JSON fields: " << jsonData << ", userId: " << sessionUserInfo.userId << ", client: " << connection->peerAddress().toIpPort();
       return;
   }

   int32_t groupId = rootObject["groupId"].asInt();

   std::list<user> groupMembers;
   Singleton<usermanager>::GetInstance().GetGroupMembers(groupId, groupMembers); // Assuming GetGroupMembers exists

   std::string membersInfoJsonArray;
   IMServer& imServer = Singleton<imserver>::GetInstance();

   for (const auto& member : groupMembers) {
       bool isOnline = imServer.IsUserOnline(member.userId);
       // Construct JSON for each member
       // Example: {"userId": 1,"username":"qqq", "nickname":"qqq", "faceType": 0, "customFace":"", "gender":0, "birthday":19900101, "signature":", "address": "", "phoneNumber": "", "email":", "clientType": 1, "status":1}
       std::ostringstream memberDetailStream;
       memberDetailStream << "{\"userId\": " << member.userId << ",\"username\":\"" << member.username << "\", \"nickname\":\"" << member.nickname
                          << "\", \"faceType\": " << member.faceType << ", \"customFace\":\"" << member.customFace << "\", \"gender\":" << member.gender
                          << ", \"birthday\":" << member.birthday << ", \"signature\":\"" << member.signature << "\", \"address\": \"" << member.address
                          << "\", \"phoneNumber\": \"" << member.phoneNumber << "\", \"email\":\"" << member.email << "\", \"clientType\": 1, \"status\":"
                          << (isOnline ? 1 : 0) << "}";

       membersInfoJsonArray += memberDetailStream.str();
       membersInfoJsonArray += ",";
   }

   if (!membersInfoJsonArray.empty()) {
       membersInfoJsonArray.pop_back(); // Remove trailing comma
   }

   std::ostringstream responseStream;
   responseStream << "{\"code\": 0, \"msg\": \"ok\", \"groupId\": " << groupId << ", \"members\":[" << membersInfoJsonArray << "]}";

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_GETGROUPMEMBERS); // Assuming MSG_TYPE_GETGROUPMEMBERS is defined
   stream.Write(sequenceNumber);
   stream.Write(responseStream.str().c_str(), responseStream.str().length());
   stream.Flush();

   // LOG_INFO << "Get group members response: cmd=" << MSG_TYPE_GETGROUPMEMBERS << ", data=" << responseStream.str() << ", userId=" << sessionUserInfo.userId;
   Send(outgoingBuffer);
}
   </imserver></usermanager></user>

Send User Status Change Message

Notifies other users about a user's status changes (online, offline, profile updates).


void ClientSession::NotifyUserStatusChange(int32_t affectedUserId, int statusType)
{
   std::string statusData;
   switch (statusType) {
       case USER_STATUS_ONLINE: // Assuming USER_STATUS_ONLINE is 1
           statusData = "{\"type\": 1, \"onlineStatus\": 1}";
           break;
       case USER_STATUS_OFFLINE: // Assuming USER_STATUS_OFFLINE is 2
           statusData = "{\"type\": 2, \"onlineStatus\": 0}";
           break;
       case USER_STATUS_PROFILE_UPDATED: // Assuming USER_STATUS_PROFILE_UPDATED is 3
           statusData = "{\"type\": 3}";
           break;
       case USER_STATUS_GROUP_UPDATED: // For group member changes
           statusData = "{\"type\": 4}"; // Example type for group update
           break;
       default:
           // LOG_WARN << "Unknown status type: " << statusType;
           return;
   }

   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_USERSTATUSCHANGE); // Assuming MSG_TYPE_USERSTATUSCHANGE is defined
   stream.Write(sequenceNumber);
   stream.Write(statusData.c_str(), statusData.length());
   stream.Write(affectedUserId); // The user whose status changed
   stream.Flush();

   Send(outgoingBuffer);
   // LOG_INFO << "Sent user status change: cmd=" << MSG_TYPE_USERSTATUSCHANGE << ", data=" << statusData << ", affectedUserId=" << affectedUserId << ", fromUserId=" << sessionUserInfo.userId;
}
   

Send Chat Message

Handles sending both direct messages and group messages. Messages are saved to the database. If the recipient is offline, the message is cached. Online recipients receive the message directly.


void ClientSession::HandleChatMessage(int32_t targetId, const std::string& messageData, const std::shared_ptr& connection)
{
   std::string outgoingBuffer;
   yt::BinaryWriteStream3 stream(&outgoingBuffer);
   stream.Write(MSG_TYPE_CHAT); // Assuming MSG_TYPE_CHAT is defined
   stream.Write(sequenceNumber);
   stream.Write(messageData.c_str(), messageData.length());
   stream.Write(sessionUserInfo.userId); // Sender ID
   stream.Write(targetId);               // Receiver ID (user or group)
   stream.Flush();

   // Save message to database
   UserManager& userMgr = Singleton<usermanager>::GetInstance();
   if (!userMgr.SaveChatMessage(sessionUserInfo.userId, targetId, messageData)) {
       // LOG_ERROR << "Failed to save chat message to DB. Sender: " << sessionUserInfo.userId << ", Target: " << targetId << ", Message: " << messageData;
   }

   IMServer& imServer = Singleton<imserver>::GetInstance();
   MsgCacheManager& msgCacheMgr = Singleton<msgcachemanager>::GetInstance();

   // Direct message
   if (targetId < GROUP_ID_BOUNDARY) {
       std::shared_ptr<clientsession> targetSession;
       if (imServer.FindSessionByUserId(targetSession, targetId)) {
           targetSession->Send(outgoingBuffer);
       } else {
           // Recipient offline, cache the message
           msgCacheMgr.AddChatMessageCache(targetId, outgoingBuffer);
       }
       return;
   }

   // Group message
   std::list<user> groupMembers;
   userMgr.GetGroupMembers(targetId, groupMembers); // Assuming GetGroupMembers exists
   for (const auto& member : groupMembers) {
       // Skip sending to self if applicable, or handle sender logic within the loop
       if (member.userId == sessionUserInfo.userId) continue;

       std::shared_ptr<clientsession> memberSession;
       if (imServer.FindSessionByUserId(memberSession, member.userId)) {
           memberSession->Send(outgoingBuffer);
       } else {
           // Member offline, cache the message
           msgCacheMgr.AddChatMessageCache(member.userId, outgoingBuffer);
       }
   }
}
   </clientsession></user></clientsession></msgcachemanager></imserver></usermanager>

Tags: chat server messaging real-time communication Protobuf Network Protocol

Posted on Sun, 14 Jun 2026 17:43:28 +0000 by monkeyj