私信功能的设计与实现

1. 效果展示

项目源码: https://gitee.com/wangzhi430/ChatSystem

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 基础准备

2.1 项目创建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 配置文件

spring.datasource.url=jdbc:mysql://localhost:3306/ChatSystem?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/**Mapper.xml

debug=true
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG

3. 数据库的设计与实现

数据表分为三个表,用户表、聊天关系表、聊天列表。
用户表用来存储用户的信息。

这里设计三个字段,用户Id,用户账户,用户密码。

聊天关系表用来存储聊天的两个用户的关系。

这里设计三个字段,关系Id,发送者Id,接收者Id。

聊天列表用来存储对应的聊天的信息,根据关系Id,来识别是哪两者的用户。

这里设计5个字段,列表Id,关系Id,发送用户Id,发送内容,发送时间。

实现代码:

create database if not exists ChatSystem;

use ChatSystem;

drop table if exists user;

-- 创建一个用户信息表
create table user (
    userId int primary key auto_increment,
    username varchar(128) unique,
    password varchar(128) not null
);

drop table if exists user_link;

-- 聊天关系表
create table user_link (
    `linkId` int primary key auto_increment,
    `from` int not null,
    `to` int not null
);

drop table if exists chat_list;

-- 聊天列表
create table chat_list(
    listId int primary key auto_increment,
    linkId int,
    userId int,
    content varchar(128) not null,
    createtime datetime
);

4. 登录注册模块的设计与实现

4.1 登录注册统一响应类

这里登录注册后端返回的数据,统一是这个格式。

import lombok.Data;

@Data
public class ResponseBodyMessage<T> {
    private int status;
    private String message;
    private T data;

    public ResponseBodyMessage(int status,String message,T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }
}

4.2 BCrypt加密

4.2.1 添加依赖类

在pom.xml中添加BCrypt的依赖类

	<!-- security依赖包 (加密)-->
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-config</artifactId>
	</dependency>

4.2.2 在启动类中添加代码


@SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

4.2.3 在AppConfig类中注入Bean对象

@Configuration
public class AppConfig {
	 @Bean
	    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
	        return new BCryptPasswordEncoder();
	    }
}

4.3 添加拦截器

防止未登录用户进入非法界面.

4.3.1 LoginInterceptor 类

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("user") != null){
            return true;
        }
        response.setStatus(403);
        response.sendRedirect("/login.html");
        return false;
    }
}

4.3.2 AppConfig 类

@Configuration
public class AppConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/*.jpg")
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.png")
                .excludePathPatterns("/**/login.html")
                .excludePathPatterns("/**/register.html")
                .excludePathPatterns("/**/login")
                .excludePathPatterns("/**/register");
    }

    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4.4 具体代码实现

之前代码中, 登录注册流程很清楚, 可以查看之前博客.

@RestController
public class UserController {
    @Resource
    private UserService userService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @RequestMapping("/login")
    public ResponseBodyMessage<Boolean> userLogin(@RequestBody User user, HttpServletRequest request) {
        User truUser = userService.selectByUserName(user.getUsername());
        if (truUser == null) {
            System.out.println("登录失败!");
            return new ResponseBodyMessage<>(-1, "用户名密码错误!", false);
        } else {
            boolean flg = bCryptPasswordEncoder.matches(user.getPassword(), truUser.getPassword());
            if (!flg) {
                return new ResponseBodyMessage<>(-1, "用户名密码错误!", false);
            }
            System.out.println("登录成功!");
            HttpSession session = request.getSession(true);
            System.out.println(session);
            session.setAttribute("user", truUser);
            return new ResponseBodyMessage<>(1, "登录成功!", true);
        }
    }


    @RequestMapping("/register")
    public ResponseBodyMessage<User> register(@RequestBody User user) {
        if(user.getUsername() == null || "".equals(user.getUsername().trim())
                || user.getPassword() == null || "".equals(user.getPassword().trim())){
            return new ResponseBodyMessage<>(-1,"输入内容为空!",null);
        }
        User truUser = userService.selectByUserName(user.getUsername());
        if (truUser != null) {
            return new ResponseBodyMessage<>(-1,"当前用户名已经存在!",null);
        } else{
            String password = bCryptPasswordEncoder.encode(user.getPassword());
            user.setPassword(password);

            userService.addUser(user);
            return new ResponseBodyMessage<>(1,"注册成功!",user);
        }
    }

    @RequestMapping("/logout")
    public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession(false);
        // 拦截器的拦截, 所以不可能出现session为空的情况
        session.removeAttribute("user");
        response.sendRedirect("login.html");
    }

}

5. 私信模块的设计与实现

这里的私信功能, 主要运用到了WebSocket

5.1 基础配置

5.1.1 依赖类

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

5.1.2 在AppConfig中配置

@Configuration
@EnableWebSocket
public class AppConfig implements WebMvcConfigurer, WebSocketConfigurer {

    @Autowired
    private ChatController chatController;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatController,"/intoChat")
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

5.2 设计思路

在成功建立websocket连接的时候, 后端发送请求给前端, 让用户根据请求来绘制好友列表.

{
	status:  1, // 这里的1为成功请求, -1 为失败的请求
	message: "getUser",	
	users: "", // 列表用户
	fromusername: "", // 当前用户
}

在绘制好好友列表之后, 可以选择好友来进行对话.在点击好友之后, 发送请求给服务器, 服务器根据两者的用户账户进行建立会话, 读取之前的聊天记录.

请求

{
	from: , 发送用户账户
	to: , 接收用户账户
	message: "loadMessage",
}

响应

{
	status: 1,
	messages: "", // 以前的聊天记录, 每一条包含, 用户Id, 内容, 是否是发送者.
	message: "loadMessage",
	tousername: "", 接收用户账户
}

在进入聊天界面之后. 可以输入对应的消息, 然后点击发送, 进行发送消息.服务器就将消息发送给当前两者用户, 谁在线, 就更新列表. 不在线就不更新.

请求

{
	from: "", // 发送用户账户
	to: "", // 接收用户账户
	content: "", // 发送的信息内容
	message: "sendMessage"
}

响应

{
	status: 1,
	message: "sendMessage",
	messages: "", // 发送的消息
}

用户异常退出或者用户退出的时候, 在在线状态中设置离线, 在进入的时候设置上线.

5.3 用户在线状态管理器

这里使用 ConcurrentHashMap 来进行存储, key为用户Id, valueWebSocketSession

主要是三个功能
① 进入私聊界面添加用户状态到哈希表中
② 退出私聊界面删除哈希表中的用户状态
③ 获取当前用户的状态.

@Component
public class OnlineUserManager {
    // 哈希表存储的是用户的当前的状态,在线就存储到哈希表中
    private ConcurrentHashMap<Integer, WebSocketSession> userState = new ConcurrentHashMap<>();

    public void enterHall(int userId, WebSocketSession webSocketSession) {
        userState.put(userId,webSocketSession);
    }

    public void exitHall(int userId) {
        userState.remove(userId);
    }

    public WebSocketSession getState(int userId) {
        return userState.get(userId);
    }
}

5.4 设计数据库操作

主要是两个功能:

  1. 在点击用户头像的时候, 加载聊天界面, 并且读取聊天记录.
  2. 在点击发送按钮的时候, 发送消息, 并且加载消息到在线用户的窗口中.

实现功能1 需要去数据中, 查找两个用户的关系, 是否聊过天, 如果没有聊过天, 查询到的记录就是null. 如果聊过天, 根据关系去聊天列表中查询对应的记录.

功能1 涉及的数据库操作:

  1. 根据两个用户的Id, 在聊天关系表中查找linkId
  2. 根据linkId, 在聊天列表中查找对应的聊天记录

实习功能2, 需要去数据中, 查询是否两个用户聊过天, 是否存在linkId, 如果不存在, 就需要建立关系, 创建一个linkId, 并且根据linkId, 去插入数据, 如果存在, 直接根据linkId插入数据.

功能2 涉及的数据库操作:

  1. 在聊天关系表中, 插入一条数据, from, to分别为两者用户的Id
  2. 在聊天列表中, 根据linkId 插入一条数据, 添加发送该消息的用户Id,消息内容, 创建时间.

5.4.1 创建实体类

ChatList 实体类

@Data
public class ChatList {
    private int listId;
    private int linkId;
    private int userId;
    private String content;
    private Timestamp createtime;
}

UserLink 实体类

@Data
public class UserLink {
    private int linkId;
    private int from;
    private int to;
}

5.4.2 在mapper文件夹下创建对应xml

ChatListMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ChatListMapper">
    <select id="selectChat" resultType="com.example.demo.model.ChatList">
        select * from chat_list where linkId = #{linkId} order by createtime asc;   
    </select>

    <insert id="insertChat" keyProperty="listId" keyColumn="listId">
        insert into chat_list(linkId,userId,content,createtime) values (#{linkId},#{fromId},#{content},#{timestamp});
    </insert>
</mapper>

5.4.3 对应Mapper接口

ChatListMapper 接口

@Mapper
public interface ChatListMapper {
    List<ChatList> selectChat(int linkId);

    void insertChat(Integer linkId, Integer fromId, String content, Timestamp timestamp);
}

UserLinkMapper 接口

@Mapper
public interface UserLinkMapper {
    Integer selectLinkId(int fromId,int toId);

    void insertLink(int min, int max);
}

5.4.4 对应的Service类

ChatListService 类

@Service
public class ChatListService {
    @Autowired
    private ChatListMapper chatListMapper;

    public List<ChatList> selectChat(int linkId){
        return chatListMapper.selectChat(linkId);
    }

    public void insertChat(Integer linkId, Integer fromId, String content, Timestamp timestamp) {
        chatListMapper.insertChat(linkId,fromId,content,timestamp);
    }
}

UserLinkService

@Service
public class UserLinkService {
    @Autowired
    private UserLinkMapper userLinkMapper;

    public Integer selectLinkId(int fromId,int toId){
        return userLinkMapper.selectLinkId(fromId,toId);
    }

    public void insertLink(int min, int max) {
        userLinkMapper.insertLink(min,max);
    }
}

5.5 Controller类的实现

这里采用了webSocket方法, 主要是四个类来实现,

  1. afterConnectionEstablished 连接成功的时候调用
  2. handleTextMessage 接收请求的时候调用
  3. handleTransportError 连接异常断开的时候调用
  4. afterConnectionClosed 连接断开的时候调用

5.5.1 连接成功的时候调用类

  1. 首先判断当前用户是否已经登录, 防止用户多开
  2. 将用户的在线状态设置为在线
  3. 从数据库中查找所有的用户
  4. 设置响应类, 并添加对应的信息
  5. 返回响应.
    // 连接成功调用
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        ResponseMessage responseMessage = new ResponseMessage();
        // 1. 首先判断当前用户是否已经登录, 防止用户多开
        User user = (User) session.getAttributes().get("user");
        if(onlineUserManager.getState(user.getUserId()) != null) {
            responseMessage.setStatus(-1);
            responseMessage.setMessage("当前用户已经登录了, 不要重复登录");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
            return;
        }
        // 2. 将用户的在线状态设置为在线
        onlineUserManager.enterHall(user.getUserId(),session);
        // 3. 从数据库中查找所有的用户
        // 4. 设置响应类, 并添加对应的信息
        responseMessage.setStatus(1);
        responseMessage.setMessage("getUser");
        responseMessage.setFromusername(user.getUsername());
        responseMessage.setUsers(userService.selectAllUser(user.getUsername()));
        // 5. 返回响应
        session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
    }

5.5.2 接收请求的时候调用类

  1. 解析请求的内容
  2. 判断是加载消息记录, 还是发送消息
    加载消息
         2.a.1 根据两者的用户Id 查看 linkId, 这里让from始终最小,to始终最大,方便查找
         2.a.2 判断当前的linkId是否存在, 不存在就不需要加载聊天记录了
         2.a.3 存在聊天记录,需要加载
         2.a.4 设置对应的响应, 并返回
    发送消息
         2.b.1 查找对应的linkId
         2.b.2 判断当前linkId是否为空, 为空需要创建linkId
         2.b.3 根据linkId, 在聊天列表中添加数据
         2.b.4 设置对应的响应信息
         2.b.5 获取两者用户的session, 并判断是否在线, 给在线的用户返回响应,刷新聊天框
  // 连接成功收到的响应
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        ResponseMessage responseMessage = new ResponseMessage();
        User user = (User) session.getAttributes().get("user");
        // 1. 解析请求的内容
        String payload = message.getPayload();
        System.out.println(payload);
        RequestMessage requestMessage = objectMapper.readValue(payload,RequestMessage.class);
        // 2. 判断是加载消息记录, 还是发送消息
        if(requestMessage.getMessage().equals("loadMessage")){
            // 2.a.1 根据两者的用户Id 查看 linkId, 这里让from始终最小,to始终最大,方便查找
            responseMessage.setMessage("loadMessage");
            responseMessage.setTousername(requestMessage.getTo());
            Integer fromId = userService.selectUserId(requestMessage.getFrom());
            Integer toId = userService.selectUserId(requestMessage.getTo());
            int min = Math.min(fromId,toId);
            int max = Math.max(fromId,toId);
            Integer linkId = userLinkService.selectLinkId(min,max);
            // 2.a.2 判断当前的linkId是否存在, 不存在就不需要加载聊天记录了
            if(linkId == null || linkId == 0) {
                responseMessage.setStatus(1);
                responseMessage.setMessages(null);
            }else{
                // 2.a.3 存在聊天记录,需要加载,
                responseMessage.setStatus(1);
                List<ChatList> chatLists = chatListService.selectChat(linkId);
                List<Message> messages = new ArrayList<>();
                for(ChatList chatList : chatLists) {
                    Message message1 = new Message();
                    message1.setMessage(chatList.getContent());
                    message1.setUserId(chatList.getUserId());
                    message1.setSender(chatList.getUserId() == user.getUserId());
                    messages.add(message1);
                }
                responseMessage.setMessages(messages);
            }
            // 2.a.4 设置对应的响应, 并返回
            session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
        }
        if(requestMessage.getMessage().equals("sendMessage")) {
            // 2.b.1 查找对应的linkId
            Integer fromId = userService.selectUserId(requestMessage.getFrom());
            Integer toId = userService.selectUserId(requestMessage.getTo());
            int min = Math.min(fromId,toId);
            int max = Math.max(fromId,toId);
            Integer linkId = userLinkService.selectLinkId(min,max);
            // 2.b.2 判断当前linkId是否为空, 为空需要创建linkId
            if(linkId == null || linkId == 0) {
                responseMessage.setStatus(1);
                userLinkService.insertLink(min,max);
                linkId = userLinkService.selectLinkId(min,max);
            }
            // 2.b.3 根据linkId, 在聊天列表中添加数据
            String content = requestMessage.getContent();

            chatListService.insertChat(linkId,fromId,content,new Timestamp(System.currentTimeMillis()));
            // 2.b.4 设置对应的响应信息
            responseMessage.setStatus(1);
            responseMessage.setMessage("sendMessage");
            Message message1 = new Message();
            // 2.b.5 获取两者用户的session, 并判断是否在线, 给在线的用户返回响应, 刷新聊天框
            WebSocketSession session1 = onlineUserManager.getState(fromId);
            WebSocketSession session2 = onlineUserManager.getState(toId);
            if(session1 != null) {
                message1.setSender(true);
                message1.setUserId(fromId);
                message1.setMessage(content);
                List<Message> list = new ArrayList<>();
                list.add(message1);
                responseMessage.setMessages(list);
                session1.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
            }
            if(session2 != null) {
                message1.setSender(false);
                message1.setUserId(toId);
                message1.setMessage(content);
                List<Message> list = new ArrayList<>();
                list.add(message1);
                responseMessage.setMessages(list);
                session2.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
            }
        }
    }

5.5.3 连接异常断开的时候调用

// 连接异常调用
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        User user = (User) session.getAttributes().get("user");
        WebSocketSession webSocketSession = onlineUserManager.getState(user.getUserId());
        if(webSocketSession == session) {
            // 2. 设置在线状态
            onlineUserManager.exitHall(user.getUserId());
        }
        System.out.println("用户"+user.getUsername()+"退出");
    }

5.5.4 连接断开的时候调用

    // 连接关闭调用
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        WebSocketSession webSocketSession = onlineUserManager.getState(user.getUserId());
        if(webSocketSession == session) {
            // 2. 设置在线状态
            onlineUserManager.exitHall(user.getUserId());
        }
        System.out.println("用户"+user.getUsername()+"退出");
    }

5.5 前端代码

5.5.1 html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>好友界面</title>
    <link rel="stylesheet" href="css/index.css">
</head>
<body>
    <div class="parent">
        <div class="left">
            <div class="titleList">
                <img src="image/头像.jpg" class="pg">
                <span class="username">1234124124</span>
            </div>
            <ul class="chatList">
                <!-- <li class="touser">
                    <img src="image/头像.jpg" class="pg">
                    <div class="information">
                        <span class="tousername">1233</span>
                        <span class="preview">dmwlfmwfmw</span>
                    </div>
                </li> -->
            </ul>
        </div>
        <div class="right">
            <!-- <div class="touserName"><span>1233123213</span></div>
            <div class="MessageList">
                <div class="tous">
                    <img src="image/头像.jpg" class="pg">
                    <div class="sendMes">123</div>
                </div>
                <div class="fromus">
                    <img src="image/头像.jpg" class="pg">
                    <div class="sendMes">123</div>
                </div>
            </div>
            <div class="inputList">
                <textarea οninput="updateNum()" maxlength="100" id="textareaContent"></textarea>
                <div class="commentText">
                    <div class="textLine">还能输入<em>100</em>个字符</div>
                    <button class="sendComment">发送</button>
                </div>     
            </div> -->
        </div>
    </div>
</body>
</html>
<script src="js/jquery.min.js"></script>
<script>
    function updateNum() {
        let text = $("#textareaContent").val();
        $("em").html(100-text.length);
    }




    let websocketUrl = 'ws://'+ location.host +'/intoChat';
    let websocket = new WebSocket(websocketUrl);


    websocket.onopen = function() {
        console.log("房间链接成功!");
    }
    websocket.onclose = function() {
        console.log("房间断开链接");
    }
    websocket.onerror = function() {
        console.log("房间出现异常");
    }
    window.onbeforeunload = function() {
        websocket.close();
    }
    websocket.onmessage = function(e) {
        console.log(e.data);
        let resp = JSON.parse(e.data);
        if(resp.status == -1) {
            alert(resp.message);
            location.assign("login.html");
            return;
        }else{
            if(resp.message == "getUser"){
                let username = document.querySelector('.username');
                username.innerHTML = resp.fromusername;
                createList(resp.users);
            }
            if(resp.message == "loadMessage") {
                createChatList(resp);
            }
            if(resp.message == "sendMessage") {
                let MessageList = document.querySelector('.MessageList');
                for(let message of resp.messages) {
                    if(message.isSender){
                        let fromus = document.createElement('div');
                        fromus.className = 'fromus';
                        let img = document.createElement('img');
                        img.src='image/头像.jpg';
                        img.className = 'pg';
                        let sendMes = document.createElement('div');
                        sendMes.className = 'sendMes';
                        sendMes.innerHTML = message.message;
                        fromus.appendChild(img);
                        fromus.appendChild(sendMes);
                        MessageList.appendChild(fromus);
                    }else{
                        let tous = document.createElement('div');
                        tous.className = 'tous';
                        let img = document.createElement('img');
                        img.src='image/头像.jpg';
                        img.className = 'pg';
                        let sendMes = document.createElement('div');
                        sendMes.className = 'sendMes';
                        sendMes.innerHTML = message.message;
                        tous.appendChild(img);
                        tous.appendChild(sendMes);
                        MessageList.appendChild(tous);
                    }
                }
            }
        }
    }

    function createChatList(resp){
        let messages = resp.messages;
        let tousername = resp.tousername;
        let s = "";
        s += "<div class='touserName'><span>"+tousername+"</span></div>";
        s += "<div class='MessageList'>";
        if(messages != null) {
            for(let message of messages) {
                if(message.isSender){
                    s+="<div class='fromus'>";
                    s+="<img src='image/头像.jpg' class='pg'>"
                    s+="<div class='sendMes'>"+message.message+"</div>"
                    s+="</div>";
                }else{
                    s+="<div class='tous'>";
                    s+="<img src='image/头像.jpg' class='pg'>"
                    s+="<div class='sendMes'>"+message.message+"</div>";
                    s+="</div>";
                }
            }
        }
        s += "</div>";
        s += "<div class='inputList'>"
        s += "<textarea οninput='updateNum()' maxlength='100' id='textareaContent'></textarea>";
        s += "<div class='commentText'>"
        s += "<div class='textLine'>还能输入<em>100</em>个字符</div>"
        s += "<button class='sendComment' οnclick=btnOn('"+tousername+"')>发送</button>";
        s += "</div></div>";
        $('.right').html(s);
    }

    function createList(users) {
        let s = "";
        for(let user of users) {
            s += "<li class='touser' οnclick=Chat('"+user.username+"')>";
            s += "<img src='image/头像.jpg' class='pg'>";
            s += "<div class='information'>";
            s += "<span class='tousername'>"+user.username+"</span>";
            s += "<span class='preview'></span></div></li>"
        }
        $('.chatList').html(s);
    }

    function Chat(s) {
        let username = document.querySelector('.username');
        let req = {
            from: username.textContent,
            to: s,
            message: "loadMessage",
        }

        websocket.send(JSON.stringify(req));
    }

    function btnOn(tousername) {
        let username = document.querySelector('.username');
        let text = $("#textareaContent").val().trim();
        if(text == ""){
            alert("请输入内容,不要输入空格");
            return;
        }
        let req = {
            from : username.textContent,
            to: tousername,
            content: text,
            message: "sendMessage",
        }
        websocket.send(JSON.stringify(req));
        let text2 = document.querySelector("#textareaContent");
        text2.value = "";
    }

</script>

5.5.2 css文件

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    /* background-image: url(../image/1.jpg); */
}
html,body{
    height: 100%;
    background-image: url(../image/1.jpg);
    background-position: center center;
    background-size: cover;
    background-repeat: no-repeat;
}
.parent{
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    width: 100%;
}

.left{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 80%;
    width: 300px;
}

.titleList{
    background-color: #2e2e2e;
    width: 100%;
    height: 75px;
    padding-left: 10px;

    display: flex;
    justify-content: flex-start;
    align-items: center;

    
    border-bottom: 1px solid white;
}

.chatList{
    background-color: #bdb0b0;
    width: 100%;
    height: calc(100% - 75px);
}

.right{
    background-color: rgba(235, 229, 229, 0.9);
    height: 80%;
    width: 700px;

    display: flex;
    flex-direction: column;
    justify-content: flex-start;
}

.pg{
    border-radius: 50%;
    height: 50px;
    width: 50px;
}
.username{
    color: white;
    margin-left: 10px;
}
.touser{
    padding: 10px;

    display: flex;
    justify-content: flex-start;
    align-items: center;

    border-bottom: 1px solid white;
}
.tousername{
    color: white;
}
.preview{
    color: white;
}
.information{
    margin-left: 10px;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
}
#textareaContent{
    display: block;
    width: 97%;
    background: rgba(248,249,251,0.8);
    border: none;
    padding: 0 16px;
    border-radius: 4px;
    resize: none;
    height: 88px;
    font-size: 14px;
    line-height: 22px;
    margin: 8px;
}


.commentText{
    padding: 0 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.sendComment{
    display: block;
    width: 77px;
    height: 24px;
    background: #fc5531;
    color: #fff;
    border-radius: 16px;
    font-size: 14px;
    text-align: center;
    line-height: 24px;
    border: none;
}
.sendComment:active{
    color: #fc5531;
    background: #fff;
}
.textLine{
    font-size: 8px;
}
em{    
    color: #222226;
    margin: 0 4px;
    font-style: normal;
}

.touserName{
    height: 64px;
    width: 100%;
    border-bottom: 2px solid black;
}

.touserName span{
    margin-left: 14px;
    line-height: 64px;
}

.MessageList{
    margin: 10px;
    height: 66%;

    overflow:auto;
}

.inputList{
    border-top: 2px solid black;
    height: calc(34% - 64px);
}

.tous{
    display: flex;
    align-items: center;
    justify-content: flex-start;
}

.fromus{
    display: flex;
    align-items: center;
    flex-direction: row-reverse;
}

.sendMes{
    margin: 10px;
    line-height: 28px;
    padding: 4px 12px;
    color: #222226;
    background: #cad9ff;
    border-radius: 5px;
}

6. 群聊功能

这里页面没设计, 主要是通过在用户在线状态管理器中添加几个类, 返回在线的所有用户, 在界面的时候一个用户发送消息, 直接返回给所有的用户.
在这里插入图片描述
在这里插入图片描述

6.1 用户状态管理器

    public WebSocketSession getState(int userId) {
        return userState.get(userId);
    }

    public int getOnlinePeople() {
        return userState.size();
    }

6.2 代码实现

6.2.1 后端代码

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.room.OnlineUserManager;
import com.example.demo.room.RequestMessage;
import com.example.demo.room.ResponseMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.Collection;

@Component
public class RoomController extends TextWebSocketHandler {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private OnlineUserManager onlineUserManager;

    // 连接成功调用
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        User user = (User) session.getAttributes().get("user");
        onlineUserManager.enterHall(user.getUserId(),session);
        System.out.println("当前人数: " + onlineUserManager.getOnlinePeople());
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setMessage("people");
        responseMessage.setNumber(onlineUserManager.getOnlinePeople());
        Collection<WebSocketSession> collection = onlineUserManager.getAllSession();
        for(WebSocketSession s : collection) {
            s.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
        }
    }

    // 连接成功收到的响应
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        User user = (User) session.getAttributes().get("user");
        String payload = message.getPayload();
        System.out.println(payload);
        RequestMessage requestMessage = objectMapper.readValue(payload,RequestMessage.class);
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setMessage("chatMessage");
        responseMessage.setContent(requestMessage.getContent());
        Collection<WebSocketSession> collection = onlineUserManager.getAllSession();
        for(WebSocketSession s : collection) {
            s.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(responseMessage)));
        }
    }

    // 连接异常调用
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        User user = (User) session.getAttributes().get("user");
        WebSocketSession webSocketSession = onlineUserManager.getState(user.getUserId());
        if(webSocketSession == session) {
            // 2. 设置在线状态
            onlineUserManager.exitHall(user.getUserId());
        }
        System.out.println("用户"+user.getUsername()+"退出");
    }

    // 连接关闭调用
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        WebSocketSession webSocketSession = onlineUserManager.getState(user.getUserId());
        if(webSocketSession == session) {
            // 2. 设置在线状态
            onlineUserManager.exitHall(user.getUserId());
        }
        System.out.println("用户"+user.getUsername()+"退出");
    }
}

6.2.2 前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" id="content">
    <input type="submit" id="up">
    <hr>
    <div id="chat">
        <div id="notic">当前在线用户: <span id="totalPeople">0</span></div>
    </div>
</body>
</html>
<script>
    let websocketUrl = 'ws://'+ location.host +'/intoRoom';
    let websocket = new WebSocket(websocketUrl);


    websocket.onopen = function() {
        console.log("房间链接成功!");
    }
    websocket.onclose = function() {
        console.log("房间断开链接");
    }
    websocket.onerror = function() {
        console.log("房间出现异常");
    }
    window.onbeforeunload = function() {
        websocket.close();
    }
    websocket.onmessage = function(e) {
        console.log(e.data);
        let resp = JSON.parse(e.data);
        let chat = document.querySelector('#chat');
        let div = document.createElement('div');
        if(resp.message == 'people'){
            let total = document.querySelector("#totalPeople");
            total.innerHTML = resp.number;
        }
        if(resp.message == 'chatMessage'){
            div.innerHTML=resp.content;
        }
        chat.appendChild(div);
    }

    let submit = document.querySelector("#up");
    submit.onclick = function() {
        let content = document.querySelector("#content");
        let req = {
            content: content.value,
        }
        websocket.send(JSON.stringify(req));
    }
</script>
  • 31
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独一无二的哈密瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值