当前位置:   article > 正文

SpringBoot+Vue实现简易网络聊天室_springboot+vue集成网页版在线聊天(极简版)

springboot+vue集成网页版在线聊天(极简版)

仓库地址

代码仓库地址

前端搭建

导航栏的创建

首先,一个网页大多会有一个公用的导航栏,因此在bootstrap中复制一个NavBar的example,稍作修改,若处于未登录状态,则右端显示登录,否则显示当前登录用户的用户名。导航栏的Vue代码如下:

<template>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <router-link class="navbar-brand" :to="{name: 'chat_index'}">聊天室</router-link>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarText">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                <router-link class="nav-link" aria-current="page" :to="{name:'chat_index'}">聊天</router-link>
                </li>
            </ul>
            <ul class="nav-item" v-if="$store.state.user.nickname !== ''">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle navbar-text" href="#" role="button"  data-bs-toggle="dropdown">
                        {{ $store.state.user.nickname }}
                    </a>
                    <ul class="dropdown-menu">
                        <li><a class="dropdown-item" @click="logout">退出登录</a></li>
                    </ul>
                </li>
            </ul>

            <span class="navbar-text" v-if="$store.state.user.nickname === null || $store.state.user.nickname === ''">
                登录
            </span>
            </div>
        </div>
    </nav>
</template>
  
<script>
    import { useStore } from 'vuex';
    import $ from 'jquery';

    export default {
    setup() {
        const store = useStore();
        const logout = () => {
            $.ajax({
                url: "http://localhost:8081/logout",
                type: "get",
                success(resp) {
                    if(resp.state === "success") {
                        store.commit("updateUser",{ userId: -1,username: "",nickname: "" });
                        localStorage.clear();
                        window.location.href = "http://localhost:8080/login/";
                    }
                }
            });
        };
        return {
            logout,
        }
    }
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

导航栏样式如下所示(已登录状态):
在这里插入图片描述
导航栏样式如下所示(未登录状态):
在这里插入图片描述
如果用户已经登录,那么点击右边的用户名之后会弹出一个下拉框,可以退出登录。
在这里插入图片描述
要实现这个效果,可以用过vuex中的store实现,当用户登录之后后端会向前端返回已登录的状态存到session和localstorage里面,在vuex中从localstorage中取出这个bool值,当为true时显示用户名,否则显示登录按键。
实现这部分功能的vue代码如下:

 			<ul class="nav-item" v-if="$store.state.user.nickname !== ''">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle navbar-text" href="#" role="button"  data-bs-toggle="dropdown">
                        {{ $store.state.user.nickname }}
                    </a>
                    <ul class="dropdown-menu">
                        <li><a class="dropdown-item" @click="logout">退出登录</a></li>
                    </ul>
                </li>
            </ul>

            <span class="navbar-text" v-if="$store.state.user.nickname === null || $store.state.user.nickname === ''">
                登录
            </span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
import { createStore } from 'vuex';
export default createStore({
  state: {
    login: localStorage.getItem("isLogin"),
    userId: localStorage.getItem("userId"),
    user: {
      username: "",
      nickname: ""
    }
  },
  getters: {
  },
  mutations: {
    updateLogin(state, login) {
      state.login = login;
    },
    updateUser(state, user) {
      state.user.username = user.username;
      state.user.nickname = user.nickname;
    }
  },
  actions: {
  },
  modules: {
  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

登录界面的创建

接着使用bootstrap创建登录界面,首先创建一个card布局,将前端划分为一个个小的卡片,这样会显得稍微好看一些,因此,外面可以提取出一个公共的组件:ContentField。ContentField的vue代码如下:

<template>
    <div class="container content-field">
        <div class="card">
            <div class="card-body">
                <slot></slot>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    setup() {
    },
}
</script>
<style scoped>
	div.content-field {
	    margin-top: 20px;
	}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

创建公共组件之后,可以开始创建登录界面了,首先也是在bootstrap官网上,找到一个关于Form的example接着用我们刚刚创建的组件ContentField将其包裹,也就是说,这个登录界面是一个新的小卡片。vue代码如下:

<template>
    <ContentField>
            <div class="mb-3">
                <label class="form-label">用户名</label>
                <input v-model="username" type="text" class="form-control" name="username">
            </div>
            <div class="mb-3">
                <label for="exampleInputPassword1" class="form-label">密码</label>
                <input v-model="password" type="password" class="form-control" name="password" id="exampleInputPassword1">
            </div>
            <span style="color: red;">{{ message }}</span>
            <br/>
            <button @click="login" class="btn btn-primary">登录</button>
    </ContentField>
</template>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以注意到,我们登录的这个button会绑定上一个名为login的方法,这个方法会向后端发送ajax请求,实现登录,代码如下:

<script>
import ContentField from '@/components/ContentField.vue';
import { ref } from 'vue';
import $ from 'jquery';

export default {
    setup() {
        // const store = useStore();
        let message = ref("");
        let username = ref("");
        let password = ref("");
        const login = () => {
            $.ajax({
                url: "http://localhost:8081/login",
                type: "post",
                data:{
                    username: username.value,
                    password: password.value
                },
                success(resp) {
                    if(resp.state === "fail") {
                        message.value = "用户名或密码错误";
                    } else {
                        localStorage.setItem("isLogin",true);
                        localStorage.setItem("userId",resp.user.id);
                        window.location.href = "http://localhost:8080/chat/";
                    }
                }
            });
        };
        return {
            message,
            username,
            password,
            login
        }
    },
    components: {
        ContentField,
    }
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

可以看到,前端后向后端发送请求,请求的路径为http://localhost:8081/login,后端经过登录校验之后会向前端发送登录状态,将登录状态设置为true,并且将用户的信息返回给前端,将用户的id保存到localstorage,最后跳转至聊天界面。

实现聊天界面

首先在element-plus中寻找合适的example,最后选择了以一个表格的形式作为聊天界面,聊天界面如下:
聊天界面
vue代码如下:

<template >
  <el-container class="layout-container-demo" style="height: 500px">
    <el-aside width="200px">
      <el-scrollbar>
        <el-table :data="userList">
          <el-table-column prop="nickname" label="用户列表" width="150" />
        </el-table>
      </el-scrollbar>
    </el-aside>

    <el-container>
      <el-header style="text-align: right; font-size: 12px">
        <div style="text-align: center;">
          <span style="font-size:30px">聊天室</span>
        </div>
      </el-header>

      <el-main>
        <el-scrollbar>
          <el-table :data="tableData">
            <el-table-column prop="date" label="时间" width="160" />
            <el-table-column prop="name" label="发送者 " width="120" />
            <el-table-column prop="message" label="消息" />
          </el-table>
        </el-scrollbar>
      </el-main>

      <el-footer>
        <textarea class="form-control" v-model="textarea" @keydown="sendMsg" aria-label="With textarea"></textarea>
      </el-footer>
    </el-container>

  </el-container>
</template>
  
<script>
import { onMounted, onUnmounted, ref } from 'vue'
import $ from "jquery";
import { useStore } from 'vuex';

const debounce = (fn, delay) => {
  let timer = null;

  return function () {
    let context = this;

    let args = arguments;

    clearTimeout(timer);

    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
};

// 解决ERROR ResizeObserver loop completed with undelivered notifications.

const _ResizeObserver = window.ResizeObserver;

window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
  constructor(callback) {
    callback = debounce(callback, 16);

    super(callback);
  }
};
export default {

  setup() {
    const store = useStore();
    onMounted(() => {
      $.ajax({
        url: "http://localhost:8081/getinfo",
        type: "get",
        data: {
          id: store.state.userId,
        },
        success(resp) {
          store.commit("updateUser", { username: resp.username, nickname: resp.nickname });
        }
      });

    });
    
    const socketUrl = `ws://localhost:8081/websocket/${store.state.userId}/`;
    let socket = new WebSocket(socketUrl);

    socket.onopen = () => {
      console.log("connect!");
    };

    


    onUnmounted(() => {
      socket.close();
    });

    const sendMsg = event => {
      if (event.shiftKey && event.keyCode === 13) {
        document.execCommand('insertLineBreak'); // 换行
        event.preventDefault();
        return false;
      } else if (event.keyCode === 13) { // 回车键
        console.log("回车发送");
        socket.send(JSON.stringify({
                    msg: textarea.value,
                }));
        textarea.value = "";
        event.preventDefault();
        return false;
      }
      
    };



    let user = [];
    console.log(user);
    const textarea = ref("");
    let dataList = [];
    const tableData = ref(dataList);
    const userList = ref(user);
    $.ajax({
      url: "http://localhost:8081/getUserList",
      type: "get",
      success(resp) {
        for (let i = 0; i < resp.length; ++i) {
          userList.value.push({ "nickname": resp[i].nickname, "username": resp[i].username });
        }
      }
    });

    $.ajax({
      url: "http://localhost:8081/getHistory",
      type: "get",
      success(resp) {
        for (let i = 0; i < resp.length; ++i) {
          tableData.value.push({ "date": resp[i].date, "name": resp[i].name ,"message": resp[i].message});
        }
      }
    });

    socket.onmessage = msg => {
      console.log("receive message" + msg.data);
      const data = JSON.parse(msg.data);
      tableData.value.push(data);
    };

    return {
      tableData,
      textarea,
      userList,
      sendMsg,
    };
  },
}



</script>
  
<style scoped>
.layout-container-demo .el-header {
  position: relative;
  background-color: var(--el-color-primary-light-7);
  color: var(--el-text-color-primary);
}

.layout-container-demo .el-aside {
  color: var(--el-text-color-primary);
  background: var(--el-color-primary-light-8);
}

.layout-container-demo .el-menu {
  border-right: none;
}

.layout-container-demo .el-main {
  padding: 0;
}

.layout-container-demo .toolbar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  right: 20px;
}
</style>
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192

其中template和style没什么好说的,基本上都是官网的例子然后随便改改,主要是script中的逻辑部分,首先,当该组件被挂载完成后会向后端发送请求,获取当前用户的信息获取用户的昵称,并且获取当前用户列表以及历史记录:

onMounted(() => {
      $.ajax({
        url: "http://localhost:8081/getinfo",
        type: "get",
        data: {
          id: store.state.userId,
        },
        success(resp) {
          store.commit("updateUser", { username: resp.username, nickname: resp.nickname });
        }
      });
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
$.ajax({
      url: "http://localhost:8081/getUserList",
      type: "get",
      success(resp) {
        for (let i = 0; i < resp.length; ++i) {
          userList.value.push({ "nickname": resp[i].nickname, "username": resp[i].username });
        }
      }
    });

    $.ajax({
      url: "http://localhost:8081/getHistory",
      type: "get",
      success(resp) {
        for (let i = 0; i < resp.length; ++i) {
          tableData.value.push({ "date": resp[i].date, "name": resp[i].name ,"message": resp[i].message});
        }
      }
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

还有最重要的一步是打开websocket连接:

    const socketUrl = `ws://localhost:8081/websocket/${store.state.userId}/`;
    let socket = new WebSocket(socketUrl);

    socket.onopen = () => {
      console.log("connect!");
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

建立websocket连接之后,当后端有消息发送过来时,以下方法会异步调用,处理后端接收到的消息。

    socket.onmessage = msg => {
      console.log("receive message" + msg.data);
      const data = JSON.parse(msg.data);
      tableData.value.push(data);
    };
  • 1
  • 2
  • 3
  • 4
  • 5

处理十分简单,后端按照指定的json格式将需要的数据发送给了前端,前端可以直接将数据放到表格中。形成一条新的聊天记录:
在这里插入图片描述
当用户在聊天消息框中输入消息后,按下Enter键之后,可以发送消息,如果按下Shift + Enter,可以换行。

const sendMsg = event => {
      if (event.shiftKey && event.keyCode === 13) {
        document.execCommand('insertLineBreak'); // 换行
        event.preventDefault();
        return false;
      } else if (event.keyCode === 13) { // 回车键
        console.log("回车发送");
        socket.send(JSON.stringify({
                    msg: textarea.value,
                }));
        textarea.value = "";
        event.preventDefault();
        return false;
      }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

最后,当组件卸载时,会将websocket连接关闭:

onUnmounted(() => {
      socket.close();
 });
  • 1
  • 2
  • 3

后端搭建

搭建环境

首先在idea中创建maven工程,接着导入依赖,pom.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ljx</groupId>
    <artifactId>chat</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>chat-backend</name>
    <description>chat-backend</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3</version>
        </dependency>


        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>

</project>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121

然后创建WebSocket的配置类

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

创建配置文件,写入数据库连接信息和后端端口号:

server.port=8081
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/chat?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
  • 1
  • 2
  • 3
  • 4
  • 5

实现查询历史记录

创建HistoryController,查询数据库,并且将日期格式化即可。

@CrossOrigin
@RestController
public class HistoryController {
    @Autowired
    private HistoryMapper historyMapper;
    @Autowired
    private UserMapper userMapper;

    @GetMapping("getHistory")
    public List getHistory() {
        ArrayList<Dto> list = new ArrayList<>();
        List<History> historyList = historyMapper.selectList(null);
        for(int i = 0;i < historyList.size();i++) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String date = sdf.format(historyList.get(i).getSendTime());
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.select("nickname").eq("id",historyList.get(i).getUserId());
            User user = userMapper.selectOne(queryWrapper);
            String name = user.getNickname();
            String message = historyList.get(i).getMessage();
            Dto dto = new Dto(name,message,date);
            list.add(dto);
        }
        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

实现用户相关操作

在UserController下实现四个接口,分别是登录,登出,根据id获取信息用户信息以及获取用户列表接口。这里功能比较复杂因此放在service中实现。Controller只完成前端发送来的数据的解析操作。

@CrossOrigin
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("login")
    public Map login(HttpServletRequest request, @RequestParam Map<String,String> data) {
        User user = new User();
        user.setUsername(data.get("username"));
        user.setPassword(data.get("password"));
        System.out.println(user);
        return userService.login(request,user);
    }
    @GetMapping("logout")
    public Map logout(HttpServletRequest request) {
        request.getSession().removeAttribute("login");
        Map<String,String> result = new HashMap<>();
        result.put("state","success");
        return result;
    }

    @GetMapping("getinfo")
    public Map getinfo(@RequestParam Integer id) {
        return userService.getinfo(id);
    }

    @GetMapping("getUserList")
    public List<User> getUserList() {
        return userService.getUserList();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

具体逻辑在service中:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public Map login(HttpServletRequest request, User user) {
        Map<String,Object> result = new HashMap<>();
        String username = user.getUsername();
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, user.getUsername()).eq(User::getPassword, user.getPassword());
        User user1 = userMapper.selectOne(wrapper);
        if(user1 == null) {
            result.put("state","fail");
            System.out.println("失败!");
            return result;
        }
        request.getSession().setAttribute("login", true);
        result.put("state","success");
        user1.setPassword("");
        result.put("user",user1);
        System.out.println("成功");
        return result;
    }

    @Override
    public Map getinfo(Integer id) {
        User user = userMapper.selectById(id);
        Map result = new HashMap();
        result.put("username",user.getUsername());
        result.put("nickname",user.getNickname());
        return result;
    }

    @Override
    public List<User> getUserList() {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> user.setPassword(""));
        return users;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

WebSocket实现群聊功能

在该类下,加了@OnOpen注解的方法是有连接建立之后调用的方法,当连接断开调用@OnCloss注解的方法,当后端发送消息,则调用加了@OnMessage注解的方法。逻辑十分清晰,因此不做过多解释,直接阅读代码即可。

@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {
    private Session session = null;
    private Integer userId;

    private static HistoryMapper historyMapper;
    @Autowired
    public void setHistoryMapper(HistoryMapper historyMapper) {
        WebSocket.historyMapper = historyMapper;
    }
    private static UserMapper userMapper;
    @Autowired
    public void setUserMapper(UserMapper userMapper) {
        WebSocket.userMapper = userMapper;
    }
    final private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>(); // 保存在线列表
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        // 建立连接
        this.session = session;
        System.out.println("connect:" + userId);
        this.userId = Integer.parseInt(userId);
        webSockets.add(this);
    }

    @OnClose
    public void onClose() {
        // 关闭链接
        System.out.println("disconnected:" + userId);
    }

    @OnMessage
    public void onMessage(String message) { // 处理消息
        JSONObject jsonObject = JSONObject.parseObject(message);
        // System.out.println(message);
        History history = new History();
        history.setSendTime(new Date());
        history.setUserId(userId);
        history.setMessage(jsonObject.get("msg").toString());
        System.out.println(history);
        // 保存历史记录并且发消息
        historyMapper.insert(history);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // System.out.println(sdf.format(historyMapper.selectById(4).getSend_time()));

        JSONObject resp = new JSONObject();
        resp.put("date",sdf.format(history.getSendTime()));
        resp.put("name",userMapper.selectById(userId).getNickname());
        resp.put("message",jsonObject.get("msg").toString());
        sendAllMessage(resp.toJSONString());
    }

    private void sendAllMessage(String message) { // 群发消息

        for(WebSocket webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

结语

这里向为本课程做出贡献的各路豪杰致以崇高敬意,没有他们的例子妄图跟上课程进度,所花费的时间只怕三国都能统一三次了(玩笑话)。但并不能因为能跑通现成的项目就心安理得或者沾沾自喜,以为掌握了这门技术;而应该仔细研读,以求温故知新,创出自己的版本来,这样才能达到这门课该有的效果。想必这也是孟宁老师用心良苦所希望的。

第一次选修这样独具匠心的课尚属首次,这种别开生面的授课方式也给我带来了以往所收获不到的惊喜。摒除了其他课程一言堂式的教学方式,每个同学可以自由而全面的发展,并能够感受不同思想的碰撞,颇有一种百家争鸣的盛世场面。大家各自繁荣,还能各取所长。但各自发展又不是任意的,这个时候就体现出老师引导的重要性:什么时候该做尝试,什么时候该做取舍,对整个项目的进展有着举足轻重的作用。毫无疑问,孟宁老师始终发挥着把控全局的作用:不至于分崩离析,又能够遍地开花;既能保证项目的前进方向,也能兼顾不同版本的依次更新。将艺高人胆大诠释的淋漓尽致。

然而分别终究是要来到了。但是课程的结束并不意味着工程的止步,这一个多月的成果,将推动我们朝着更高的要求前进。希望在以后的工作中,我们能够怀揣着网络程序设计带来的理念和精神,在网络程序设计的领域中乘万里风,破万里浪!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/576616
推荐阅读
相关标签
  

闽ICP备14008679号